AaronBruin 2 zile în urmă
comite
53a81e345b
100 a modificat fișierele cu 11887 adăugiri și 0 ștergeri
  1. 24 0
      App.vue
  2. 37 0
      common/appcomact.js
  3. 48 0
      common/index.scss
  4. 63 0
      components/battery.vue
  5. 119 0
      components/global/g-confirm.vue
  6. 33 0
      components/global/g-icon-fonts.vue
  7. 65 0
      components/global/g-page.vue
  8. 99 0
      components/global/g-popup.vue
  9. 87 0
      components/global/g-statusbar.vue
  10. 126 0
      components/virtualList.vue
  11. 172 0
      components/x-clock.vue
  12. 55 0
      components/x-tabs.vue
  13. 20 0
      index.html
  14. 27 0
      main.js
  15. 72 0
      manifest.json
  16. 92 0
      pages.json
  17. 669 0
      pages/index/components/calendar.js
  18. 755 0
      pages/index/components/calendar.vue
  19. 132 0
      pages/index/components/information.js
  20. 311 0
      pages/index/components/minute.js
  21. 8 0
      pages/index/horoscope.vue
  22. 328 0
      pages/index/index.vue
  23. 8 0
      pages/record/index.vue
  24. 195 0
      pages/school/bookParticulars.vue
  25. 237 0
      pages/school/index.vue
  26. 85 0
      pages/school/reader.vue
  27. 120 0
      pages/school/reader/novel-service.js
  28. 97 0
      pages/school/reader/reader-catalog.vue
  29. 500 0
      pages/school/reader/reader-component.vue
  30. 74 0
      pages/school/reader/reader-footer.vue
  31. 114 0
      pages/school/reader/reader-header.vue
  32. 218 0
      pages/school/reader/reader-settings.vue
  33. 191 0
      pages/school/reader/sliding-container.vue
  34. 147 0
      pages/setting/index.vue
  35. BIN
      static/background0.jpg
  36. BIN
      static/background1.jpg
  37. BIN
      static/bk.png
  38. BIN
      static/centre.png
  39. BIN
      static/hour.png
  40. BIN
      static/logo.png
  41. BIN
      static/mh-BsqlF_P3.png
  42. BIN
      static/minute.png
  43. BIN
      static/plate.png
  44. BIN
      static/tabs/array_default.png
  45. BIN
      static/tabs/array_selected.png
  46. BIN
      static/tabs/option_default.png
  47. BIN
      static/tabs/option_selected.png
  48. BIN
      static/tabs/record_default.png
  49. BIN
      static/tabs/record_selected.png
  50. BIN
      static/tabs/school_default.png
  51. BIN
      static/tabs/school_selected.png
  52. 82 0
      styles/iconfont.scss
  53. 13 0
      uni.promisify.adaptor.js
  54. 76 0
      uni.scss
  55. 21 0
      uni_modules/uview-ui/LICENSE
  56. 66 0
      uni_modules/uview-ui/README.md
  57. 376 0
      uni_modules/uview-ui/changelog.md
  58. 78 0
      uni_modules/uview-ui/components/u--form/u--form.vue
  59. 47 0
      uni_modules/uview-ui/components/u--image/u--image.vue
  60. 73 0
      uni_modules/uview-ui/components/u--input/u--input.vue
  61. 44 0
      uni_modules/uview-ui/components/u--text/u--text.vue
  62. 48 0
      uni_modules/uview-ui/components/u--textarea/u--textarea.vue
  63. 54 0
      uni_modules/uview-ui/components/u-action-sheet/props.js
  64. 278 0
      uni_modules/uview-ui/components/u-action-sheet/u-action-sheet.vue
  65. 59 0
      uni_modules/uview-ui/components/u-album/props.js
  66. 259 0
      uni_modules/uview-ui/components/u-album/u-album.vue
  67. 44 0
      uni_modules/uview-ui/components/u-alert/props.js
  68. 243 0
      uni_modules/uview-ui/components/u-alert/u-alert.vue
  69. 52 0
      uni_modules/uview-ui/components/u-avatar-group/props.js
  70. 103 0
      uni_modules/uview-ui/components/u-avatar-group/u-avatar-group.vue
  71. 78 0
      uni_modules/uview-ui/components/u-avatar/props.js
  72. 58 0
      uni_modules/uview-ui/components/u-avatar/u-avatar.vue
  73. 54 0
      uni_modules/uview-ui/components/u-back-top/props.js
  74. 129 0
      uni_modules/uview-ui/components/u-back-top/u-back-top.vue
  75. 72 0
      uni_modules/uview-ui/components/u-badge/props.js
  76. 171 0
      uni_modules/uview-ui/components/u-badge/u-badge.vue
  77. 46 0
      uni_modules/uview-ui/components/u-button/nvue.scss
  78. 161 0
      uni_modules/uview-ui/components/u-button/props.js
  79. 495 0
      uni_modules/uview-ui/components/u-button/u-button.vue
  80. 80 0
      uni_modules/uview-ui/components/u-button/vue.scss
  81. 99 0
      uni_modules/uview-ui/components/u-calendar/header.vue
  82. 579 0
      uni_modules/uview-ui/components/u-calendar/month.vue
  83. 144 0
      uni_modules/uview-ui/components/u-calendar/props.js
  84. 384 0
      uni_modules/uview-ui/components/u-calendar/u-calendar.vue
  85. 85 0
      uni_modules/uview-ui/components/u-calendar/util.js
  86. 14 0
      uni_modules/uview-ui/components/u-car-keyboard/props.js
  87. 311 0
      uni_modules/uview-ui/components/u-car-keyboard/u-car-keyboard.vue
  88. 14 0
      uni_modules/uview-ui/components/u-cell-group/props.js
  89. 61 0
      uni_modules/uview-ui/components/u-cell-group/u-cell-group.vue
  90. 110 0
      uni_modules/uview-ui/components/u-cell/props.js
  91. 229 0
      uni_modules/uview-ui/components/u-cell/u-cell.vue
  92. 82 0
      uni_modules/uview-ui/components/u-checkbox-group/props.js
  93. 103 0
      uni_modules/uview-ui/components/u-checkbox-group/u-checkbox-group.vue
  94. 69 0
      uni_modules/uview-ui/components/u-checkbox/props.js
  95. 344 0
      uni_modules/uview-ui/components/u-checkbox/u-checkbox.vue
  96. 8 0
      uni_modules/uview-ui/components/u-circle-progress/props.js
  97. 198 0
      uni_modules/uview-ui/components/u-circle-progress/u-circle-progress.vue
  98. 79 0
      uni_modules/uview-ui/components/u-code-input/props.js
  99. 252 0
      uni_modules/uview-ui/components/u-code-input/u-code-input.vue
  100. 34 0
      uni_modules/uview-ui/components/u-code/props.js

+ 24 - 0
App.vue

@@ -0,0 +1,24 @@
+<script>
+	// 八字排盘
+	export default {
+		onLaunch: function() {
+			console.log('App Launch')
+		},
+		onShow: function() {
+			console.log('App Show')
+		},
+		onHide: function() {
+			console.log('App Hide')
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import '@/styles/iconfont.scss';
+	@import "@/uni_modules/uview-ui/theme.scss";
+	@import "common/index.scss";
+	page {
+		background-color: #F8F8F8;
+	}
+	/*每个页面公共css */
+</style>

+ 37 - 0
common/appcomact.js

@@ -0,0 +1,37 @@
+
+
+export function getItem(key){
+		// #ifdef APP-PLUS
+		return plus.storage.getItem(key);
+		// #endif
+		// #ifdef APP-PLUS
+		return uni.getStorageSync(key);
+		// #endif
+		
+	};
+export function setItem(key,value){
+		// #ifdef APP-PLUS
+		return plus.storage.setItem(key,value);
+		// #endif
+		// #ifdef APP-PLUS
+		return uni.setStorageSync(key,value);
+		// #endif
+	};
+	
+export function	removeItem(key){
+		// #ifdef APP-PLUS
+		return plus.storage.removeItem(key)
+		// #endif
+		// #ifdef APP-PLUS
+		return uni.removeStorageSync(key);
+		// #endif
+	};
+	
+export function setFullscreen(type){
+	// #ifdef APP-PLUS
+		plus.navigator.setFullscreen(type);
+	// #endif
+	// #ifndef APP-PLUS
+		return false;
+	// #endif
+}

+ 48 - 0
common/index.scss

@@ -0,0 +1,48 @@
+body,html{
+	margin: 0;
+	padding: 0;
+}
+.center_in_sequence{
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+}
+.center_in{
+	width: 100%;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+}
+.space_between{
+	width: 100%;
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+}
+.same_row_in {
+	display: flex;
+	align-items: center;
+}
+.up_and_down{
+	display: flex;
+	flex-direction: column;
+	justify-content: space-between;
+	align-items: center;
+}
+// 伪元素1rpx边框
+.frame {
+	position: relative; //重要
+}
+
+.frame::after {
+	position: absolute;
+	content: '';
+	border-bottom: 4rpx solid #e4e7ed;
+	width: 200%;
+	height: 200%;
+	top: 0;
+	left: 0;
+	transform: scale(0.5);
+	transform-origin: 0 0;
+	pointer-events: none; /* 使伪元素不会阻止鼠标事件 */
+}

+ 63 - 0
components/battery.vue

@@ -0,0 +1,63 @@
+<template>
+	<view class="battery-container">
+		<view class="battery-body"><view class="battery" :style="{ width: `${level}%` }"></view></view>
+		<view class="battery-head"></view>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		level: {
+			type: Number,
+			default: 0
+		},
+		charging: {
+			type: Boolean,
+			default: false
+		}
+	},
+	data() {
+		return {};
+	},
+	mounted() {},
+	methods: {}
+};
+</script>
+
+<style lang="scss" scoped>
+.battery-container {
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	width: 25px;
+	height: 10px;
+	.battery-body {
+		position: relative;
+		padding: 1px;
+		width: 22px;
+		height: 100%;
+		border-radius: 1px;
+		border: $minor-text-color solid 1px;
+		.battery {
+			height: 100%;
+			background-color: $minor-text-color;
+		}
+		.charging {
+			position: absolute;
+			left: 50%;
+			top: 50%;
+			transform: translate(-50%, -50%);
+			height: 12px;
+			line-height: 12px;
+			font-size: 15px;
+			color: #333;
+		}
+	}
+	.battery-head {
+		width: 2px;
+		height: 6px;
+		background-color: $minor-text-color;
+	}
+}
+</style>

+ 119 - 0
components/global/g-confirm.vue

@@ -0,0 +1,119 @@
+<template>
+  <uni-popup ref="popup" type="dialog" :class="`popup-${options.mode}`">
+    <uni-popup-dialog
+      :type="options.type"
+      :mode="options.mode"
+      :maxlength="options.maxlength"
+      :showClose="options.showClose"
+      :placeholder="options.placeholder"
+      :cancelText="options.cancelText"
+      :confirmText="options.confirmText"
+      :title="options.title"
+      :content="options.content"
+      :before-close="options.beforeClose"
+      v-model="options.value"
+      @confirm="handleConfirm"
+      @close="handleCancel"
+    ></uni-popup-dialog>
+  </uni-popup>
+</template>
+
+<script setup lang="ts">
+import { cloneDeep } from "lodash";
+import { ref } from "vue";
+
+const OPTIONS: ConfirmOptions = {
+  type: "success",
+  cancelText: "取消",
+  confirmText: "确定",
+  title: "提示",
+  content: "提示信息",
+  beforeClose: false,
+  mode: "base",
+  maxlength: 20,
+  showClose: true,
+  placeholder: "20字以内",
+  value: "",
+  key: "default",
+};
+const popup = ref<PopupInstance | null>(null);
+const options = ref<ConfirmOptions>(cloneDeep(OPTIONS));
+
+function show(params: Partial<ConfirmOptions>) {
+  options.value = cloneDeep(OPTIONS);
+  updateObject(options.value, params);
+  popup.value?.open();
+}
+
+function close() {
+  popup.value?.close();
+}
+
+function updateObject<T>(target: T, updates: Partial<T>): void {
+  Object.keys(updates).forEach((key) => {
+    const typedKey = key as keyof T;
+    const value = updates[typedKey];
+    if (Object.prototype.hasOwnProperty.call(target, typedKey)) {
+      target[typedKey] = value!;
+    }
+  });
+}
+
+const emit = defineEmits<{
+  (e: "handleConfirm", key: string, inputValue: string): void;
+  (e: "handleCancel"): void;
+}>();
+
+function handleConfirm() {
+  emit("handleConfirm", options.value.key, options.value.value.trim());
+}
+
+function handleCancel() {
+  emit("handleCancel");
+}
+
+defineExpose({
+  show,
+  close,
+});
+</script>
+
+<style lang="scss" scoped>
+::v-deep {
+  .uni-popup-dialog {
+    background-color: var(--theme-bg-color-deep);
+    color: var(--theme-primary-color);
+    .uni-dialog-content {
+      color: var(--theme-primary-color-light-1);
+    }
+    .uni-dialog-title-text {
+      color: var(--theme-primary-color);
+    }
+    .uni-dialog-input {
+      background-color: var(--theme-bg-color);
+      border: unset;
+    }
+    .uni-dialog-button-group {
+      border: unset;
+    }
+    .uni-dialog-button {
+      height: 100rpx;
+      border-radius: 50rpx;
+      line-height: 100rpx;
+      text-align: center;
+      background-color: #fae0da;
+      border: unset;
+      margin: 0 24rpx 24rpx;
+      .uni-dialog-button-text {
+        color: #d44842;
+      }
+    }
+    .uni-border-left {
+      background-color: #d44842;
+      .uni-dialog-button-text {
+        color: #fef8f2;
+      }
+    }
+  }
+}
+</style>

+ 33 - 0
components/global/g-icon-fonts.vue

@@ -0,0 +1,33 @@
+<template>
+  <text
+    :style="{ color: props.color, 'font-size': props.size + 'px' }"
+    :class="`iconfont icon-${props.name}`"
+    @click="theClick"
+  ></text>
+</template>
+<script lang="ts" setup>
+const props = defineProps({
+  name: {
+    type: String,
+    default: "",
+  },
+  color: {
+    type: String,
+    default: "var(--theme-grey-icon-color)",
+  },
+  size: {
+    type: [Number, String],
+    default: 16,
+  },
+});
+
+const emit = defineEmits<{
+  (e: "click"): void;
+}>();
+
+function theClick() {
+  emit("click");
+}
+</script>
+
+<style scoped lang="scss"></style>

+ 65 - 0
components/global/g-page.vue

@@ -0,0 +1,65 @@
+<template>
+  <view
+    :class="['hz-page', store.appOption.theme === 'dark' ? 'theme-dark' : 'theme-light']"
+    :style="{
+      '--theme-bg-color': themeStyle.bgColor,
+      '--theme-bg-color-light-1': themeStyle.bgColorLight1,
+      '--theme-bg-white-color': themeStyle.bgWhiteColor,
+      '--theme-bg-color-deep': themeStyle.bgColorDeep,
+      '--theme-bg-book-color': themeStyle.bgColorBook,
+
+      '--theme-primary-color': themeStyle.primaryColor,
+      '--theme-primary-book-color': themeStyle.primaryBookColor,
+      '--theme-primary-color-light-1': themeStyle.primaryColorLight1,
+      '--theme-grey-color': themeStyle.greyColor,
+      '--theme-link-color': themeStyle.linkColor,
+
+      '--theme-border-color': themeStyle.borderColor,
+      '--theme-border-color-light-1': themeStyle.borderColorLight1,
+
+      '--theme-light-icon-color': themeStyle.lightIconColor,
+      '--theme-grey-icon-color': themeStyle.greyIconColor,
+      '--theme-icon-active-color': themeStyle.iconActiveColor,
+      '--theme-icon-link-color': themeStyle.iconLinkColor,
+    }"
+  >
+    <slot></slot>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { computed } from "vue";
+import store from "@/store";
+
+const pages = getCurrentPages();
+let currentRoute = pages[pages.length - 1].route;
+// console.log("当前页面路径:" + currentRoute);
+
+const themeStyle = computed(() => {
+  let isDark = store.appOption.theme === "dark";
+  if (currentRoute === "pages/reader/index") {
+    isDark = store.bookOption.theme === "dark";
+  }
+  return {
+    bgColor: isDark ? "#2A241D" : "#FEF7F1",
+    bgColorLight1: isDark ? "#3B352E" : "#F4EEE7",
+    bgColorDeep: isDark ? "#1D1710" : "#f4eee7",
+    bgWhiteColor: isDark ? "#38312B" : "#f1edea",
+    bgColorBook: isDark ? "#4c4945" : "#c0a374",
+
+    lightIconColor: isDark ? "#FEF7F1" : "#251e18",
+    greyIconColor: isDark ? "#a8a19a" : "#554334",
+    iconActiveColor: isDark ? "#EC5D57" : "#D44842",
+    iconLinkColor: isDark ? "#4F5C75" : "#92afe2",
+
+    primaryColor: isDark ? "#E9E3DC" : "#251e18",
+    primaryBookColor: isDark ? "#c9c9c9" : "#482936",
+    primaryColorLight1: isDark ? "#BDB7B1" : "#554e48",
+    greyColor: isDark ? "#8F8982" : "#857f78",
+    linkColor: isDark ? "#7E9EDA" : "#92afe2",
+
+    borderColor: isDark ? "#5d5c5a" : "#eae0e0",
+    borderColorLight1: isDark ? "#383838" : "#eee",
+  };
+});
+</script>

+ 99 - 0
components/global/g-popup.vue

@@ -0,0 +1,99 @@
+<template>
+  <uni-popup
+    :safe-area="false"
+    ref="popup"
+    :type="type"
+    :class="['u-poup', `popup-${type}`]"
+    :is-mask-click="false"
+    background-color="transparent"
+    @maskClick="close"
+  >
+    <view class="popup-content">
+      <slot></slot>
+      <view v-if="showCancel" class="group-cancel hz-button-cancel" @click="close">{{ cancelTest }}</view>
+    </view>
+  </uni-popup>
+</template>
+
+<script setup lang="ts">
+import { watch } from "vue";
+import { ref, nextTick } from "vue";
+
+const popup = ref<PopupInstance | null>(null);
+const popopAnimationCompulete = ref(false);
+
+const emit = defineEmits<{
+  (e: "update:showDialog", value: boolean): void;
+}>();
+const props = defineProps({
+  showDialog: {
+    type: Boolean,
+    default: false,
+  },
+  type: {
+    type: String,
+    default: "bottom",
+  },
+  showCancel: {
+    type: Boolean,
+    default: true,
+  },
+  cancelTest: {
+    type: String,
+    default: "取消",
+  },
+});
+watch(
+  () => props.showDialog,
+  (newVal, oldVal) => {
+    if (newVal) {
+      show();
+    } else {
+      close();
+    }
+  },
+  {
+    immediate: true,
+    deep: true,
+  },
+);
+
+function show() {
+  if (!popup.value) {
+    nextTick(() => {
+      openPopup();
+    });
+  } else {
+    openPopup();
+  }
+}
+
+function openPopup() {
+  popup.value?.open();
+  popopAnimationCompulete.value = false;
+  setTimeout(() => {
+    popopAnimationCompulete.value = true;
+  }, 400);
+}
+
+function close() {
+  if (!popopAnimationCompulete.value) return;
+  popup.value?.close();
+  emit("update:showDialog", false);
+}
+</script>
+
+<style lang="scss" scoped>
+.popup-bottom {
+  .popup-content {
+    font-size: 30rpx;
+    padding: 32rpx 32rpx 64rpx;
+    position: relative;
+    border-top-left-radius: 50rpx;
+    border-top-right-radius: 50rpx;
+    background-color: var(--theme-bg-color);
+    color: var(--theme-primary-color);
+  }
+}
+
+</style>

+ 87 - 0
components/global/g-statusbar.vue

@@ -0,0 +1,87 @@
+<template>
+  <view
+    class="status_bar"
+    :style="{
+      height: appOption.statusBarHeight + 'px',
+      backgroundColor: backgroundColor,
+      width: '100%',
+    }"
+  ></view>
+  <view
+    class="page_header"
+    :style="{
+      height: height + 'px',
+      backgroundColor: backgroundColor,
+    }"
+  >
+    <view
+      class="status_bar"
+      :style="{
+        height: height + 'px',
+      }"
+    >
+      <g-icon-fonts name="arrowleft" size="20" class="icon" @click="back" color="var(--theme-primary-color)" />
+      <view class="status_left">
+        <slot name="left"></slot>
+      </view>
+      <view class="status_title">
+        {{ props.title }}
+      </view>
+      <view class="status_right">
+        <slot name="right"></slot>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import store from "@/store";
+import { ref } from "vue";
+const appOption = store.appOption;
+
+const height = ref(appOption.statusBarHeight || 44);
+const props = defineProps({
+  title: {
+    type: String,
+    default: "",
+  },
+  backgroundColor: {
+    type: String,
+    default: "var(--theme-bg-color)",
+  },
+});
+function back() {
+  uni.navigateBack();
+}
+</script>
+
+<style scoped lang="scss">
+.status_bar {
+  width: 100%;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 0 16rpx;
+  color: var(--theme-primary-color);
+  .status_title {
+    font-weight: 600;
+    font-size: 16px;
+    flex: 1;
+    text-align: center;
+  }
+  .status_left {
+    text-align: left;
+  }
+  .icon {
+    width: 50rpx;
+    text-align: left;
+  }
+  .status_left {
+    min-width: 50rpx;
+  }
+  .status_right {
+    width: 50px;
+    text-align: right;
+  }
+}
+</style>

+ 126 - 0
components/virtualList.vue

@@ -0,0 +1,126 @@
+<template>
+	<view class="virtual-list" style="position: relative;">
+		<movable-area style="position: absolute;right: 0;width: 30px;height: 100%;">
+			<movable-view class="action-bar-box" direction="vertical" @change="change" :y="y" :animation="false">
+				<view style="border-bottom: #000 solid 2px;width: 100%;"></view>
+				<view style="border-bottom: #000 solid 2px;width: 100%;"></view>
+			</movable-view>
+		</movable-area>
+		<scroll-view scroll-y="true" :style="{
+				height: scrollHeight + 'px',
+				position: 'relative',
+				zIndex: 1
+			}" @scroll="handleScroll" :scroll-top="scrollTop" :show-scrollbar="false">
+			<view class="scroll-bar" :style="{
+					height: localHeight + 'px'
+				}"></view>
+			<view class="list" :style="{
+					transform: `translateY(${offset}px)`
+				}">
+				<view class="item-wrap" v-for="(item, index) in visibleData" :key="index">
+					<slot :item="item" :active="active"></slot>
+				</view>
+			</view>
+		</scroll-view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'VirtualList',
+		props: {
+			// 所有的items
+			items: Array,
+			// 可视区域的item数量
+			remain: Number,
+			// item大小
+			size: Number,
+			// 当前章节
+			active: Number,
+			// 可使区域高度
+			scrollHeight: Number
+		},
+		data() {
+			return {
+				// 起始
+				start: 0,
+				// 结束
+				end: this.remain,
+				// list 偏移量
+				offset: 0,
+				scrollTop: 0,
+				y: 0
+			};
+		},
+		created() {
+			//当前章节滚动至顶部
+			this.scrollTop = this.size * this.active;
+		},
+		computed: {
+			// 预留项
+			preCount() {
+				return Math.min(this.start, this.remain);
+			},
+			nextCount() {
+				return Math.min(this.items.length - this.end, this.remain);
+			},
+			// 可视区域的item
+			visibleData() {
+				const start = this.start - this.preCount;
+				const end = this.end + this.nextCount;
+				return this.items.slice(start, end);
+			},
+			localHeight() {
+				return this.items.length * this.size;
+			}
+		},
+		methods: {
+			change(e) {
+				if (e.detail.source !== 'touch') {
+					return;
+				}
+				let y = e.detail.y;
+				let scroll = (y / (this.scrollHeight - 40)) * (this.localHeight - this.scrollHeight);
+				scroll = scroll < 0 ? 0 : scroll;
+				this.scrollTop = scroll;
+			},
+			handleScroll(ev) {
+				const scrollTop = ev.detail.scrollTop;
+				this.y = (scrollTop / (this.localHeight - this.scrollHeight)) * (this.scrollHeight - 40);
+				// 开始位置
+				const start = Math.floor(scrollTop / this.size);
+				this.start = start < 0 ? 0 : start;
+				// 结束位置
+				this.end = this.start + this.remain;
+				// 计算偏移
+				const offset = scrollTop - (scrollTop % this.size) - this.preCount * this.size;
+				this.offset = offset < 0 ? 0 : offset;
+			}
+		}
+	};
+</script>
+
+<style scoped>
+	.list {
+		position: absolute;
+		top: 0;
+		left: 0;
+		width: 100%;
+	}
+
+	.action-bar-box {
+		padding: 3px;
+		display: flex;
+		flex-flow: column;
+		justify-content: space-around;
+		align-items: center;
+		position: absolute;
+		right: 0;
+		background-color: transparent;
+		border-radius: 10rpx;
+		box-shadow: 0 0 5px #000;
+		width: 20px;
+		height: 40px;
+		z-index: 2;
+	}
+</style>

+ 172 - 0
components/x-clock.vue

@@ -0,0 +1,172 @@
+<template>
+	<view class="clock-container">
+		<view class="clock-face" :style="{width:`${size}rpx`,height:`${size}rpx`}">
+			<!-- 时钟刻度 -->
+			<view v-for="(item, index) in 12" :key="index" class="hour-marker"
+				:style="{ transform: `rotate(${index * 30}deg) translateY(-${size * 0.44}rpx)` }"></view>
+			<!-- 时针 -->
+			<view class="hour-hand" :style="[hourHandStyle, { transform: `rotate(${hourDegree}deg)` }]"></view>
+			<!-- 分针 -->
+			<view class="minute-hand" :style="[minuteHandStyle, { transform: `rotate(${minuteDegree}deg)` }]"></view>
+			<!-- 秒针 -->
+			<view class="second-hand" :style="[secondHandStyle, { transform: `rotate(${secondDegree}deg)` }]"
+				v-if="showSecond"></view>
+			<!-- 中心点 -->
+			<view class="center-point" :style="centerPointStyle"></view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'DynamicClock',
+		props: {
+			showSecond: {
+				type: Boolean,
+				default: true
+			},
+			size: {
+				type: Number,
+				default: 300 // 默认时钟大小,单位rpx
+			}
+		},
+		data() {
+			return {
+				hourDegree: 0,
+				minuteDegree: 0,
+				secondDegree: 0,
+				timer: null
+			}
+		},
+		computed: {
+			hourHandStyle() {
+				return {
+					width: `${this.size * 0.02}rpx`,
+					height: `${this.size * 0.2}rpx`,
+					marginLeft: `${-this.size * 0.01}rpx`
+				}
+			},
+			minuteHandStyle() {
+				return {
+					width: `${this.size * 0.013}rpx`,
+					height: `${this.size * 0.3}rpx`,
+					marginLeft: `${-this.size * 0.0065}rpx`
+				}
+			},
+			secondHandStyle() {
+				return {
+					width: `${this.size * 0.007}rpx`,
+					height: `${this.size * 0.33}rpx`,
+					marginLeft: `${-this.size * 0.0035}rpx`
+				}
+			},
+			centerPointStyle() {
+				return {
+					width: `${this.size * 0.06}rpx`,
+					height: `${this.size * 0.06}rpx`
+				}
+			}
+		},
+		mounted() {
+			this.updateClock()
+			this.startClock()
+		},
+		beforeDestroy() {
+			this.stopClock()
+		},
+		methods: {
+			updateClock() {
+				const now = new Date()
+				const hours = now.getHours() % 12
+				const minutes = now.getMinutes()
+				const seconds = now.getSeconds()
+
+				// 计算指针角度
+				// 时针:每小时旋转30度,每分钟额外旋转0.5度
+				this.hourDegree = (hours * 30) + (minutes * 0.5)
+
+				// 分针:每分钟旋转6度
+				this.minuteDegree = minutes * 6
+
+				// 秒针:每秒旋转6度
+				this.secondDegree = seconds * 6
+			},
+			startClock() {
+				// 每秒更新一次时钟
+				this.timer = setInterval(() => {
+					this.updateClock()
+				}, 1000)
+			},
+			stopClock() {
+				if (this.timer) {
+					clearInterval(this.timer)
+					this.timer = null
+				}
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.clock-container {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		/* width: 100%; */
+	}
+
+	.clock-face {
+		position: relative;
+		border-radius: 50%;
+		background-color: #ffffff;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		border: 6rpx solid #f5f5f5;
+	}
+
+	.hour-marker {
+		position: absolute;
+		width: 6rpx;
+		height: 6rpx;
+		border-radius: 50%;
+		background-color: #aaa;
+		top: 50%;
+		left: 50%;
+		transform-origin: center;
+	}
+
+	.hour-hand {
+		position: absolute;
+		background-color: #333;
+		bottom: 50%;
+		left: 50%;
+		transform-origin: bottom center;
+		border-radius: 6rpx;
+	}
+
+	.minute-hand {
+		position: absolute;
+		background-color: #333;
+		bottom: 50%;
+		left: 50%;
+		transform-origin: bottom center;
+		border-radius: 4rpx;
+	}
+
+	.second-hand {
+		position: absolute;
+		background-color: #666;
+		bottom: 50%;
+		left: 50%;
+		transform-origin: bottom center;
+		border-radius: 2rpx;
+	}
+
+	.center-point {
+		position: absolute;
+		background-color: #333;
+		border-radius: 50%;
+		z-index: 10;
+	}
+</style>

+ 55 - 0
components/x-tabs.vue

@@ -0,0 +1,55 @@
+<template>
+	<view class="card_tag">
+		<view class="tab_item center_in" :class="current == item.type ? 'activate_tab' :''" v-for="(item,index) in list"
+			:key="index" @click="getTab(item)">{{item.title}}</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		props: {
+			list: {
+				type: Array,
+				default: []
+			},
+			current: {
+				type: [String, Number],
+				default: ''
+			}
+		},
+		data() {
+			return {
+
+			}
+		},
+		methods:{
+			getTab(event){
+				this.$emit('change',event)
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.card_tag {
+		display: flex;
+		width: fit-content;
+		width: -webkit-fit-content;
+		width: -moz-fit-content;
+		border: 1rpx solid rgba(175, 148, 93, 1);
+		border-radius: 40rpx;
+	}
+
+	.tab_item {
+		font-size: 30rpx;
+		min-width: 100rpx;
+		height: 48rpx;
+		color: rgba(177, 177, 177, 1);
+	}
+
+	.activate_tab {
+		color: #fff;
+		background-color: rgba(175, 148, 93, 1);
+		border-radius: 40rpx;
+	}
+</style>

+ 20 - 0
index.html

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <script>
+      var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
+        CSS.supports('top: constant(a)'))
+      document.write(
+        '<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
+        (coverSupport ? ', viewport-fit=cover' : '') + '" />')
+    </script>
+    <title></title>
+    <!--preload-links-->
+    <!--app-context-->
+  </head>
+  <body>
+    <div id="app"><!--app-html--></div>
+    <script type="module" src="/main.js"></script>
+  </body>
+</html>

+ 27 - 0
main.js

@@ -0,0 +1,27 @@
+import App from './App'
+import Vue from 'vue'
+// 引入全局 uView 框架
+import uView from '@/uni_modules/uview-ui';
+Vue.use(uView);
+
+// #ifndef VUE3
+import './uni.promisify.adaptor'
+Vue.config.productionTip = false
+App.mpType = 'app'
+const app = new Vue({
+	...App
+})
+app.$mount()
+// #endif
+
+// #ifdef VUE3
+import {
+	createSSRApp
+} from 'vue'
+export function createApp() {
+	const app = createSSRApp(App)
+	return {
+		app
+	}
+}
+// #endif

+ 72 - 0
manifest.json

@@ -0,0 +1,72 @@
+{
+    "name" : "baozhida-figure-platter",
+    "appid" : "__UNI__3D28200",
+    "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\"/>"
+                ]
+            },
+            /* ios打包配置 */
+            "ios" : {},
+            /* SDK配置 */
+            "sdkConfigs" : {}
+        }
+    },
+    /* 快应用特有相关 */
+    "quickapp" : {},
+    /* 小程序特有相关 */
+    "mp-weixin" : {
+        "appid" : "",
+        "setting" : {
+            "urlCheck" : false
+        },
+        "usingComponents" : true
+    },
+    "mp-alipay" : {
+        "usingComponents" : true
+    },
+    "mp-baidu" : {
+        "usingComponents" : true
+    },
+    "mp-toutiao" : {
+        "usingComponents" : true
+    },
+    "uniStatistics" : {
+        "enable" : false
+    },
+    "vueVersion" : "2"
+}

+ 92 - 0
pages.json

@@ -0,0 +1,92 @@
+{
+	"easycom": {
+		"^x-(.*)": "@/components/x-$1.vue", // 匹配components目录内的vue文件
+		"^g-(.*)": "@/components/global/g-$1.vue",
+		"^u-(.*)": "@/uni_modules/uview-ui/components/u-$1/u-$1.vue"
+	},
+	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
+		{
+			"path": "pages/index/index",
+			"style": {
+				// "navigationStyle": "custom",
+				"navigationBarTitleText": "问真排盘"
+			}
+		},
+		{
+			"path": "pages/index/horoscope",
+			"style": {
+				"navigationBarTitleText": "问真八字"
+			}
+		},
+		{
+			"path": "pages/record/index",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/school/index",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/school/reader",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/school/bookParticulars",
+			"style": {
+				// "navigationStyle": "custom",
+				"navigationBarTitleText": ""
+			}
+		},
+		{
+			"path": "pages/setting/index",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		}
+	],
+	"globalStyle": {
+		"navigationBarTextStyle": "black",
+		"navigationBarTitleText": "uni-app",
+		"navigationBarBackgroundColor": "#ffffff",
+		"backgroundColor": "#F8F8F8"
+	},
+	// 设置 TabBar
+	"tabBar": {
+		"color": "#707070",
+		"selectedColor": "#b2955d",
+		"backgroundColor": "#fff",
+		"borderStyle": "white",
+		"list": [{
+				"text": "排盘",
+				"pagePath": "pages/index/index",
+				"iconPath": "static/tabs/array_default.png",
+				"selectedIconPath": "static/tabs/array_selected.png"
+			},
+			{
+				"text": "记录",
+				"pagePath": "pages/record/index",
+				"iconPath": "static/tabs/record_default.png",
+				"selectedIconPath": "static/tabs/record_selected.png"
+			},
+			{
+				"text": "学堂",
+				"pagePath": "pages/school/index",
+				"iconPath": "static/tabs/school_default.png",
+				"selectedIconPath": "static/tabs/school_selected.png"
+			},
+			{
+				"text": "设置",
+				"pagePath": "pages/setting/index",
+				"iconPath": "static/tabs/option_default.png",
+				"selectedIconPath": "static/tabs/option_selected.png"
+			}
+		]
+	},
+	"uniIdRouter": {}
+}

+ 669 - 0
pages/index/components/calendar.js

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

+ 755 - 0
pages/index/components/calendar.vue

@@ -0,0 +1,755 @@
+<template>
+	<view class="calendar">
+		<view class="content" @tap.stop>
+			<view class="head_calendar">
+				<view>取消</view>
+				<view class="card_tag">
+					<view class="tab_item center_in" :class="current == item.type ? 'activate_tab' :''"
+						v-for="(item,index) in tagData" :key="index" @click="getTab(item)">{{item.title}}</view>
+				</view>
+				<view class="today_card">
+					<view class="today_box" v-if="current != 'four'">今</view>
+				</view>
+			</view>
+			<view class="title" v-if="current == 'solar'">
+				<view class="left">
+					{{showData}}
+				</view>
+				<view class="sure" @click="sure">
+					确定
+				</view>
+			</view>
+			<view class="card_time_tag" v-if="current != 'four'">
+				<view class="time_tag">年</view>
+				<view class="time_tag">月</view>
+				<view class="time_tag">日</view>
+				<view class="time_tag">时</view>
+				<view class="time_tag">分</view>
+			</view>
+			<picker-view :indicator-style="indicatorStyle" :value="selectValue" @change="bindChange"
+				v-if="current != 'four'">
+				<picker-view-column>
+					<view class="item" v-for="(item,index) in years" :key="index">{{item}}</view>
+				</picker-view-column>
+				<picker-view-column>
+					<view class="item" v-for="(item,index) in months" :key="index">{{item}}</view>
+				</picker-view-column>
+				<picker-view-column>
+					<view class="item" v-for="(item,index) in days" :key="index">{{item}}</view>
+				</picker-view-column>
+				<picker-view-column v-if="isHourShow">
+					<view class="item" v-for="(item,index) in lunarHour" :key="index">
+						{{isActive ? item.solar : item.label}}
+					</view>
+				</picker-view-column>
+				<picker-view-column v-if="isHourShow">
+					<view class="item" v-for="(item,index) in minuteHour" :key="index">
+						{{isActive ? item.solar : item.label}}
+					</view>
+				</picker-view-column>
+			</picker-view>
+			<view class="card_time_tag" v-if="current == 'four'">
+				<view class="time_tag">年柱</view>
+				<view class="time_tag">月柱</view>
+				<view class="time_tag">日柱</view>
+				<view class="time_tag">时柱</view>
+			</view>
+			<view class="card_four_box" v-if="current == 'four'">
+				<view class="card_prin">
+					<view class="cipal_box_flex" v-for="(item1,index) in principal" :key="index"
+						@click="getCloudy(item1,index)">
+						<view class="cipal_box" :class="getfour(item1,index) ? 'activate_cipal' : ''">{{item1}}
+						</view>
+					</view>
+				</view>
+				<view class="range_hint">查找范围:1801-2099年</view>
+			</view>
+			<view class="saving_time">
+				<span class="iconfont icon-yiwen"></span>
+				<span class="saving_title">夏令时</span>
+				<u-switch v-model="summer" activeColor="#b2955d" inactiveColor="#e9e9eb" size="18"></u-switch>
+			</view>
+			<view class="confirm_title">确定</view>
+			<!-- <view class="bottom" v-if="isSwitch">
+				<view class="switch">
+					<view class="left" :class="{active:isActive}" @click="switchBtn('solar')">
+						公历
+					</view>
+					<view class="right" :class="{active:!isActive}" @click="switchBtn('lunar')">
+						农历
+					</view>
+					<view class="bg" :class="type"></view>
+				</view>
+			</view> -->
+		</view>
+	</view>
+</template>
+
+<script>
+	import conversion from "./calendar.js"
+	import lunarHour from "./information.js"
+	import minuteHour from "./minute.js"
+	export default {
+		name: "calendar",
+		data() {
+			return {
+				oldYear: 1950, //最大年份
+				type: "solar", //lunar农历,solar公历
+				isActive: true, //true公历
+				showData: "", //显示数据
+				// showMask:false,
+				checked: true,
+				years: [],
+				months: [],
+				days: [],
+				indicatorStyle: `height: ${Math.round(uni.getSystemInfoSync().screenWidth/(750/100))}px;`,
+				lunarHour: [],
+				minuteHour: [],
+				date: "",
+				hour: "",
+				leap_month: 0, //1为闰月0为非闰月
+				sureYear: "1990", //默认弹出显示年份
+				sureMonth: "06", //默认弹出显示月份
+				selectValue: [40, 9, 9, 11, 31], //默认弹出选择的状态1990-06-10 10
+				showYear: '',
+				showMonth: '',
+				nextFinally: true,
+				tagData: [{
+					type: 'solar',
+					title: '公历',
+				}, {
+					type: 'lunar',
+					title: '农历',
+				}, {
+					type: 'four',
+					title: '四柱',
+				}],
+				summer: false,
+			};
+		},
+		props: {
+			showMask: {
+				type: Boolean,
+				default: false
+			},
+			isHourShow: {
+				type: Boolean,
+				default: true
+			},
+			isSwitch: {
+				type: Boolean,
+				default: true
+			},
+			current: {
+				type: [String, Number],
+				default: 'solar'
+			},
+			// 主柱
+			principal: {
+				type: Array,
+				default: []
+			},
+			// 副柱
+			subpost: {
+				type: Array,
+				default: []
+			},
+		},
+		components: {
+
+		},
+		created() {
+			this.getTime();
+		},
+		methods: {
+			pickerHS() {
+				this.$emit("sure", false);
+			},
+			// 提交
+			sure() {
+				if (!this.nextFinally) {
+					this.$u.toast("别着急,正在选时间");
+					return false;
+				}
+				let data = {
+					"text": this.showData, //显示数据
+					"type": this.type,
+					"brithday": {
+						"brithday[date]": this.date,
+						"brithday[hour]": this.hour,
+						"brithday[type]": this.type,
+						// "brithday[leap_month]":this.leap_month,
+					},
+					"newBirthday": {
+						brithday: {
+							date: this.date,
+							hour: this.hour,
+							type: this.type,
+						}
+					},
+					"date": this.date,
+					"hour": this.hour || 0,
+					"year": this.showYear,
+					"month": this.coverTo(Number(this.showMonth)),
+					"day": this.coverTo(this.selectValue[2] + 1),
+				}
+				if (this.type == 'lunar') {
+					data.brithday["brithday[leap_month]"] = this.leap_month;
+					data.newBirthday.brithday.leap_month = this.leap_month;
+					data.leap_month = this.leap_month;
+				}
+				this.$emit("sure", data);
+				this.nextFinally = true;
+			},
+			//进来获取公历的1990-02-10 10,初始化
+			getTime() {
+				const date = new Date();
+				const year = date.getFullYear();
+				const month = date.getMonth() + 1;
+				const day = date.getDate();
+				this.years = [];
+				this.days = [];
+				this.months = [];
+				for (let i = this.oldYear; i <= date.getFullYear(); i++) {
+					if (this.isActive) {
+						this.years.push(i);
+					} else {
+						this.years.push(i);
+						// this.years.push(conversion.toChinaYear(i));
+					}
+				}
+				if (this.isActive) {
+					this.getsolarMonthDays(this.sureYear, this.sureMonth, this.selectValue);
+				} else {
+					this.getlunarMonthDays(this.sureYear, this.sureMonth, this.selectValue);
+				}
+				this.lunarHour = lunarHour.lunarHour;
+				this.minuteHour = minuteHour.minuteHour
+			},
+			bindChange: function(e) {
+				const val = e.detail.value;
+				let year = val[0] + this.oldYear,
+					month = val[1] + 1,
+					day = val[2] + 1;
+				this.days = [];
+				this.months = [];
+				this.selectValue = e.detail.value;
+				this.nextFinally = false;
+				//公历返回每个月天数
+				if (this.isActive) {
+					this.getsolarMonthDays(year, month, val);
+				} else {
+					this.getlunarMonthDays(year, month, val);
+				}
+			},
+			// D=>DD,M=>MM,日,月转换 1=>01
+			coverTo(num) {
+				return num < 10 ? ("0" + num) : num
+			},
+			// 获取公历的月份和天数
+			getsolarMonthDays(year, month, val) {
+				// console.log(val, "下标")
+				function padZero(month) {
+					return month < 10 ? '0' + month : month.toString();
+				}
+				for (let i = 1; i <= 12; i++) {
+					// this.months.push(i + "月");
+					this.months.push(padZero(i));
+				}
+				for (let i = 1; i <= conversion.solarDays(year, month); i++) {
+					// this.days.push(i + "日");
+					this.days.push(padZero(i));
+				}
+				this.leap_month = 0;
+				this.date = `${val[0]+this.oldYear}-${this.coverTo(val[1]+1)}-${this.coverTo(val[2]+1)}`;
+				this.hour = val[3] - 1;
+				let showHour = this.isHourShow ? `${val[3] != 0 ? (val[3] - 1) + '时' : '' }` : '';
+				let minute = this.isHourShow ? `${val[4] != 0 ? (val[4] - 1) + '分' : '' }` : '';
+				this.showData =
+					`公历 ${val[0]+this.oldYear}年${this.coverTo(val[1]+1)}月${this.coverTo(val[2]+1)}日 ${showHour}${minute}`;
+				this.showYear = `${val[0]+this.oldYear}`;
+				this.showMonth = `${val[1]+1}`;
+				setTimeout(e => {
+					this.nextFinally = true;
+				}, 200)
+			},
+			// 获取农历的月份和天数
+			getlunarMonthDays(year, month, e) {
+				console.log(e, "下标")
+				let leap_month = conversion.leapMonth(year);
+				//返回农历 闰月没有就返回0
+				for (let i = 1; i <= 12; i++) {
+					this.months.push(conversion.toChinaMonth(i));
+					if (i == leap_month) {
+						this.months.push("闰" + conversion.toChinaMonth(i));
+					}
+				}
+				// 农历返回天数
+				// **leapMonth 返回闰月 conversion.leapMonth(year)
+				// **monthDays 返回非闰月的天数
+				// **leapDays 返回闰月的天数
+				if (conversion.leapMonth(year) != 0 && (conversion.leapMonth(year) == month || (month - 1) == conversion
+						.leapMonth(year))) {
+					for (let i = 1; i <= conversion.leapDays(year, conversion.leapMonth(year)); i++) {
+						this.days.push(conversion.toChinaDay(i));
+					}
+				} else {
+					let lunarMonth = '';
+					if (conversion.leapMonth(year)) {
+						lunarMonth = month < conversion.leapMonth(year) ? month : (month - 1);
+					} else {
+						lunarMonth = month;
+					}
+					for (let i = 1; i <= conversion.monthDays(year, lunarMonth); i++) {
+						this.days.push(conversion.toChinaDay(i));
+					}
+				}
+				if (e) {
+					const val = e;
+					let year = val[0] + this.oldYear,
+						month = val[1],
+						day = val[2],
+						hour = val[3] ? val[3] : 0,
+						m = "",
+						d = "",
+						h = "";
+					m = this.months[month];
+					d = this.days[day]
+					h = this.lunarHour[hour].value + "时";
+					this.leap_month = (month == leap_month && leap_month != 0) ? 1 : 0;
+					let lunarToMonth = this.coverTo(leap_month != 0 ? ((month + 1) <= leap_month ? (month + 1) : month) : (
+						month + 1));
+					console.log(lunarToMonth, "获取当前月份")
+					this.date = `${year}-${lunarToMonth}-${this.coverTo(day+1)}`;
+					this.hour = hour - 1;
+					let showHour = this.isHourShow ? `${h}` : '';
+					this.showData = `农历 ${conversion.toChinaYear(year)}年${m}${d} ${showHour == '24时' ? '' : showHour}`;
+					this.showYear = year;
+					this.showMonth = lunarToMonth;
+				}
+				setTimeout(e => {
+					this.nextFinally = true;
+				}, 150)
+			},
+			// 切换公历<=>农历 回显以及切换 this.$refs.yourname.switchBtb(type,data),type:"lunar" or "solar", data:{date:"1992-02-10",hour:3,leap_month:"0",type:"lunar"}
+			switchBtn(type, data) {
+				console.log(type, data, "切换")
+				if (data) {
+					// console.log(data,"回显")
+					// 回显
+					let year = this.$utils.formatDate(data.date).YYYY;
+					let leapMonth = type == 'solar' ? 0 : conversion.leapMonth(year),
+						month = this.$utils.formatDate(data.date).MM,
+						day = this.$utils.formatDate(data.date).D,
+						y = year - this.oldYear,
+						// m = leapMonth != 0 ? (Number(month)-1 <= leapMonth ? Number(month) - 1 : Number(month)) : Number(month) - 1,
+						m = data.leap_month == 1 ? Number(month) : (leapMonth != 0 ? (Number(month) <= leapMonth ? Number(
+							month) - 1 : Number(month)) : Number(month) - 1),
+						d = day - 1,
+						h = Number(data.hour) + 1,
+						s = Number(data.minute) + 1;
+					// console.log(leapMonth,"是否闰月",m)
+					this.leap_month = data.leap_month ? 1 : 0;
+					this.type = data.type;
+					this.sureYear = year; //默认弹出显示年份
+					this.sureMonth = month; //默认弹出显示月份
+					// console.log(m,"回显月份")
+					this.selectValue = [y, m, d, h, s]; //默认弹出选择的状态1990-06-10 10
+					// console.log(m,Number(month),leapMonth)
+					// console.log(this.sureYear,this.sureMonth,this.selectValue,"回显")
+				} else {
+					// 农历公历互相联动切换
+					if (this.type == type) {
+						return false;
+					}
+					if (type == 'lunar') { //公历切换为农历
+						let solarYear = this.selectValue[0] + this.oldYear; //公历年份
+						let leapMonth = conversion.leapMonth(solarYear);
+						let solarMonth = this.selectValue[1] + 1, //公历年月份
+							solarDay = this.selectValue[2] + 1, //公历年日份
+							hour = this.selectValue[3],
+							minute = this.selectValue[4];
+						let lunarDate = conversion.solar2lunar(solarYear, solarMonth, solarDay);
+						this.sureYear = lunarDate.lYear; //默认弹出显示年份
+						this.sureMonth = lunarDate.lMonth; //默认弹出显示月份
+						this.leap_month = leapMonth != 0 ? 1 : 0;
+						let y = lunarDate.lYear - this.oldYear,
+							m = this.leap_month == 0 ? lunarDate.lMonth - 1 : (this.selectValue[1] < leapMonth ? lunarDate
+								.lMonth - 1 : lunarDate.lMonth),
+							d = lunarDate.lDay - 1;
+						console.log(this.leap_month, "显示", leapMonth, lunarDate.lMonth, this.selectValue[1])
+						this.selectValue = [y, m, d, hour, minute]; //默认弹出选择的状态1990-06-10 10
+						// console.log(this.sureYear,this.sureMonth,this.selectValue,"回显农历")
+					} else { //农历切换为公历
+						let lunarYear = this.selectValue[0] + this.oldYear; //农历年份
+						let leapMonth = conversion.leapMonth(lunarYear); //一年中的几月份是闰月
+						let lunarMonth = leapMonth == 0 ? this.selectValue[1] + 1 : ((this.selectValue[1] + 1) <=
+								leapMonth ? (this.selectValue[1] + 1) : this.selectValue[1]), //农历年月份
+							lunarDay = this.selectValue[2] + 1, //农历年日份
+							hour = this.selectValue[3],
+							minute = this.selectValue[4];
+						// console.log(this.selectValue[1],"月份index")
+						let lunarDate = conversion.lunar2solar(lunarYear, lunarMonth, lunarDay, (leapMonth == this
+							.selectValue[1] ? true : false));
+						// console.log(lunarDate,"回显公历全部数据")
+						this.sureYear = lunarDate.cYear; //默认弹出显示年份
+						this.sureMonth = lunarDate.cMonth; //默认弹出显示月份
+						this.leap_month = leapMonth != 0 ? 1 : 0;
+						let y = lunarDate.cYear - this.oldYear,
+							m = lunarDate.cMonth - 1,
+							d = lunarDate.cDay - 1;
+						this.selectValue = [y, m, d, hour, minute]; //默认弹出选择的状态1990-06-10 10
+						// console.log(this.sureYear,this.sureMonth,this.selectValue,"回显公历")
+					}
+				}
+				this.isActive = type == 'solar' ? true : false;
+				this.type = type;
+				this.getTime();
+			},
+			getTab(event) {
+				if (event.type == 'solar') {
+					this.switchBtn('solar')
+				} else if (event.type == 'lunar') {
+					this.switchBtn('lunar')
+				}
+				this.$emit('change', event)
+			},
+			getfour(event, index) {
+				if (this.subpost[index]) {
+					return true
+				} else {
+					return false
+				}
+			},
+			getCloudy(event, index) {
+				this.$emit('cloudy', event, index)
+			},
+		}
+	}
+</script>
+
+<style lang="scss">
+	.card_time_tag {
+		width: 100%;
+		display: flex;
+		align-items: center;
+		padding-top: 10rpx;
+		border-top: 1rpx solid #f5f5f5;
+	}
+
+	.time_tag {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		font-size: 36rpx;
+		color: #82848a;
+		flex: 1;
+	}
+
+	.u-mask-zoom {
+		transform: scale(1, 1);
+	}
+
+	.calendar {
+		display: flex;
+		align-items: flex-end;
+		justify-content: center;
+		height: 100%;
+		margin-top: 20rpx;
+
+		.content {
+			display: flex;
+			flex-direction: column;
+			justify-content: center;
+			align-items: center;
+			width: 100%;
+			// height: 100%;
+			background-color: #fff;
+			border-top-left-radius: 24rpx;
+			border-top-right-radius: 24rpx;
+
+			.title {
+				display: flex;
+				flex-direction: row;
+				align-items: center;
+				height: 76rpx;
+				width: 100%;
+				background-color: #f3f4f6;
+				justify-content: space-between;
+				border-radius: 40rpx;
+				margin-bottom: 20rpx;
+
+				.left {
+					color: #666666;
+					font-size: 30rpx;
+					padding-left: 30rpx;
+				}
+
+				.sure {
+					display: flex;
+					justify-content: center;
+					align-items: center;
+					width: 120rpx;
+					height: 100%;
+					background-color: #c4c4c4;
+					color: #fff;
+					font-size: 30rpx;
+					border-radius: 50rpx;
+				}
+			}
+
+			picker-view {
+				width: 100%;
+				height: 480rpx;
+				// margin-top: 20rpx;
+
+				/deep/.item {
+					display: flex;
+					align-items: center;
+					justify-content: center;
+					color: #333333;
+					font-size: 32rpx;
+					font-weight: bold;
+				}
+
+				.uni-picker-view-wrapper {
+					uni-picker-view-column {
+						display: flex;
+						align-items: center;
+						justify-content: center;
+						color: #333333;
+						font-size: 32rpx;
+						font-weight: bold;
+
+						.uni-picker-view-group {
+							.uni-picker-view-content {
+								text-align: center;
+								line-height: 110rpx;
+
+								.item {
+									display: flex;
+									align-items: center;
+									justify-content: center;
+									color: #333333;
+									font-size: 32rpx;
+									font-weight: bold;
+								}
+							}
+						}
+					}
+				}
+
+				/deep/.uni-picker-view-indicator {
+					// text-align: center;
+					// line-height: 110rpx;
+				}
+			}
+
+			.bottom {
+				height: 110rpx;
+				width: calc(100% - 31rpx);
+				display: flex;
+				align-items: center;
+				justify-content: flex-end;
+				padding-right: 31rpx;
+				background-color: #FFFFFF;
+
+				.switch {
+					background-color: #E6E6E6;
+					width: 180rpx;
+					height: 70rpx;
+					display: flex;
+					flex-direction: row;
+					border-radius: 35rpx;
+					justify-content: space-around;
+					align-items: center;
+					position: relative;
+
+					.left {
+						position: absolute;
+						z-index: 1;
+						left: 25%;
+						font-size: 28rpx;
+						transform: translateX(-50%);
+					}
+
+					.right {
+						color: #333333;
+						font-size: 28rpx;
+						position: absolute;
+						right: 0;
+						transform: translateX(-25%);
+						color: #333333;
+						z-index: 1;
+					}
+
+					.bg {
+						background-color: #EB344A;
+						color: #333333;
+						height: 70rpx;
+						border-radius: 40rpx;
+						width: 90rpx;
+						position: absolute;
+						// right: 0;
+						top: 0;
+						z-index: 0;
+
+					}
+
+					.active {
+						color: #FFFFFF;
+					}
+
+					.lunar {
+						left: 90rpx;
+						animation: switchsolar 500ms;
+					}
+
+					@keyframes switchsolar {
+						0% {
+							left: 0;
+						}
+
+						100% {
+							left: 90rpx;
+						}
+					}
+
+					.solar {
+						left: 0;
+						animation: switchlunar 500ms;
+					}
+
+					@keyframes switchlunar {
+						0% {
+							left: 90rpx;
+						}
+
+						100% {
+							left: 0;
+						}
+					}
+				}
+			}
+		}
+	}
+
+	.head_calendar {
+		width: 100%;
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		margin-bottom: 20rpx;
+	}
+
+	.card_tag {
+		display: flex;
+		width: fit-content;
+		width: -webkit-fit-content;
+		width: -moz-fit-content;
+		border: 1rpx solid #e9e9e9;
+		border-radius: 40rpx;
+	}
+
+	.tab_item {
+		font-size: 30rpx;
+		min-width: 140rpx;
+		height: 64rpx;
+		color: rgba(177, 177, 177, 1);
+	}
+
+	.activate_tab {
+		color: #d5bd9d;
+		background-color: #010101;
+		border-radius: 40rpx;
+	}
+
+	.today_card {
+		width: 80rpx;
+	}
+
+	.today_box {
+		width: 80rpx;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		height: 64rpx;
+		color: #d5bd9d;
+		background-color: #010101;
+		border-radius: 40rpx;
+	}
+
+	.range_hint {
+		display: flex;
+		justify-content: center;
+		font-size: 32rpx;
+		margin-top: 30rpx;
+	}
+
+	.saving_time {
+		width: 100%;
+		display: flex;
+		align-items: center;
+		justify-content: flex-end;
+	}
+
+	.icon-yiwen {
+		color: #82848a;
+		font-size: 40rpx;
+	}
+
+	.saving_title {
+		color: #82848a;
+		font-size: 32rpx;
+		padding: 0rpx 10rpx;
+	}
+
+	.confirm_title {
+		margin-top: 20rpx;
+		border-radius: 40rpx;
+		color: #d5bd9d;
+		width: 100%;
+		height: 80rpx;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		background-color: #010101;
+	}
+
+	.card_four_box {
+		width: 100%;
+		margin-bottom: 50rpx;
+	}
+
+	.card_prin {
+		display: flex;
+		align-items: center;
+		justify-content: space-around;
+		flex-wrap: wrap;
+		margin-top: 20rpx;
+	}
+
+	.cipal_box_flex {
+		flex: 0 0 25%;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+	}
+
+	.cipal_box {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		color: #fff;
+		width: 120rpx;
+		height: 120rpx;
+		font-size: 50rpx;
+		border-radius: 50%;
+		background-color: #b8b8b8;
+		margin-bottom: 20rpx;
+	}
+
+	.activate_cipal {
+		background-color: #b2955d;
+	}
+</style>

+ 132 - 0
pages/index/components/information.js

@@ -0,0 +1,132 @@
+const data = {
+	// 农历新历时辰
+	lunarHour:[
+		{
+			value:"24",
+			solar:"未知",
+			label:"未知"
+		},
+		{
+			value:"0",
+			solar:"00",
+			label:"00"
+		},
+		{
+			value:"1",
+			solar:"01",
+			label:"01"
+		},
+		{
+			value:"2",
+			solar:"02",
+			label:"02"
+		},
+		{
+			value:"3",
+			solar:"03",
+			label:"03"
+		},
+		{
+			value:"4",
+			solar:"04",
+			label:"04"
+		},
+		{
+			value:"5",
+			solar:"05",
+			label:"05"
+		},
+		{
+			value:"6",
+			solar:"06",
+			label:"06"
+		},
+		{
+			value:"7",
+			solar:"07",
+			label:"07"
+		},
+		{
+			value:"8",
+			solar:"08",
+			label:"08"
+		},
+		{
+			value:"9",
+			solar:"09",
+			label:"09"
+		},
+		{
+			value:"10",
+			solar:"10",
+			label:"10"
+		},
+		{
+			value:"11",
+			solar:"11",
+			label:"11"
+		},
+		{
+			value:"12",
+			solar:"12",
+			label:"12"
+		},
+		{
+			value:"13",
+			solar:"13",
+			label:"13"
+		},
+		{
+			value:"14",
+			solar:"14",
+			label:"14"
+		},
+		{
+			value:"15",
+			solar:"15",
+			label:"15"
+		},
+		{
+			value:"16",
+			solar:"16",
+			label:"16"
+		},
+		{
+			value:"17",
+			solar:"17",
+			label:"17"
+		},
+		{
+			value:"18",
+			solar:"18",
+			label:"18"
+		},
+		{
+			value:"19",
+			solar:"19",
+			label:"19"
+		},
+		{
+			value:"20",
+			solar:"20",
+			label:"20"
+		},
+		{
+			value:"21",
+			solar:"21",
+			label:"21"
+		},
+		{
+			value:"22",
+			solar:"22",
+			label:"22"
+		},
+		{
+			value:"23",
+			solar:"23",
+			label:"23"
+		},
+	]
+}
+
+export default data

+ 311 - 0
pages/index/components/minute.js

@@ -0,0 +1,311 @@
+const data = {
+	// 农历新历时辰
+	minuteHour: [{
+			value: "99",
+			solar: "未知",
+			label: "未知"
+		},
+		{
+			value: "0",
+			solar: "00",
+			label: "00"
+		},
+		{
+			value: "1",
+			solar: "01",
+			label: "01"
+		},
+		{
+			value: "2",
+			solar: "02",
+			label: "02"
+		},
+		{
+			value: "3",
+			solar: "03",
+			label: "03"
+		},
+		{
+			value: "4",
+			solar: "04",
+			label: "04"
+		},
+		{
+			value: "5",
+			solar: "05",
+			label: "05"
+		},
+		{
+			value: "6",
+			solar: "06",
+			label: "06"
+		},
+		{
+			value: "7",
+			solar: "07",
+			label: "07"
+		},
+		{
+			value: "8",
+			solar: "08",
+			label: "08"
+		},
+		{
+			value: "9",
+			solar: "09",
+			label: "09"
+		},
+		{
+			value: "10",
+			solar: "10",
+			label: "10"
+		},
+		{
+			value: "11",
+			solar: "11",
+			label: "11"
+		},
+		{
+			value: "12",
+			solar: "12",
+			label: "12"
+		},
+		{
+			value: "13",
+			solar: "13",
+			label: "13"
+		},
+		{
+			value: "14",
+			solar: "14",
+			label: "14"
+		},
+		{
+			value: "15",
+			solar: "15",
+			label: "15"
+		},
+		{
+			value: "16",
+			solar: "16",
+			label: "16"
+		},
+		{
+			value: "17",
+			solar: "17",
+			label: "17"
+		},
+		{
+			value: "18",
+			solar: "18",
+			label: "18"
+		},
+		{
+			value: "19",
+			solar: "19",
+			label: "19"
+		},
+		{
+			value: "20",
+			solar: "20",
+			label: "20"
+		},
+		{
+			value: "21",
+			solar: "21",
+			label: "21"
+		},
+		{
+			value: "22",
+			solar: "22",
+			label: "22"
+		},
+		{
+			value: "23",
+			solar: "23",
+			label: "23"
+		},
+		{
+			value: "24",
+			solar: "24",
+			label: "24"
+		},
+		{
+			value: "25",
+			solar: "25",
+			label: "25"
+		},
+		{
+			value: "26",
+			solar: "26",
+			label: "26"
+		},
+		{
+			value: "27",
+			solar: "27",
+			label: "27"
+		},
+		{
+			value: "28",
+			solar: "28",
+			label: "28"
+		},
+		{
+			value: "29",
+			solar: "29",
+			label: "29"
+		},
+		{
+			value: "30",
+			solar: "30",
+			label: "30"
+		},
+		{
+			value: "31",
+			solar: "31",
+			label: "31"
+		},
+		{
+			value: "32",
+			solar: "32",
+			label: "32"
+		},
+		{
+			value: "33",
+			solar: "33",
+			label: "33"
+		},
+		{
+			value: "34",
+			solar: "34",
+			label: "34"
+		},
+		{
+			value: "35",
+			solar: "35",
+			label: "35"
+		},
+		{
+			value: "36",
+			solar: "36",
+			label: "36"
+		},
+		{
+			value: "37",
+			solar: "37",
+			label: "37"
+		},
+		{
+			value: "38",
+			solar: "38",
+			label: "38"
+		},
+		{
+			value: "39",
+			solar: "39",
+			label: "39"
+		},
+		{
+			value: "40",
+			solar: "40",
+			label: "40"
+		},
+		{
+			value: "41",
+			solar: "41",
+			label: "41"
+		},
+		{
+			value: "42",
+			solar: "42",
+			label: "42"
+		},
+		{
+			value: "43",
+			solar: "43",
+			label: "43"
+		},
+		{
+			value: "44",
+			solar: "44",
+			label: "44"
+		},
+		{
+			value: "45",
+			solar: "45",
+			label: "45"
+		},
+		{
+			value: "46",
+			solar: "46",
+			label: "46"
+		},
+		{
+			value: "47",
+			solar: "47",
+			label: "47"
+		},
+		{
+			value: "48",
+			solar: "48",
+			label: "48"
+		},
+		{
+			value: "49",
+			solar: "49",
+			label: "49"
+		},
+		{
+			value: "50",
+			solar: "50",
+			label: "50"
+		},
+		{
+			value: "51",
+			solar: "51",
+			label: "51"
+		},
+		{
+			value: "52",
+			solar: "52",
+			label: "52"
+		},
+		{
+			value: "53",
+			solar: "53",
+			label: "53"
+		},
+		{
+			value: "54",
+			solar: "54",
+			label: "54"
+		},
+		{
+			value: "55",
+			solar: "55",
+			label: "55"
+		},
+		{
+			value: "56",
+			solar: "56",
+			label: "56"
+		},
+		{
+			value: "57",
+			solar: "57",
+			label: "57"
+		},
+		{
+			value: "58",
+			solar: "58",
+			label: "58"
+		},
+		{
+			value: "59",
+			solar: "59",
+			label: "59"
+		},
+	]
+}
+
+export default data

+ 8 - 0
pages/index/horoscope.vue

@@ -0,0 +1,8 @@
+<template>
+</template>
+
+<script>
+</script>
+
+<style>
+</style>

+ 328 - 0
pages/index/index.vue

@@ -0,0 +1,328 @@
+<template>
+	<view class="content">
+		<view class="notice_box">
+			<u-notice-bar :text="text1" duration="7000" color="#000000" bgColor="#ffffff"></u-notice-bar>
+		</view>
+		<view class="box_platter">
+			<input class="ter_input" :value="input" type="text" placeholder="请输入姓名" />
+			<view class="tabs_card_ter">
+				<x-tabs :current="current" :list="genderList" @change="change($event,1)"></x-tabs>
+				<x-tabs :current="current1" :list="genderList1" @change="change($event,2)"></x-tabs>
+			</view>
+			<view class="box_date">{{solarTime}}</view>
+			<view class="box_date">未知地区-即北京时间</view>
+			<view class="space_between" style="margin-top: 20rpx;">
+				<view class="same_row_in title_time">
+					<span>真太阳时:</span>
+					<span>{{solarTime}}</span>
+				</view>
+				<view class="same_row_in title_time">
+					<span class="save_title">保存</span>
+					<u-switch v-model="saveSummer" activeColor="#b2955d" inactiveColor="#e9e9eb" size="18"></u-switch>
+				</view>
+			</view>
+			<view class="same_row_in title_time" style="margin-top: 10rpx;">
+				<span>地址经纬:</span>
+				<span>北纬39.93 东经116.42</span>
+			</view>
+			<view style="margin-top: 10rpx;">
+				<span class="name_title">分类:</span>
+			</view>
+			<view class="same_row_in box_clock">
+				<x-clock :size="160" :showSecond="true"></x-clock>
+				<view class="card_clock_box">
+					<view class="card_prin">
+						<view class="cipal_box_flexil" v-for="(item,index) in principal" :key="index">
+							{{item}}
+						</view>
+					</view>
+					<view class="title_time marginTop_10">农历:2025年二月初一 巳时</view>
+					<view class="title_time marginTop_10">公历:2025-02-28 09:55:34</view>
+				</view>
+			</view>
+			<view class="btn_ter center_in" @click="getStartPlatting">开始排盘</view>
+			<!-- <calendar :current="current1" @change="change($event,2)"></calendar> -->
+		</view>
+		<u-popup :show="timeShow" :round="10" closeOnClickOverlay @close="close">
+			<view class="card_calendar">
+				<calendar :current="current1" :principal="principal" :subpost="subpost" @cloudy="cloudy"
+					@change="change($event,2)">
+				</calendar>
+			</view>
+			<view class="card_four" v-if="eraData.length > 0 && eraFlag">
+				<view class="card_column">
+					<view class="card_era_ial">
+						<view class="era_item center_in" :class="item == currentEra?'activate_title':''"
+							v-for="(item,index) in eraData" :key="index" @click="selectEra(item,index)">
+							{{item}}
+						</view>
+					</view>
+					<!-- 90rpx 260rpx 440rpx 620rpx-->
+					<view class="arrows_box" :style="{left: arrowLeft}"></view>
+				</view>
+			</view>
+		</u-popup>
+	</view>
+</template>
+
+<script>
+	import calendar from './components/calendar.vue';
+	export default {
+		components: {
+			calendar
+		},
+		data() {
+			return {
+				timeShow: false,
+				input: '',
+				current: '男',
+				genderList: [{
+					type: '男',
+					title: '男',
+				}, {
+					type: '女',
+					title: '女',
+				}],
+				current1: 'solar',
+				genderList1: [{
+					type: 'solar',
+					title: '公历',
+				}, {
+					type: 'lunar',
+					title: '农历',
+				}, {
+					type: 'four',
+					title: '四柱',
+				}],
+				currentEra: '',
+				eraFlag: false,
+				eraData: [],
+				eraData1: ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'],
+				eraData2: ['子', '寅', '辰', '午', '申', '戌'],
+				eraData3: ['丑', '卯', '巳', '未', '酉', '亥'],
+				principal: ['甲', '乙', '丙', '丁', '子', '丑', '寅', '卯'],
+				currentland: 0,
+				subpost: [],
+				arrowLeft: '90rpx',
+				text1: 'uView UI众多组件覆盖开发过程的各个需求,组件功能丰富,多端兼容。让您快速集成,开箱即用',
+				solarTime: '1990年10月10日 10:30',
+				saveSummer: true,
+			}
+		},
+		onLoad() {
+
+		},
+		methods: {
+			change(event, type) {
+				if (type == 1) {
+					this.current = event.type
+				}
+				if (type == 2) {
+					uni.hideTabBar()
+					this.timeShow = true
+					this.current1 = event.type
+					if(event.type != 'four'){
+						this.eraFlag = false
+					}
+				}
+			},
+			// 选择
+			cloudy(event, index) {
+				this.currentland = index
+				if (index == 0 || index == 4) {
+					this.arrowLeft = '90rpx'
+				} else if (index == 1 || index == 5) {
+					this.arrowLeft = '260rpx'
+				} else if (index == 2 || index == 6) {
+					this.arrowLeft = '440rpx'
+				} else if (index == 3 || index == 7) {
+					this.arrowLeft = '620rpx'
+				}
+				if (index < 4) {
+					this.eraFlag = true
+					this.eraData = this.eraData1
+				} else {
+					var num = index - 4
+					var title = this.principal[num]
+					var array1 = ['甲', '丙', '戊', '庚', '壬']
+					var array2 = ['乙', '丁', '己', '辛', '癸']
+					var flag = array1.includes(title)
+					var flag1 = array2.includes(title)
+					if (flag) {
+						this.eraData = this.eraData2
+					}
+					if (flag1) {
+						this.eraData = this.eraData3
+					}
+					this.eraFlag = true
+				}
+				this.currentEra = event
+			},
+			// 选择天干地支
+			selectEra(event, index) {
+				this.$set(this.principal, this.currentland, event);
+				this.$set(this.subpost, this.currentland, event);
+				let num = 0
+				if (this.currentland < 4) {
+					num = this.currentland + 4
+				} else {
+					num = this.currentland - 3
+				}
+				if (this.currentland == 7) {
+					this.eraFlag = false
+				} else {
+					this.cloudy(event, num)
+				}
+			},
+			getStartPlatting(){
+				uni.navigateTo({
+					url: '/pages/index/horoscope'
+				})
+			},
+			close() {
+				setTimeout(() => {
+					uni.showTabBar()
+				}, 100)
+				this.timeShow = false
+			}
+		}
+	}
+</script>
+
+<style>
+	.notice_box {
+		width: calc(100% - 40rpx);
+		padding: 20rpx;
+	}
+
+	.box_platter {
+		margin: 0rpx 20rpx 20rpx 20rpx;
+		padding: 50rpx;
+		background-color: #fff;
+		border-radius: 20rpx;
+	}
+
+	.ter_input {
+		padding-bottom: 10rpx;
+		border-bottom: 1rpx solid #f5f5f5;
+	}
+
+	.tabs_card_ter {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		margin-top: 20rpx;
+	}
+
+	.btn_ter {
+		margin-top: 30rpx;
+		width: 100%;
+		padding: 26rpx 0rpx;
+		border-radius: 50rpx;
+		background-color: rgba(1, 1, 1, 1);
+		color: rgba(213, 189, 157, 1);
+	}
+
+	.card_calendar {
+		padding: 30rpx;
+		// #ifdef APP-PLUS || MP-WEIXIN
+		padding-bottom: 0 !important;
+		padding-bottom: constant(safe-area-inset-bottom) !important;
+		padding-bottom: env(safe-area-inset-bottom) !important;
+		// #endif
+	}
+
+	.card_four {
+		position: absolute;
+		top: -60rpx;
+		width: 100%;
+		height: auto;
+	}
+
+	.card_column {
+		position: absolute;
+		bottom: 0;
+		width: calc(100% - 40rpx);
+		height: auto;
+		border-radius: 20rpx;
+		background-color: #fff;
+		padding: 20rpx;
+	}
+
+	.card_era_ial {
+		display: flex;
+		flex-direction: row;
+		flex-wrap: wrap;
+		justify-content: space-between;
+	}
+
+	.era_item {
+		color: #a3a3a3;
+		font-size: 46rpx;
+		width: 140rpx;
+		padding: 10rpx 0rpx;
+		border-radius: 40rpx;
+	}
+
+	.activate_title {
+		color: #fff;
+		background-color: #b2955d;
+	}
+
+	.arrows_box {
+		position: absolute;
+		bottom: -54rpx;
+		border: 24rpx solid transparent;
+		border-top: 36rpx solid #ffffff;
+	}
+
+	.box_date {
+		font-size: 30rpx;
+		padding: 20rpx 10rpx;
+		border-bottom: 1rpx solid #f5f5f5;
+	}
+
+	.title_time {
+		font-size: 22rpx;
+		color: #9e9e9e;
+	}
+
+	.save_title {
+		color: #333333;
+		font-size: 28rpx;
+		margin-right: 10rpx;
+	}
+
+	.marginTop_10 {
+		margin-top: 10rpx;
+	}
+
+	.name_title {
+		color: #333;
+		margin-right: 10rpx;
+		font-size: 30rpx;
+	}
+
+	.box_clock {
+		padding: 20rpx 0rpx;
+	}
+
+	.card_clock_box {
+		margin-left: 40rpx;
+	}
+
+	.card_prin {
+		display: flex;
+		align-items: center;
+		justify-content: space-around;
+		flex-wrap: wrap;
+	}
+
+	.cipal_box_flexil {
+		flex: 0 0 25%;
+		font-size: 32rpx;
+		display: flex;
+		justify-content: flex-start;
+		align-items: center;
+	}
+</style>

+ 8 - 0
pages/record/index.vue

@@ -0,0 +1,8 @@
+<template>
+</template>
+
+<script>
+</script>
+
+<style>
+</style>

+ 195 - 0
pages/school/bookParticulars.vue

@@ -0,0 +1,195 @@
+<template>
+	<view>
+		<view class="box_particular">
+			<image class="img_book" mode="scaleToFill" src="/static/mh-BsqlF_P3.png"></image>
+			<view style="display: flex;flex-direction: column;margin-left: 20rpx;overflow: hidden;width: 100%;">
+				<view class="book_title">
+					《奇门遁甲入门基础》
+				</view>
+				<view class="author_title">[未知]紫武真人</view>
+			</view>
+		</view>
+		<view class="card_brief">
+			<view class="box_brief">
+				<view class="title_brief">阅读进度</view>
+				<view class="quantity_num"><span class="num_title">78%</span>进度</view>
+			</view>
+			<view class="box_brief">
+				<view class="title_brief">阅读人数</view>
+				<view class="quantity_num"><span class="num_title">1000+</span>人</view>
+			</view>
+			<view class="box_brief">
+				<view class="title_brief">本书字书</view>
+				<view class="quantity_num"><span class="num_title">2.29</span>万字</view>
+			</view>
+		</view>
+		<view class="card_synopsis">
+			<view class="synopsis_title">书籍简介</view>
+			<view class="synopsis">{{bookIntroduction}}</view>
+		</view>
+		<view style="width: 100%;height: 180rpx;" class="subside"></view>
+		<view class="bottom_menu">
+			<!-- <view class="menu_btn center_in">加入书架</view> -->
+			<view class="menu_btnil center_in">已加入书架</view>
+			<view class="menu_btn center_in">目录</view>
+			<view class="menu_btn_orange center_in" @click="goRead">阅读</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				bookIntroduction: '初学命理,晦涩难懂的古籍令诸多习命者不得其门,深感命学之深奥。本系列基础知识,通过简单通俗地论述,将命理古籍的基础通俗表述,其内容涵盖了五行阴阳、生克制化、十神含义等重点,更有利于习命者对命理的全面掌握。'
+			}
+		},
+		methods: {
+			goRead() {
+				uni.navigateTo({
+					url: '/pages/school/reader'
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	page {
+		background-color: #fff;
+	}
+
+	.box_particular {
+		display: flex;
+		padding: 0rpx 20rpx;
+		border-bottom: 1rpx solid #e4e7ed;
+	}
+
+	.img_book {
+		flex: none;
+		width: 260rpx;
+		height: 320rpx;
+		border-radius: 20rpx;
+		margin-bottom: 60rpx;
+	}
+
+	.book_title {
+		width: 100%;
+		font-size: 46rpx;
+		font-weight: bold;
+		white-space: nowrap;
+		text-overflow: ellipsis;
+		overflow: hidden;
+	}
+
+	.author_title {
+		margin-top: 30rpx;
+		margin-left: 20rpx;
+		font-size: 30rpx;
+		color: #999999;
+	}
+
+	.card_brief {
+		display: flex;
+		align-items: center;
+		padding: 30rpx 0rpx;
+		border-bottom: 20rpx solid #F8F8F8;
+	}
+
+	.box_brief {
+		flex: 1;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+	}
+
+	.box_brief:nth-child(-n + 2) {
+		border-right: 1rpx solid #e4e7ed;
+	}
+
+	.title_brief {
+		font-size: 28rpx;
+		color: #999999;
+	}
+
+
+	.quantity_num {
+		margin-top: 10rpx;
+		font-size: 28rpx;
+		color: #555555;
+
+		.num_title {
+			font-size: 40rpx;
+			font-weight: bold;
+		}
+	}
+
+	.card_synopsis {
+		padding: 30rpx;
+	}
+
+	.synopsis_title {
+		font-size: 36rpx;
+		font-weight: bold;
+		margin-bottom: 10rpx;
+	}
+
+	.synopsis {
+		font-size: 32rpx;
+		line-height: 48rpx;
+	}
+
+	.bottom_menu {
+		position: fixed;
+		bottom: 0;
+		left: 0;
+		right: 0;
+		border-top: 1rpx solid #e4e7ed;
+		display: flex;
+		align-items: center;
+		padding: 20rpx 30rpx;
+		// #ifdef APP-PLUS || MP-WEIXIN
+		padding-bottom: 0 !important;
+		padding-bottom: constant(safe-area-inset-bottom) !important;
+		padding-bottom: env(safe-area-inset-bottom) !important;
+		// #endif
+	}
+
+	.subside {
+		padding-bottom: 0 !important;
+		padding-bottom: constant(safe-area-inset-bottom) !important;
+		padding-bottom: env(safe-area-inset-bottom) !important;
+	}
+
+	.menu_btn {
+		font-size: 30rpx;
+		flex: 1;
+		padding: 26rpx;
+		border-radius: 20rpx;
+		background-color: #eeeeee;
+		margin: 0rpx 10rpx;
+		border: 1rpx solid #eeeeee;
+	}
+
+	.menu_btnil {
+		font-size: 30rpx;
+		flex: 1;
+		padding: 26rpx;
+		border-radius: 20rpx;
+		background-color: #ffffff;
+		margin: 0rpx 10rpx;
+		color: #b2955d;
+		border: 1rpx solid #b2955d;
+	}
+
+	.menu_btn_orange {
+		font-size: 30rpx;
+		flex: 1;
+		padding: 26rpx;
+		border-radius: 20rpx;
+		background-color: #cea872;
+		margin: 0rpx 10rpx;
+		color: #fff;
+		border: 1rpx solid #cea872;
+	}
+</style>

+ 237 - 0
pages/school/index.vue

@@ -0,0 +1,237 @@
+<template>
+	<view style="display: flex;flex-direction: column;height: calc(100vh - 100rpx);"
+		:style="{paddingTop:safetyTop+'px'}">
+		<view class="box_school">
+			<view class="box_warehouse" :class="curveFlag ? '' : 'activate_ware'" @click="getCurve(1)">书库</view>
+			<view class="box_warehouse" :class="curveFlag ? 'activate_ware' : ''" @click="getCurve(2)">我的</view>
+		</view>
+		<view class="head_school_box">
+			<view class="bank_box border_bank">
+				<view class="item_bank center_in" :class="item == tabCurrent?'activate_box':''"
+					v-for="(item,index) in tabList" :key="index">
+					{{item}}
+				</view>
+			</view>
+			<view class="bank_box">
+				<view class="item_bank center_in" :class="item == tabCurrent1?'activate_box':''"
+					v-for="(item,index) in tabList1" :key="index">
+					{{item}}
+				</view>
+			</view>
+		</view>
+		<!-- 滚动容器 -->
+		<scroll-view class="scroll-view" scroll-y>
+			<view class="head_school">
+				<image class="img_bsq" mode="scaleToFill" src="/static/mh-BsqlF_P3.png"></image>
+				<view class="card_book">
+					<view class="box_book" v-for="(item,index) in bookList" :key="index">
+						<view class="book_img">
+							<image class="img_book" mode="scaleToFill" src="/static/bk.png"></image>
+						</view>
+						<view class="book_title center_in">{{item}}</view>
+					</view>
+				</view>
+				<view class="card_numerology">
+					<view class="book_numerology" v-for="(item,index) in bookList" :key="index" @click="goBook">
+						<view class="box_numerology">
+							<image class="img_numerology" mode="scaleToFill" src="/static/bk.png"></image>
+							<view class="numerology_title center_in">{{item}}</view>
+						</view>
+					</view>
+					<view style="width: 33%;height: 1rpx;"></view>
+				</view>
+			</view>
+		</scroll-view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				safetyTop: 0,
+				curveFlag: false,
+				tabList: ['山', '医', '命', '相', '卜'],
+				tabList1: ['八字', '紫薇斗数', '七政四余'],
+				tabCurrent: '命',
+				tabCurrent1: '八字',
+				bookList: ['基础知识', '专业知识', '神煞大全', '懒人表']
+			}
+		},
+		onLoad() {
+			const {
+				safeAreaInsets
+			} = uni.getSystemInfoSync()
+			if (safeAreaInsets) {
+				this.safetyTop = safeAreaInsets.top
+			}
+		},
+		methods: {
+			getCurve(type) {
+				if (type == 1) {
+					this.curveFlag = false
+				} else {
+					this.curveFlag = true
+				}
+			},
+			// 书详情
+			goBook() {
+				uni.navigateTo({
+					url: '/pages/school/bookParticulars'
+				})
+			},
+		}
+	}
+</script>
+
+<style lang="scss">
+	page {
+		background-color: #fff;
+	}
+
+	.box_school {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		background-color: #fff;
+		padding: 24rpx 30rpx 24rpx 30rpx;
+	}
+
+	.box_warehouse {
+		width: 150rpx;
+		text-align: center;
+		font-size: 32rpx;
+		font-weight: bold;
+		color: rgba(192, 192, 192, 1);
+	}
+
+	.activate_ware {
+		position: relative;
+		color: #000;
+	}
+
+	.activate_ware::after {
+		content: '';
+		position: absolute;
+		left: calc(50% - 25rpx);
+		bottom: -10rpx;
+		width: 50rpx;
+		height: 6rpx;
+		background-color: $color-subject;
+	}
+
+	.scroll-view {
+		flex: 1;
+		overflow: hidden;
+	}
+
+	.head_school {
+		height: 100%;
+		background-color: #fff;
+	}
+
+	.head_school_box {
+		border-top: 20rpx solid #F8F8F8;
+		background-color: #fff;
+		padding: 0rpx 20rpx;
+	}
+
+	.bank_box {
+		display: flex;
+		align-items: center;
+		padding: 20rpx 0rpx;
+	}
+
+	.border_bank {
+		border-bottom: 1rpx solid #e4e7ed;
+	}
+
+	.item_bank {
+		flex: 1;
+		color: #646566;
+		font-size: 34rpx;
+	}
+
+	.activate_box {
+		color: #b2955d;
+	}
+
+	.img_bsq {
+		vertical-align: middle;
+		margin: 10rpx 20rpx 0rpx 20rpx;
+		width: calc(100% - 40rpx);
+		height: 380rpx;
+	}
+
+	.card_book {
+		display: flex;
+		align-items: center;
+		background-color: #f3f4f6;
+		padding-top: 10rpx;
+		margin: 0rpx 20rpx;
+		border-bottom-left-radius: 30rpx;
+		border-bottom-right-radius: 30rpx;
+	}
+
+	.box_book {
+		flex: 1;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+	}
+
+	.book_img {
+		width: calc(100% - 40rpx);
+		height: 200rpx;
+		padding: 0rpx 20rpx 6rpx 20rpx;
+		border-bottom: 1rpx solid #e4e7ed;
+	}
+
+	.img_book {
+		width: 100%;
+		height: 200rpx;
+		border-radius: 6rpx;
+	}
+
+	.book_title {
+		width: calc(100% - 40rpx);
+		padding: 20rpx;
+		font-size: 30rpx;
+	}
+
+	.box_book:nth-child(-n + 3) .book_title {
+		border-right: 1rpx solid #e4e7ed;
+	}
+
+	.card_numerology {
+		margin-top: 20rpx;
+		display: flex;
+		flex-direction: row;
+		flex-wrap: wrap;
+		justify-content: space-between;
+		margin-bottom: 20rpx;
+	}
+
+	.book_numerology {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		width: 33%;
+		margin-bottom: 10rpx;
+	}
+
+	.box_numerology {
+		width: 180rpx;
+	}
+
+	.img_numerology {
+		width: 100%;
+		height: 230rpx;
+		border-radius: 10rpx;
+	}
+
+	.numerology_title {
+		font-size: 30rpx;
+		padding: 10rpx 0rpx;
+	}
+</style>

+ 85 - 0
pages/school/reader.vue

@@ -0,0 +1,85 @@
+<template>
+	<reader-component :novel-id="novelId" @back="handleBack" @search="handleSearch" @add-to-shelf="handleAddToShelf"
+		@add-bookmark="handleAddBookmark" @show-notes="handleShowNotes" @show-progress="handleShowProgress"
+		@prev-page="handlePrevPage" @next-page="handleNextPage" @chapter-change="handleChapterChange"
+		@chapter-loaded="handleChapterLoaded" @chapters-loaded="handleChaptersLoaded" @theme-change="handleThemeChange"
+		@font-size-change="handleFontSizeChange" @margin-change="handleMarginChange"
+		@line-height-change="handleLineHeightChange" />
+</template>
+
+<script>
+	import ReaderComponent from './reader/reader-component.vue'
+
+	export default {
+		components: {
+			ReaderComponent
+		},
+		data() {
+			return {
+				novelId: 1
+			}
+		},
+		onLoad(options) {
+			if (options && options.id) {
+				this.novelId = Number(options.id)
+			}
+		},
+		methods: {
+			handleBack() {
+				uni.navigateBack(1)
+				console.log('返回上一页')
+			},
+			handleSearch() {
+				console.log('搜索')
+			},
+			handleAddToShelf() {
+				uni.showToast({
+					title: '已加入书架',
+					icon: 'success'
+				})
+			},
+			handleAddBookmark() {
+				console.log('添加书签')
+			},
+			handleShowNotes() {
+				uni.showToast({
+					title: '笔记功能开发中',
+					icon: 'none'
+				})
+			},
+			handleShowProgress() {
+				uni.showToast({
+					title: '进度功能开发中',
+					icon: 'none'
+				})
+			},
+			handlePrevPage() {
+				console.log('上一页')
+			},
+			handleNextPage() {
+				console.log('下一页')
+			},
+			handleChapterChange(chapter) {
+				console.log('章节切换', chapter)
+			},
+			handleChapterLoaded(chapter) {
+				console.log('章节加载完成', chapter)
+			},
+			handleChaptersLoaded(chapters) {
+				console.log('目录加载完成', chapters)
+			},
+			handleThemeChange(index) {
+				console.log('主题切换', index)
+			},
+			handleFontSizeChange(size) {
+				console.log('字体大小改变', size)
+			},
+			handleMarginChange(margin) {
+				console.log('边距改变', margin)
+			},
+			handleLineHeightChange(lineHeight) {
+				console.log('行高改变', lineHeight)
+			}
+		}
+	}
+</script>

+ 120 - 0
pages/school/reader/novel-service.js

@@ -0,0 +1,120 @@
+// 小说数据服务
+// 引入 uni-app API
+// import uni from "@dcloudio/uni-app"
+export default {
+	// 获取小说章节列表
+	getChapterList(novelId) {
+		// 这里应该从API获取数据,这里使用模拟数据
+		return new Promise((resolve) => {
+			setTimeout(() => {
+				resolve([{
+						id: 1,
+						title: "题记与引"
+					},
+					{
+						id: 2,
+						title: "第一章 命理之源"
+					},
+					{
+						id: 3,
+						title: "第二章 八字基础"
+					},
+					{
+						id: 4,
+						title: "第三章 五行生克"
+					},
+					{
+						id: 5,
+						title: "第四章 十神详解"
+					},
+					// 更多章节...
+				])
+			}, 300)
+		})
+	},
+
+	// 获取章节内容
+	getChapterContent(chapterId) {
+		// 这里应该从API获取数据,这里使用模拟数据
+		return new Promise((resolve) => {
+			setTimeout(() => {
+				let content = ""
+
+				if (chapterId === 1) {
+					content = `子平书,宋徐公东斋已详明矣。传有《渊海》、《渊源》之集,其理则一,篇句俱同。今之用者,惟宗《渊海》。而《渊源》亦有妙用,或未之集。今将二书合并参考,遗失总归一轨。加之诗诀、起例,增解字义。後学识之,则二书了然在目,无遗矣。谨白。
+
+引
+
+子平渊海之理,始自唐大夫李公虚中,以人生年月日时,生克旺相,休囚制化,决人生之祸福,其验神矣。及公薨,昌黎韩炉为之作墓志,以记之後。经吕大夫才又裁定之,并无述作之由,但云得之于师,不敢私传,恐后人妄加穿凿,遂秘其说。`
+				} else if (chapterId === 2) {
+					content = `命理之源,始于先天。人生于世,皆有定数。八字者,乃年月日时四柱,每柱一字,故曰八字。此八字包含天干地支,阴阳五行,生克制化,无所不包。
+
+古人云:"一命二运三风水,四积阴德五读书。"命为先天,乃父母所生;运为后天,乃时运所至;风水为居住环境;积德为善行义举;读书为增长智慧。五者相辅相成,缺一不可。
+
+然而,命理之学,重在明理,不在迷信。知命而不认命,顺应天时而不违背人伦。明白此理,方能趋吉避凶,改善人生。`
+				} else {
+					content = `这是第${chapterId}章的内容,正在加载中...`
+				}
+
+				resolve({
+					id: chapterId,
+					title: `第${chapterId}章`,
+					content: content,
+				})
+			}, 300)
+		})
+	},
+
+	// 保存阅读进度
+	saveReadingProgress(novelId, chapterId, position) {
+		return new Promise((resolve) => {
+			// 保存到本地存储
+			uni.setStorageSync(`novel_progress_${novelId}`, {
+				chapterId,
+				position,
+			})
+			resolve(true)
+		})
+	},
+
+	// 获取阅读进度
+	getReadingProgress(novelId) {
+		return new Promise((resolve) => {
+			const progress = uni.getStorageSync(`novel_progress_${novelId}`)
+			resolve(progress || {
+				chapterId: 1,
+				position: 0
+			})
+		})
+	},
+
+	// 添加书签
+	addBookmark(novelId, chapterId, position, text) {
+		return new Promise((resolve) => {
+			// 获取现有书签
+			const bookmarksKey = `novel_bookmarks_${novelId}`
+			const bookmarks = uni.getStorageSync(bookmarksKey) || []
+
+			// 添加新书签
+			bookmarks.push({
+				id: Date.now(),
+				chapterId,
+				position,
+				text,
+				createTime: new Date().toISOString(),
+			})
+
+			// 保存书签
+			uni.setStorageSync(bookmarksKey, bookmarks)
+			resolve(true)
+		})
+	},
+
+	// 获取书签列表
+	getBookmarks(novelId) {
+		return new Promise((resolve) => {
+			const bookmarks = uni.getStorageSync(`novel_bookmarks_${novelId}`) || []
+			resolve(bookmarks)
+		})
+	},
+}

+ 97 - 0
pages/school/reader/reader-catalog.vue

@@ -0,0 +1,97 @@
+<template>
+  <view class="catalog-container" v-if="show">
+    <view class="catalog-header">
+      <text class="title">目录</text>
+      <text class="close-btn" @tap="onClose">×</text>
+    </view>
+    <scroll-view scroll-y class="chapter-list">
+      <view 
+        v-for="chapter in chapters" 
+        :key="chapter.id" 
+        class="chapter-item"
+        :class="{ active: currentChapterId === chapter.id }"
+        @tap="onSelect(chapter)"
+      >
+        <text>{{ chapter.title }}</text>
+      </view>
+    </scroll-view>
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'ReaderCatalog',
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    },
+    chapters: {
+      type: Array,
+      default: () => []
+    },
+    currentChapterId: {
+      type: Number,
+      default: 1
+    }
+  },
+  methods: {
+    onClose() {
+      this.$emit('close')
+    },
+    onSelect(chapter) {
+      this.$emit('select', chapter)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.catalog-container {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 80%;
+  height: 100%;
+  background-color: #fff;
+  z-index: 300;
+  display: flex;
+  flex-direction: column;
+  box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
+}
+
+.catalog-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 30rpx;
+  border-bottom: 1px solid #eee;
+}
+
+.title {
+  font-size: 36rpx;
+  font-weight: bold;
+}
+
+.close-btn {
+  font-size: 40rpx;
+  padding: 0 20rpx;
+}
+
+.chapter-list {
+  flex: 1;
+  padding: 20rpx 0;
+}
+
+.chapter-item {
+  padding: 30rpx;
+  border-bottom: 1px solid #f5f5f5;
+  font-size: 30rpx;
+}
+
+.chapter-item.active {
+  color: #007aff;
+  background-color: #f0f0f0;
+}
+</style>
+

+ 500 - 0
pages/school/reader/reader-component.vue

@@ -0,0 +1,500 @@
+<template>
+	<view class="reader-container" :style="containerStyle">
+		<!-- 顶部导航栏 -->
+		<reader-header :show="showControls" :capsule-info="capsuleInfo" :header-height="headerHeight" @back="goBack"
+			@search="handleSearch" @add-to-shelf="handleAddToShelf" @add-bookmark="handleAddBookmark" />
+		<!-- 内容区域 -->
+		<view class="content-wrapper" @tap="toggleControls">
+			<sliding-container :disabled="showControls || showSettingsPanel || showCatalogPanel"
+				@slide-prev="handleSlidePrev" @slide-next="handleSlideNext">
+				<!-- 上一章内容 -->
+				<template #prev>
+					<view class="content-area" :style="contentAreaStyle" v-if="prevChapter">
+						<view class="chapter-title">{{ prevChapter.title }}</view>
+						<view class="chapter-content" :style="contentStyle">{{ prevChapter.content }}</view>
+					</view>
+				</template>
+				<!-- 当前章节内容 -->
+				<template #current>
+					<view class="content-area" :style="contentAreaStyle">
+						<view class="chapter-title" v-if="currentChapter.title">{{ currentChapter.title }}</view>
+						<view class="chapter-content" :style="contentStyle">{{ currentChapter.content }}</view>
+					</view>
+				</template>
+				<!-- 下一章内容 -->
+				<template #next>
+					<view class="content-area" :style="contentAreaStyle" v-if="nextChapter">
+						<view class="chapter-title">{{ nextChapter.title }}</view>
+						<view class="chapter-content" :style="contentStyle">{{ nextChapter.content }}</view>
+					</view>
+				</template>
+			</sliding-container>
+		</view>
+		<!-- 底部控制栏 -->
+		<reader-footer :show="showControls" @show-catalog="toggleCatalog" @show-notes="toggleNotes"
+			@show-progress="toggleProgress" @show-settings="toggleSettings" />
+		<!-- 设置面板 -->
+		<reader-settings :show="showSettingsPanel" :theme-index="themeIndex" :font-size="fontSize" :margin="margin"
+			:line-height="lineHeight" :bg-colors="bgColors" @close="toggleSettings" @theme-change="changeTheme"
+			@font-size-change="changeFontSize" @margin-change="changeMargin" @line-height-change="changeLineHeight" />
+		<!-- 目录面板 -->
+		<reader-catalog :show="showCatalogPanel" :chapters="chapters" :current-chapter-id="currentChapterId"
+			@select="selectChapter" @close="toggleCatalog" />
+		<!-- 遮罩层 -->
+		<view class="mask" v-if="showCatalogPanel || showNotesPanel || showProgressPanel" @tap="closeAllPanels"></view>
+		<!-- 翻页提示 -->
+		<view class="page-tip" v-if="showPageTip">
+			<text>{{ pageTipText }}</text>
+		</view>
+	</view>
+</template>
+
+<script>
+	import ReaderHeader from './reader-header.vue'
+	import ReaderFooter from './reader-footer.vue'
+	import ReaderSettings from './reader-settings.vue'
+	import ReaderCatalog from './reader-catalog.vue'
+	import SlidingContainer from './sliding-container.vue'
+	import novelService from './novel-service.js'
+
+	export default {
+		name: 'ReaderComponent',
+		components: {
+			ReaderHeader,
+			ReaderFooter,
+			ReaderSettings,
+			ReaderCatalog,
+			SlidingContainer
+		},
+		props: {
+			novelId: {
+				type: Number,
+				required: true
+			}
+		},
+		data() {
+			return {
+				currentChapterId: 1,
+				chapters: [],
+				currentChapter: {
+					title: '',
+					content: '加载中...'
+				},
+				showControls: false,
+				showSettingsPanel: false,
+				showCatalogPanel: false,
+				showNotesPanel: false,
+				showProgressPanel: false,
+				fontSize: 18,
+				margin: 20,
+				lineHeight: 1.8,
+				themeIndex: 0,
+				bgColors: [{
+						bg: '#f8f4e9',
+						text: '#3e3d3b'
+					},
+					{
+						bg: '#ffffff',
+						text: '#333333'
+					},
+					{
+						bg: '#e9e9e9',
+						text: '#333333'
+					},
+					{
+						bg: '#cce8cf',
+						text: '#333333'
+					},
+					{
+						bg: '#333333',
+						text: '#c4c4c4'
+					},
+				],
+				touchStartX: 0,
+				touchEndX: 0,
+				showPageTip: false,
+				pageTipText: '',
+				capsuleInfo: {
+					height: 0,
+					top: 0,
+					right: 0,
+					statusBarHeight: 0
+				},
+				headerHeight: 90,
+				prevChapter: null,
+				nextChapter: null,
+			}
+		},
+		computed: {
+			containerStyle() {
+				const theme = this.bgColors[this.themeIndex];
+				return {
+					backgroundColor: theme.bg,
+					color: theme.text,
+					// padding: `0 ${this.margin}px`,
+					minHeight: '100vh'
+				}
+			},
+			contentStyle() {
+				return {
+					fontSize: `${this.fontSize}px`,
+					lineHeight: String(this.lineHeight), // 确保 lineHeight 是字符串
+				}
+			},
+			contentAreaStyle() {
+				// #ifdef MP-WEIXIN
+				const topPadding = this.capsuleInfo.statusBarHeight + this.headerHeight / 2;
+				return {
+					paddingTop: `${topPadding}px`,
+					paddingBottom: '120rpx',
+					minHeight: '100vh',
+					boxSizing: 'border-box'
+				}
+				// #endif
+
+				// #ifndef MP-WEIXIN
+				return {
+					paddingTop: '100rpx',
+					paddingBottom: '120rpx',
+					minHeight: '100vh',
+					boxSizing: 'border-box'
+				}
+				// #endif
+			}
+		},
+		methods: {
+			initPage() {
+				this.getCapsuleInfo();
+				this.loadSettings();
+				this.loadChapterList();
+				this.loadReadingProgress();
+			},
+			toggleControls() {
+				this.showControls = !this.showControls
+				if (!this.showControls) {
+					this.closeAllPanels()
+				}
+			},
+			closeAllPanels() {
+				this.showSettingsPanel = false
+				this.showCatalogPanel = false
+				this.showNotesPanel = false
+				this.showProgressPanel = false
+			},
+			goBack() {
+				this.$emit('back')
+			},
+			handleSearch() {
+				this.$emit('search')
+			},
+			handleAddToShelf() {
+				this.$emit('add-to-shelf')
+			},
+			handleAddBookmark() {
+				novelService.addBookmark(
+					this.novelId,
+					this.currentChapterId,
+					0,
+					this.currentChapter.content.substring(0, 50)
+				).then(() => {
+					uni.showToast({
+						title: '已添加书签',
+						icon: 'success'
+					})
+					this.$emit('add-bookmark')
+				})
+			},
+			toggleCatalog() {
+				this.showCatalogPanel = !this.showCatalogPanel
+				this.showSettingsPanel = false
+				this.showNotesPanel = false
+				this.showProgressPanel = false
+			},
+			toggleNotes() {
+				this.showNotesPanel = !this.showNotesPanel
+				this.showSettingsPanel = false
+				this.showCatalogPanel = false
+				this.showProgressPanel = false
+				this.$emit('show-notes')
+			},
+			toggleProgress() {
+				this.showProgressPanel = !this.showProgressPanel
+				this.showSettingsPanel = false
+				this.showCatalogPanel = false
+				this.showNotesPanel = false
+				this.$emit('show-progress')
+			},
+			toggleSettings() {
+				this.showSettingsPanel = !this.showSettingsPanel
+			},
+			changeTheme(index) {
+				this.themeIndex = index;
+				// 强制更新视图
+				this.$forceUpdate();
+				// 保存设置
+				uni.setStorageSync('reader_theme', index);
+				this.$emit('theme-change', index);
+			},
+			changeFontSize(value) {
+				this.fontSize = Number(value)
+				uni.setStorageSync('reader_font_size', this.fontSize)
+				this.$emit('font-size-change', this.fontSize)
+			},
+			changeMargin(value) {
+				this.margin = Number(value)
+				uni.setStorageSync('reader_margin', this.margin)
+				this.$emit('margin-change', this.margin)
+			},
+			changeLineHeight(value) {
+				this.lineHeight = Number(value)
+				uni.setStorageSync('reader_line_height', this.lineHeight)
+				this.$emit('line-height-change', this.lineHeight)
+			},
+			touchStart(e) {
+				this.touchStartX = e.changedTouches[0].clientX
+			},
+			touchEnd(e) {
+				this.touchEndX = e.changedTouches[0].clientX
+				const diffX = this.touchEndX - this.touchStartX
+
+				if (this.showControls) return
+
+				if (diffX > 100) {
+					this.prevPage()
+				} else if (diffX < -100) {
+					this.nextPage()
+				}
+			},
+			prevPage() {
+				if (this.currentChapterId <= 1) {
+					this.showPageTip = true
+					this.pageTipText = '已经是第一章了'
+					setTimeout(() => {
+						this.showPageTip = false
+					}, 1500)
+					return
+				}
+
+				this.loadChapter(this.currentChapterId - 1)
+
+				this.showPageTip = true
+				this.pageTipText = '上一章'
+				setTimeout(() => {
+					this.showPageTip = false
+				}, 1500)
+
+				this.$emit('prev-page')
+			},
+			nextPage() {
+				if (this.currentChapterId >= this.chapters.length) {
+					this.showPageTip = true
+					this.pageTipText = '已经是最后一章了'
+					setTimeout(() => {
+						this.showPageTip = false
+					}, 1500)
+					return
+				}
+
+				this.loadChapter(this.currentChapterId + 1)
+
+				this.showPageTip = true
+				this.pageTipText = '下一章'
+				setTimeout(() => {
+					this.showPageTip = false
+				}, 1500)
+
+				this.$emit('next-page')
+			},
+			selectChapter(chapter) {
+				this.loadChapter(chapter.id)
+				this.showCatalogPanel = false
+				this.$emit('chapter-change', chapter)
+			},
+			loadChapter(chapterId) {
+				this.currentChapter = {
+					title: '',
+					content: '加载中...'
+				}
+
+				novelService.getChapterContent(chapterId).then(chapter => {
+					this.currentChapter = chapter
+					this.currentChapterId = chapterId
+
+					// 预加载相邻章节
+					this.preloadAdjacentChapters()
+
+					novelService.saveReadingProgress(this.novelId, chapterId, 0)
+					this.$emit('chapter-loaded', chapter)
+				})
+			},
+			loadChapterList() {
+				novelService.getChapterList(this.novelId).then(chapters => {
+					this.chapters = chapters
+					this.$emit('chapters-loaded', chapters)
+				})
+			},
+			loadReadingProgress() {
+				novelService.getReadingProgress(this.novelId).then(progress => {
+					if (progress && progress.chapterId) {
+						this.loadChapter(progress.chapterId)
+					} else {
+						this.loadChapter(1)
+					}
+				})
+			},
+			loadSettings() {
+				const theme = uni.getStorageSync('reader_theme')
+				const fontSize = uni.getStorageSync('reader_font_size')
+				const margin = uni.getStorageSync('reader_margin')
+				const lineHeight = uni.getStorageSync('reader_line_height')
+
+				this.themeIndex = theme !== '' ? Number(theme) : 0
+				this.fontSize = fontSize !== '' ? Number(fontSize) : 18
+				this.margin = margin !== '' ? Number(margin) : 20
+				this.lineHeight = lineHeight !== '' ? Number(lineHeight) : 1.8
+			},
+			getCapsuleInfo() {
+				// #ifdef MP-WEIXIN
+				const systemInfo = uni.getSystemInfoSync();
+				const menuButtonInfo = uni.getMenuButtonBoundingClientRect();
+
+				this.capsuleInfo = {
+					height: menuButtonInfo.height,
+					top: menuButtonInfo.top,
+					right: systemInfo.windowWidth - menuButtonInfo.right,
+					statusBarHeight: systemInfo.statusBarHeight
+				};
+
+				this.headerHeight = (menuButtonInfo.top - systemInfo.statusBarHeight) * 2 + menuButtonInfo.height + 10;
+				// #endif
+
+				// #ifndef MP-WEIXIN
+				const systemInfo = uni.getSystemInfoSync();
+				this.capsuleInfo.statusBarHeight = systemInfo.statusBarHeight;
+				this.headerHeight = 90;
+				// #endif
+			},
+			// 处理滑动到上一章
+			handleSlidePrev() {
+				if (this.currentChapterId <= 1) {
+					this.showPageTip = true
+					this.pageTipText = '已经是第一章了'
+					setTimeout(() => {
+						this.showPageTip = false
+					}, 1500)
+					return
+				}
+
+				const targetChapterId = this.currentChapterId - 1
+				this.loadChapter(targetChapterId)
+				this.$emit('prev-page')
+			},
+
+			// 处理滑动到下一章
+			handleSlideNext() {
+				if (this.currentChapterId >= this.chapters.length) {
+					this.showPageTip = true
+					this.pageTipText = '已经是最后一章了'
+					setTimeout(() => {
+						this.showPageTip = false
+					}, 1500)
+					return
+				}
+
+				const targetChapterId = this.currentChapterId + 1
+				this.loadChapter(targetChapterId)
+				this.$emit('next-page')
+			},
+
+			// 预加载相邻章节
+			preloadAdjacentChapters() {
+				// 预加载上一章
+				if (this.currentChapterId > 1) {
+					novelService.getChapterContent(this.currentChapterId - 1).then(chapter => {
+						this.prevChapter = chapter
+					})
+				} else {
+					this.prevChapter = null
+				}
+
+				// 预加载下一章
+				if (this.currentChapterId < this.chapters.length) {
+					novelService.getChapterContent(this.currentChapterId + 1).then(chapter => {
+						this.nextChapter = chapter
+					})
+				} else {
+					this.nextChapter = null
+				}
+			},
+		},
+		created() {
+			this.initPage()
+		},
+		onShow() {
+			this.getCapsuleInfo()
+		},
+		onResize() {
+			this.getCapsuleInfo()
+		}
+	}
+</script>
+
+<style scoped>
+	.reader-container {
+		position: relative;
+		width: 100%;
+		min-height: 100vh;
+		display: flex;
+		flex-direction: column;
+		transition: all 0.3s ease;
+		overflow: hidden;
+	}
+
+	.content-area {
+		flex: 1;
+		min-height: 100vh;
+		padding: 20rpx;
+	}
+
+	.chapter-title {
+		font-size: 36rpx;
+		font-weight: bold;
+		text-align: center;
+		margin: 30rpx 0;
+	}
+
+	.chapter-content {
+		text-align: justify;
+	}
+
+	.mask {
+		position: fixed;
+		top: 0;
+		right: 0;
+		bottom: 0;
+		left: 0;
+		background-color: rgba(0, 0, 0, 0.5);
+		z-index: 250;
+	}
+
+	.page-tip {
+		position: fixed;
+		top: 50%;
+		left: 50%;
+		transform: translate(-50%, -50%);
+		background-color: rgba(0, 0, 0, 0.7);
+		color: #fff;
+		padding: 20rpx 40rpx;
+		border-radius: 10rpx;
+		font-size: 28rpx;
+		z-index: 300;
+	}
+
+	/* 添加新的样式 */
+	.content-wrapper {
+		flex: 1;
+		position: relative;
+		overflow: hidden;
+		min-height: 100vh;
+	}
+</style>

+ 74 - 0
pages/school/reader/reader-footer.vue

@@ -0,0 +1,74 @@
+<template>
+	<view class="footer" v-if="show">
+		<view class="footer-item" @tap="onCatalog">
+			<text class="iconfont icon-mulu"></text>
+			<text>目录</text>
+		</view>
+		<view class="footer-item" @tap="onNotes">
+			<text class="iconfont icon-liuyan"></text>
+			<text>记录</text>
+		</view>
+		<view class="footer-item" @tap="onProgress">
+			<text class="iconfont icon-schedule"></text>
+			<text>进度</text>
+		</view>
+		<view class="footer-item" @tap="onSettings">
+			<text class="iconfont icon-Aa"></text>
+			<text>设置</text>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'ReaderFooter',
+		props: {
+			show: {
+				type: Boolean,
+				default: false
+			}
+		},
+		methods: {
+			onCatalog() {
+				this.$emit('show-catalog')
+			},
+			onNotes() {
+				this.$emit('show-notes')
+			},
+			onProgress() {
+				this.$emit('show-progress')
+			},
+			onSettings() {
+				this.$emit('show-settings')
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.footer {
+		position: fixed;
+		bottom: 0;
+		left: 0;
+		right: 0;
+		height: 100rpx;
+		display: flex;
+		justify-content: space-around;
+		align-items: center;
+		background-color: #ffffff;
+		z-index: 100;
+	}
+
+	.footer-item {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		color: #000000;
+		font-size: 24rpx;
+	}
+
+	.icon {
+		font-family: "iconfont";
+		font-size: 40rpx;
+	}
+</style>

+ 114 - 0
pages/school/reader/reader-header.vue

@@ -0,0 +1,114 @@
+<template>
+	<view class="header" v-if="show" :style="headerStyle">
+		<view class="header-left">
+			<view class="back-btn" @tap="onBack">
+				<text class="iconfont icon-left"></text>
+			</view>
+		</view>
+		<view class="header-right">
+			<view class="icon-btn" @tap="onSearch">
+				<text class="iconfont icon-search"></text>
+				<text class="icon-text">搜索</text>
+			</view>
+			<view class="icon-btn" @tap="onAddToShelf">
+				<text class="iconfont icon-bookshelf"></text>
+				<text class="icon-text">加入书架</text>
+			</view>
+			<view class="icon-btn" @tap="onAddBookmark">
+				<text class="iconfont icon-bookmark"></text>
+				<text class="icon-text">添加书签</text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'ReaderHeader',
+		props: {
+			show: {
+				type: Boolean,
+				default: false
+			},
+			capsuleInfo: {
+				type: Object,
+				default: () => ({
+					height: 0,
+					top: 0,
+					right: 0,
+					statusBarHeight: 0
+				})
+			},
+			headerHeight: {
+				type: Number,
+				default: 90
+			}
+		},
+		computed: {
+			headerStyle() {
+				return {
+					height: `${this.headerHeight}rpx`,
+					paddingTop: `${this.capsuleInfo.statusBarHeight}px`
+				}
+			}
+		},
+		methods: {
+			onBack() {
+				this.$emit('back')
+			},
+			onSearch() {
+				this.$emit('search')
+			},
+			onAddToShelf() {
+				this.$emit('add-to-shelf')
+			},
+			onAddBookmark() {
+				this.$emit('add-bookmark')
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.header {
+		position: fixed;
+		top: 0;
+		left: 0;
+		right: 0;
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		padding-left: 20rpx;
+		padding-right: 20rpx;
+		background-color: #ffffff;
+		z-index: 100;
+	}
+
+	.header-left,
+	.header-right {
+		display: flex;
+		align-items: center;
+	}
+
+	.header-right {
+		gap: 30rpx;
+	}
+
+	.back-btn,
+	.icon-btn {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		color: #000000;
+	}
+
+	.icon {
+		font-family: "iconfont";
+		font-size: 40rpx;
+	}
+
+	.icon-text {
+		font-size: 20rpx;
+		margin-top: 4rpx;
+	}
+</style>

+ 218 - 0
pages/school/reader/reader-settings.vue

@@ -0,0 +1,218 @@
+<template>
+	<view class="settings-panel" v-if="show">
+		<view class="settings-header">
+			<text>设置</text>
+			<text class="close-btn" @tap="onClose">×</text>
+		</view>
+		<view class="settings-section">
+			<text class="section-title">页面颜色</text>
+			<view class="color-options">
+				<view v-for="(color, index) in bgColors" :key="index" class="color-option"
+					:class="{ active: themeIndex === index }" :style="{ backgroundColor: color.bg }"
+					@tap="onThemeChange(index)"></view>
+			</view>
+		</view>
+		<view class="settings-section">
+			<text class="section-title">文字</text>
+			<view class="slider-container">
+				<slider class="custom-slider" :value="fontSize" activeColor="#cbbfa7" block-color="#988153"
+					background-color="#f8f8f8" :min="12" :max="30" :step="1" show-value @change="onFontSizeChange"
+					@changing="onFontSizeChanging" />
+			</view>
+		</view>
+		<view class="settings-section">
+			<text class="section-title">边距</text>
+			<view class="slider-container">
+				<slider class="custom-slider" :value="margin" activeColor="#cbbfa7" block-color="#988153"
+					background-color="#f8f8f8" :min="10" :max="50" :step="5" show-value @change="onMarginChange"
+					@changing="onMarginChanging" />
+			</view>
+		</view>
+		<view class="settings-section">
+			<text class="section-title">行距</text>
+			<view class="slider-container">
+				<slider class="custom-slider" :value="lineHeight" activeColor="#cbbfa7" block-color="#988153"
+					background-color="#f8f8f8" :min="1" :max="3" :step="0.1" show-value @change="onLineHeightChange"
+					@changing="onLineHeightChanging" />
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'ReaderSettings',
+		props: {
+			show: {
+				type: Boolean,
+				default: false
+			},
+			themeIndex: {
+				type: Number,
+				default: 0
+			},
+			fontSize: {
+				type: Number,
+				default: 18
+			},
+			margin: {
+				type: Number,
+				default: 20
+			},
+			lineHeight: {
+				type: Number,
+				default: 1.8
+			},
+			bgColors: {
+				type: Array,
+				default: () => [{
+						bg: '#f8f4e9',
+						text: '#3e3d3b'
+					}, // 米黄色
+					{
+						bg: '#ffffff',
+						text: '#333333'
+					}, // 白色
+					{
+						bg: '#e9e9e9',
+						text: '#333333'
+					}, // 浅灰色
+					{
+						bg: '#cce8cf',
+						text: '#333333'
+					}, // 浅绿色
+					{
+						bg: '#333333',
+						text: '#c4c4c4'
+					}, // 夜间模式
+				]
+			}
+		},
+		methods: {
+			onClose() {
+				this.$emit('close')
+			},
+			onThemeChange(index) {
+				// 添加触感反馈
+				uni.vibrateShort({
+					success: () => {
+						this.$emit('theme-change', index)
+					}
+				});
+			},
+			onFontSizeChange(e) {
+				this.$emit('font-size-change', e.detail.value)
+			},
+			onMarginChange(e) {
+				this.$emit('margin-change', e.detail.value)
+			},
+			onLineHeightChange(e) {
+				this.$emit('line-height-change', e.detail.value)
+			},
+			onFontSizeChanging(e) {
+				this.$emit('font-size-change', Number(e.detail.value))
+			},
+			onMarginChanging(e) {
+				this.$emit('margin-change', Number(e.detail.value))
+			},
+			onLineHeightChanging(e) {
+				this.$emit('line-height-change', Number(e.detail.value))
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.settings-panel {
+		position: fixed;
+		bottom: 0;
+		left: 0;
+		right: 0;
+		background-color: #fff;
+		border-top-left-radius: 30rpx;
+		border-top-right-radius: 30rpx;
+		padding: 30rpx;
+		z-index: 200;
+		/* box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1); */
+	}
+
+	.settings-header {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		margin-bottom: 30rpx;
+		font-size: 32rpx;
+		font-weight: bold;
+	}
+
+	.close-btn {
+		font-size: 40rpx;
+		padding: 0 20rpx;
+	}
+
+	.settings-section {
+		display: flex;
+		align-items: center;
+		margin-bottom: 30rpx;
+	}
+
+	.section-title {
+		width: 120rpx;
+		flex: none;
+		font-size: 28rpx;
+		margin-right: 20rpx;
+		margin-bottom: 20rpx;
+		color: #666;
+	}
+
+	.color-options {
+		width: 100%;
+		display: flex;
+		justify-content: space-between;
+		margin-bottom: 20rpx;
+	}
+
+	.color-option {
+		width: 70rpx;
+		height: 70rpx;
+		border-radius: 50%;
+		border: 2rpx solid #ddd;
+		transition: all 0.3s ease;
+		cursor: pointer;
+	}
+
+	.color-option.active {
+		border: 4rpx solid #988153;
+		transform: scale(1.1);
+	}
+
+	.slider-container {
+		width: 100%;
+		display: flex;
+		align-items: center;
+	}
+
+	.slider-label {
+		width: 60rpx;
+		text-align: center;
+		font-size: 28rpx;
+		color: #666;
+	}
+
+	::v-deep uni-slider {
+		margin: 0px;
+	}
+
+	.custom-slider {
+		width: 100%;
+	}
+
+	::v-deep uni-slider .uni-slider-handle-wrapper {
+		height: 30rpx;
+		border-radius: 40rpx;
+	}
+
+	::v-deep uni-slider .uni-slider-track {
+		border-radius: 40rpx;
+	}
+</style>

+ 191 - 0
pages/school/reader/sliding-container.vue

@@ -0,0 +1,191 @@
+<template>
+  <view 
+    class="sliding-container" 
+    :style="containerStyle"
+    @touchstart="touchStart"
+    @touchmove="touchMove"
+    @touchend="touchEnd"
+  >
+    <!-- 上一页内容 -->
+    <view class="page prev-page" :style="prevPageStyle">
+      <slot name="prev"></slot>
+    </view>
+    
+    <!-- 当前页内容 -->
+    <view class="page current-page" :style="currentPageStyle">
+      <slot name="current"></slot>
+    </view>
+    
+    <!-- 下一页内容 -->
+    <view class="page next-page" :style="nextPageStyle">
+      <slot name="next"></slot>
+    </view>
+  </view>
+</template>
+
+<script>
+const SWIPE_THRESHOLD = 50; // 滑动阈值
+const ANIMATION_DURATION = 300; // 动画持续时间(毫秒)
+
+export default {
+  name: 'SlidingContainer',
+  data() {
+    return {
+      touchStartX: 0,
+      touchStartY: 0,
+      deltaX: 0,
+      deltaY: 0,
+      isAnimating: false,
+      containerWidth: 0,
+    }
+  },
+  props: {
+    // 是否禁用滑动
+    disabled: {
+      type: Boolean,
+      default: false
+    }
+  },
+  computed: {
+    containerStyle() {
+      return {
+        transform: `translateX(${this.deltaX}px)`,
+        transition: this.isAnimating ? `transform ${ANIMATION_DURATION}ms ease-out` : 'none'
+      }
+    },
+    prevPageStyle() {
+      return {
+        transform: 'translateX(-100%)',
+      }
+    },
+    currentPageStyle() {
+      return {
+        transform: 'translateX(0)',
+      }
+    },
+    nextPageStyle() {
+      return {
+        transform: 'translateX(100%)',
+      }
+    }
+  },
+  mounted() {
+    // 获取容器宽度
+    const query = uni.createSelectorQuery().in(this);
+    query.select('.sliding-container').boundingClientRect(data => {
+      if (data) {
+        this.containerWidth = data.width;
+      }
+    }).exec();
+  },
+  methods: {
+    touchStart(e) {
+      if (this.disabled || this.isAnimating) return;
+      
+      const touch = e.touches[0];
+      this.touchStartX = touch.clientX;
+      this.touchStartY = touch.clientY;
+      this.deltaX = 0;
+      this.deltaY = 0;
+    },
+    touchMove(e) {
+      if (this.disabled || this.isAnimating) return;
+      
+      const touch = e.touches[0];
+      this.deltaX = touch.clientX - this.touchStartX;
+      this.deltaY = touch.clientY - this.touchStartY;
+      
+      // 如果垂直滑动距离大于水平滑动距离,则不处理
+      if (Math.abs(this.deltaY) > Math.abs(this.deltaX)) {
+        return;
+      }
+      
+      // 阻止默认行为
+      e.preventDefault();
+      
+      // 添加阻尼效果
+      const damping = 0.5;
+      this.deltaX = this.deltaX * damping;
+    },
+    touchEnd() {
+      if (this.disabled || this.isAnimating) return;
+      
+      // 判断滑动方向和距离
+      if (Math.abs(this.deltaX) > SWIPE_THRESHOLD) {
+        if (this.deltaX > 0) {
+          this.slideToPrev();
+        } else {
+          this.slideToNext();
+        }
+      } else {
+        this.resetPosition();
+      }
+    },
+    slideToPrev() {
+      this.isAnimating = true;
+      this.deltaX = this.containerWidth;
+      
+      setTimeout(() => {
+        this.isAnimating = false;
+        this.deltaX = 0;
+        this.$emit('slide-prev');
+      }, ANIMATION_DURATION);
+    },
+    slideToNext() {
+      this.isAnimating = true;
+      this.deltaX = -this.containerWidth;
+      
+      setTimeout(() => {
+        this.isAnimating = false;
+        this.deltaX = 0;
+        this.$emit('slide-next');
+      }, ANIMATION_DURATION);
+    },
+    resetPosition() {
+      this.isAnimating = true;
+      this.deltaX = 0;
+      
+      setTimeout(() => {
+        this.isAnimating = false;
+      }, ANIMATION_DURATION);
+    }
+  }
+}
+</script>
+
+<style>
+.sliding-container {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  min-height: 100vh;
+  overflow: hidden;
+  touch-action: pan-y pinch-zoom;
+  background-color: inherit;
+}
+
+.page {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  min-height: 100vh;
+  will-change: transform;
+  background-color: inherit;
+  overflow-y: auto;
+}
+
+.prev-page {
+  z-index: 1;
+}
+
+.current-page {
+  z-index: 2;
+}
+
+.next-page {
+  z-index: 1;
+}
+</style>
+

+ 147 - 0
pages/setting/index.vue

@@ -0,0 +1,147 @@
+<template>
+	<!-- 设置 -->
+	<view class="card_setting">
+		<view class="bg_color_head" :style="{paddingTop:(safetyTop + 20)+'px'}">
+			<view class="space_between card_user_box">
+				<view class="same_row_in">
+					<image class="box_head_portrait" mode="scaleToFill" :src="src"></image>
+					<view class="name_title_login">
+						<view class="login_title">登录/注册</view>
+						<view class="login_hint"><span class="hint_title">初学弟子</span>您已研究0个八字</view>
+					</view>
+				</view>
+				<u-icon name="arrow-right" color="#ffffff" size="22"></u-icon>
+			</view>
+			<view class="card_box_fingerpost">
+				<view class="box_instrument">
+					<view class="box_guide" v-for="(item,index) in guideList" :key="index">
+						<u-icon name="question-circle" size="30"></u-icon>
+						<view class="title_guide">{{item.title}}</view>
+					</view>
+				</view>
+			</view>
+			<view class="card_box_fingerpost">
+				<span class="headline_title">其他工具</span>
+				<view class="box_instrument">
+					<view class="box_guide" v-for="(item,index) in otherTools" :key="index">
+						<u-icon name="question-circle" size="30"></u-icon>
+						<view class="title_guide">{{item.title}}</view>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				safetyTop: 0,
+				src: 'https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/shuijiao.jpg',
+				guideList: [{
+					title: '新手指南',
+				}, {
+					title: '2.4.6版本',
+				}, {
+					title: '联系反馈',
+				}],
+				otherTools: [{
+					title: '紫薇斗数',
+				}, {
+					title: '吉真万年历',
+				}, {
+					title: '五运六气',
+				}, {
+					title: '问真罗盘',
+				}]
+			}
+		},
+		onLoad() {
+			const {
+				safeAreaInsets
+			} = uni.getSystemInfoSync()
+			if (safeAreaInsets) {
+				this.safetyTop = safeAreaInsets.top
+			}
+		},
+	}
+</script>
+
+<style lang="scss">
+	.bg_color_head {
+		padding-top: 30rpx;
+		width: 100%;
+		height: 260rpx;
+		background-color: rgba(42, 42, 52, 1);
+		border-bottom-left-radius: 30rpx;
+		border-bottom-right-radius: 30rpx;
+	}
+
+	.card_user_box {
+		width: calc(100% - 60rpx) !important;
+		padding: 30rpx 30rpx 50rpx 30rpx;
+	}
+
+	.box_head_portrait {
+		width: 70px;
+		height: 70px;
+		border-radius: 50%;
+		margin-right: 24rpx;
+	}
+
+	.name_title_login {
+		display: flex;
+		flex-direction: column;
+	}
+
+	.login_title {
+		color: #fff;
+		font-size: 40rpx;
+		margin-bottom: 10rpx;
+	}
+
+	.login_hint {
+		color: #fff;
+		font-size: 28rpx;
+
+		.hint_title {
+			color: $color-subject;
+			margin-right: 10rpx;
+		}
+	}
+
+	.card_box_fingerpost {
+		display: flex;
+		flex-direction: column;
+		margin: 0rpx 30rpx 30rpx 30rpx;
+		width: calc(100% - 60rpx);
+		// height: 100rpx;
+		background-color: #fff;
+		border-radius: 20rpx;
+		padding: 20rpx 0rpx;
+	}
+
+	.box_guide {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		padding: 0rpx 30rpx;
+
+		.title_guide {
+			margin-top: 10rpx;
+			font-size: 28rpx;
+		}
+	}
+
+	.headline_title {
+		font-size: 30rpx;
+		font-weight: bold;
+		margin-left: 20rpx;
+		margin-bottom: 30rpx;
+	}
+
+	.box_instrument {
+		display: flex;
+	}
+</style>

BIN
static/background0.jpg


BIN
static/background1.jpg


BIN
static/bk.png


BIN
static/centre.png


BIN
static/hour.png


BIN
static/logo.png


BIN
static/mh-BsqlF_P3.png


BIN
static/minute.png


BIN
static/plate.png


BIN
static/tabs/array_default.png


BIN
static/tabs/array_selected.png


BIN
static/tabs/option_default.png


BIN
static/tabs/option_selected.png


BIN
static/tabs/record_default.png


BIN
static/tabs/record_selected.png


BIN
static/tabs/school_default.png


BIN
static/tabs/school_selected.png


+ 82 - 0
styles/iconfont.scss

@@ -0,0 +1,82 @@
+@font-face {
+  font-family: "iconfont"; /* Project id 4837432 */
+  src: url('https://at.alicdn.com/t/c/font_4837432_m0jprcc44g.woff?t=1740725521618') format('woff'),
+       url('https://at.alicdn.com/t/c/font_4837432_m0jprcc44g.ttf?t=1740725521618') format('truetype'),
+       url('https://at.alicdn.com/t/c/font_4837432_m0jprcc44g.svg?t=1740725521618#iconfont') format('svg');
+}
+
+.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.icon-search:before {
+  content: "\e652";
+}
+
+.icon-left:before {
+  content: "\e627";
+}
+
+.icon-yiwen:before {
+  content: "\e6f0";
+}
+
+.icon-mulu:before {
+  content: "\e779";
+}
+
+.icon-Aa:before {
+  content: "\e636";
+}
+
+.icon-liuyan:before {
+  content: "\e650";
+}
+
+.icon-zuotijilu:before {
+  content: "\e61a";
+}
+
+.icon-xiezi:before {
+  content: "\e651";
+}
+
+.icon-bookmark:before {
+  content: "\e89c";
+}
+
+.icon-bookshelf:before {
+  content: "\e63d";
+}
+
+.icon-addbookshelf:before {
+  content: "\e62d";
+}
+
+.icon-schedule:before {
+  content: "\e672";
+}
+
+.icon-xuetang:before {
+  content: "\e79c";
+}
+
+.icon-shezhi:before {
+  content: "\e603";
+}
+
+.icon-xuanxiang:before {
+  content: "\f105";
+}
+
+.icon-jilu:before {
+  content: "\e62e";
+}
+
+.icon-bagua:before {
+  content: "\e625";
+}

+ 13 - 0
uni.promisify.adaptor.js

@@ -0,0 +1,13 @@
+uni.addInterceptor({
+  returnValue (res) {
+    if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) {
+      return res;
+    }
+    return new Promise((resolve, reject) => {
+      res.then((res) => {
+        if (!res) return resolve(res) 
+        return res[0] ? reject(res[0]) : resolve(res[1])
+      });
+    });
+  },
+});

+ 76 - 0
uni.scss

@@ -0,0 +1,76 @@
+@import "@/uni_modules/uview-ui/theme.scss";
+/**
+ * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
+ *
+ * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
+ */
+
+/* 颜色变量 */
+// 颜色
+$color-subject: #b2955d;
+$color-blue: #3387FF;
+$color-white: #FFFFFF;
+$minor-text-color: #dd524d;
+// 字体大小
+$font_size_34: 34rpx;
+$font_size_36: 36rpx;
+/* 行为相关颜色 */
+$uni-color-primary: #007aff;
+$uni-color-success: #4cd964;
+$uni-color-warning: #f0ad4e;
+$uni-color-error: #dd524d;
+
+/* 文字基本颜色 */
+$uni-text-color:#333;//基本色
+$uni-text-color-inverse:#fff;//反色
+$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
+$uni-text-color-placeholder: #808080;
+$uni-text-color-disable:#c0c0c0;
+
+/* 背景颜色 */
+$uni-bg-color:#ffffff;
+$uni-bg-color-grey:#f8f8f8;
+$uni-bg-color-hover:#f1f1f1;//点击状态颜色
+$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
+
+/* 边框颜色 */
+$uni-border-color:#c8c7cc;
+
+/* 尺寸变量 */
+
+/* 文字尺寸 */
+$uni-font-size-sm:12px;
+$uni-font-size-base:14px;
+$uni-font-size-lg:16px;
+
+/* 图片尺寸 */
+$uni-img-size-sm:20px;
+$uni-img-size-base:26px;
+$uni-img-size-lg:40px;
+
+/* Border Radius */
+$uni-border-radius-sm: 2px;
+$uni-border-radius-base: 3px;
+$uni-border-radius-lg: 6px;
+$uni-border-radius-circle: 50%;
+
+/* 水平间距 */
+$uni-spacing-row-sm: 5px;
+$uni-spacing-row-base: 10px;
+$uni-spacing-row-lg: 15px;
+
+/* 垂直间距 */
+$uni-spacing-col-sm: 4px;
+$uni-spacing-col-base: 8px;
+$uni-spacing-col-lg: 12px;
+
+/* 透明度 */
+$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
+
+/* 文章场景相关 */
+$uni-color-title: #2C405A; // 文章标题颜色
+$uni-font-size-title:20px;
+$uni-color-subtitle: #555555; // 二级标题颜色
+$uni-font-size-subtitle:26px;
+$uni-color-paragraph: #3F536E; // 文章段落颜色
+$uni-font-size-paragraph:15px;

+ 21 - 0
uni_modules/uview-ui/LICENSE

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

+ 66 - 0
uni_modules/uview-ui/README.md

@@ -0,0 +1,66 @@
+<p align="center">
+    <img alt="logo" src="https://uviewui.com/common/logo.png" width="120" height="120" style="margin-bottom: 10px;">
+</p>
+<h3 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uView 2.0</h3>
+<h3 align="center">多平台快速开发的UI框架</h3>
+
+[![stars](https://img.shields.io/github/stars/umicro/uView2.0?style=flat-square&logo=GitHub)](https://github.com/umicro/uView2.0)
+[![forks](https://img.shields.io/github/forks/umicro/uView2.0?style=flat-square&logo=GitHub)](https://github.com/umicro/uView2.0)
+[![issues](https://img.shields.io/github/issues/umicro/uView2.0?style=flat-square&logo=GitHub)](https://github.com/umicro/uView2.0/issues)
+[![Website](https://img.shields.io/badge/uView-up-blue?style=flat-square)](https://uviewui.com)
+[![release](https://img.shields.io/github/v/release/umicro/uView2.0?style=flat-square)](https://gitee.com/umicro/uView2.0/releases)
+[![license](https://img.shields.io/github/license/umicro/uView2.0?style=flat-square)](https://en.wikipedia.org/wiki/MIT_License)
+
+## 说明
+
+uView UI,是[uni-app](https://uniapp.dcloud.io/)全面兼容nvue的uni-app生态框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水
+
+## [官方文档:https://uviewui.com](https://uviewui.com)
+
+
+## 预览
+
+您可以通过**微信**扫码,查看最佳的演示效果。
+<br>
+<br>
+<img src="https://uviewui.com/common/weixin_mini_qrcode.png" width="220" height="220" >
+
+
+## 链接
+
+- [官方文档](https://www.uviewui.com/)
+- [更新日志](https://www.uviewui.com/components/changelog.html)
+- [升级指南](https://www.uviewui.com/components/changeGuide.html)
+- [关于我们](https://www.uviewui.com/cooperation/about.html)
+
+## 交流反馈
+
+欢迎加入我们的QQ群交流反馈:[点此跳转](https://www.uviewui.com/components/addQQGroup.html)
+
+## 关于PR
+
+> 我们非常乐意接受各位的优质PR,但在此之前我希望您了解uView2.0是一个需要兼容多个平台的(小程序、h5、ios app、android app)包括nvue页面、vue页面。
+> 所以希望在您修复bug并提交之前尽可能的去这些平台测试一下兼容性。最好能携带测试截图以方便审核。非常感谢!
+
+## 安装
+
+#### **uni-app插件市场链接** —— [https://ext.dcloud.net.cn/plugin?id=1593](https://ext.dcloud.net.cn/plugin?id=1593)
+
+请通过[官网安装文档](https://www.uviewui.com/components/install.html)了解更详细的内容
+
+## 快速上手
+
+请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容
+
+## 使用方法
+配置easycom规则后,自动按需引入,无需`import`组件,直接引用即可。
+
+```html
+<template>
+	<u-button text="按钮"></u-button>
+</template>
+```
+
+## 版权信息
+uView遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uView应用到您的产品中。
+

+ 376 - 0
uni_modules/uview-ui/changelog.md

@@ -0,0 +1,376 @@
+## 2.0.38(2024-06-12)
+插件市场处理
+## 2.0.37(2024-03-17)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复表单校验`trigger`触发器参数无效问题
+2. 修复`u-input`组件的`password`属性在动态切换为`false`时失效的问题
+3. 添加微信小程序用户同意隐私协议事件回调
+4. 修复支付宝小程序picker样式问题
+5. `u-modal`添加`duration`字段控制动画过度时间
+6. 修复`picker` `lastIndex`异常导致的`column`异常问题
+7. `tabs`增加长按事件支持
+8. 修复`u-avatar` `square`属性在小程序`open-data`下无效问题
+9. 其他一些修复
+## 2.0.36(2023-03-27)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 重构`deepClone` & `deepMerge`方法
+2. 其他优化
+## 2.0.34(2022-09-24)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. `u-input`、`u-textarea`增加`ignoreCompositionEvent`属性
+2. 修复`route`方法调用可能报错的问题
+3. 修复`u-no-network`组件`z-index`无效的问题
+4. 修复`textarea`组件在h5上confirmType=""报错的问题
+5. `u-rate`适配`nvue`
+6. 优化验证手机号码的正则表达式(根据工信部发布的《电信网编号计划(2017年版)》进行修改。)
+7. `form-item`添加`labelPosition`属性
+8. `u-calendar`修复`maxDate`设置为当前日期,并且当前时间大于08:00时无法显示日期列表的问题 (#724)
+9. `u-radio`增加一个默认插槽用于自定义修改label内容 (#680)
+10. 修复`timeFormat`函数在safari重的兼容性问题 (#664)
+## 2.0.33(2022-06-17)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复`loadmore`组件`lineColor`类型错误问题
+2. 修复`u-parse`组件`imgtap`、`linktap`不生效问题
+## 2.0.32(2022-06-16)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+1. `u-loadmore`新增自定义颜色、虚/实线
+2. 修复`u-swiper-action`组件部分平台不能上下滑动的问题
+3. 修复`u-list`回弹问题
+4. 修复`notice-bar`组件动画在低端安卓机可能会抖动的问题
+5. `u-loading-page`添加控制图标大小的属性`iconSize`
+6. 修复`u-tooltip`组件`color`参数不生效的问题
+7. 修复`u--input`组件使用`blur`事件输出为`undefined`的bug
+8. `u-code-input`组件新增键盘弹起时,是否自动上推页面参数`adjustPosition`
+9. 修复`image`组件`load`事件无回调对象问题
+10. 修复`button`组件`loadingSize`设置无效问题
+10. 其他修复
+## 2.0.31(2022-04-19)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复`upload`在`vue`页面上传成功后没有成功标志的问题
+2. 解决演示项目中微信小程序模拟上传图片一直出于上传中问题
+3. 修复`u-code-input`组件在`nvue`页面编译到`app`平台上光标异常问题(`app`去除此功能)
+4. 修复`actionSheet`组件标题关闭按钮点击事件名称错误的问题
+5. 其他修复
+## 2.0.30(2022-04-04)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. `u-rate`增加`readonly`属性
+2. `tabs`滑块支持设置背景图片
+3. 修复`u-subsection` `mode`为`subsection`时,滑块样式不正确的问题
+4. `u-code-input`添加光标效果动画
+5. 修复`popup`的`open`事件不触发
+6. 修复`u-flex-column`无效的问题
+7. 修复`u-datetime-picker`索引在特定场合异常问题
+8. 修复`u-datetime-picker`最小时间字符串模板错误问题
+9. `u-swiper`添加`m3u8`验证
+10. `u-swiper`修改判断image和video逻辑
+11. 修复`swiper`无法使用本地图片问题,增加`type`参数
+12. 修复`u-row-notice`格式错误问题
+13. 修复`u-switch`组件当`unit`为`rpx`时,`nodeStyle`消失的问题
+14. 修复`datetime-picker`组件`showToolbar`与`visibleItemCount`属性无效的问题
+15. 修复`upload`组件条件编译位置判断错误,导致`previewImage`属性设置为`false`时,整个组件都会被隐藏的问题
+16. 修复`u-checkbox-group`设置`shape`属性无效的问题
+17. 修复`u-upload`的`capture`传入字符串的时候不生效的问题
+18. 修复`u-action-sheet`组件,关闭事件逻辑错误的问题
+19. 修复`u-list`触顶事件的触发错误的问题
+20. 修复`u-text`只有手机号可拨打的问题
+21. 修复`u-textarea`不能换行的问题
+22. 其他修复
+## 2.0.29(2022-03-13)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复`u--text`组件设置`decoration`属性未生效的问题
+2. 修复`u-datetime-picker`使用`formatter`后返回值不正确
+3. 修复`u-datetime-picker` `intercept` 可能为undefined
+4. 修复已设置单位 uni..config.unit = 'rpx'时,线型指示器 `transform` 的位置翻倍,导致指示器超出宽度
+5. 修复mixin中bem方法生成的类名在支付宝和字节小程序中失效
+6. 修复默认值传值为空的时候,打开`u-datetime-picker`报错,不能选中第一列时间的bug
+7. 修复`u-datetime-picker`使用`formatter`后返回值不正确
+8. 修复`u-image`组件`loading`无效果的问题
+9. 修复`config.unit`属性设为`rpx`时,导航栏占用高度不足导致塌陷的问题
+10. 修复`u-datetime-picker`组件`itemHeight`无效问题
+11. 其他修复
+## 2.0.28(2022-02-22)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. search组件新增searchIconSize属性
+2. 兼容Safari/Webkit中传入时间格式如2022-02-17 12:00:56
+3. 修复text value.js 判断日期出format错误问题
+4. priceFormat格式化金额出现精度错误
+5. priceFormat在部分情况下出现精度损失问题
+6. 优化表单rules提示
+7. 修复avatar组件src为空时,展示状态不对
+8. 其他修复
+## 2.0.27(2022-01-28)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1.样式修复
+## 2.0.26(2022-01-28)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1.样式修复
+## 2.0.25(2022-01-27)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复text组件mode=price时,可能会导致精度错误的问题
+2. 添加$u.setConfig()方法,可设置uView内置的config, props, zIndex, color属性,详见:[修改uView内置配置方案](https://uviewui.com/components/setting.html#%E9%BB%98%E8%AE%A4%E5%8D%95%E4%BD%8D%E9%85%8D%E7%BD%AE)
+3. 优化form组件在errorType=toast时,如果输入错误页面会有抖动的问题
+4. 修复$u.addUnit()对配置默认单位可能无效的问题
+## 2.0.24(2022-01-25)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复swiper在current指定非0时缩放有误
+2. 修复u-icon添加stop属性的时候报错
+3. 优化遗留的通过正则判断rpx单位的问题
+4. 优化Layout布局 vue使用gutter时,会超出固定区域
+5. 优化search组件高度单位问题(rpx -> px)
+6. 修复u-image slot 加载和错误的图片失去了高度
+7. 修复u-index-list中footer插槽与header插槽存在性判断错误
+8. 修复部分机型下u-popup关闭时会闪烁
+9. 修复u-image在nvue-app下失去宽高
+10. 修复u-popup运行报错
+11. 修复u-tooltip报错
+12. 修复box-sizing在app下的警告
+13. 修复u-navbar在小程序中报运行时错误
+14. 其他修复
+## 2.0.23(2022-01-24)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复image组件在hx3.3.9的nvue下可能会显示异常的问题
+2. 修复col组件gutter参数带rpx单位处理不正确的问题
+3. 修复text组件单行时无法显示省略号的问题
+4. navbar添加titleStyle参数
+5. 升级到hx3.3.9可消除nvue下控制台样式警告的问题
+## 2.0.22(2022-01-19)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. $u.page()方法优化,避免在特殊场景可能报错的问题
+2. picker组件添加immediateChange参数
+3. 新增$u.pages()方法
+## 2.0.21(2022-01-19)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 优化:form组件在用户设置rules的时候提示用户model必传
+2. 优化遗留的通过正则判断rpx单位的问题
+3. 修复微信小程序环境中tabbar组件开启safeAreaInsetBottom属性后,placeholder高度填充不正确
+4. 修复swiper在current指定非0时缩放有误
+5. 修复u-icon添加stop属性的时候报错
+6. 修复upload组件在accept=all的时候没有作用
+7. 修复在text组件mode为phone时call属性无效的问题
+8. 处理u-form clearValidate方法
+9. 其他修复
+## 2.0.20(2022-01-14)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复calendar默认会选择一个日期,如果直接点确定的话,无法取到值的问题
+2. 修复Slider缺少disabled props 还有注释
+3. 修复u-notice-bar点击事件无法拿到index索引值的问题
+4. 修复u-collapse-item在vue文件下,app端自定义插槽不生效的问题
+5. 优化头像为空时显示默认头像 
+6. 修复图片地址赋值后判断加载状态为完成问题
+7. 修复日历滚动到默认日期月份区域
+8. search组件暴露点击左边icon事件
+9. 修复u-form clearValidate方法不生效
+10. upload h5端增加返回文件参数(文件的name参数)
+11. 处理upload选择文件后url为blob类型无法预览的问题
+12. u-code-input 修复输入框没有往左移出一半屏幕
+13. 修复Upload上传 disabled为true时,控制台报hoverClass类型错误
+14. 临时处理ios app下grid点击坍塌问题
+15. 其他修复
+## 2.0.19(2021-12-29)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 优化微信小程序包体积可在微信中预览,请升级HbuilderX3.3.4,同时在“运行->运行到小程序模拟器”中勾选“运行时是否压缩代码”
+2. 优化微信小程序setData性能,处理某些方法如$u.route()无法在模板中使用的问题
+3. navbar添加autoBack参数
+4. 允许avatar组件的事件冒泡
+5. 修复cell组件报错问题
+6. 其他修复
+## 2.0.18(2021-12-28)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复app端编译报错问题
+2. 重新处理微信小程序端setData过大的性能问题
+3. 修复边框问题
+4. 修复最大最小月份不大于0则没有数据出现的问题
+5. 修复SwipeAction微信小程序端无法上下滑动问题
+6. 修复input的placeholder在小程序端默认显示为true问题
+7. 修复divider组件click事件无效问题
+8. 修复u-code-input maxlength 属性值为 String 类型时显示异常
+9. 修复当 grid只有 1到2时 在小程序端algin设置无效的问题
+10. 处理form-item的label为top时,取消错误提示的左边距
+11. 其他修复
+## 2.0.17(2021-12-26)
+## uView正在参与开源中国的“年度最佳项目”评选,之前投过票的现在也可以投票,恳请同学们投一票,[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 解决HBuilderX3.3.3.20211225版本导致的样式问题
+2. calendar日历添加monthNum参数
+3. navbar添加center slot
+## 2.0.16(2021-12-25)
+## uView正在参与开源中国的“年度最佳项目”评选,之前投过票的现在也可以投票,恳请同学们投一票,[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 解决微信小程序setData性能问题
+2. 修复count-down组件change事件不触发问题
+## 2.0.15(2021-12-21)
+## uView正在参与开源中国的“年度最佳项目”评选,之前投过票的现在也可以投票,恳请同学们投一票,[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复Cell单元格titleWidth无效
+2. 修复cheakbox组件ischecked不更新
+3. 修复keyboard是否显示"."按键默认值问题
+4. 修复number-keyboard是否显示键盘的"."符号问题
+5. 修复Input输入框 readonly无效
+6. 修复u-avatar 导致打包app、H5时候报错问题
+7. 修复Upload上传deletable无效
+8. 修复upload当设置maxSize时无效的问题
+9. 修复tabs lineWidth传入带单位的字符串的时候偏移量计算错误问题
+10. 修复rate组件在有padding的view内,显示的星星位置和可触摸区域不匹配,无法正常选中星星
+## 2.0.13(2021-12-14)
+## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复配置默认单位为rpx可能会导致自定义导航栏高度异常的问题
+## 2.0.12(2021-12-14)
+## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复tabs组件在vue环境下划线消失的问题
+2. 修复upload组件在安卓小程序无法选择视频的问题
+3. 添加uni.$u.config.unit配置,用于配置参数默认单位,详见:[默认单位配置](https://www.uviewui.com/components/setting.html#%E9%BB%98%E8%AE%A4%E5%8D%95%E4%BD%8D%E9%85%8D%E7%BD%AE)
+4. 修复textarea组件在没绑定v-model时,字符统计不生效问题
+5. 修复nvue下控制是否出现滚动条失效问题
+## 2.0.11(2021-12-13)
+## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. text组件align参数无效的问题
+2. subsection组件添加keyName参数
+3. upload组件无法判断[Object file]类型的问题
+4. 处理notify层级过低问题
+5. codeInput组件添加disabledDot参数
+6. 处理actionSheet组件round参数无效的问题
+7. calendar组件添加round参数用于控制圆角值
+8. 处理swipeAction组件在vue环境下默认被打开的问题
+9. button组件的throttleTime节流参数无效的问题
+10. 解决u-notify手动关闭方法close()无效的问题
+11. input组件readonly不生效问题
+12. tag组件type参数为info不生效问题
+## 2.0.10(2021-12-08)
+## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复button sendMessagePath属性不生效
+2. 修复DatetimePicker选择器title无效
+3. 修复u-toast设置loading=true不生效
+4. 修复u-text金额模式传0报错
+5. 修复u-toast组件的icon属性配置不生效
+6. button的icon在特殊场景下的颜色优化
+7. IndexList优化,增加#
+## 2.0.9(2021-12-01)
+## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 优化swiper的height支持100%值(仅vue有效),修复嵌入视频时click事件无法触发的问题
+2. 优化tabs组件对list值为空的判断,或者动态变化list时重新计算相关尺寸的问题
+3. 优化datetime-picker组件逻辑,让其后续打开的默认值为上一次的选中值,需要通过v-model绑定值才有效
+4. 修复upload内嵌在其他组件中,选择图片可能不会换行的问题
+## 2.0.8(2021-12-01)
+## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复toast的position参数无效问题
+2. 处理input在ios nvue上无法获得焦点的问题
+3. avatar-group组件添加extraValue参数,让剩余展示数量可手动控制
+4. tabs组件添加keyName参数用于配置从对象中读取的键名
+5. 处理text组件名字脱敏默认配置无效的问题
+6. 处理picker组件item文本太长换行问题
+## 2.0.7(2021-11-30)
+## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复radio和checkbox动态改变v-model无效的问题。
+2. 优化form规则validator在微信小程序用法
+3. 修复backtop组件mode参数在微信小程序无效的问题
+4. 处理Album的previewFullImage属性无效的问题
+5. 处理u-datetime-picker组件mode='time'在选择改变时间时,控制台报错的问题
+## 2.0.6(2021-11-27)
+## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 处理tag组件在vue下边框无效的问题。
+2. 处理popup组件圆角参数可能无效的问题。
+3. 处理tabs组件lineColor参数可能无效的问题。
+4. propgress组件在值很小时,显示异常的问题。
+## 2.0.5(2021-11-25)
+## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. calendar在vue下显示异常问题。 
+2. form组件labelPosition和errorType参数无效的问题
+3. input组件inputAlign无效的问题
+4. 其他一些修复
+## 2.0.4(2021-11-23)
+## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+0. input组件缺失@confirm事件,以及subfix和prefix无效问题
+1. component.scss文件样式在vue下干扰全局布局问题
+2. 修复subsection在vue环境下表现异常的问题
+3. tag组件的bgColor等参数无效的问题
+4. upload组件不换行的问题
+5. 其他的一些修复处理
+## 2.0.3(2021-11-16)
+## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. uView2.0已实现全面兼容nvue
+2. uView2.0对1.x进行了架构重构,细节和性能都有极大提升
+3. 目前uView2.0为公测阶段,相关细节可能会有变动
+4. 我们写了一份与1.x的对比指南,详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)
+5. 处理modal的confirm回调事件拼写错误问题
+6. 处理input组件@input事件参数错误问题
+7. 其他一些修复
+## 2.0.2(2021-11-16)
+## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. uView2.0已实现全面兼容nvue
+2. uView2.0对1.x进行了架构重构,细节和性能都有极大提升
+3. 目前uView2.0为公测阶段,相关细节可能会有变动
+4. 我们写了一份与1.x的对比指南,详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)
+5. 修复input组件formatter参数缺失问题
+6. 优化loading-icon组件的scss写法问题,防止不兼容新版本scss
+## 2.0.0(2020-11-15)
+## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. uView2.0已实现全面兼容nvue
+2. uView2.0对1.x进行了架构重构,细节和性能都有极大提升
+3. 目前uView2.0为公测阶段,相关细节可能会有变动
+4. 我们写了一份与1.x的对比指南,详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)
+5. 修复input组件formatter参数缺失问题
+
+

+ 78 - 0
uni_modules/uview-ui/components/u--form/u--form.vue

@@ -0,0 +1,78 @@
+<template>
+	<uvForm
+		ref="uForm"
+		:model="model"
+		:rules="rules"
+		:errorType="errorType"
+		:borderBottom="borderBottom"
+		:labelPosition="labelPosition"
+		:labelWidth="labelWidth"
+		:labelAlign="labelAlign"
+		:labelStyle="labelStyle"
+		:customStyle="customStyle"
+	>
+		<slot />
+	</uvForm>
+</template>
+
+<script>
+	/**
+	 * 此组件存在的理由是,在nvue下,u-form被uni-app官方占用了,u-form在nvue中相当于form组件
+	 * 所以在nvue下,取名为u--form,内部其实还是u-form.vue,只不过做一层中转
+	 */
+	import uvForm from '../u-form/u-form.vue';
+	import props from '../u-form/props.js'
+	export default {
+		// #ifdef MP-WEIXIN
+		name: 'u-form',
+		// #endif
+		// #ifndef MP-WEIXIN
+		name: 'u--form',
+		// #endif
+		mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+		components: {
+			uvForm
+		},
+		created() {
+			this.children = []
+		},
+		methods: {
+			// 手动设置校验的规则,如果规则中有函数的话,微信小程序中会过滤掉,所以只能手动调用设置规则
+			setRules(rules) {
+				this.$refs.uForm.setRules(rules)
+			},
+			validate() {
+				/**
+				 * 在微信小程序中,通过this.$parent拿到的父组件是u--form,而不是其内嵌的u-form
+				 * 导致在u-form组件中,拿不到对应的children数组,从而校验无效,所以这里每次调用u-form组件中的
+				 * 对应方法的时候,在小程序中都先将u--form的children赋值给u-form中的children
+				 */
+				// #ifdef MP-WEIXIN
+				this.setMpData()
+				// #endif
+				return this.$refs.uForm.validate()
+			},
+			validateField(value, callback, event) {
+				// #ifdef MP-WEIXIN
+				this.setMpData()
+				// #endif
+				return this.$refs.uForm.validateField(value, callback, event)
+			},
+			resetFields() {
+				// #ifdef MP-WEIXIN
+				this.setMpData()
+				// #endif
+				return this.$refs.uForm.resetFields()
+			},
+			clearValidate(props) {
+				// #ifdef MP-WEIXIN
+				this.setMpData()
+				// #endif
+				return this.$refs.uForm.clearValidate(props)
+			},
+			setMpData() {
+				this.$refs.uForm.children = this.children
+			}
+		},
+	}
+</script>

+ 47 - 0
uni_modules/uview-ui/components/u--image/u--image.vue

@@ -0,0 +1,47 @@
+<template>
+	<uvImage 
+		:src="src"
+		:mode="mode"
+		:width="width"
+		:height="height"
+		:shape="shape"
+		:radius="radius"
+		:lazyLoad="lazyLoad"
+		:showMenuByLongpress="showMenuByLongpress"
+		:loadingIcon="loadingIcon"
+		:errorIcon="errorIcon"
+		:showLoading="showLoading"
+		:showError="showError"
+		:fade="fade"
+		:webp="webp"
+		:duration="duration"
+		:bgColor="bgColor"
+		:customStyle="customStyle"
+		@click="$emit('click')"
+		@error="$emit('error')"
+		@load="$emit('load')"
+	>
+		<template v-slot:loading>
+			<slot name="loading"></slot>
+		</template>
+		<template v-slot:error>
+			<slot name="error"></slot>
+		</template>
+	</uvImage>
+</template>
+
+<script>
+	/**
+	 * 此组件存在的理由是,在nvue下,u-image被uni-app官方占用了,u-image在nvue中相当于image组件
+	 * 所以在nvue下,取名为u--image,内部其实还是u-iamge.vue,只不过做一层中转
+	 */
+	import uvImage from '../u-image/u-image.vue';
+	import props from '../u-image/props.js';
+	export default {
+		name: 'u--image',
+		mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+		components: {
+			uvImage
+		},
+	}
+</script>

+ 73 - 0
uni_modules/uview-ui/components/u--input/u--input.vue

@@ -0,0 +1,73 @@
+<template>
+	<uvInput 
+		:value="value"
+		:type="type"
+		:fixed="fixed"
+		:disabled="disabled"
+		:disabledColor="disabledColor"
+		:clearable="clearable"
+		:password="password"
+		:maxlength="maxlength"
+		:placeholder="placeholder"
+		:placeholderClass="placeholderClass"
+		:placeholderStyle="placeholderStyle"
+		:showWordLimit="showWordLimit"
+		:confirmType="confirmType"
+		:confirmHold="confirmHold"
+		:holdKeyboard="holdKeyboard"
+		:focus="focus"
+		:autoBlur="autoBlur"
+		:disableDefaultPadding="disableDefaultPadding"
+		:cursor="cursor"
+		:cursorSpacing="cursorSpacing"
+		:selectionStart="selectionStart"
+		:selectionEnd="selectionEnd"
+		:adjustPosition="adjustPosition"
+		:inputAlign="inputAlign"
+		:fontSize="fontSize"
+		:color="color"
+		:prefixIcon="prefixIcon"
+		:suffixIcon="suffixIcon"
+		:suffixIconStyle="suffixIconStyle"
+		:prefixIconStyle="prefixIconStyle"
+		:border="border"
+		:readonly="readonly"
+		:shape="shape"
+		:customStyle="customStyle"
+		:formatter="formatter"
+		:ignoreCompositionEvent="ignoreCompositionEvent"
+		@focus="$emit('focus')"
+		@blur="e => $emit('blur', e)"
+		@keyboardheightchange="$emit('keyboardheightchange')"
+		@change="e => $emit('change', e)"
+		@input="e => $emit('input', e)"
+		@confirm="e => $emit('confirm', e)"
+		@clear="$emit('clear')"
+		@click="$emit('click')"
+	>
+		<!-- #ifdef MP -->
+		<slot name="prefix"></slot>
+		<slot name="suffix"></slot>
+		<!-- #endif -->
+		<!-- #ifndef MP -->
+		<slot name="prefix" slot="prefix"></slot>
+		<slot name="suffix" slot="suffix"></slot>
+		<!-- #endif -->
+	</uvInput>
+</template>
+
+<script>
+	/**
+	 * 此组件存在的理由是,在nvue下,u-input被uni-app官方占用了,u-input在nvue中相当于input组件
+	 * 所以在nvue下,取名为u--input,内部其实还是u-input.vue,只不过做一层中转
+	 */
+	import uvInput from '../u-input/u-input.vue';
+	import props from '../u-input/props.js'
+	export default {
+		name: 'u--input',
+		mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+		components: {
+			uvInput
+		},
+	}
+</script>

+ 44 - 0
uni_modules/uview-ui/components/u--text/u--text.vue

@@ -0,0 +1,44 @@
+<template>
+    <uvText
+        :type="type"
+        :show="show"
+        :text="text"
+        :prefixIcon="prefixIcon"
+        :suffixIcon="suffixIcon"
+        :mode="mode"
+        :href="href"
+        :format="format"
+        :call="call"
+        :openType="openType"
+        :bold="bold"
+        :block="block"
+        :lines="lines"
+        :color="color"
+		:decoration="decoration"
+        :size="size"
+        :iconStyle="iconStyle"
+        :margin="margin"
+        :lineHeight="lineHeight"
+        :align="align"
+        :wordWrap="wordWrap"
+        :customStyle="customStyle"
+        @click="$emit('click')"
+    ></uvText>
+</template>
+
+<script>
+/**
+ * 此组件存在的理由是,在nvue下,u-text被uni-app官方占用了,u-text在nvue中相当于input组件
+ * 所以在nvue下,取名为u--input,内部其实还是u-text.vue,只不过做一层中转
+ * 不使用v-bind="$attrs",而是分开独立写传参,是因为微信小程序不支持此写法
+ */
+import uvText from "../u-text/u-text.vue";
+import props from "../u-text/props.js";
+export default {
+    name: "u--text",
+    mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+    components: {
+        uvText,
+    },
+};
+</script>

+ 48 - 0
uni_modules/uview-ui/components/u--textarea/u--textarea.vue

@@ -0,0 +1,48 @@
+<template>
+	<uvTextarea
+		:value="value"
+		:placeholder="placeholder"
+		:height="height"
+		:confirmType="confirmType"
+		:disabled="disabled"
+		:count="count"
+		:focus="focus"
+		:autoHeight="autoHeight"
+		:fixed="fixed"
+		:cursorSpacing="cursorSpacing"
+		:cursor="cursor"
+		:showConfirmBar="showConfirmBar"
+		:selectionStart="selectionStart"
+		:selectionEnd="selectionEnd"
+		:adjustPosition="adjustPosition"
+		:disableDefaultPadding="disableDefaultPadding"
+		:holdKeyboard="holdKeyboard"
+		:maxlength="maxlength"
+		:border="border"
+		:customStyle="customStyle"
+		:formatter="formatter"
+		:ignoreCompositionEvent="ignoreCompositionEvent"
+		@focus="e => $emit('focus')"
+		@blur="e => $emit('blur')"
+		@linechange="e => $emit('linechange', e)"
+		@confirm="e => $emit('confirm')"
+		@input="e => $emit('input', e)"
+		@keyboardheightchange="e => $emit('keyboardheightchange')"
+	></uvTextarea>
+</template>
+
+<script>
+	/**
+	 * 此组件存在的理由是,在nvue下,u--textarea被uni-app官方占用了,u-textarea在nvue中相当于textarea组件
+	 * 所以在nvue下,取名为u--textarea,内部其实还是u-textarea.vue,只不过做一层中转
+	 */
+	import uvTextarea from '../u-textarea/u-textarea.vue';
+	import props from '../u-textarea/props.js'
+	export default {
+		name: 'u--textarea',
+		mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+		components: {
+			uvTextarea
+		},
+	}
+</script>

+ 54 - 0
uni_modules/uview-ui/components/u-action-sheet/props.js

@@ -0,0 +1,54 @@
+export default {
+    props: {
+        // 操作菜单是否展示 (默认false)
+        show: {
+            type: Boolean,
+            default: uni.$u.props.actionSheet.show
+        },
+        // 标题
+        title: {
+            type: String,
+            default: uni.$u.props.actionSheet.title
+        },
+        // 选项上方的描述信息
+        description: {
+            type: String,
+            default: uni.$u.props.actionSheet.description
+        },
+        // 数据
+        actions: {
+            type: Array,
+            default: uni.$u.props.actionSheet.actions
+        },
+        // 取消按钮的文字,不为空时显示按钮
+        cancelText: {
+            type: String,
+            default: uni.$u.props.actionSheet.cancelText
+        },
+        // 点击某个菜单项时是否关闭弹窗
+        closeOnClickAction: {
+            type: Boolean,
+            default: uni.$u.props.actionSheet.closeOnClickAction
+        },
+        // 处理底部安全区(默认true)
+        safeAreaInsetBottom: {
+            type: Boolean,
+            default: uni.$u.props.actionSheet.safeAreaInsetBottom
+        },
+        // 小程序的打开方式
+        openType: {
+            type: String,
+            default: uni.$u.props.actionSheet.openType
+        },
+        // 点击遮罩是否允许关闭 (默认true)
+        closeOnClickOverlay: {
+            type: Boolean,
+            default: uni.$u.props.actionSheet.closeOnClickOverlay
+        },
+        // 圆角值
+        round: {
+            type: [Boolean, String, Number],
+            default: uni.$u.props.actionSheet.round
+        }
+    }
+}

+ 278 - 0
uni_modules/uview-ui/components/u-action-sheet/u-action-sheet.vue

@@ -0,0 +1,278 @@
+
+<template>
+	<u-popup
+	    :show="show"
+	    mode="bottom"
+	    @close="closeHandler"
+	    :safeAreaInsetBottom="safeAreaInsetBottom"
+	    :round="round"
+	>
+		<view class="u-action-sheet">
+			<view
+			    class="u-action-sheet__header"
+			    v-if="title"
+			>
+				<text class="u-action-sheet__header__title u-line-1">{{title}}</text>
+				<view
+				    class="u-action-sheet__header__icon-wrap"
+				    @tap.stop="cancel"
+				>
+					<u-icon
+					    name="close"
+					    size="17"
+					    color="#c8c9cc"
+					    bold
+					></u-icon>
+				</view>
+			</view>
+			<text
+			    class="u-action-sheet__description"
+				:style="[{
+					marginTop: `${title && description ? 0 : '18px'}`
+				}]"
+			    v-if="description"
+			>{{description}}</text>
+			<slot>
+				<u-line v-if="description"></u-line>
+				<view class="u-action-sheet__item-wrap">
+					<template v-for="(item, index) in actions">
+						<!-- #ifdef MP -->
+						<button
+						    :key="index"
+						    class="u-reset-button"
+						    :openType="item.openType"
+						    @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"
+						    @tap="selectHandler(index)"
+						    :hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''"
+						>
+							<!-- #endif -->
+							<view
+							    class="u-action-sheet__item-wrap__item"
+							    @tap.stop="selectHandler(index)"
+							    :hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''"
+							    :hover-stay-time="150"
+							>
+								<template v-if="!item.loading">
+									<text
+									    class="u-action-sheet__item-wrap__item__name"
+									    :style="[itemStyle(index)]"
+									>{{ item.name }}</text>
+									<text
+									    v-if="item.subname"
+									    class="u-action-sheet__item-wrap__item__subname"
+									>{{ item.subname }}</text>
+								</template>
+								<u-loading-icon
+								    v-else
+								    custom-class="van-action-sheet__loading"
+								    size="18"
+								    mode="circle"
+								/>
+							</view>
+							<!-- #ifdef MP -->
+						</button>
+						<!-- #endif -->
+						<u-line v-if="index !== actions.length - 1"></u-line>
+					</template>
+				</view>
+			</slot>
+			<u-gap
+			    bgColor="#eaeaec"
+			    height="6"
+			    v-if="cancelText"
+			></u-gap>
+			<view hover-class="u-action-sheet--hover">
+				<text
+				    @touchmove.stop.prevent
+				    :hover-stay-time="150"
+				    v-if="cancelText"
+				    class="u-action-sheet__cancel-text"
+				    @tap="cancel"
+				>{{cancelText}}</text>
+			</view>
+		</view>
+	</u-popup>
+</template>
+
+<script>
+	import openType from '../../libs/mixin/openType'
+	import button from '../../libs/mixin/button'
+	import props from './props.js';
+	/**
+	 * ActionSheet 操作菜单
+	 * @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。
+	 * @tutorial https://www.uviewui.com/components/actionSheet.html
+	 * 
+	 * @property {Boolean}			show				操作菜单是否展示 (默认 false )
+	 * @property {String}			title				操作菜单标题
+	 * @property {String}			description			选项上方的描述信息
+	 * @property {Array<Object>}	actions				按钮的文字数组,见官方文档示例
+	 * @property {String}			cancelText			取消按钮的提示文字,不为空时显示按钮
+	 * @property {Boolean}			closeOnClickAction	点击某个菜单项时是否关闭弹窗 (默认 true )
+	 * @property {Boolean}			safeAreaInsetBottom	处理底部安全区 (默认 true )
+	 * @property {String}			openType			小程序的打开方式 (contact | launchApp | getUserInfo | openSetting |getPhoneNumber |error )
+	 * @property {Boolean}			closeOnClickOverlay	点击遮罩是否允许关闭  (默认 true )
+	 * @property {Number|String}	round				圆角值,默认无圆角  (默认 0 )
+	 * @property {String}			lang				指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文
+	 * @property {String}			sessionFrom			会话来源,openType="contact"时有效
+	 * @property {String}			sendMessageTitle	会话内消息卡片标题,openType="contact"时有效
+	 * @property {String}			sendMessagePath		会话内消息卡片点击跳转小程序路径,openType="contact"时有效
+	 * @property {String}			sendMessageImg		会话内消息卡片图片,openType="contact"时有效
+	 * @property {Boolean}			showMessageCard		是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效 (默认 false )
+	 * @property {String}			appParameter		打开 APP 时,向 APP 传递的参数,openType=launchApp 时有效
+	 * 
+	 * @event {Function} select			点击ActionSheet列表项时触发 
+	 * @event {Function} close			点击取消按钮时触发
+	 * @event {Function} getuserinfo	用户点击该按钮时,会返回获取到的用户信息,回调的 detail 数据与 wx.getUserInfo 返回的一致,openType="getUserInfo"时有效
+	 * @event {Function} contact		客服消息回调,openType="contact"时有效
+	 * @event {Function} getphonenumber	获取用户手机号回调,openType="getPhoneNumber"时有效
+	 * @event {Function} error			当使用开放能力时,发生错误的回调,openType="error"时有效
+	 * @event {Function} launchapp		打开 APP 成功的回调,openType="launchApp"时有效
+	 * @event {Function} opensetting	在打开授权设置页后回调,openType="openSetting"时有效
+	 * @example <u-action-sheet :actions="list" :title="title" :show="show"></u-action-sheet>
+	 */
+	export default {
+		name: "u-action-sheet",
+		// 一些props参数和methods方法,通过mixin混入,因为其他文件也会用到
+		mixins: [openType, button, uni.$u.mixin, props],
+		data() {
+			return {
+
+			}
+		},
+		computed: {
+			// 操作项目的样式
+			itemStyle() {
+				return (index) => {
+					let style = {};
+					if (this.actions[index].color) style.color = this.actions[index].color
+					if (this.actions[index].fontSize) style.fontSize = uni.$u.addUnit(this.actions[index].fontSize)
+					// 选项被禁用的样式
+					if (this.actions[index].disabled) style.color = '#c0c4cc'
+					return style;
+				}
+			},
+		},
+		methods: {
+			closeHandler() {
+				// 允许点击遮罩关闭时,才发出close事件
+				if(this.closeOnClickOverlay) {
+					this.$emit('close')
+				}
+			},
+			// 点击取消按钮
+			cancel() {
+				this.$emit('close')
+			},
+			selectHandler(index) {
+				const item = this.actions[index]
+				if (item && !item.disabled && !item.loading) {
+					this.$emit('select', item)
+					if (this.closeOnClickAction) {
+						this.$emit('close')
+					}
+				}
+			},
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+	$u-action-sheet-reset-button-width:100% !default;
+	$u-action-sheet-title-font-size: 16px !default;
+	$u-action-sheet-title-padding: 12px 30px !default;
+	$u-action-sheet-title-color: $u-main-color !default;
+	$u-action-sheet-header-icon-wrap-right:15px !default;
+	$u-action-sheet-header-icon-wrap-top:15px !default;
+	$u-action-sheet-description-font-size:13px !default;
+	$u-action-sheet-description-color:14px !default;
+	$u-action-sheet-description-margin: 18px 15px !default;
+	$u-action-sheet-item-wrap-item-padding:15px !default;
+	$u-action-sheet-item-wrap-name-font-size:16px !default;
+	$u-action-sheet-item-wrap-subname-font-size:13px !default;
+	$u-action-sheet-item-wrap-subname-color: #c0c4cc !default;
+	$u-action-sheet-item-wrap-subname-margin-top:10px !default;
+	$u-action-sheet-cancel-text-font-size:16px !default;
+	$u-action-sheet-cancel-text-color:$u-content-color !default;
+	$u-action-sheet-cancel-text-font-size:15px !default;
+	$u-action-sheet-cancel-text-hover-background-color:rgb(242, 243, 245) !default;
+
+	.u-reset-button {
+		width: $u-action-sheet-reset-button-width;
+	}
+
+	.u-action-sheet {
+		text-align: center;
+		&__header {
+			position: relative;
+			padding: $u-action-sheet-title-padding;
+			&__title {
+				font-size: $u-action-sheet-title-font-size;
+				color: $u-action-sheet-title-color;
+				font-weight: bold;
+				text-align: center;
+			}
+
+			&__icon-wrap {
+				position: absolute;
+				right: $u-action-sheet-header-icon-wrap-right;
+				top: $u-action-sheet-header-icon-wrap-top;
+			}
+		}
+
+		&__description {
+			font-size: $u-action-sheet-description-font-size;
+			color: $u-tips-color;
+			margin: $u-action-sheet-description-margin;
+			text-align: center;
+		}
+
+		&__item-wrap {
+
+			&__item {
+				padding: $u-action-sheet-item-wrap-item-padding;
+				@include flex;
+				align-items: center;
+				justify-content: center;
+				flex-direction: column;
+
+				&__name {
+					font-size: $u-action-sheet-item-wrap-name-font-size;
+					color: $u-main-color;
+					text-align: center;
+				}
+
+				&__subname {
+					font-size: $u-action-sheet-item-wrap-subname-font-size;
+					color: $u-action-sheet-item-wrap-subname-color;
+					margin-top: $u-action-sheet-item-wrap-subname-margin-top;
+					text-align: center;
+				}
+			}
+		}
+
+		&__cancel-text {
+			font-size: $u-action-sheet-cancel-text-font-size;
+			color: $u-action-sheet-cancel-text-color;
+			text-align: center;
+			padding: $u-action-sheet-cancel-text-font-size;
+		}
+
+		&--hover {
+			background-color: $u-action-sheet-cancel-text-hover-background-color;
+		}
+	}
+</style>

+ 59 - 0
uni_modules/uview-ui/components/u-album/props.js

@@ -0,0 +1,59 @@
+export default {
+    props: {
+        // 图片地址,Array<String>|Array<Object>形式
+        urls: {
+            type: Array,
+            default: uni.$u.props.album.urls
+        },
+        // 指定从数组的对象元素中读取哪个属性作为图片地址
+        keyName: {
+            type: String,
+            default: uni.$u.props.album.keyName
+        },
+        // 单图时,图片长边的长度
+        singleSize: {
+            type: [String, Number],
+            default: uni.$u.props.album.singleSize
+        },
+        // 多图时,图片边长
+        multipleSize: {
+            type: [String, Number],
+            default: uni.$u.props.album.multipleSize
+        },
+        // 多图时,图片水平和垂直之间的间隔
+        space: {
+            type: [String, Number],
+            default: uni.$u.props.album.space
+        },
+        // 单图时,图片缩放裁剪的模式
+        singleMode: {
+            type: String,
+            default: uni.$u.props.album.singleMode
+        },
+        // 多图时,图片缩放裁剪的模式
+        multipleMode: {
+            type: String,
+            default: uni.$u.props.album.multipleMode
+        },
+        // 最多展示的图片数量,超出时最后一个位置将会显示剩余图片数量
+        maxCount: {
+            type: [String, Number],
+            default: uni.$u.props.album.maxCount
+        },
+        // 是否可以预览图片
+        previewFullImage: {
+            type: Boolean,
+            default: uni.$u.props.album.previewFullImage
+        },
+        // 每行展示图片数量,如设置,singleSize和multipleSize将会无效
+        rowCount: {
+            type: [String, Number],
+            default: uni.$u.props.album.rowCount
+        },
+        // 超出maxCount时是否显示查看更多的提示
+        showMore: {
+            type: Boolean,
+            default: uni.$u.props.album.showMore
+        }
+    }
+}

+ 259 - 0
uni_modules/uview-ui/components/u-album/u-album.vue

@@ -0,0 +1,259 @@
+<template>
+    <view class="u-album">
+        <view
+            class="u-album__row"
+            ref="u-album__row"
+            v-for="(arr, index) in showUrls"
+            :forComputedUse="albumWidth"
+            :key="index"
+        >
+            <view
+                class="u-album__row__wrapper"
+                v-for="(item, index1) in arr"
+                :key="index1"
+                :style="[imageStyle(index + 1, index1 + 1)]"
+                @tap="previewFullImage ? onPreviewTap(getSrc(item)) : ''"
+            >
+                <image
+                    :src="getSrc(item)"
+                    :mode="
+                        urls.length === 1
+                            ? imageHeight > 0
+                                ? singleMode
+                                : 'widthFix'
+                            : multipleMode
+                    "
+                    :style="[
+                        {
+                            width: imageWidth,
+                            height: imageHeight
+                        }
+                    ]"
+                ></image>
+                <view
+                    v-if="
+                        showMore &&
+                        urls.length > rowCount * showUrls.length &&
+                        index === showUrls.length - 1 &&
+                        index1 === showUrls[showUrls.length - 1].length - 1
+                    "
+                    class="u-album__row__wrapper__text"
+                >
+                    <u--text
+                        :text="`+${urls.length - maxCount}`"
+                        color="#fff"
+                        :size="multipleSize * 0.3"
+                        align="center"
+                        customStyle="justify-content: center"
+                    ></u--text>
+                </view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+import props from './props.js'
+// #ifdef APP-NVUE
+// 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度
+const dom = uni.requireNativePlugin('dom')
+// #endif
+
+/**
+ * Album 相册
+ * @description 本组件提供一个类似相册的功能,让开发者开发起来更加得心应手。减少重复的模板代码
+ * @tutorial https://www.uviewui.com/components/album.html
+ *
+ * @property {Array}           urls             图片地址列表 Array<String>|Array<Object>形式
+ * @property {String}          keyName          指定从数组的对象元素中读取哪个属性作为图片地址
+ * @property {String | Number} singleSize       单图时,图片长边的长度  (默认 180 )
+ * @property {String | Number} multipleSize     多图时,图片边长 (默认 70 )
+ * @property {String | Number} space            多图时,图片水平和垂直之间的间隔 (默认 6 )
+ * @property {String}          singleMode       单图时,图片缩放裁剪的模式 (默认 'scaleToFill' )
+ * @property {String}          multipleMode     多图时,图片缩放裁剪的模式 (默认 'aspectFill' )
+ * @property {String | Number} maxCount         取消按钮的提示文字 (默认 9 )
+ * @property {Boolean}         previewFullImage 是否可以预览图片 (默认 true )
+ * @property {String | Number} rowCount         每行展示图片数量,如设置,singleSize和multipleSize将会无效	(默认 3 )
+ * @property {Boolean}         showMore         超出maxCount时是否显示查看更多的提示 (默认 true )
+ *
+ * @event    {Function}        albumWidth       某些特殊的情况下,需要让文字与相册的宽度相等,这里事件的形式对外发送  (回调参数 width )
+ * @example <u-album :urls="urls2" @albumWidth="width => albumWidth = width" multipleSize="68" ></u-album>
+ */
+export default {
+    name: 'u-album',
+    mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+    data() {
+        return {
+            // 单图的宽度
+            singleWidth: 0,
+            // 单图的高度
+            singleHeight: 0,
+            // 单图时,如果无法获取图片的尺寸信息,让图片宽度默认为容器的一定百分比
+            singlePercent: 0.6
+        }
+    },
+    watch: {
+        urls: {
+            immediate: true,
+            handler(newVal) {
+                if (newVal.length === 1) {
+                    this.getImageRect()
+                }
+            }
+        }
+    },
+    computed: {
+        imageStyle() {
+            return (index1, index2) => {
+                const { space, rowCount, multipleSize, urls } = this,
+                    { addUnit, addStyle } = uni.$u,
+                    rowLen = this.showUrls.length,
+                    allLen = this.urls.length
+                const style = {
+                    marginRight: addUnit(space),
+                    marginBottom: addUnit(space)
+                }
+                // 如果为最后一行,则每个图片都无需下边框
+                if (index1 === rowLen) style.marginBottom = 0
+                // 每行的最右边一张和总长度的最后一张无需右边框
+                if (
+                    index2 === rowCount ||
+                    (index1 === rowLen &&
+                        index2 === this.showUrls[index1 - 1].length)
+                )
+                    style.marginRight = 0
+                return style
+            }
+        },
+        // 将数组划分为二维数组
+        showUrls() {
+            const arr = []
+            this.urls.map((item, index) => {
+                // 限制最大展示数量
+                if (index + 1 <= this.maxCount) {
+                    // 计算该元素为第几个素组内
+                    const itemIndex = Math.floor(index / this.rowCount)
+                    // 判断对应的索引是否存在
+                    if (!arr[itemIndex]) {
+                        arr[itemIndex] = []
+                    }
+                    arr[itemIndex].push(item)
+                }
+            })
+            return arr
+        },
+        imageWidth() {
+            return uni.$u.addUnit(
+                this.urls.length === 1 ? this.singleWidth : this.multipleSize
+            )
+        },
+        imageHeight() {
+            return uni.$u.addUnit(
+                this.urls.length === 1 ? this.singleHeight : this.multipleSize
+            )
+        },
+        // 此变量无实际用途,仅仅是为了利用computed特性,让其在urls长度等变化时,重新计算图片的宽度
+        // 因为用户在某些特殊的情况下,需要让文字与相册的宽度相等,所以这里事件的形式对外发送
+        albumWidth() {
+            let width = 0
+            if (this.urls.length === 1) {
+                width = this.singleWidth
+            } else {
+                width =
+                    this.showUrls[0].length * this.multipleSize +
+                    this.space * (this.showUrls[0].length - 1)
+            }
+            this.$emit('albumWidth', width)
+            return width
+        }
+    },
+    methods: {
+        // 预览图片
+        onPreviewTap(url) {
+            const urls = this.urls.map((item) => {
+                return this.getSrc(item)
+            })
+            uni.previewImage({
+                current: url,
+                urls
+            })
+        },
+        // 获取图片的路径
+        getSrc(item) {
+            return uni.$u.test.object(item)
+                ? (this.keyName && item[this.keyName]) || item.src
+                : item
+        },
+        // 单图时,获取图片的尺寸
+        // 在小程序中,需要将网络图片的的域名添加到小程序的download域名才可能获取尺寸
+        // 在没有添加的情况下,让单图宽度默认为盒子的一定宽度(singlePercent)
+        getImageRect() {
+            const src = this.getSrc(this.urls[0])
+            uni.getImageInfo({
+                src,
+                success: (res) => {
+                    // 判断图片横向还是竖向展示方式
+                    const isHorizotal = res.width >= res.height
+                    this.singleWidth = isHorizotal
+                        ? this.singleSize
+                        : (res.width / res.height) * this.singleSize
+                    this.singleHeight = !isHorizotal
+                        ? this.singleSize
+                        : (res.height / res.width) * this.singleWidth
+                },
+                fail: () => {
+                    this.getComponentWidth()
+                }
+            })
+        },
+        // 获取组件的宽度
+        async getComponentWidth() {
+            // 延时一定时间,以获取dom尺寸
+            await uni.$u.sleep(30)
+            // #ifndef APP-NVUE
+            this.$uGetRect('.u-album__row').then((size) => {
+                this.singleWidth = size.width * this.singlePercent
+            })
+            // #endif
+
+            // #ifdef APP-NVUE
+            // 这里ref="u-album__row"所在的标签为通过for循环出来,导致this.$refs['u-album__row']是一个数组
+            const ref = this.$refs['u-album__row'][0]
+            ref &&
+                dom.getComponentRect(ref, (res) => {
+                    this.singleWidth = res.size.width * this.singlePercent
+                })
+            // #endif
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+@import '../../libs/css/components.scss';
+
+.u-album {
+    @include flex(column);
+
+    &__row {
+        @include flex(row);
+        flex-wrap: wrap;
+
+        &__wrapper {
+            position: relative;
+
+            &__text {
+                position: absolute;
+                top: 0;
+                left: 0;
+                right: 0;
+                bottom: 0;
+                background-color: rgba(0, 0, 0, 0.3);
+                @include flex(row);
+                justify-content: center;
+                align-items: center;
+            }
+        }
+    }
+}
+</style>

+ 44 - 0
uni_modules/uview-ui/components/u-alert/props.js

@@ -0,0 +1,44 @@
+export default {
+    props: {
+        // 显示文字
+        title: {
+            type: String,
+            default: uni.$u.props.alert.title
+        },
+        // 主题,success/warning/info/error
+        type: {
+            type: String,
+            default: uni.$u.props.alert.type
+        },
+        // 辅助性文字
+        description: {
+            type: String,
+            default: uni.$u.props.alert.description
+        },
+        // 是否可关闭
+        closable: {
+            type: Boolean,
+            default: uni.$u.props.alert.closable
+        },
+        // 是否显示图标
+        showIcon: {
+            type: Boolean,
+            default: uni.$u.props.alert.showIcon
+        },
+        // 浅或深色调,light-浅色,dark-深色
+        effect: {
+            type: String,
+            default: uni.$u.props.alert.effect
+        },
+        // 文字是否居中
+        center: {
+            type: Boolean,
+            default: uni.$u.props.alert.center
+        },
+        // 字体大小
+        fontSize: {
+            type: [String, Number],
+            default: uni.$u.props.alert.fontSize
+        }
+    }
+}

+ 243 - 0
uni_modules/uview-ui/components/u-alert/u-alert.vue

@@ -0,0 +1,243 @@
+<template>
+	<u-transition
+	    mode="fade"
+	    :show="show"
+	>
+		<view
+		    class="u-alert"
+		    :class="[`u-alert--${type}--${effect}`]"
+		    @tap.stop="clickHandler"
+		    :style="[$u.addStyle(customStyle)]"
+		>
+			<view
+			    class="u-alert__icon"
+			    v-if="showIcon"
+			>
+				<u-icon
+				    :name="iconName"
+				    size="18"
+				    :color="iconColor"
+				></u-icon>
+			</view>
+			<view
+			    class="u-alert__content"
+			    :style="[{
+					paddingRight: closable ? '20px' : 0
+				}]"
+			>
+				<text
+				    class="u-alert__content__title"
+				    v-if="title"
+					:style="[{
+						fontSize: $u.addUnit(fontSize),
+						textAlign: center ? 'center' : 'left'
+					}]"
+				    :class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]"
+				>{{ title }}</text>
+				<text
+				    class="u-alert__content__desc"
+					v-if="description"
+					:style="[{
+						fontSize: $u.addUnit(fontSize),
+						textAlign: center ? 'center' : 'left'
+					}]"
+				    :class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]"
+				>{{ description }}</text>
+			</view>
+			<view
+			    class="u-alert__close"
+			    v-if="closable"
+			    @tap.stop="closeHandler"
+			>
+				<u-icon
+				    name="close"
+				    :color="iconColor"
+				    size="15"
+				></u-icon>
+			</view>
+		</view>
+	</u-transition>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * Alert  警告提示
+	 * @description 警告提示,展现需要关注的信息。
+	 * @tutorial https://www.uviewui.com/components/alertTips.html
+	 * 
+	 * @property {String}			title       显示的文字 
+	 * @property {String}			type        使用预设的颜色  (默认 'warning' )
+	 * @property {String}			description 辅助性文字,颜色比title浅一点,字号也小一点,可选  
+	 * @property {Boolean}			closable    关闭按钮(默认为叉号icon图标)  (默认 false )
+	 * @property {Boolean}			showIcon    是否显示左边的辅助图标   ( 默认 false )
+	 * @property {String}			effect      多图时,图片缩放裁剪的模式  (默认 'light' )
+	 * @property {Boolean}			center		文字是否居中  (默认 false )
+	 * @property {String | Number}	fontSize    字体大小  (默认 14 )
+	 * @property {Object}			customStyle	定义需要用到的外部样式
+	 * @event    {Function}        click       点击组件时触发
+	 * @example  <u-alert :title="title"  type = "warning" :closable="closable" :description = "description"></u-alert>
+	 */
+	export default {
+		name: 'u-alert',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+		data() {
+			return {
+				show: true
+			}
+		},
+		computed: {
+			iconColor() {
+				return this.effect === 'light' ? this.type : '#fff'
+			},
+			// 不同主题对应不同的图标
+			iconName() {
+				switch (this.type) {
+					case 'success':
+						return 'checkmark-circle-fill';
+						break;
+					case 'error':
+						return 'close-circle-fill';
+						break;
+					case 'warning':
+						return 'error-circle-fill';
+						break;
+					case 'info':
+						return 'info-circle-fill';
+						break;
+					case 'primary':
+						return 'more-circle-fill';
+						break;
+					default: 
+						return 'error-circle-fill';
+				}
+			}
+		},
+		methods: {
+			// 点击内容
+			clickHandler() {
+				this.$emit('click')
+			},
+			// 点击关闭按钮
+			closeHandler() {
+				this.show = false
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+
+	.u-alert {
+		position: relative;
+		background-color: $u-primary;
+		padding: 8px 10px;
+		@include flex(row);
+		align-items: center;
+		border-top-left-radius: 4px;
+		border-top-right-radius: 4px;
+		border-bottom-left-radius: 4px;
+		border-bottom-right-radius: 4px;
+
+		&--primary--dark {
+			background-color: $u-primary;
+		}
+
+		&--primary--light {
+			background-color: #ecf5ff;
+		}
+
+		&--error--dark {
+			background-color: $u-error;
+		}
+
+		&--error--light {
+			background-color: #FEF0F0;
+		}
+
+		&--success--dark {
+			background-color: $u-success;
+		}
+
+		&--success--light {
+			background-color: #f5fff0;
+		}
+
+		&--warning--dark {
+			background-color: $u-warning;
+		}
+
+		&--warning--light {
+			background-color: #FDF6EC;
+		}
+
+		&--info--dark {
+			background-color: $u-info;
+		}
+
+		&--info--light {
+			background-color: #f4f4f5;
+		}
+
+		&__icon {
+			margin-right: 5px;
+		}
+
+		&__content {
+			@include flex(column);
+			flex: 1;
+
+			&__title {
+				color: $u-main-color;
+				font-size: 14px;
+				font-weight: bold;
+				color: #fff;
+				margin-bottom: 2px;
+			}
+
+			&__desc {
+				color: $u-main-color;
+				font-size: 14px;
+				flex-wrap: wrap;
+				color: #fff;
+			}
+		}
+
+		&__title--dark,
+		&__desc--dark {
+			color: #FFFFFF;
+		}
+
+		&__text--primary--light,
+		&__text--primary--light {
+			color: $u-primary;
+		}
+
+		&__text--success--light,
+		&__text--success--light {
+			color: $u-success;
+		}
+
+		&__text--warning--light,
+		&__text--warning--light {
+			color: $u-warning;
+		}
+
+		&__text--error--light,
+		&__text--error--light {
+			color: $u-error;
+		}
+
+		&__text--info--light,
+		&__text--info--light {
+			color: $u-info;
+		}
+
+		&__close {
+			position: absolute;
+			top: 11px;
+			right: 10px;
+		}
+	}
+</style>

+ 52 - 0
uni_modules/uview-ui/components/u-avatar-group/props.js

@@ -0,0 +1,52 @@
+export default {
+    props: {
+        // 头像图片组
+        urls: {
+            type: Array,
+            default: uni.$u.props.avatarGroup.urls
+        },
+        // 最多展示的头像数量
+        maxCount: {
+            type: [String, Number],
+            default: uni.$u.props.avatarGroup.maxCount
+        },
+        // 头像形状
+        shape: {
+            type: String,
+            default: uni.$u.props.avatarGroup.shape
+        },
+        // 图片裁剪模式
+        mode: {
+            type: String,
+            default: uni.$u.props.avatarGroup.mode
+        },
+        // 超出maxCount时是否显示查看更多的提示
+        showMore: {
+            type: Boolean,
+            default: uni.$u.props.avatarGroup.showMore
+        },
+        // 头像大小
+        size: {
+            type: [String, Number],
+            default: uni.$u.props.avatarGroup.size
+        },
+        // 指定从数组的对象元素中读取哪个属性作为图片地址
+        keyName: {
+            type: String,
+            default: uni.$u.props.avatarGroup.keyName
+        },
+		// 头像之间的遮挡比例
+        gap: {
+            type: [String, Number],
+            validator(value) {
+                return value >= 0 && value <= 1
+            },
+            default: uni.$u.props.avatarGroup.gap
+        },
+		// 需额外显示的值
+		extraValue: {
+			type: [Number, String],
+			default: uni.$u.props.avatarGroup.extraValue
+		}
+    }
+}

+ 103 - 0
uni_modules/uview-ui/components/u-avatar-group/u-avatar-group.vue

@@ -0,0 +1,103 @@
+<template>
+	<view class="u-avatar-group">
+		<view
+		    class="u-avatar-group__item"
+		    v-for="(item, index) in showUrl"
+		    :key="index"
+		    :style="{
+				marginLeft: index === 0 ? 0 : $u.addUnit(-size * gap)
+			}"
+		>
+			<u-avatar
+			    :size="size"
+			    :shape="shape"
+			    :mode="mode"
+			    :src="$u.test.object(item) ? keyName && item[keyName] || item.url : item"
+			></u-avatar>
+			<view
+			    class="u-avatar-group__item__show-more"
+			    v-if="showMore && index === showUrl.length - 1 && (urls.length > maxCount || extraValue > 0)"
+				@tap="clickHandler"
+			>
+				<u--text
+				    color="#ffffff"
+				    :size="size * 0.4"
+				    :text="`+${extraValue || urls.length - showUrl.length}`"
+					align="center"
+					customStyle="justify-content: center"
+				></u--text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * AvatarGroup  头像组
+	 * @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
+	 * @tutorial https://www.uviewui.com/components/avatar.html
+	 * 
+	 * @property {Array}           urls     头像图片组 (默认 [] )
+	 * @property {String | Number} maxCount 最多展示的头像数量 ( 默认 5 )
+	 * @property {String}          shape    头像形状( 'circle' (默认) | 'square' )
+	 * @property {String}          mode     图片裁剪模式(默认 'scaleToFill' )
+	 * @property {Boolean}         showMore 超出maxCount时是否显示查看更多的提示 (默认 true )
+	 * @property {String | Number} size      头像大小 (默认 40 )
+	 * @property {String}          keyName  指定从数组的对象元素中读取哪个属性作为图片地址 
+	 * @property {String | Number} gap      头像之间的遮挡比例(0.4代表遮挡40%)  (默认 0.5 )
+	 * @property {String | Number} extraValue  需额外显示的值
+	 * @event    {Function}        showMore 头像组更多点击
+	 * @example  <u-avatar-group:urls="urls" size="35" gap="0.4" ></u-avatar-group:urls=>
+	 */
+	export default {
+		name: 'u-avatar-group',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+		data() {
+			return {
+
+			}
+		},
+		computed: {
+			showUrl() {
+				return this.urls.slice(0, this.maxCount)
+			}
+		},
+		methods: {
+			clickHandler() {
+				this.$emit('showMore')
+			}
+		},
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+
+	.u-avatar-group {
+		@include flex;
+
+		&__item {
+			margin-left: -10px;
+			position: relative;
+
+			&--no-indent {
+				// 如果你想质疑作者不会使用:first-child,说明你太年轻,因为nvue不支持
+				margin-left: 0;
+			}
+
+			&__show-more {
+				position: absolute;
+				top: 0;
+				bottom: 0;
+				left: 0;
+				right: 0;
+				background-color: rgba(0, 0, 0, 0.3);
+				@include flex;
+				align-items: center;
+				justify-content: center;
+				border-radius: 100px;
+			}
+		}
+	}
+</style>

+ 78 - 0
uni_modules/uview-ui/components/u-avatar/props.js

@@ -0,0 +1,78 @@
+export default {
+    props: {
+        // 头像图片路径(不能为相对路径)
+        src: {
+            type: String,
+            default: uni.$u.props.avatar.src
+        },
+        // 头像形状,circle-圆形,square-方形
+        shape: {
+            type: String,
+            default: uni.$u.props.avatar.shape
+        },
+        // 头像尺寸
+        size: {
+            type: [String, Number],
+            default: uni.$u.props.avatar.size
+        },
+        // 裁剪模式
+        mode: {
+            type: String,
+            default: uni.$u.props.avatar.mode
+        },
+        // 显示的文字
+        text: {
+            type: String,
+            default: uni.$u.props.avatar.text
+        },
+        // 背景色
+        bgColor: {
+            type: String,
+            default: uni.$u.props.avatar.bgColor
+        },
+        // 文字颜色
+        color: {
+            type: String,
+            default: uni.$u.props.avatar.color
+        },
+        // 文字大小
+        fontSize: {
+            type: [String, Number],
+            default: uni.$u.props.avatar.fontSize
+        },
+        // 显示的图标
+        icon: {
+            type: String,
+            default: uni.$u.props.avatar.icon
+        },
+        // 显示小程序头像,只对百度,微信,QQ小程序有效
+        mpAvatar: {
+            type: Boolean,
+            default: uni.$u.props.avatar.mpAvatar
+        },
+        // 是否使用随机背景色
+        randomBgColor: {
+            type: Boolean,
+            default: uni.$u.props.avatar.randomBgColor
+        },
+        // 加载失败的默认头像(组件有内置默认图片)
+        defaultUrl: {
+            type: String,
+            default: uni.$u.props.avatar.defaultUrl
+        },
+        // 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间
+        colorIndex: {
+            type: [String, Number],
+            // 校验参数规则,索引在0-19之间
+            validator(n) {
+                return uni.$u.test.range(n, [0, 19]) || n === ''
+            },
+            default: uni.$u.props.avatar.colorIndex
+        },
+        // 组件标识符
+        name: {
+            type: String,
+            default: uni.$u.props.avatar.name
+        }
+    }
+}

Fișier diff suprimat deoarece este prea mare
+ 58 - 0
uni_modules/uview-ui/components/u-avatar/u-avatar.vue


+ 54 - 0
uni_modules/uview-ui/components/u-back-top/props.js

@@ -0,0 +1,54 @@
+export default {
+    props: {
+        // 返回顶部的形状,circle-圆形,square-方形
+        mode: {
+            type: String,
+            default: uni.$u.props.backtop.mode
+        },
+        // 自定义图标
+        icon: {
+            type: String,
+            default: uni.$u.props.backtop.icon
+        },
+        // 提示文字
+        text: {
+            type: String,
+            default: uni.$u.props.backtop.text
+        },
+        // 返回顶部滚动时间
+        duration: {
+            type: [String, Number],
+            default: uni.$u.props.backtop.duration
+        },
+        // 滚动距离
+        scrollTop: {
+            type: [String, Number],
+            default: uni.$u.props.backtop.scrollTop
+        },
+        // 距离顶部多少距离显示,单位px
+        top: {
+            type: [String, Number],
+            default: uni.$u.props.backtop.top
+        },
+        // 返回顶部按钮到底部的距离,单位px
+        bottom: {
+            type: [String, Number],
+            default: uni.$u.props.backtop.bottom
+        },
+        // 返回顶部按钮到右边的距离,单位px
+        right: {
+            type: [String, Number],
+            default: uni.$u.props.backtop.right
+        },
+        // 层级
+        zIndex: {
+            type: [String, Number],
+            default: uni.$u.props.backtop.zIndex
+        },
+        // 图标的样式,对象形式
+        iconStyle: {
+            type: Object,
+            default: uni.$u.props.backtop.iconStyle
+        }
+    }
+}

+ 129 - 0
uni_modules/uview-ui/components/u-back-top/u-back-top.vue

@@ -0,0 +1,129 @@
+<template>
+	<u-transition
+	    mode="fade"
+	    :customStyle="backTopStyle"
+	    :show="show"
+	>
+		<view
+		    class="u-back-top"
+			:style="[contentStyle]"
+		    v-if="!$slots.default && !$slots.$default"
+			@click="backToTop"
+		>
+			<u-icon
+			    :name="icon"
+			    :custom-style="iconStyle"
+			></u-icon>
+			<text
+			    v-if="text"
+			    class="u-back-top__text"
+			>{{text}}</text>
+		</view>
+		<slot v-else />
+	</u-transition>
+</template>
+
+<script>
+	import props from './props.js';
+	// #ifdef APP-NVUE
+	const dom = weex.requireModule('dom')
+	// #endif
+	/**
+	 * backTop 返回顶部
+	 * @description 本组件一个用于长页面,滑动一定距离后,出现返回顶部按钮,方便快速返回顶部的场景。
+	 * @tutorial https://uviewui.com/components/backTop.html
+	 * 
+	 * @property {String}			mode  		返回顶部的形状,circle-圆形,square-方形 (默认 'circle' )
+	 * @property {String} 			icon 		自定义图标 (默认 'arrow-upward' ) 见官方文档示例
+	 * @property {String} 			text 		提示文字 
+	 * @property {String | Number}  duration	返回顶部滚动时间 (默认 100)
+	 * @property {String | Number}  scrollTop	滚动距离 (默认 0 )
+	 * @property {String | Number}  top  		距离顶部多少距离显示,单位px (默认 400 )
+	 * @property {String | Number}  bottom  	返回顶部按钮到底部的距离,单位px (默认 100 )
+	 * @property {String | Number}  right  		返回顶部按钮到右边的距离,单位px (默认 20 )
+	 * @property {String | Number}  zIndex 		层级   (默认 9 )
+	 * @property {Object<Object>}  	iconStyle 	图标的样式,对象形式   (默认 {color: '#909399',fontSize: '19px'})
+	 * @property {Object}			customStyle	定义需要用到的外部样式
+	 * 
+	 * @example <u-back-top :scrollTop="scrollTop"></u-back-top>
+	 */
+	export default {
+		name: 'u-back-top',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
+		computed: {
+			backTopStyle() {
+				// 动画组件样式
+				const style = {
+					bottom: uni.$u.addUnit(this.bottom),
+					right: uni.$u.addUnit(this.right),
+					width: '40px',
+					height: '40px',
+					position: 'fixed',
+					zIndex: 10,
+				}
+				return style
+			},
+			show() {
+				return uni.$u.getPx(this.scrollTop) > uni.$u.getPx(this.top)
+			},
+			contentStyle() {
+				const style = {}
+				let radius = 0
+				// 是否圆形
+				if(this.mode === 'circle') {
+					radius = '100px'
+				} else {
+					radius = '4px'
+				}
+				// 为了兼容安卓nvue,只能这么分开写
+				style.borderTopLeftRadius = radius
+				style.borderTopRightRadius = radius
+				style.borderBottomLeftRadius = radius
+				style.borderBottomRightRadius = radius
+				return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))
+			}
+		},
+		methods: {
+			backToTop() {
+				// #ifdef APP-NVUE
+				if (!this.$parent.$refs['u-back-top']) {
+					uni.$u.error(`nvue页面需要给页面最外层元素设置"ref='u-back-top'`)
+				}
+				dom.scrollToElement(this.$parent.$refs['u-back-top'], {
+					offset: 0
+				})
+				// #endif
+				
+				// #ifndef APP-NVUE
+				uni.pageScrollTo({
+					scrollTop: 0,
+					duration: this.duration
+				});
+				// #endif
+				this.$emit('click')
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import '../../libs/css/components.scss';
+     $u-back-top-flex:1 !default;
+     $u-back-top-height:100% !default;
+     $u-back-top-background-color:#E1E1E1 !default;
+     $u-back-top-tips-font-size:12px !default;
+	.u-back-top {
+		@include flex;
+		flex-direction: column;
+		align-items: center;
+		flex:$u-back-top-flex;
+		height: $u-back-top-height;
+		justify-content: center;
+		background-color: $u-back-top-background-color;
+
+		&__tips {
+			font-size:$u-back-top-tips-font-size;
+			transform: scale(0.8);
+		}
+	}
+</style>

+ 72 - 0
uni_modules/uview-ui/components/u-badge/props.js

@@ -0,0 +1,72 @@
+export default {
+    props: {
+        // 是否显示圆点
+        isDot: {
+            type: Boolean,
+            default: uni.$u.props.badge.isDot
+        },
+        // 显示的内容
+        value: {
+            type: [Number, String],
+            default: uni.$u.props.badge.value
+        },
+        // 是否显示
+        show: {
+            type: Boolean,
+            default: uni.$u.props.badge.show
+        },
+        // 最大值,超过最大值会显示 '{max}+'
+        max: {
+            type: [Number, String],
+            default: uni.$u.props.badge.max
+        },
+        // 主题类型,error|warning|success|primary
+        type: {
+            type: String,
+            default: uni.$u.props.badge.type
+        },
+        // 当数值为 0 时,是否展示 Badge
+        showZero: {
+            type: Boolean,
+            default: uni.$u.props.badge.showZero
+        },
+        // 背景颜色,优先级比type高,如设置,type参数会失效
+        bgColor: {
+            type: [String, null],
+            default: uni.$u.props.badge.bgColor
+        },
+        // 字体颜色
+        color: {
+            type: [String, null],
+            default: uni.$u.props.badge.color
+        },
+        // 徽标形状,circle-四角均为圆角,horn-左下角为直角
+        shape: {
+            type: String,
+            default: uni.$u.props.badge.shape
+        },
+        // 设置数字的显示方式,overflow|ellipsis|limit
+        // overflow会根据max字段判断,超出显示`${max}+`
+        // ellipsis会根据max判断,超出显示`${max}...`
+        // limit会依据1000作为判断条件,超出1000,显示`${value/1000}K`,比如2.2k、3.34w,最多保留2位小数
+        numberType: {
+            type: String,
+            default: uni.$u.props.badge.numberType
+        },
+        // 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效
+        offset: {
+            type: Array,
+            default: uni.$u.props.badge.offset
+        },
+        // 是否反转背景和字体颜色
+        inverted: {
+            type: Boolean,
+            default: uni.$u.props.badge.inverted
+        },
+        // 是否绝对定位
+        absolute: {
+            type: Boolean,
+            default: uni.$u.props.badge.absolute
+        }
+    }
+}

+ 171 - 0
uni_modules/uview-ui/components/u-badge/u-badge.vue

@@ -0,0 +1,171 @@
+<template>
+	<text
+		v-if="show && ((Number(value) === 0 ? showZero : true) || isDot)"
+		:class="[isDot ? 'u-badge--dot' : 'u-badge--not-dot', inverted && 'u-badge--inverted', shape === 'horn' && 'u-badge--horn', `u-badge--${type}${inverted ? '--inverted' : ''}`]"
+		:style="[$u.addStyle(customStyle), badgeStyle]"
+		class="u-badge"
+	>{{ isDot ? '' :showValue }}</text>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * badge 徽标数
+	 * @description 该组件一般用于图标右上角显示未读的消息数量,提示用户点击,有圆点和圆包含文字两种形式。
+	 * @tutorial https://uviewui.com/components/badge.html
+	 * 
+	 * @property {Boolean} 			isDot 		是否显示圆点 (默认 false )
+	 * @property {String | Number} 	value 		显示的内容
+	 * @property {Boolean} 			show 		是否显示 (默认 true )
+	 * @property {String | Number} 	max 		最大值,超过最大值会显示 '{max}+'  (默认999)
+	 * @property {String} 			type 		主题类型,error|warning|success|primary (默认 'error' )
+	 * @property {Boolean} 			showZero	当数值为 0 时,是否展示 Badge (默认 false )
+	 * @property {String} 			bgColor 	背景颜色,优先级比type高,如设置,type参数会失效
+	 * @property {String} 			color 		字体颜色 (默认 '#ffffff' )
+	 * @property {String} 			shape 		徽标形状,circle-四角均为圆角,horn-左下角为直角 (默认 'circle' )
+	 * @property {String} 			numberType	设置数字的显示方式,overflow|ellipsis|limit  (默认 'overflow' )
+	 * @property {Array}} 			offset		设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效
+	 * @property {Boolean} 			inverted	是否反转背景和字体颜色(默认 false )
+	 * @property {Boolean} 			absolute	是否绝对定位(默认 false )
+	 * @property {Object}			customStyle	定义需要用到的外部样式
+	 * @example <u-badge :type="type" :count="count"></u-badge>
+	 */
+	export default {
+		name: 'u-badge',
+		mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+		computed: {
+			// 是否将badge中心与父组件右上角重合
+			boxStyle() {
+				let style = {};
+				return style;
+			},
+			// 整个组件的样式
+			badgeStyle() {
+				const style = {}
+				if(this.color) {
+					style.color = this.color
+				}
+				if (this.bgColor && !this.inverted) {
+					style.backgroundColor = this.bgColor
+				}
+				if (this.absolute) {
+					style.position = 'absolute'
+					// 如果有设置offset参数
+					if(this.offset.length) {
+						// top和right分为为offset的第一个和第二个值,如果没有第二个值,则right等于top
+						const top = this.offset[0]
+						const right = this.offset[1] || top
+						style.top = uni.$u.addUnit(top)
+						style.right = uni.$u.addUnit(right)
+					}
+				}
+				return style
+			},
+			showValue() {
+				switch (this.numberType) {
+					case "overflow":
+						return Number(this.value) > Number(this.max) ? this.max + "+" : this.value
+						break;
+					case "ellipsis":
+						return Number(this.value) > Number(this.max) ? "..." : this.value
+						break;
+					case "limit":
+						return Number(this.value) > 999 ? Number(this.value) >= 9999 ?
+							Math.floor(this.value / 1e4 * 100) / 100 + "w" : Math.floor(this.value /
+								1e3 * 100) / 100 + "k" : this.value
+						break;
+					default:
+						return Number(this.value)
+				}
+			},
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+
+	$u-badge-primary: $u-primary !default;
+	$u-badge-error: $u-error !default;
+	$u-badge-success: $u-success !default;
+	$u-badge-info: $u-info !default;
+	$u-badge-warning: $u-warning !default;
+	$u-badge-dot-radius: 100px !default;
+	$u-badge-dot-size: 8px !default;
+	$u-badge-dot-right: 4px !default;
+	$u-badge-dot-top: 0 !default;
+	$u-badge-text-font-size: 11px !default;
+	$u-badge-text-right: 10px !default;
+	$u-badge-text-padding: 2px 5px !default;
+	$u-badge-text-align: center !default;
+	$u-badge-text-color: #FFFFFF !default;
+
+	.u-badge {
+		border-top-right-radius: $u-badge-dot-radius;
+		border-top-left-radius: $u-badge-dot-radius;
+		border-bottom-left-radius: $u-badge-dot-radius;
+		border-bottom-right-radius: $u-badge-dot-radius;
+		@include flex;
+		line-height: $u-badge-text-font-size;
+		text-align: $u-badge-text-align;
+		font-size: $u-badge-text-font-size;
+		color: $u-badge-text-color;
+
+		&--dot {
+			height: $u-badge-dot-size;
+			width: $u-badge-dot-size;
+		}
+		
+		&--inverted {
+			font-size: 13px;
+		}
+		
+		&--not-dot {
+			padding: $u-badge-text-padding;
+		}
+
+		&--horn {
+			border-bottom-left-radius: 0;
+		}
+
+		&--primary {
+			background-color: $u-badge-primary;
+		}
+		
+		&--primary--inverted {
+			color: $u-badge-primary;
+		}
+
+		&--error {
+			background-color: $u-badge-error;
+		}
+		
+		&--error--inverted {
+			color: $u-badge-error;
+		}
+
+		&--success {
+			background-color: $u-badge-success;
+		}
+		
+		&--success--inverted {
+			color: $u-badge-success;
+		}
+
+		&--info {
+			background-color: $u-badge-info;
+		}
+		
+		&--info--inverted {
+			color: $u-badge-info;
+		}
+
+		&--warning {
+			background-color: $u-badge-warning;
+		}
+		
+		&--warning--inverted {
+			color: $u-badge-warning;
+		}
+	}
+</style>

+ 46 - 0
uni_modules/uview-ui/components/u-button/nvue.scss

@@ -0,0 +1,46 @@
+$u-button-active-opacity:0.75 !default;
+$u-button-loading-text-margin-left:4px !default;
+$u-button-text-color: #FFFFFF !default;
+$u-button-text-plain-error-color:$u-error !default;
+$u-button-text-plain-warning-color:$u-warning !default;
+$u-button-text-plain-success-color:$u-success !default;
+$u-button-text-plain-info-color:$u-info !default;
+$u-button-text-plain-primary-color:$u-primary !default;
+.u-button {
+	&--active {
+		opacity: $u-button-active-opacity;
+	}
+	
+	&--active--plain {
+		background-color: rgb(217, 217, 217);
+	}
+	
+	&__loading-text {
+		margin-left:$u-button-loading-text-margin-left;
+	}
+	
+	&__text,
+	&__loading-text {
+		color:$u-button-text-color;
+	}
+	
+	&__text--plain--error {
+		color:$u-button-text-plain-error-color;
+	}
+	
+	&__text--plain--warning {
+		color:$u-button-text-plain-warning-color;
+	}
+	
+	&__text--plain--success{
+		color:$u-button-text-plain-success-color;
+	}
+	
+	&__text--plain--info {
+		color:$u-button-text-plain-info-color;
+	}
+	
+	&__text--plain--primary {
+		color:$u-button-text-plain-primary-color;
+	}
+}

+ 161 - 0
uni_modules/uview-ui/components/u-button/props.js

@@ -0,0 +1,161 @@
+/*
+ * @Author       : LQ
+ * @Description  :
+ * @version      : 1.0
+ * @Date         : 2021-08-16 10:04:04
+ * @LastAuthor   : LQ
+ * @lastTime     : 2021-08-16 10:04:24
+ * @FilePath     : /u-view2.0/uview-ui/components/u-button/props.js
+ */
+export default {
+    props: {
+        // 是否细边框
+        hairline: {
+            type: Boolean,
+            default: uni.$u.props.button.hairline
+        },
+        // 按钮的预置样式,info,primary,error,warning,success
+        type: {
+            type: String,
+            default: uni.$u.props.button.type
+        },
+        // 按钮尺寸,large,normal,small,mini
+        size: {
+            type: String,
+            default: uni.$u.props.button.size
+        },
+        // 按钮形状,circle(两边为半圆),square(带圆角)
+        shape: {
+            type: String,
+            default: uni.$u.props.button.shape
+        },
+        // 按钮是否镂空
+        plain: {
+            type: Boolean,
+            default: uni.$u.props.button.plain
+        },
+        // 是否禁止状态
+        disabled: {
+            type: Boolean,
+            default: uni.$u.props.button.disabled
+        },
+        // 是否加载中
+        loading: {
+            type: Boolean,
+            default: uni.$u.props.button.loading
+        },
+        // 加载中提示文字
+        loadingText: {
+            type: [String, Number],
+            default: uni.$u.props.button.loadingText
+        },
+        // 加载状态图标类型
+        loadingMode: {
+            type: String,
+            default: uni.$u.props.button.loadingMode
+        },
+        // 加载图标大小
+        loadingSize: {
+            type: [String, Number],
+            default: uni.$u.props.button.loadingSize
+        },
+        // 开放能力,具体请看uniapp稳定关于button组件部分说明
+        // https://uniapp.dcloud.io/component/button
+        openType: {
+            type: String,
+            default: uni.$u.props.button.openType
+        },
+        // 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
+        // 取值为submit(提交表单),reset(重置表单)
+        formType: {
+            type: String,
+            default: uni.$u.props.button.formType
+        },
+        // 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效
+        // 只微信小程序、QQ小程序有效
+        appParameter: {
+            type: String,
+            default: uni.$u.props.button.appParameter
+        },
+        // 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效
+        hoverStopPropagation: {
+            type: Boolean,
+            default: uni.$u.props.button.hoverStopPropagation
+        },
+        // 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效
+        lang: {
+            type: String,
+            default: uni.$u.props.button.lang
+        },
+        // 会话来源,open-type="contact"时有效。只微信小程序有效
+        sessionFrom: {
+            type: String,
+            default: uni.$u.props.button.sessionFrom
+        },
+        // 会话内消息卡片标题,open-type="contact"时有效
+        // 默认当前标题,只微信小程序有效
+        sendMessageTitle: {
+            type: String,
+            default: uni.$u.props.button.sendMessageTitle
+        },
+        // 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效
+        // 默认当前分享路径,只微信小程序有效
+        sendMessagePath: {
+            type: String,
+            default: uni.$u.props.button.sendMessagePath
+        },
+        // 会话内消息卡片图片,open-type="contact"时有效
+        // 默认当前页面截图,只微信小程序有效
+        sendMessageImg: {
+            type: String,
+            default: uni.$u.props.button.sendMessageImg
+        },
+        // 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,
+        // 用户点击后可以快速发送小程序消息,open-type="contact"时有效
+        showMessageCard: {
+            type: Boolean,
+            default: uni.$u.props.button.showMessageCard
+        },
+        // 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
+        dataName: {
+            type: String,
+            default: uni.$u.props.button.dataName
+        },
+        // 节流,一定时间内只能触发一次
+        throttleTime: {
+            type: [String, Number],
+            default: uni.$u.props.button.throttleTime
+        },
+        // 按住后多久出现点击态,单位毫秒
+        hoverStartTime: {
+            type: [String, Number],
+            default: uni.$u.props.button.hoverStartTime
+        },
+        // 手指松开后点击态保留时间,单位毫秒
+        hoverStayTime: {
+            type: [String, Number],
+            default: uni.$u.props.button.hoverStayTime
+        },
+        // 按钮文字,之所以通过props传入,是因为slot传入的话
+        // nvue中无法控制文字的样式
+        text: {
+            type: [String, Number],
+            default: uni.$u.props.button.text
+        },
+        // 按钮图标
+        icon: {
+            type: String,
+            default: uni.$u.props.button.icon
+        },
+        // 按钮图标
+        iconColor: {
+            type: String,
+            default: uni.$u.props.button.icon
+        },
+        // 按钮颜色,支持传入linear-gradient渐变色
+        color: {
+            type: String,
+            default: uni.$u.props.button.color
+        }
+    }
+}

+ 495 - 0
uni_modules/uview-ui/components/u-button/u-button.vue

@@ -0,0 +1,495 @@
+<template>
+    <!-- #ifndef APP-NVUE -->
+    <button
+        :hover-start-time="Number(hoverStartTime)"
+        :hover-stay-time="Number(hoverStayTime)"
+        :form-type="formType"
+        :open-type="openType"
+        :app-parameter="appParameter"
+        :hover-stop-propagation="hoverStopPropagation"
+        :send-message-title="sendMessageTitle"
+        :send-message-path="sendMessagePath"
+        :lang="lang"
+        :data-name="dataName"
+        :session-from="sessionFrom"
+        :send-message-img="sendMessageImg"
+        :show-message-card="showMessageCard"
+        @getphonenumber="getphonenumber"
+        @getuserinfo="getuserinfo"
+        @error="error"
+        @opensetting="opensetting"
+        @launchapp="launchapp"
+        @agreeprivacyauthorization="agreeprivacyauthorization"
+        :hover-class="!disabled && !loading ? 'u-button--active' : ''"
+        class="u-button u-reset-button"
+        :style="[baseColor, $u.addStyle(customStyle)]"
+        @tap="clickHandler"
+        :class="bemClass"
+    >
+        <template v-if="loading">
+            <u-loading-icon
+                :mode="loadingMode"
+                :size="loadingSize * 1.15"
+                :color="loadingColor"
+            ></u-loading-icon>
+            <text
+                class="u-button__loading-text"
+                :style="[{ fontSize: textSize + 'px' }]"
+                >{{ loadingText || text }}</text
+            >
+        </template>
+        <template v-else>
+            <u-icon
+                v-if="icon"
+                :name="icon"
+                :color="iconColorCom"
+                :size="textSize * 1.35"
+                :customStyle="{ marginRight: '2px' }"
+            ></u-icon>
+            <slot>
+                <text
+                    class="u-button__text"
+                    :style="[{ fontSize: textSize + 'px' }]"
+                    >{{ text }}</text
+                >
+            </slot>
+        </template>
+    </button>
+    <!-- #endif -->
+
+    <!-- #ifdef APP-NVUE -->
+    <view
+        :hover-start-time="Number(hoverStartTime)"
+        :hover-stay-time="Number(hoverStayTime)"
+        class="u-button"
+        :hover-class="
+            !disabled && !loading && !color && (plain || type === 'info')
+                ? 'u-button--active--plain'
+                : !disabled && !loading && !plain
+                ? 'u-button--active'
+                : ''
+        "
+        @tap="clickHandler"
+        :class="bemClass"
+        :style="[baseColor, $u.addStyle(customStyle)]"
+    >
+        <template v-if="loading">
+            <u-loading-icon
+                :mode="loadingMode"
+                :size="loadingSize * 1.15"
+                :color="loadingColor"
+            ></u-loading-icon>
+            <text
+                class="u-button__loading-text"
+                :style="[nvueTextStyle]"
+                :class="[plain && `u-button__text--plain--${type}`]"
+                >{{ loadingText || text }}</text
+            >
+        </template>
+        <template v-else>
+            <u-icon
+                v-if="icon"
+                :name="icon"
+                :color="iconColorCom"
+                :size="textSize * 1.35"
+            ></u-icon>
+            <text
+                class="u-button__text"
+                :style="[
+                    {
+                        marginLeft: icon ? '2px' : 0,
+                    },
+                    nvueTextStyle,
+                ]"
+                :class="[plain && `u-button__text--plain--${type}`]"
+                >{{ text }}</text
+            >
+        </template>
+    </view>
+    <!-- #endif -->
+</template>
+
+<script>
+import button from "../../libs/mixin/button.js";
+import openType from "../../libs/mixin/openType.js";
+import props from "./props.js";
+/**
+ * button 按钮
+ * @description Button 按钮
+ * @tutorial https://www.uviewui.com/components/button.html
+ *
+ * @property {Boolean}			hairline				是否显示按钮的细边框 (默认 true )
+ * @property {String}			type					按钮的预置样式,info,primary,error,warning,success (默认 'info' )
+ * @property {String}			size					按钮尺寸,large,normal,mini (默认 normal)
+ * @property {String}			shape					按钮形状,circle(两边为半圆),square(带圆角) (默认 'square' )
+ * @property {Boolean}			plain					按钮是否镂空,背景色透明 (默认 false)
+ * @property {Boolean}			disabled				是否禁用 (默认 false)
+ * @property {Boolean}			loading					按钮名称前是否带 loading 图标(App-nvue 平台,在 ios 上为雪花,Android上为圆圈) (默认 false)
+ * @property {String | Number}	loadingText				加载中提示文字
+ * @property {String}			loadingMode				加载状态图标类型 (默认 'spinner' )
+ * @property {String | Number}	loadingSize				加载图标大小 (默认 15 )
+ * @property {String}			openType				开放能力,具体请看uniapp稳定关于button组件部分说明
+ * @property {String}			formType				用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
+ * @property {String}			appParameter			打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效 (注:只微信小程序、QQ小程序有效)
+ * @property {Boolean}			hoverStopPropagation	指定是否阻止本节点的祖先节点出现点击态,微信小程序有效(默认 true )
+ * @property {String}			lang					指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文(默认 en )
+ * @property {String}			sessionFrom				会话来源,openType="contact"时有效
+ * @property {String}			sendMessageTitle		会话内消息卡片标题,openType="contact"时有效
+ * @property {String}			sendMessagePath			会话内消息卡片点击跳转小程序路径,openType="contact"时有效
+ * @property {String}			sendMessageImg			会话内消息卡片图片,openType="contact"时有效
+ * @property {Boolean}			showMessageCard			是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效(默认false)
+ * @property {String}			dataName				额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
+ * @property {String | Number}	throttleTime			节流,一定时间内只能触发一次 (默认 0 )
+ * @property {String | Number}	hoverStartTime			按住后多久出现点击态,单位毫秒 (默认 0 )
+ * @property {String | Number}	hoverStayTime			手指松开后点击态保留时间,单位毫秒 (默认 200 )
+ * @property {String | Number}	text					按钮文字,之所以通过props传入,是因为slot传入的话(注:nvue中无法控制文字的样式)
+ * @property {String}			icon					按钮图标
+ * @property {String}			iconColor				按钮图标颜色
+ * @property {String}			color					按钮颜色,支持传入linear-gradient渐变色
+ * @property {Object}			customStyle				定义需要用到的外部样式
+ *
+ * @event {Function}	click			非禁止并且非加载中,才能点击
+ * @event {Function}	getphonenumber	open-type="getPhoneNumber"时有效
+ * @event {Function}	getuserinfo		用户点击该按钮时,会返回获取到的用户信息,从返回参数的detail中获取到的值同uni.getUserInfo
+ * @event {Function}	error			当使用开放能力时,发生错误的回调
+ * @event {Function}	opensetting		在打开授权设置页并关闭后回调
+ * @event {Function}	launchapp		打开 APP 成功的回调
+ * @event {Function}	agreeprivacyauthorization	用户同意隐私协议事件回调
+ * @example <u-button>月落</u-button>
+ */
+export default {
+    name: "u-button",
+    // #ifdef MP
+    mixins: [uni.$u.mpMixin, uni.$u.mixin, button, openType, props],
+    // #endif
+    // #ifndef MP
+    mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+    // #endif
+    data() {
+        return {};
+    },
+    computed: {
+        // 生成bem风格的类名
+        bemClass() {
+            // this.bem为一个computed变量,在mixin中
+            if (!this.color) {
+                return this.bem(
+                    "button",
+                    ["type", "shape", "size"],
+                    ["disabled", "plain", "hairline"]
+                );
+            } else {
+                // 由于nvue的原因,在有color参数时,不需要传入type,否则会生成type相关的类型,影响最终的样式
+                return this.bem(
+                    "button",
+                    ["shape", "size"],
+                    ["disabled", "plain", "hairline"]
+                );
+            }
+        },
+        loadingColor() {
+            if (this.plain) {
+                // 如果有设置color值,则用color值,否则使用type主题颜色
+                return this.color
+                    ? this.color
+                    : uni.$u.config.color[`u-${this.type}`];
+            }
+            if (this.type === "info") {
+                return "#c9c9c9";
+            }
+            return "rgb(200, 200, 200)";
+        },
+        iconColorCom() {
+            // 如果是镂空状态,设置了color就用color值,否则使用主题颜色,
+            // u-icon的color能接受一个主题颜色的值
+			if (this.iconColor) return this.iconColor;
+			if (this.plain) {
+                return this.color ? this.color : this.type;
+            } else {
+                return this.type === "info" ? "#000000" : "#ffffff";
+            }
+        },
+        baseColor() {
+            let style = {};
+            if (this.color) {
+                // 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色
+                style.color = this.plain ? this.color : "white";
+                if (!this.plain) {
+                    // 非镂空,背景色使用自定义的颜色
+                    style["background-color"] = this.color;
+                }
+                if (this.color.indexOf("gradient") !== -1) {
+                    // 如果自定义的颜色为渐变色,不显示边框,以及通过backgroundImage设置渐变色
+                    // weex文档说明可以写borderWidth的形式,为什么这里需要分开写?
+                    // 因为weex是阿里巴巴为了部门业绩考核而做的你懂的东西,所以需要这么写才有效
+                    style.borderTopWidth = 0;
+                    style.borderRightWidth = 0;
+                    style.borderBottomWidth = 0;
+                    style.borderLeftWidth = 0;
+                    if (!this.plain) {
+                        style.backgroundImage = this.color;
+                    }
+                } else {
+                    // 非渐变色,则设置边框相关的属性
+                    style.borderColor = this.color;
+                    style.borderWidth = "1px";
+                    style.borderStyle = "solid";
+                }
+            }
+            return style;
+        },
+        // nvue版本按钮的字体不会继承父组件的颜色,需要对每一个text组件进行单独的设置
+        nvueTextStyle() {
+            let style = {};
+            // 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色
+            if (this.type === "info") {
+                style.color = "#323233";
+            }
+            if (this.color) {
+                style.color = this.plain ? this.color : "white";
+            }
+            style.fontSize = this.textSize + "px";
+            return style;
+        },
+        // 字体大小
+        textSize() {
+            let fontSize = 14,
+                { size } = this;
+            if (size === "large") fontSize = 16;
+            if (size === "normal") fontSize = 14;
+            if (size === "small") fontSize = 12;
+            if (size === "mini") fontSize = 10;
+            return fontSize;
+        },
+    },
+    methods: {
+        clickHandler() {
+            // 非禁止并且非加载中,才能点击
+            if (!this.disabled && !this.loading) {
+				// 进行节流控制,每this.throttle毫秒内,只在开始处执行
+				uni.$u.throttle(() => {
+					this.$emit("click");
+				}, this.throttleTime);
+            }
+        },
+        // 下面为对接uniapp官方按钮开放能力事件回调的对接
+        getphonenumber(res) {
+            this.$emit("getphonenumber", res);
+        },
+        getuserinfo(res) {
+            this.$emit("getuserinfo", res);
+        },
+        error(res) {
+            this.$emit("error", res);
+        },
+        opensetting(res) {
+            this.$emit("opensetting", res);
+        },
+        launchapp(res) {
+            this.$emit("launchapp", res);
+        },
+        agreeprivacyauthorization(res) {
+            this.$emit("agreeprivacyauthorization", res);
+        },
+    },
+};
+</script>
+
+<style lang="scss" scoped>
+@import "../../libs/css/components.scss";
+
+/* #ifndef APP-NVUE */
+@import "./vue.scss";
+/* #endif */
+
+/* #ifdef APP-NVUE */
+@import "./nvue.scss";
+/* #endif */
+
+$u-button-u-button-height: 40px !default;
+$u-button-text-font-size: 15px !default;
+$u-button-loading-text-font-size: 15px !default;
+$u-button-loading-text-margin-left: 4px !default;
+$u-button-large-width: 100% !default;
+$u-button-large-height: 50px !default;
+$u-button-normal-padding: 0 12px !default;
+$u-button-large-padding: 0 15px !default;
+$u-button-normal-font-size: 14px !default;
+$u-button-small-min-width: 60px !default;
+$u-button-small-height: 30px !default;
+$u-button-small-padding: 0px 8px !default;
+$u-button-mini-padding: 0px 8px !default;
+$u-button-small-font-size: 12px !default;
+$u-button-mini-height: 22px !default;
+$u-button-mini-font-size: 10px !default;
+$u-button-mini-min-width: 50px !default;
+$u-button-disabled-opacity: 0.5 !default;
+$u-button-info-color: #323233 !default;
+$u-button-info-background-color: #fff !default;
+$u-button-info-border-color: #ebedf0 !default;
+$u-button-info-border-width: 1px !default;
+$u-button-info-border-style: solid !default;
+$u-button-success-color: #fff !default;
+$u-button-success-background-color: $u-success !default;
+$u-button-success-border-color: $u-button-success-background-color !default;
+$u-button-success-border-width: 1px !default;
+$u-button-success-border-style: solid !default;
+$u-button-primary-color: #fff !default;
+$u-button-primary-background-color: $u-primary !default;
+$u-button-primary-border-color: $u-button-primary-background-color !default;
+$u-button-primary-border-width: 1px !default;
+$u-button-primary-border-style: solid !default;
+$u-button-error-color: #fff !default;
+$u-button-error-background-color: $u-error !default;
+$u-button-error-border-color: $u-button-error-background-color !default;
+$u-button-error-border-width: 1px !default;
+$u-button-error-border-style: solid !default;
+$u-button-warning-color: #fff !default;
+$u-button-warning-background-color: $u-warning !default;
+$u-button-warning-border-color: $u-button-warning-background-color !default;
+$u-button-warning-border-width: 1px !default;
+$u-button-warning-border-style: solid !default;
+$u-button-block-width: 100% !default;
+$u-button-circle-border-top-right-radius: 100px !default;
+$u-button-circle-border-top-left-radius: 100px !default;
+$u-button-circle-border-bottom-left-radius: 100px !default;
+$u-button-circle-border-bottom-right-radius: 100px !default;
+$u-button-square-border-top-right-radius: 3px !default;
+$u-button-square-border-top-left-radius: 3px !default;
+$u-button-square-border-bottom-left-radius: 3px !default;
+$u-button-square-border-bottom-right-radius: 3px !default;
+$u-button-icon-min-width: 1em !default;
+$u-button-plain-background-color: #fff !default;
+$u-button-hairline-border-width: 0.5px !default;
+
+.u-button {
+    height: $u-button-u-button-height;
+    position: relative;
+    align-items: center;
+    justify-content: center;
+    @include flex;
+    /* #ifndef APP-NVUE */
+    box-sizing: border-box;
+    /* #endif */
+    flex-direction: row;
+
+    &__text {
+        font-size: $u-button-text-font-size;
+    }
+
+    &__loading-text {
+        font-size: $u-button-loading-text-font-size;
+        margin-left: $u-button-loading-text-margin-left;
+    }
+
+    &--large {
+        /* #ifndef APP-NVUE */
+        width: $u-button-large-width;
+        /* #endif */
+        height: $u-button-large-height;
+        padding: $u-button-large-padding;
+    }
+
+    &--normal {
+        padding: $u-button-normal-padding;
+        font-size: $u-button-normal-font-size;
+    }
+
+    &--small {
+        /* #ifndef APP-NVUE */
+        min-width: $u-button-small-min-width;
+        /* #endif */
+        height: $u-button-small-height;
+        padding: $u-button-small-padding;
+        font-size: $u-button-small-font-size;
+    }
+
+    &--mini {
+        height: $u-button-mini-height;
+        font-size: $u-button-mini-font-size;
+        /* #ifndef APP-NVUE */
+        min-width: $u-button-mini-min-width;
+        /* #endif */
+        padding: $u-button-mini-padding;
+    }
+
+    &--disabled {
+        opacity: $u-button-disabled-opacity;
+    }
+
+    &--info {
+        color: $u-button-info-color;
+        background-color: $u-button-info-background-color;
+        border-color: $u-button-info-border-color;
+        border-width: $u-button-info-border-width;
+        border-style: $u-button-info-border-style;
+    }
+
+    &--success {
+        color: $u-button-success-color;
+        background-color: $u-button-success-background-color;
+        border-color: $u-button-success-border-color;
+        border-width: $u-button-success-border-width;
+        border-style: $u-button-success-border-style;
+    }
+
+    &--primary {
+        color: $u-button-primary-color;
+        background-color: $u-button-primary-background-color;
+        border-color: $u-button-primary-border-color;
+        border-width: $u-button-primary-border-width;
+        border-style: $u-button-primary-border-style;
+    }
+
+    &--error {
+        color: $u-button-error-color;
+        background-color: $u-button-error-background-color;
+        border-color: $u-button-error-border-color;
+        border-width: $u-button-error-border-width;
+        border-style: $u-button-error-border-style;
+    }
+
+    &--warning {
+        color: $u-button-warning-color;
+        background-color: $u-button-warning-background-color;
+        border-color: $u-button-warning-border-color;
+        border-width: $u-button-warning-border-width;
+        border-style: $u-button-warning-border-style;
+    }
+
+    &--block {
+        @include flex;
+        width: $u-button-block-width;
+    }
+
+    &--circle {
+        border-top-right-radius: $u-button-circle-border-top-right-radius;
+        border-top-left-radius: $u-button-circle-border-top-left-radius;
+        border-bottom-left-radius: $u-button-circle-border-bottom-left-radius;
+        border-bottom-right-radius: $u-button-circle-border-bottom-right-radius;
+    }
+
+    &--square {
+        border-bottom-left-radius: $u-button-square-border-top-right-radius;
+        border-bottom-right-radius: $u-button-square-border-top-left-radius;
+        border-top-left-radius: $u-button-square-border-bottom-left-radius;
+        border-top-right-radius: $u-button-square-border-bottom-right-radius;
+    }
+
+    &__icon {
+        /* #ifndef APP-NVUE */
+        min-width: $u-button-icon-min-width;
+        line-height: inherit !important;
+        vertical-align: top;
+        /* #endif */
+    }
+
+    &--plain {
+        background-color: $u-button-plain-background-color;
+    }
+
+    &--hairline {
+        border-width: $u-button-hairline-border-width !important;
+    }
+}
+</style>

+ 80 - 0
uni_modules/uview-ui/components/u-button/vue.scss

@@ -0,0 +1,80 @@
+// nvue下hover-class无效
+$u-button-before-top:50% !default;
+$u-button-before-left:50% !default;
+$u-button-before-width:100% !default;
+$u-button-before-height:100% !default;
+$u-button-before-transform:translate(-50%, -50%) !default;
+$u-button-before-opacity:0 !default;
+$u-button-before-background-color:#000 !default;
+$u-button-before-border-color:#000 !default;
+$u-button-active-before-opacity:.15 !default;
+$u-button-icon-margin-left:4px !default;
+$u-button-plain-u-button-info-color:$u-info;
+$u-button-plain-u-button-success-color:$u-success;
+$u-button-plain-u-button-error-color:$u-error;
+$u-button-plain-u-button-warning-color:$u-error;
+
+.u-button {
+	width: 100%;
+	
+	&__text {
+		white-space: nowrap;
+		line-height: 1;
+	}
+	
+	&:before {
+		position: absolute;
+		top:$u-button-before-top;
+		left:$u-button-before-left;
+		width:$u-button-before-width;
+		height:$u-button-before-height;
+		border: inherit;
+		border-radius: inherit;
+		transform:$u-button-before-transform;
+		opacity:$u-button-before-opacity;
+		content: " ";
+		background-color:$u-button-before-background-color;
+		border-color:$u-button-before-border-color;
+	}
+	
+	&--active {
+		&:before {
+			opacity: .15
+		}
+	}
+	
+	&__icon+&__text:not(:empty),
+	&__loading-text {
+		margin-left:$u-button-icon-margin-left;
+	}
+	
+	&--plain {
+		&.u-button--primary {
+			color: $u-primary;
+		}
+	}
+	
+	&--plain {
+		&.u-button--info {
+			color:$u-button-plain-u-button-info-color;
+		}
+	}
+	
+	&--plain {
+		&.u-button--success {
+			color:$u-button-plain-u-button-success-color;
+		}
+	}
+	
+	&--plain {
+		&.u-button--error {
+			color:$u-button-plain-u-button-error-color;
+		}
+	}
+	
+	&--plain {
+		&.u-button--warning {
+			color:$u-button-plain-u-button-warning-color;
+		}
+	}
+}

+ 99 - 0
uni_modules/uview-ui/components/u-calendar/header.vue

@@ -0,0 +1,99 @@
+<template>
+	<view class="u-calendar-header u-border-bottom">
+		<text
+			class="u-calendar-header__title"
+			v-if="showTitle"
+		>{{ title }}</text>
+		<text
+			class="u-calendar-header__subtitle"
+			v-if="showSubtitle"
+		>{{ subtitle }}</text>
+		<view class="u-calendar-header__weekdays">
+			<text class="u-calendar-header__weekdays__weekday">一</text>
+			<text class="u-calendar-header__weekdays__weekday">二</text>
+			<text class="u-calendar-header__weekdays__weekday">三</text>
+			<text class="u-calendar-header__weekdays__weekday">四</text>
+			<text class="u-calendar-header__weekdays__weekday">五</text>
+			<text class="u-calendar-header__weekdays__weekday">六</text>
+			<text class="u-calendar-header__weekdays__weekday">日</text>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'u-calendar-header',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin],
+		props: {
+			// 标题
+			title: {
+				type: String,
+				default: ''
+			},
+			// 副标题
+			subtitle: {
+				type: String,
+				default: ''
+			},
+			// 是否显示标题
+			showTitle: {
+				type: Boolean,
+				default: true
+			},
+			// 是否显示副标题
+			showSubtitle: {
+				type: Boolean,
+				default: true
+			},
+		},
+		data() {
+			return {
+
+			}
+		},
+		methods: {
+			name() {
+
+			}
+		},
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+
+	.u-calendar-header {
+		padding-bottom: 4px;
+
+		&__title {
+			font-size: 16px;
+			color: $u-main-color;
+			text-align: center;
+			height: 42px;
+			line-height: 42px;
+			font-weight: bold;
+		}
+
+		&__subtitle {
+			font-size: 14px;
+			color: $u-main-color;
+			height: 40px;
+			text-align: center;
+			line-height: 40px;
+			font-weight: bold;
+		}
+
+		&__weekdays {
+			@include flex;
+			justify-content: space-between;
+
+			&__weekday {
+				font-size: 13px;
+				color: $u-main-color;
+				line-height: 30px;
+				flex: 1;
+				text-align: center;
+			}
+		}
+	}
+</style>

+ 579 - 0
uni_modules/uview-ui/components/u-calendar/month.vue

@@ -0,0 +1,579 @@
+<template>
+	<view class="u-calendar-month-wrapper" ref="u-calendar-month-wrapper">
+		<view v-for="(item, index) in months" :key="index" :class="[`u-calendar-month-${index}`]"
+			:ref="`u-calendar-month-${index}`" :id="`month-${index}`">
+			<text v-if="index !== 0" class="u-calendar-month__title">{{ item.year }}年{{ item.month }}月</text>
+			<view class="u-calendar-month__days">
+				<view v-if="showMark" class="u-calendar-month__days__month-mark-wrapper">
+					<text class="u-calendar-month__days__month-mark-wrapper__text">{{ item.month }}</text>
+				</view>
+				<view class="u-calendar-month__days__day" v-for="(item1, index1) in item.date" :key="index1"
+					:style="[dayStyle(index, index1, item1)]" @tap="clickHandler(index, index1, item1)"
+					:class="[item1.selected && 'u-calendar-month__days__day__select--selected']">
+					<view class="u-calendar-month__days__day__select" :style="[daySelectStyle(index, index1, item1)]">
+						<text class="u-calendar-month__days__day__select__info"
+							:class="[item1.disabled && 'u-calendar-month__days__day__select__info--disabled']"
+							:style="[textStyle(item1)]">{{ item1.day }}</text>
+						<text v-if="getBottomInfo(index, index1, item1)"
+							class="u-calendar-month__days__day__select__buttom-info"
+							:class="[item1.disabled && 'u-calendar-month__days__day__select__buttom-info--disabled']"
+							:style="[textStyle(item1)]">{{ getBottomInfo(index, index1, item1) }}</text>
+						<text v-if="item1.dot" class="u-calendar-month__days__day__select__dot"></text>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	// #ifdef APP-NVUE
+	// 由于nvue不支持百分比单位,需要查询宽度来计算每个日期的宽度
+	const dom = uni.requireNativePlugin('dom')
+	// #endif
+	import dayjs from '../../libs/util/dayjs.js';
+	export default {
+		name: 'u-calendar-month',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin],
+		props: {
+			// 是否显示月份背景色
+			showMark: {
+				type: Boolean,
+				default: true
+			},
+			// 主题色,对底部按钮和选中日期有效
+			color: {
+				type: String,
+				default: '#3c9cff'
+			},
+			// 月份数据
+			months: {
+				type: Array,
+				default: () => []
+			},
+			// 日期选择类型
+			mode: {
+				type: String,
+				default: 'single'
+			},
+			// 日期行高
+			rowHeight: {
+				type: [String, Number],
+				default: 58
+			},
+			// mode=multiple时,最多可选多少个日期
+			maxCount: {
+				type: [String, Number],
+				default: Infinity
+			},
+			// mode=range时,第一个日期底部的提示文字
+			startText: {
+				type: String,
+				default: '开始'
+			},
+			// mode=range时,最后一个日期底部的提示文字
+			endText: {
+				type: String,
+				default: '结束'
+			},
+			// 默认选中的日期,mode为multiple或range是必须为数组格式
+			defaultDate: {
+				type: [Array, String, Date],
+				default: null
+			},
+			// 最小的可选日期
+			minDate: {
+				type: [String, Number],
+				default: 0
+			},
+			// 最大可选日期
+			maxDate: {
+				type: [String, Number],
+				default: 0
+			},
+			// 如果没有设置maxDate,则往后推多少个月
+			maxMonth: {
+				type: [String, Number],
+				default: 2
+			},
+			// 是否为只读状态,只读状态下禁止选择日期
+			readonly: {
+				type: Boolean,
+				default: uni.$u.props.calendar.readonly
+			},
+			// 日期区间最多可选天数,默认无限制,mode = range时有效
+			maxRange: {
+				type: [Number, String],
+				default: Infinity
+			},
+			// 范围选择超过最多可选天数时的提示文案,mode = range时有效
+			rangePrompt: {
+				type: String,
+				default: ''
+			},
+			// 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效
+			showRangePrompt: {
+				type: Boolean,
+				default: true
+			},
+			// 是否允许日期范围的起止时间为同一天,mode = range时有效
+			allowSameDay: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				// 每个日期的宽度
+				width: 0,
+				// 当前选中的日期item
+				item: {},
+				selected: []
+			}
+		},
+		watch: {
+			selectedChange: {
+				immediate: true,
+				handler(n) {
+					this.setDefaultDate()
+				}
+			}
+		},
+		computed: {
+			// 多个条件的变化,会引起选中日期的变化,这里统一管理监听
+			selectedChange() {
+				return [this.minDate, this.maxDate, this.defaultDate]
+			},
+			dayStyle(index1, index2, item) {
+				return (index1, index2, item) => {
+					const style = {}
+					let week = item.week
+					// 不进行四舍五入的形式保留2位小数
+					const dayWidth = Number(parseFloat(this.width / 7).toFixed(3).slice(0, -1))
+					// 得出每个日期的宽度
+					// #ifdef APP-NVUE
+					style.width = uni.$u.addUnit(dayWidth)
+					// #endif
+					style.height = uni.$u.addUnit(this.rowHeight)
+					if (index2 === 0) {
+						// 获取当前为星期几,如果为0,则为星期天,减一为每月第一天时,需要向左偏移的item个数
+						week = (week === 0 ? 7 : week) - 1
+						style.marginLeft = uni.$u.addUnit(week * dayWidth)
+					}
+					if (this.mode === 'range') {
+						// 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug
+						style.paddingLeft = 0
+						style.paddingRight = 0
+						style.paddingBottom = 0
+						style.paddingTop = 0
+					}
+					return style
+				}
+			},
+			daySelectStyle() {
+				return (index1, index2, item) => {
+					let date = dayjs(item.date).format("YYYY-MM-DD"),
+						style = {}
+					// 判断date是否在selected数组中,因为月份可能会需要补0,所以使用dateSame判断,而不用数组的includes判断
+					if (this.selected.some(item => this.dateSame(item, date))) {
+						style.backgroundColor = this.color
+					}
+					if (this.mode === 'single') {
+						if (date === this.selected[0]) {
+							// 因为需要对nvue的兼容,只能这么写,无法缩写,也无法通过类名控制等等
+							style.borderTopLeftRadius = '3px'
+							style.borderBottomLeftRadius = '3px'
+							style.borderTopRightRadius = '3px'
+							style.borderBottomRightRadius = '3px'
+						}
+					} else if (this.mode === 'range') {
+						if (this.selected.length >= 2) {
+							const len = this.selected.length - 1
+							// 第一个日期设置左上角和左下角的圆角
+							if (this.dateSame(date, this.selected[0])) {
+								style.borderTopLeftRadius = '3px'
+								style.borderBottomLeftRadius = '3px'
+							}
+							// 最后一个日期设置右上角和右下角的圆角
+							if (this.dateSame(date, this.selected[len])) {
+								style.borderTopRightRadius = '3px'
+								style.borderBottomRightRadius = '3px'
+							}
+							// 处于第一和最后一个之间的日期,背景色设置为浅色,通过将对应颜色进行等分,再取其尾部的颜色值
+							if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this
+									.selected[len]))) {
+								style.backgroundColor = uni.$u.colorGradient(this.color, '#ffffff', 100)[90]
+								// 增加一个透明度,让范围区间的背景色也能看到底部的mark水印字符
+								style.opacity = 0.7
+							}
+						} else if (this.selected.length === 1) {
+							// 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug
+							// 进行还原操作,否则在nvue的iOS,uni-app有bug,会导致诡异的表现
+							style.borderTopLeftRadius = '3px'
+							style.borderBottomLeftRadius = '3px'
+						}
+					} else {
+						if (this.selected.some(item => this.dateSame(item, date))) {
+							style.borderTopLeftRadius = '3px'
+							style.borderBottomLeftRadius = '3px'
+							style.borderTopRightRadius = '3px'
+							style.borderBottomRightRadius = '3px'
+						}
+					}
+					return style
+				}
+			},
+			// 某个日期是否被选中
+			textStyle() {
+				return (item) => {
+					const date = dayjs(item.date).format("YYYY-MM-DD"),
+						style = {}
+					// 选中的日期,提示文字设置白色
+					if (this.selected.some(item => this.dateSame(item, date))) {
+						style.color = '#ffffff'
+					}
+					if (this.mode === 'range') {
+						const len = this.selected.length - 1
+						// 如果是范围选择模式,第一个和最后一个之间的日期,文字颜色设置为高亮的主题色
+						if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this
+								.selected[len]))) {
+							style.color = this.color
+						}
+					}
+					return style
+				}
+			},
+			// 获取底部的提示文字
+			getBottomInfo() {
+				return (index1, index2, item) => {
+					const date = dayjs(item.date).format("YYYY-MM-DD")
+					const bottomInfo = item.bottomInfo
+					// 当为日期范围模式时,且选择的日期个数大于0时
+					if (this.mode === 'range' && this.selected.length > 0) {
+						if (this.selected.length === 1) {
+							// 选择了一个日期时,如果当前日期为数组中的第一个日期,则显示底部文字为“开始”
+							if (this.dateSame(date, this.selected[0])) return this.startText
+							else return bottomInfo
+						} else {
+							const len = this.selected.length - 1
+							// 如果数组中的日期大于2个时,第一个和最后一个显示为开始和结束日期
+							if (this.dateSame(date, this.selected[0]) && this.dateSame(date, this.selected[1]) &&
+								len === 1) {
+								// 如果长度为2,且第一个等于第二个日期,则提示语放在同一个item中
+								return `${this.startText}/${this.endText}`
+							} else if (this.dateSame(date, this.selected[0])) {
+								return this.startText
+							} else if (this.dateSame(date, this.selected[len])) {
+								return this.endText
+							} else {
+								return bottomInfo
+							}
+						}
+					} else {
+						return bottomInfo
+					}
+				}
+			}
+		},
+		mounted() {
+			this.init()
+		},
+		methods: {
+			init() {
+				// 初始化默认选中
+				this.$emit('monthSelected', this.selected)
+				this.$nextTick(() => {
+					// 这里需要另一个延时,因为获取宽度后,会进行月份数据渲染,只有渲染完成之后,才有真正的高度
+					// 因为nvue下,$nextTick并不是100%可靠的
+					uni.$u.sleep(10).then(() => {
+						this.getWrapperWidth()
+						this.getMonthRect()
+					})
+				})
+			},
+			// 判断两个日期是否相等
+			dateSame(date1, date2) {
+				return dayjs(date1).isSame(dayjs(date2))
+			},
+			// 获取月份数据区域的宽度,因为nvue不支持百分比,所以无法通过css设置每个日期item的宽度
+			getWrapperWidth() {
+				// #ifdef APP-NVUE
+				dom.getComponentRect(this.$refs['u-calendar-month-wrapper'], res => {
+					this.width = res.size.width
+				})
+				// #endif
+				// #ifndef APP-NVUE
+				this.$uGetRect('.u-calendar-month-wrapper').then(size => {
+					this.width = size.width
+				})
+				// #endif
+			},
+			getMonthRect() {
+				// 获取每个月份数据的尺寸,用于父组件在scroll-view滚动事件中,监听当前滚动到了第几个月份
+				const promiseAllArr = this.months.map((item, index) => this.getMonthRectByPromise(
+					`u-calendar-month-${index}`))
+				// 一次性返回
+				Promise.all(promiseAllArr).then(
+					sizes => {
+						let height = 1
+						const topArr = []
+						for (let i = 0; i < this.months.length; i++) {
+							// 添加到months数组中,供scroll-view滚动事件中,判断当前滚动到哪个月份
+							topArr[i] = height
+							height += sizes[i].height
+						}
+						// 由于微信下,无法通过this.months[i].top的形式(引用类型)去修改父组件的month的top值,所以使用事件形式对外发出
+						this.$emit('updateMonthTop', topArr)
+					})
+			},
+			// 获取每个月份区域的尺寸
+			getMonthRectByPromise(el) {
+				// #ifndef APP-NVUE
+				// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://www.uviewui.com/js/getRect.html
+				// 组件内部一般用this.$uGetRect,对外的为uni.$u.getRect,二者功能一致,名称不同
+				return new Promise(resolve => {
+					this.$uGetRect(`.${el}`).then(size => {
+						resolve(size)
+					})
+				})
+				// #endif
+
+				// #ifdef APP-NVUE
+				// nvue下,使用dom模块查询元素高度
+				// 返回一个promise,让调用此方法的主体能使用then回调
+				return new Promise(resolve => {
+					dom.getComponentRect(this.$refs[el][0], res => {
+						resolve(res.size)
+					})
+				})
+				// #endif
+			},
+			// 点击某一个日期
+			clickHandler(index1, index2, item) {
+				if (this.readonly) {
+					return;
+				}
+				this.item = item
+				const date = dayjs(item.date).format("YYYY-MM-DD")
+				if (item.disabled) return
+				// 对上一次选择的日期数组进行深度克隆
+				let selected = uni.$u.deepClone(this.selected)
+				if (this.mode === 'single') {
+					// 单选情况下,让数组中的元素为当前点击的日期
+					selected = [date]
+				} else if (this.mode === 'multiple') {
+					if (selected.some(item => this.dateSame(item, date))) {
+						// 如果点击的日期已在数组中,则进行移除操作,也就是达到反选的效果
+						const itemIndex = selected.findIndex(item => item === date)
+						selected.splice(itemIndex, 1)
+					} else {
+						// 如果点击的日期不在数组中,且已有的长度小于总可选长度时,则添加到数组中去
+						if (selected.length < this.maxCount) selected.push(date)
+					}
+				} else {
+					// 选择区间形式
+					if (selected.length === 0 || selected.length >= 2) {
+						// 如果原来就为0或者大于2的长度,则当前点击的日期,就是开始日期
+						selected = [date]
+					} else if (selected.length === 1) {
+						// 如果已经选择了开始日期
+						const existsDate = selected[0]
+						// 如果当前选择的日期小于上一次选择的日期,则当前的日期定为开始日期
+						if (dayjs(date).isBefore(existsDate)) {
+							selected = [date]
+						} else if (dayjs(date).isAfter(existsDate)) {
+							// 当前日期减去最大可选的日期天数,如果大于起始时间,则进行提示
+							if(dayjs(dayjs(date).subtract(this.maxRange, 'day')).isAfter(dayjs(selected[0])) && this.showRangePrompt) {
+								if(this.rangePrompt) {
+									uni.$u.toast(this.rangePrompt)
+								} else {
+									uni.$u.toast(`选择天数不能超过 ${this.maxRange} 天`)
+								}
+								return
+							}
+							// 如果当前日期大于已有日期,将当前的添加到数组尾部
+							selected.push(date)
+							const startDate = selected[0]
+							const endDate = selected[1]
+							const arr = []
+							let i = 0
+							do {
+								// 将开始和结束日期之间的日期添加到数组中
+								arr.push(dayjs(startDate).add(i, 'day').format("YYYY-MM-DD"))
+								i++
+								// 累加的日期小于结束日期时,继续下一次的循环
+							} while (dayjs(startDate).add(i, 'day').isBefore(dayjs(endDate)))
+							// 为了一次性修改数组,避免computed中多次触发,这里才用arr变量一次性赋值的方式,同时将最后一个日期添加近来
+							arr.push(endDate)
+							selected = arr
+						} else {
+							// 选择区间时,只有一个日期的情况下,且不允许选择起止为同一天的话,不允许选择自己
+							if (selected[0] === date && !this.allowSameDay) return
+							selected.push(date)
+						}
+					}
+				}
+				this.setSelected(selected)
+			},
+			// 设置默认日期
+			setDefaultDate() {
+				if (!this.defaultDate) {
+					// 如果没有设置默认日期,则将当天日期设置为默认选中的日期
+					const selected = [dayjs().format("YYYY-MM-DD")]
+					return this.setSelected(selected, false)
+				}
+				let defaultDate = []
+				const minDate = this.minDate || dayjs().format("YYYY-MM-DD")
+				const maxDate = this.maxDate || dayjs(minDate).add(this.maxMonth - 1, 'month').format("YYYY-MM-DD")
+				if (this.mode === 'single') {
+					// 单选模式,可以是字符串或数组,Date对象等
+					if (!uni.$u.test.array(this.defaultDate)) {
+						defaultDate = [dayjs(this.defaultDate).format("YYYY-MM-DD")]
+					} else {
+						defaultDate = [this.defaultDate[0]]
+					}
+				} else {
+					// 如果为非数组,则不执行
+					if (!uni.$u.test.array(this.defaultDate)) return
+					defaultDate = this.defaultDate
+				}
+				// 过滤用户传递的默认数组,取出只在可允许最大值与最小值之间的元素
+				defaultDate = defaultDate.filter(item => {
+					return dayjs(item).isAfter(dayjs(minDate).subtract(1, 'day')) && dayjs(item).isBefore(dayjs(
+						maxDate).add(1, 'day'))
+				})
+				this.setSelected(defaultDate, false)
+			},
+			setSelected(selected, event = true) {
+				this.selected = selected
+				event && this.$emit('monthSelected', this.selected)
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+
+	.u-calendar-month-wrapper {
+		margin-top: 4px;
+	}
+
+	.u-calendar-month {
+
+		&__title {
+			font-size: 14px;
+			line-height: 42px;
+			height: 42px;
+			color: $u-main-color;
+			text-align: center;
+			font-weight: bold;
+		}
+
+		&__days {
+			position: relative;
+			@include flex;
+			flex-wrap: wrap;
+
+			&__month-mark-wrapper {
+				position: absolute;
+				top: 0;
+				bottom: 0;
+				left: 0;
+				right: 0;
+				@include flex;
+				justify-content: center;
+				align-items: center;
+
+				&__text {
+					font-size: 155px;
+					color: rgba(231, 232, 234, 0.83);
+				}
+			}
+
+			&__day {
+				@include flex;
+				padding: 2px;
+				/* #ifndef APP-NVUE */
+				// vue下使用css进行宽度计算,因为某些安卓机会无法进行js获取父元素宽度进行计算得出,会有偏移
+				width: calc(100% / 7);
+				box-sizing: border-box;
+				/* #endif */
+
+				&__select {
+					flex: 1;
+					@include flex;
+					align-items: center;
+					justify-content: center;
+					position: relative;
+
+					&__dot {
+						width: 7px;
+						height: 7px;
+						border-radius: 100px;
+						background-color: $u-error;
+						position: absolute;
+						top: 12px;
+						right: 7px;
+					}
+
+					&__buttom-info {
+						color: $u-content-color;
+						text-align: center;
+						position: absolute;
+						bottom: 5px;
+						font-size: 10px;
+						text-align: center;
+						left: 0;
+						right: 0;
+
+						&--selected {
+							color: #ffffff;
+						}
+
+						&--disabled {
+							color: #cacbcd;
+						}
+					}
+
+					&__info {
+						text-align: center;
+						font-size: 16px;
+
+						&--selected {
+							color: #ffffff;
+						}
+
+						&--disabled {
+							color: #cacbcd;
+						}
+					}
+
+					&--selected {
+						background-color: $u-primary;
+						@include flex;
+						justify-content: center;
+						align-items: center;
+						flex: 1;
+						border-radius: 3px;
+					}
+
+					&--range-selected {
+						opacity: 0.3;
+						border-radius: 0;
+					}
+
+					&--range-start-selected {
+						border-top-right-radius: 0;
+						border-bottom-right-radius: 0;
+					}
+
+					&--range-end-selected {
+						border-top-left-radius: 0;
+						border-bottom-left-radius: 0;
+					}
+				}
+			}
+		}
+	}
+</style>

+ 144 - 0
uni_modules/uview-ui/components/u-calendar/props.js

@@ -0,0 +1,144 @@
+export default {
+    props: {
+        // 日历顶部标题
+        title: {
+            type: String,
+            default: uni.$u.props.calendar.title
+        },
+        // 是否显示标题
+        showTitle: {
+            type: Boolean,
+            default: uni.$u.props.calendar.showTitle
+        },
+        // 是否显示副标题
+        showSubtitle: {
+            type: Boolean,
+            default: uni.$u.props.calendar.showSubtitle
+        },
+        // 日期类型选择,single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围
+        mode: {
+            type: String,
+            default: uni.$u.props.calendar.mode
+        },
+        // mode=range时,第一个日期底部的提示文字
+        startText: {
+            type: String,
+            default: uni.$u.props.calendar.startText
+        },
+        // mode=range时,最后一个日期底部的提示文字
+        endText: {
+            type: String,
+            default: uni.$u.props.calendar.endText
+        },
+        // 自定义列表
+        customList: {
+            type: Array,
+            default: uni.$u.props.calendar.customList
+        },
+        // 主题色,对底部按钮和选中日期有效
+        color: {
+            type: String,
+            default: uni.$u.props.calendar.color
+        },
+        // 最小的可选日期
+        minDate: {
+            type: [String, Number],
+            default: uni.$u.props.calendar.minDate
+        },
+        // 最大可选日期
+        maxDate: {
+            type: [String, Number],
+            default: uni.$u.props.calendar.maxDate
+        },
+        // 默认选中的日期,mode为multiple或range是必须为数组格式
+        defaultDate: {
+            type: [Array, String, Date, null],
+            default: uni.$u.props.calendar.defaultDate
+        },
+        // mode=multiple时,最多可选多少个日期
+        maxCount: {
+            type: [String, Number],
+            default: uni.$u.props.calendar.maxCount
+        },
+        // 日期行高
+        rowHeight: {
+            type: [String, Number],
+            default: uni.$u.props.calendar.rowHeight
+        },
+        // 日期格式化函数
+        formatter: {
+            type: [Function, null],
+            default: uni.$u.props.calendar.formatter
+        },
+        // 是否显示农历
+        showLunar: {
+            type: Boolean,
+            default: uni.$u.props.calendar.showLunar
+        },
+        // 是否显示月份背景色
+        showMark: {
+            type: Boolean,
+            default: uni.$u.props.calendar.showMark
+        },
+        // 确定按钮的文字
+        confirmText: {
+            type: String,
+            default: uni.$u.props.calendar.confirmText
+        },
+        // 确认按钮处于禁用状态时的文字
+        confirmDisabledText: {
+            type: String,
+            default: uni.$u.props.calendar.confirmDisabledText
+        },
+        // 是否显示日历弹窗
+        show: {
+            type: Boolean,
+            default: uni.$u.props.calendar.show
+        },
+        // 是否允许点击遮罩关闭日历
+        closeOnClickOverlay: {
+            type: Boolean,
+            default: uni.$u.props.calendar.closeOnClickOverlay
+        },
+        // 是否为只读状态,只读状态下禁止选择日期
+        readonly: {
+            type: Boolean,
+            default: uni.$u.props.calendar.readonly
+        },
+        // 	是否展示确认按钮
+        showConfirm: {
+            type: Boolean,
+            default: uni.$u.props.calendar.showConfirm
+        },
+        // 日期区间最多可选天数,默认无限制,mode = range时有效
+        maxRange: {
+            type: [Number, String],
+            default: uni.$u.props.calendar.maxRange
+        },
+        // 范围选择超过最多可选天数时的提示文案,mode = range时有效
+        rangePrompt: {
+            type: String,
+            default: uni.$u.props.calendar.rangePrompt
+        },
+        // 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效
+        showRangePrompt: {
+            type: Boolean,
+            default: uni.$u.props.calendar.showRangePrompt
+        },
+        // 是否允许日期范围的起止时间为同一天,mode = range时有效
+        allowSameDay: {
+            type: Boolean,
+            default: uni.$u.props.calendar.allowSameDay
+        },
+		// 圆角值
+		round: {
+		    type: [Boolean, String, Number],
+		    default: uni.$u.props.calendar.round
+		},
+		// 最多展示月份数量
+		monthNum: {
+			type: [Number, String],
+			default: 3
+		}	
+    }
+}

+ 384 - 0
uni_modules/uview-ui/components/u-calendar/u-calendar.vue

@@ -0,0 +1,384 @@
+<template>
+	<u-popup
+		:show="show"
+		mode="bottom"
+		closeable
+		@close="close"
+		:round="round"
+		:closeOnClickOverlay="closeOnClickOverlay"
+	>
+		<view class="u-calendar">
+			<uHeader
+				:title="title"
+				:subtitle="subtitle"
+				:showSubtitle="showSubtitle"
+				:showTitle="showTitle"
+			></uHeader>
+			<scroll-view
+				:style="{
+                    height: $u.addUnit(listHeight)
+                }"
+				scroll-y
+				@scroll="onScroll"
+				:scroll-top="scrollTop"
+				:scrollIntoView="scrollIntoView"
+			>
+				<uMonth
+					:color="color"
+					:rowHeight="rowHeight"
+					:showMark="showMark"
+					:months="months"
+					:mode="mode"
+					:maxCount="maxCount"
+					:startText="startText"
+					:endText="endText"
+					:defaultDate="defaultDate"
+					:minDate="innerMinDate"
+					:maxDate="innerMaxDate"
+					:maxMonth="monthNum"
+					:readonly="readonly"
+					:maxRange="maxRange"
+					:rangePrompt="rangePrompt"
+					:showRangePrompt="showRangePrompt"
+					:allowSameDay="allowSameDay"
+					ref="month"
+					@monthSelected="monthSelected"
+					@updateMonthTop="updateMonthTop"
+				></uMonth>
+			</scroll-view>
+			<slot name="footer" v-if="showConfirm">
+				<view class="u-calendar__confirm">
+					<u-button
+						shape="circle"
+						:text="
+                            buttonDisabled ? confirmDisabledText : confirmText
+                        "
+						:color="color"
+						@click="confirm"
+						:disabled="buttonDisabled"
+					></u-button>
+				</view>
+			</slot>
+		</view>
+	</u-popup>
+</template>
+
+<script>
+import uHeader from './header.vue'
+import uMonth from './month.vue'
+import props from './props.js'
+import util from './util.js'
+import dayjs from '../../libs/util/dayjs.js'
+import Calendar from '../../libs/util/calendar.js'
+/**
+ * Calendar 日历
+ * @description  此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中.
+ * @tutorial https://www.uviewui.com/components/calendar.html
+ *
+ * @property {String}				title				标题内容 (默认 日期选择 )
+ * @property {Boolean}				showTitle			是否显示标题  (默认 true )
+ * @property {Boolean}				showSubtitle		是否显示副标题	(默认 true )
+ * @property {String}				mode				日期类型选择  single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围 ( 默认 'single' )
+ * @property {String}				startText			mode=range时,第一个日期底部的提示文字  (默认 '开始' )
+ * @property {String}				endText				mode=range时,最后一个日期底部的提示文字 (默认 '结束' )
+ * @property {Array}				customList			自定义列表
+ * @property {String}				color				主题色,对底部按钮和选中日期有效  (默认 ‘#3c9cff' )
+ * @property {String | Number}		minDate				最小的可选日期	 (默认 0 )
+ * @property {String | Number}		maxDate				最大可选日期  (默认 0 )
+ * @property {Array | String| Date}	defaultDate			默认选中的日期,mode为multiple或range是必须为数组格式
+ * @property {String | Number}		maxCount			mode=multiple时,最多可选多少个日期  (默认 	Number.MAX_SAFE_INTEGER  )
+ * @property {String | Number}		rowHeight			日期行高 (默认 56 )
+ * @property {Function}				formatter			日期格式化函数
+ * @property {Boolean}				showLunar			是否显示农历  (默认 false )
+ * @property {Boolean}				showMark			是否显示月份背景色 (默认 true )
+ * @property {String}				confirmText			确定按钮的文字 (默认 '确定' )
+ * @property {String}				confirmDisabledText	确认按钮处于禁用状态时的文字 (默认 '确定' )
+ * @property {Boolean}				show				是否显示日历弹窗 (默认 false )
+ * @property {Boolean}				closeOnClickOverlay	是否允许点击遮罩关闭日历 (默认 false )
+ * @property {Boolean}				readonly	        是否为只读状态,只读状态下禁止选择日期 (默认 false )
+ * @property {String | Number}		maxRange	        日期区间最多可选天数,默认无限制,mode = range时有效
+ * @property {String}				rangePrompt	        范围选择超过最多可选天数时的提示文案,mode = range时有效
+ * @property {Boolean}				showRangePrompt	    范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效 (默认 true )
+ * @property {Boolean}				allowSameDay	    是否允许日期范围的起止时间为同一天,mode = range时有效 (默认 false )
+ * @property {Number|String}	    round				圆角值,默认无圆角  (默认 0 )
+ * @property {Number|String}	    monthNum			最多展示的月份数量  (默认 3 )
+ *
+ * @event {Function()} confirm 		点击确定按钮时触发		选择日期相关的返回参数
+ * @event {Function()} close 		日历关闭时触发			可定义页面关闭时的回调事件
+ * @example <u-calendar  :defaultDate="defaultDateMultiple" :show="show" mode="multiple" @confirm="confirm">
+	</u-calendar>
+ * */
+export default {
+	name: 'u-calendar',
+	mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+	components: {
+		uHeader,
+		uMonth
+	},
+	data() {
+		return {
+			// 需要显示的月份的数组
+			months: [],
+			// 在月份滚动区域中,当前视图中月份的index索引
+			monthIndex: 0,
+			// 月份滚动区域的高度
+			listHeight: 0,
+			// month组件中选择的日期数组
+			selected: [],
+			scrollIntoView: '',
+			scrollTop:0,
+			// 过滤处理方法
+			innerFormatter: (value) => value
+		}
+	},
+	watch: {
+		selectedChange: {
+			immediate: true,
+			handler(n) {
+				this.setMonth()
+			}
+		},
+		// 打开弹窗时,设置月份数据
+		show: {
+			immediate: true,
+			handler(n) {
+				this.setMonth()
+			}
+		}
+	},
+	computed: {
+		// 由于maxDate和minDate可以为字符串(2021-10-10),或者数值(时间戳),但是dayjs如果接受字符串形式的时间戳会有问题,这里进行处理
+		innerMaxDate() {
+			return uni.$u.test.number(this.maxDate)
+				? Number(this.maxDate)
+				: this.maxDate
+		},
+		innerMinDate() {
+			return uni.$u.test.number(this.minDate)
+				? Number(this.minDate)
+				: this.minDate
+		},
+		// 多个条件的变化,会引起选中日期的变化,这里统一管理监听
+		selectedChange() {
+			return [this.innerMinDate, this.innerMaxDate, this.defaultDate]
+		},
+		subtitle() {
+			// 初始化时,this.months为空数组,所以需要特别判断处理
+			if (this.months.length) {
+				return `${this.months[this.monthIndex].year}年${
+					this.months[this.monthIndex].month
+				}月`
+			} else {
+				return ''
+			}
+		},
+		buttonDisabled() {
+			// 如果为range类型,且选择的日期个数不足1个时,让底部的按钮出于disabled状态
+			if (this.mode === 'range') {
+				if (this.selected.length <= 1) {
+					return true
+				} else {
+					return false
+				}
+			} else {
+				return false
+			}
+		}
+	},
+	mounted() {
+		this.start = Date.now()
+		this.init()
+	},
+	methods: {
+		// 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
+		setFormatter(e) {
+			this.innerFormatter = e
+		},
+		// month组件内部选择日期后,通过事件通知给父组件
+		monthSelected(e) {
+			this.selected = e
+			if (!this.showConfirm) {
+				// 在不需要确认按钮的情况下,如果为单选,或者范围多选且已选长度大于2,则直接进行返还
+				if (
+					this.mode === 'multiple' ||
+					this.mode === 'single' ||
+					(this.mode === 'range' && this.selected.length >= 2)
+				) {
+					this.$emit('confirm', this.selected)
+				}
+			}
+		},
+		init() {
+			// 校验maxDate,不能小于minDate
+			if (
+				this.innerMaxDate &&
+				this.innerMinDate &&
+				new Date(this.innerMaxDate).getTime() < new Date(this.innerMinDate).getTime()
+			) {
+				return uni.$u.error('maxDate不能小于minDate')
+			}
+			// 滚动区域的高度
+			this.listHeight = this.rowHeight * 5 + 30
+			this.setMonth()
+		},
+		close() {
+			this.$emit('close')
+		},
+		// 点击确定按钮
+		confirm() {
+			if (!this.buttonDisabled) {
+				this.$emit('confirm', this.selected)
+			}
+		},
+		// 获得两个日期之间的月份数
+		getMonths(minDate, maxDate) {
+			const minYear = dayjs(minDate).year()
+			const minMonth = dayjs(minDate).month() + 1
+			const maxYear = dayjs(maxDate).year()
+			const maxMonth = dayjs(maxDate).month() + 1
+			return (maxYear - minYear) * 12 + (maxMonth - minMonth) + 1
+		},
+		// 设置月份数据
+		setMonth() {
+			// 最小日期的毫秒数
+			const minDate = this.innerMinDate || dayjs().valueOf()
+			// 如果没有指定最大日期,则往后推3个月
+			const maxDate =
+				this.innerMaxDate ||
+				dayjs(minDate)
+					.add(this.monthNum - 1, 'month')
+					.valueOf()
+			// 最大最小月份之间的共有多少个月份,
+			const months = uni.$u.range(
+				1,
+				this.monthNum,
+				this.getMonths(minDate, maxDate)
+			)
+			// 先清空数组
+			this.months = []
+			for (let i = 0; i < months; i++) {
+				this.months.push({
+					date: new Array(
+						dayjs(minDate).add(i, 'month').daysInMonth()
+					)
+						.fill(1)
+						.map((item, index) => {
+							// 日期,取值1-31
+							let day = index + 1
+							// 星期,0-6,0为周日
+							const week = dayjs(minDate)
+								.add(i, 'month')
+								.date(day)
+								.day()
+							const date = dayjs(minDate)
+								.add(i, 'month')
+								.date(day)
+								.format('YYYY-MM-DD')
+							let bottomInfo = ''
+							if (this.showLunar) {
+								// 将日期转为农历格式
+								const lunar = Calendar.solar2lunar(
+									dayjs(date).year(),
+									dayjs(date).month() + 1,
+									dayjs(date).date()
+								)
+								bottomInfo = lunar.IDayCn
+							}
+							let config = {
+								day,
+								week,
+								// 小于最小允许的日期,或者大于最大的日期,则设置为disabled状态
+								disabled:
+									dayjs(date).isBefore(
+										dayjs(minDate).format('YYYY-MM-DD')
+									) ||
+									dayjs(date).isAfter(
+										dayjs(maxDate).format('YYYY-MM-DD')
+									),
+								// 返回一个日期对象,供外部的formatter获取当前日期的年月日等信息,进行加工处理
+								date: new Date(date),
+								bottomInfo,
+								dot: false,
+								month:
+									dayjs(minDate).add(i, 'month').month() + 1
+							}
+							const formatter =
+								this.formatter || this.innerFormatter
+							return formatter(config)
+						}),
+					// 当前所属的月份
+					month: dayjs(minDate).add(i, 'month').month() + 1,
+					// 当前年份
+					year: dayjs(minDate).add(i, 'month').year()
+				})
+			}
+
+		},
+		// 滚动到默认设置的月份
+		scrollIntoDefaultMonth(selected) {
+			// 查询默认日期在可选列表的下标
+			const _index = this.months.findIndex(({
+				  year,
+				  month
+			  }) => {
+				month = uni.$u.padZero(month)
+				return `${year}-${month}` === selected
+			})
+			if (_index !== -1) {
+				// #ifndef MP-WEIXIN
+				this.$nextTick(() => {
+					this.scrollIntoView = `month-${_index}`
+				})
+				// #endif
+				// #ifdef MP-WEIXIN
+				this.scrollTop = this.months[_index].top || 0;
+				// #endif
+			}
+		},
+		// scroll-view滚动监听
+		onScroll(event) {
+			// 不允许小于0的滚动值,如果scroll-view到顶了,继续下拉,会出现负数值
+			const scrollTop = Math.max(0, event.detail.scrollTop)
+			// 将当前滚动条数值,除以滚动区域的高度,可以得出当前滚动到了哪一个月份的索引
+			for (let i = 0; i < this.months.length; i++) {
+				if (scrollTop >= (this.months[i].top || this.listHeight)) {
+					this.monthIndex = i
+				}
+			}
+		},
+		// 更新月份的top值
+		updateMonthTop(topArr = []) {
+			// 设置对应月份的top值,用于onScroll方法更新月份
+			topArr.map((item, index) => {
+				this.months[index].top = item
+			})
+
+			// 获取默认日期的下标
+			if (!this.defaultDate) {
+				// 如果没有设置默认日期,则将当天日期设置为默认选中的日期
+				const selected = dayjs().format("YYYY-MM")
+				this.scrollIntoDefaultMonth(selected)
+				return
+			}
+			let selected = dayjs().format("YYYY-MM");
+			// 单选模式,可以是字符串或数组,Date对象等
+			if (!uni.$u.test.array(this.defaultDate)) {
+				selected = dayjs(this.defaultDate).format("YYYY-MM")
+			} else {
+				selected = dayjs(this.defaultDate[0]).format("YYYY-MM");
+			}
+			this.scrollIntoDefaultMonth(selected)
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+@import '../../libs/css/components.scss';
+
+.u-calendar {
+	&__confirm {
+		padding: 7px 18px;
+	}
+}
+</style>

+ 85 - 0
uni_modules/uview-ui/components/u-calendar/util.js

@@ -0,0 +1,85 @@
+export default {
+    methods: {
+        // 设置月份数据
+        setMonth() {
+            // 月初是周几
+            const day = dayjs(this.date).date(1).day()
+            const start = day == 0 ? 6 : day - 1
+
+            // 本月天数
+            const days = dayjs(this.date).endOf('month').format('D')
+
+            // 上个月天数
+            const prevDays = dayjs(this.date).endOf('month').subtract(1, 'month').format('D')
+
+            // 日期数据
+            const arr = []
+            // 清空表格
+            this.month = []
+
+            // 添加上月数据
+            arr.push(
+                ...new Array(start).fill(1).map((e, i) => {
+                    const day = prevDays - start + i + 1
+
+                    return {
+                        value: day,
+                        disabled: true,
+                        date: dayjs(this.date).subtract(1, 'month').date(day).format('YYYY-MM-DD')
+                    }
+                })
+            )
+
+            // 添加本月数据
+            arr.push(
+                ...new Array(days - 0).fill(1).map((e, i) => {
+                    const day = i + 1
+
+                    return {
+                        value: day,
+                        date: dayjs(this.date).date(day).format('YYYY-MM-DD')
+                    }
+                })
+            )
+
+            // 添加下个月
+            arr.push(
+                ...new Array(42 - days - start).fill(1).map((e, i) => {
+                    const day = i + 1
+
+                    return {
+                        value: day,
+                        disabled: true,
+                        date: dayjs(this.date).add(1, 'month').date(day).format('YYYY-MM-DD')
+                    }
+                })
+            )
+
+            // 分割数组
+            for (let n = 0; n < arr.length; n += 7) {
+                this.month.push(
+                    arr.slice(n, n + 7).map((e, i) => {
+                        e.index = i + n
+
+                        // 自定义信息
+                        const custom = this.customList.find((c) => c.date == e.date)
+
+                        // 农历
+                        if (this.lunar) {
+                            const {
+                                IDayCn,
+                                IMonthCn
+                            } = this.getLunar(e.date)
+                            e.lunar = IDayCn == '初一' ? IMonthCn : IDayCn
+                        }
+
+                        return {
+                            ...e,
+                            ...custom
+                        }
+                    })
+                )
+            }
+        }
+    }
+}

+ 14 - 0
uni_modules/uview-ui/components/u-car-keyboard/props.js

@@ -0,0 +1,14 @@
+export default {
+    props: {
+        // 是否打乱键盘按键的顺序
+        random: {
+            type: Boolean,
+            default: false
+        },
+        // 输入一个中文后,是否自动切换到英文
+        autoChange: {
+            type: Boolean,
+            default: false
+        }
+    }
+}

+ 311 - 0
uni_modules/uview-ui/components/u-car-keyboard/u-car-keyboard.vue

@@ -0,0 +1,311 @@
+<template>
+	<view
+		class="u-keyboard"
+		@touchmove.stop.prevent="noop"
+	>
+		<view
+			v-for="(group, i) in abc ? engKeyBoardList : areaList"
+			:key="i"
+			class="u-keyboard__button"
+			:index="i"
+			:class="[i + 1 === 4 && 'u-keyboard__button--center']"
+		>
+			<view
+				v-if="i === 3"
+				class="u-keyboard__button__inner-wrapper"
+			>
+				<view
+					class="u-keyboard__button__inner-wrapper__left"
+					hover-class="u-hover-class"
+					:hover-stay-time="200"
+					@tap="changeCarInputMode"
+				>
+					<text
+						class="u-keyboard__button__inner-wrapper__left__lang"
+						:class="[!abc && 'u-keyboard__button__inner-wrapper__left__lang--active']"
+					>中</text>
+					<text class="u-keyboard__button__inner-wrapper__left__line">/</text>
+					<text
+						class="u-keyboard__button__inner-wrapper__left__lang"
+						:class="[abc && 'u-keyboard__button__inner-wrapper__left__lang--active']"
+					>英</text>
+				</view>
+			</view>
+			<view
+				class="u-keyboard__button__inner-wrapper"
+				v-for="(item, j) in group"
+				:key="j"
+			>
+				<view
+					class="u-keyboard__button__inner-wrapper__inner"
+					:hover-stay-time="200"
+					@tap="carInputClick(i, j)"
+					hover-class="u-hover-class"
+				>
+					<text class="u-keyboard__button__inner-wrapper__inner__text">{{ item }}</text>
+				</view>
+			</view>
+			<view
+				v-if="i === 3"
+				@touchstart="backspaceClick"
+				@touchend="clearTimer"
+				class="u-keyboard__button__inner-wrapper"
+			>
+				<view
+					class="u-keyboard__button__inner-wrapper__right"
+					hover-class="u-hover-class"
+					:hover-stay-time="200"
+				>
+					<u-icon
+						size="28"
+						name="backspace"
+						color="#303133"
+					></u-icon>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * keyboard 键盘组件
+	 * @description 此为uView自定义的键盘面板,内含了数字键盘,车牌号键,身份证号键盘3种模式,都有可以打乱按键顺序的选项。
+	 * @tutorial https://uviewui.com/components/keyboard.html
+	 * @property {Boolean} random 是否打乱键盘的顺序
+	 * @event {Function} change 点击键盘触发
+	 * @event {Function} backspace 点击退格键触发
+	 * @example <u-keyboard ref="uKeyboard" mode="car" v-model="show"></u-keyboard>
+	 */
+	export default {
+		name: "u-keyboard",
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+		data() {
+			return {
+				// 车牌输入时,abc=true为输入车牌号码,bac=false为输入省份中文简称
+				abc: false
+			};
+		},
+		computed: {
+			areaList() {
+				let data = [
+					'京',
+					'沪',
+					'粤',
+					'津',
+					'冀',
+					'豫',
+					'云',
+					'辽',
+					'黑',
+					'湘',
+					'皖',
+					'鲁',
+					'苏',
+					'浙',
+					'赣',
+					'鄂',
+					'桂',
+					'甘',
+					'晋',
+					'陕',
+					'蒙',
+					'吉',
+					'闽',
+					'贵',
+					'渝',
+					'川',
+					'青',
+					'琼',
+					'宁',
+					'挂',
+					'藏',
+					'港',
+					'澳',
+					'新',
+					'使',
+					'学'
+				];
+				let tmp = [];
+				// 打乱顺序
+				if (this.random) data = uni.$u.randomArray(data);
+				// 切割成二维数组
+				tmp[0] = data.slice(0, 10);
+				tmp[1] = data.slice(10, 20);
+				tmp[2] = data.slice(20, 30);
+				tmp[3] = data.slice(30, 36);
+				return tmp;
+			},
+			engKeyBoardList() {
+				let data = [
+					1,
+					2,
+					3,
+					4,
+					5,
+					6,
+					7,
+					8,
+					9,
+					0,
+					'Q',
+					'W',
+					'E',
+					'R',
+					'T',
+					'Y',
+					'U',
+					'I',
+					'O',
+					'P',
+					'A',
+					'S',
+					'D',
+					'F',
+					'G',
+					'H',
+					'J',
+					'K',
+					'L',
+					'Z',
+					'X',
+					'C',
+					'V',
+					'B',
+					'N',
+					'M'
+				];
+				let tmp = [];
+				if (this.random) data = uni.$u.randomArray(data);
+				tmp[0] = data.slice(0, 10);
+				tmp[1] = data.slice(10, 20);
+				tmp[2] = data.slice(20, 30);
+				tmp[3] = data.slice(30, 36);
+				return tmp;
+			}
+		},
+		methods: {
+			// 点击键盘按钮
+			carInputClick(i, j) {
+				let value = '';
+				// 不同模式,获取不同数组的值
+				if (this.abc) value = this.engKeyBoardList[i][j];
+				else value = this.areaList[i][j];
+				// 如果允许自动切换,则将中文状态切换为英文
+				if (!this.abc && this.autoChange) uni.$u.sleep(200).then(() => this.abc = true)
+				this.$emit('change', value);
+			},
+			// 修改汽车牌键盘的输入模式,中文|英文
+			changeCarInputMode() {
+				this.abc = !this.abc;
+			},
+			// 点击退格键
+			backspaceClick() {
+				this.$emit('backspace');
+				clearInterval(this.timer); //再次清空定时器,防止重复注册定时器
+				this.timer = null;
+				this.timer = setInterval(() => {
+					this.$emit('backspace');
+				}, 250);
+			},
+			clearTimer() {
+				clearInterval(this.timer);
+				this.timer = null;
+			},
+		}
+	};
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+	$u-car-keyboard-background-color: rgb(224, 228, 230) !default;
+	$u-car-keyboard-padding:6px 0 6px !default;
+	$u-car-keyboard-button-inner-width:64rpx !default;
+	$u-car-keyboard-button-inner-background-color:#FFFFFF !default;
+	$u-car-keyboard-button-height:80rpx !default;
+	$u-car-keyboard-button-inner-box-shadow:0 1px 0px #999992 !default;
+	$u-car-keyboard-button-border-radius:4px !default;
+	$u-car-keyboard-button-inner-margin:8rpx 5rpx !default;
+	$u-car-keyboard-button-text-font-size:16px !default;
+	$u-car-keyboard-button-text-color:$u-main-color !default;
+	$u-car-keyboard-center-inner-margin: 0 4rpx !default;
+	$u-car-keyboard-special-button-width:134rpx !default;
+	$u-car-keyboard-lang-font-size:16px !default;
+	$u-car-keyboard-lang-color:$u-main-color !default;
+	$u-car-keyboard-active-color:$u-primary !default;
+	$u-car-keyboard-line-font-size:15px !default;
+	$u-car-keyboard-line-color:$u-main-color !default;
+	$u-car-keyboard-line-margin:0 1px !default;
+	$u-car-keyboard-u-hover-class-background-color:#BBBCC6 !default;
+
+	.u-keyboard {
+		@include flex(column);
+		justify-content: space-around;
+		background-color: $u-car-keyboard-background-color;
+		align-items: stretch;
+		padding: $u-car-keyboard-padding;
+
+		&__button {
+			@include flex;
+			justify-content: center;
+			flex: 1;
+			/* #ifndef APP-NVUE */
+			/* #endif */
+
+			&__inner-wrapper {
+				box-shadow: $u-car-keyboard-button-inner-box-shadow;
+				margin: $u-car-keyboard-button-inner-margin;
+				border-radius: $u-car-keyboard-button-border-radius;
+
+				&__inner {
+					@include flex;
+					justify-content: center;
+					align-items: center;
+					width: $u-car-keyboard-button-inner-width;
+					background-color: $u-car-keyboard-button-inner-background-color;
+					height: $u-car-keyboard-button-height;
+					border-radius: $u-car-keyboard-button-border-radius;
+
+					&__text {
+						font-size: $u-car-keyboard-button-text-font-size;
+						color: $u-car-keyboard-button-text-color;
+					}
+				}
+
+				&__left,
+				&__right {
+					border-radius: $u-car-keyboard-button-border-radius;
+					width: $u-car-keyboard-special-button-width;
+					height: $u-car-keyboard-button-height;
+					background-color: $u-car-keyboard-u-hover-class-background-color;
+					@include flex;
+					justify-content: center;
+					align-items: center;
+					box-shadow: $u-car-keyboard-button-inner-box-shadow;
+				}
+
+				&__left {
+					&__line {
+						font-size: $u-car-keyboard-line-font-size;
+						color: $u-car-keyboard-line-color;
+						margin: $u-car-keyboard-line-margin;
+					}
+
+					&__lang {
+						font-size: $u-car-keyboard-lang-font-size;
+						color: $u-car-keyboard-lang-color;
+
+						&--active {
+							color: $u-car-keyboard-active-color;
+						}
+					}
+				}
+			}
+		}
+	}
+
+	.u-hover-class {
+		background-color: $u-car-keyboard-u-hover-class-background-color;
+	}
+</style>

+ 14 - 0
uni_modules/uview-ui/components/u-cell-group/props.js

@@ -0,0 +1,14 @@
+export default {
+    props: {
+        // 分组标题
+        title: {
+            type: String,
+            default: uni.$u.props.cellGroup.title
+        },
+        // 是否显示外边框
+        border: {
+            type: Boolean,
+            default: uni.$u.props.cellGroup.border
+        }
+    }
+}

+ 61 - 0
uni_modules/uview-ui/components/u-cell-group/u-cell-group.vue

@@ -0,0 +1,61 @@
+<template>
+    <view :style="[$u.addStyle(customStyle)]" :class="[customClass]" class="u-cell-group">
+        <view v-if="title" class="u-cell-group__title">
+            <slot name="title">
+				<text class="u-cell-group__title__text">{{ title }}</text>
+			</slot>
+        </view>
+        <view class="u-cell-group__wrapper">
+			<u-line v-if="border"></u-line>
+            <slot />
+        </view>
+    </view>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * cellGroup  单元格
+	 * @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。
+	 * @tutorial https://uviewui.com/components/cell.html
+	 * 
+	 * @property {String}	title		分组标题
+	 * @property {Boolean}	border		是否显示外边框 (默认 true )
+	 * @property {Object}	customStyle	定义需要用到的外部样式
+	 * 
+	 * @event {Function} click 	点击cell列表时触发
+	 * @example <u-cell-group title="设置喜好">
+	 */
+	export default {
+		name: 'u-cell-group',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+	
+	$u-cell-group-title-padding: 16px 16px 8px !default;
+	$u-cell-group-title-font-size: 15px !default;
+	$u-cell-group-title-line-height: 16px !default;
+	$u-cell-group-title-color: $u-main-color !default;
+
+    .u-cell-group {
+		flex: 1;
+		
+        &__title {
+            padding: $u-cell-group-title-padding;
+
+            &__text {
+                font-size: $u-cell-group-title-font-size;
+                line-height: $u-cell-group-title-line-height;
+                color: $u-cell-group-title-color;
+            }
+        }
+		
+		&__wrapper {
+			position: relative;
+		}
+    }
+</style>
+

+ 110 - 0
uni_modules/uview-ui/components/u-cell/props.js

@@ -0,0 +1,110 @@
+export default {
+    props: {
+        // 标题
+        title: {
+            type: [String, Number],
+            default: uni.$u.props.cell.title
+        },
+        // 标题下方的描述信息
+        label: {
+            type: [String, Number],
+            default: uni.$u.props.cell.label
+        },
+        // 右侧的内容
+        value: {
+            type: [String, Number],
+            default: uni.$u.props.cell.value
+        },
+        // 左侧图标名称,或者图片链接(本地文件建议使用绝对地址)
+        icon: {
+            type: String,
+            default: uni.$u.props.cell.icon
+        },
+        // 是否禁用cell
+        disabled: {
+            type: Boolean,
+            default: uni.$u.props.cell.disabled
+        },
+        // 是否显示下边框
+        border: {
+            type: Boolean,
+            default: uni.$u.props.cell.border
+        },
+        // 内容是否垂直居中(主要是针对右侧的value部分)
+        center: {
+            type: Boolean,
+            default: uni.$u.props.cell.center
+        },
+        // 点击后跳转的URL地址
+        url: {
+            type: String,
+            default: uni.$u.props.cell.url
+        },
+        // 链接跳转的方式,内部使用的是uView封装的route方法,可能会进行拦截操作
+        linkType: {
+            type: String,
+            default: uni.$u.props.cell.linkType
+        },
+        // 是否开启点击反馈(表现为点击时加上灰色背景)
+        clickable: {
+            type: Boolean,
+            default: uni.$u.props.cell.clickable
+        },
+        // 是否展示右侧箭头并开启点击反馈
+        isLink: {
+            type: Boolean,
+            default: uni.$u.props.cell.isLink
+        },
+        // 是否显示表单状态下的必填星号(此组件可能会内嵌入input组件)
+        required: {
+            type: Boolean,
+            default: uni.$u.props.cell.required
+        },
+        // 右侧的图标箭头
+        rightIcon: {
+            type: String,
+            default: uni.$u.props.cell.rightIcon
+        },
+        // 右侧箭头的方向,可选值为:left,up,down
+        arrowDirection: {
+            type: String,
+            default: uni.$u.props.cell.arrowDirection
+        },
+        // 左侧图标样式
+        iconStyle: {
+            type: [Object, String],
+            default: () => {
+				return uni.$u.props.cell.iconStyle
+			}
+        },
+        // 右侧箭头图标的样式
+        rightIconStyle: {
+            type: [Object, String],
+            default: () => {
+				return uni.$u.props.cell.rightIconStyle
+			}
+        },
+        // 标题的样式
+        titleStyle: {
+            type: [Object, String],
+			default: () => {
+				return uni.$u.props.cell.titleStyle
+			}
+        },
+        // 单位元的大小,可选值为large
+        size: {
+            type: String,
+            default: uni.$u.props.cell.size
+        },
+        // 点击cell是否阻止事件传播
+        stop: {
+            type: Boolean,
+            default: uni.$u.props.cell.stop
+        },
+        // 标识符,cell被点击时返回
+        name: {
+            type: [Number, String],
+            default: uni.$u.props.cell.name
+        }
+    }
+}

+ 229 - 0
uni_modules/uview-ui/components/u-cell/u-cell.vue

@@ -0,0 +1,229 @@
+<template>
+	<view class="u-cell" :class="[customClass]" :style="[$u.addStyle(customStyle)]"
+		:hover-class="(!disabled && (clickable || isLink)) ? 'u-cell--clickable' : ''" :hover-stay-time="250"
+		@tap="clickHandler">
+		<view class="u-cell__body" :class="[ center && 'u-cell--center', size === 'large' && 'u-cell__body--large']">
+			<view class="u-cell__body__content">
+				<view class="u-cell__left-icon-wrap" v-if="$slots.icon || icon">
+					<slot name="icon" v-if="$slots.icon">
+					</slot>
+					<u-icon v-else :name="icon" :custom-style="iconStyle" :size="size === 'large' ? 22 : 18"></u-icon>
+				</view>
+				<view class="u-cell__title">
+					<slot name="title">
+						<text v-if="title" class="u-cell__title-text" :style="[titleTextStyle]"
+							:class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__title-text--large']">{{ title }}</text>
+					</slot>
+					<slot name="label">
+						<text class="u-cell__label" v-if="label"
+							:class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__label--large']">{{ label }}</text>
+					</slot>
+				</view>
+			</view>
+			<slot name="value">
+				<text class="u-cell__value"
+					:class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__value--large']"
+					v-if="!$u.test.empty(value)">{{ value }}</text>
+			</slot>
+			<view class="u-cell__right-icon-wrap" v-if="$slots['right-icon'] || isLink"
+				:class="[`u-cell__right-icon-wrap--${arrowDirection}`]">
+				<slot name="right-icon" v-if="$slots['right-icon']">
+				</slot>
+				<u-icon v-else :name="rightIcon" :custom-style="rightIconStyle" :color="disabled ? '#c8c9cc' : 'info'"
+					:size="size === 'large' ? 18 : 16"></u-icon>
+			</view>
+		</view>
+		<u-line v-if="border"></u-line>
+	</view>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * cell  单元格
+	 * @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。
+	 * @tutorial https://uviewui.com/components/cell.html
+	 * @property {String | Number}	title			标题
+	 * @property {String | Number}	label			标题下方的描述信息
+	 * @property {String | Number}	value			右侧的内容
+	 * @property {String}			icon			左侧图标名称,或者图片链接(本地文件建议使用绝对地址)
+	 * @property {Boolean}			disabled		是否禁用cell	
+	 * @property {Boolean}			border			是否显示下边框 (默认 true )
+	 * @property {Boolean}			center			内容是否垂直居中(主要是针对右侧的value部分) (默认 false )
+	 * @property {String}			url				点击后跳转的URL地址
+	 * @property {String}			linkType		链接跳转的方式,内部使用的是uView封装的route方法,可能会进行拦截操作 (默认 'navigateTo' )
+	 * @property {Boolean}			clickable		是否开启点击反馈(表现为点击时加上灰色背景) (默认 false ) 
+	 * @property {Boolean}			isLink			是否展示右侧箭头并开启点击反馈 (默认 false )
+	 * @property {Boolean}			required		是否显示表单状态下的必填星号(此组件可能会内嵌入input组件) (默认 false )
+	 * @property {String}			rightIcon		右侧的图标箭头 (默认 'arrow-right')
+	 * @property {String}			arrowDirection	右侧箭头的方向,可选值为:left,up,down
+	 * @property {Object | String}			rightIconStyle	右侧箭头图标的样式
+	 * @property {Object | String}			titleStyle		标题的样式
+	 * @property {Object | String}			iconStyle		左侧图标样式
+	 * @property {String}			size			单位元的大小,可选值为 large,normal,mini 
+	 * @property {Boolean}			stop			点击cell是否阻止事件传播 (默认 true )
+	 * @property {Object}			customStyle		定义需要用到的外部样式
+	 * 
+	 * @event {Function}			click			点击cell列表时触发
+	 * @example 该组件需要搭配cell-group组件使用,见官方文档示例
+	 */
+	export default {
+		name: 'u-cell',
+		data() {
+			return {
+
+			}
+		},
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+		computed: {
+			titleTextStyle() {
+				return uni.$u.addStyle(this.titleStyle)
+			}
+		},
+		methods: {
+			// 点击cell
+			clickHandler(e) {
+				if (this.disabled) return
+				this.$emit('click', {
+					name: this.name
+				})
+				// 如果配置了url(此props参数通过mixin引入)参数,跳转页面
+				this.openPage()
+				// 是否阻止事件传播
+				this.stop && this.preventEvent(e)
+			},
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+
+	$u-cell-padding: 10px 15px !default;
+	$u-cell-font-size: 15px !default;
+	$u-cell-line-height: 24px !default;
+	$u-cell-color: $u-main-color !default;
+	$u-cell-icon-size: 16px !default;
+	$u-cell-title-font-size: 15px !default;
+	$u-cell-title-line-height: 22px !default;
+	$u-cell-title-color: $u-main-color !default;
+	$u-cell-label-font-size: 12px !default;
+	$u-cell-label-color: $u-tips-color !default;
+	$u-cell-label-line-height: 18px !default;
+	$u-cell-value-font-size: 14px !default;
+	$u-cell-value-color: $u-content-color !default;
+	$u-cell-clickable-color: $u-bg-color !default;
+	$u-cell-disabled-color: #c8c9cc !default;
+	$u-cell-padding-top-large: 13px !default;
+	$u-cell-padding-bottom-large: 13px !default;
+	$u-cell-value-font-size-large: 15px !default;
+	$u-cell-label-font-size-large: 14px !default;
+	$u-cell-title-font-size-large: 16px !default;
+	$u-cell-left-icon-wrap-margin-right: 4px !default;
+	$u-cell-right-icon-wrap-margin-left: 4px !default;
+	$u-cell-title-flex:1 !default;
+	$u-cell-label-margin-top:5px !default;
+
+
+	.u-cell {
+		&__body {
+			@include flex();
+			/* #ifndef APP-NVUE */
+			box-sizing: border-box;
+			/* #endif */
+			padding: $u-cell-padding;
+			font-size: $u-cell-font-size;
+			color: $u-cell-color;
+			// line-height: $u-cell-line-height;
+			align-items: center;
+
+			&__content {
+				@include flex(row);
+				align-items: center;
+				flex: 1;
+			}
+
+			&--large {
+				padding-top: $u-cell-padding-top-large;
+				padding-bottom: $u-cell-padding-bottom-large;
+			}
+		}
+
+		&__left-icon-wrap,
+		&__right-icon-wrap {
+			@include flex();
+			align-items: center;
+			// height: $u-cell-line-height;
+			font-size: $u-cell-icon-size;
+		}
+
+		&__left-icon-wrap {
+			margin-right: $u-cell-left-icon-wrap-margin-right;
+		}
+
+		&__right-icon-wrap {
+			margin-left: $u-cell-right-icon-wrap-margin-left;
+			transition: transform 0.3s;
+
+			&--up {
+				transform: rotate(-90deg);
+			}
+
+			&--down {
+				transform: rotate(90deg);
+			}
+		}
+
+		&__title {
+			flex: $u-cell-title-flex;
+
+			&-text {
+				font-size: $u-cell-title-font-size;
+				line-height: $u-cell-title-line-height;
+				color: $u-cell-title-color;
+
+				&--large {
+					font-size: $u-cell-title-font-size-large;
+				}
+			}
+
+		}
+
+		&__label {
+			margin-top: $u-cell-label-margin-top;
+			font-size: $u-cell-label-font-size;
+			color: $u-cell-label-color;
+			line-height: $u-cell-label-line-height;
+
+			&--large {
+				font-size: $u-cell-label-font-size-large;
+			}
+		}
+
+		&__value {
+			text-align: right;
+			font-size: $u-cell-value-font-size;
+			line-height: $u-cell-line-height;
+			color: $u-cell-value-color;
+
+			&--large {
+				font-size: $u-cell-value-font-size-large;
+			}
+		}
+
+		&--clickable {
+			background-color: $u-cell-clickable-color;
+		}
+
+		&--disabled {
+			color: $u-cell-disabled-color;
+			/* #ifndef APP-NVUE */
+			cursor: not-allowed;
+			/* #endif */
+		}
+
+		&--center {
+			align-items: center;
+		}
+	}
+</style>

+ 82 - 0
uni_modules/uview-ui/components/u-checkbox-group/props.js

@@ -0,0 +1,82 @@
+export default {
+    props: {
+        // 标识符
+        name: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.name
+        },
+        // 绑定的值
+        value: {
+            type: Array,
+            default: uni.$u.props.checkboxGroup.value
+        },
+        // 形状,circle-圆形,square-方形
+        shape: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.shape
+        },
+        // 是否禁用全部checkbox
+        disabled: {
+            type: Boolean,
+            default: uni.$u.props.checkboxGroup.disabled
+        },
+
+        // 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值
+        activeColor: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.activeColor
+        },
+        // 未选中的颜色
+        inactiveColor: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.inactiveColor
+        },
+
+        // 整个组件的尺寸,默认px
+        size: {
+            type: [String, Number],
+            default: uni.$u.props.checkboxGroup.size
+        },
+        // 布局方式,row-横向,column-纵向
+        placement: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.placement
+        },
+        // label的字体大小,px单位
+        labelSize: {
+            type: [String, Number],
+            default: uni.$u.props.checkboxGroup.labelSize
+        },
+        // label的字体颜色
+        labelColor: {
+            type: [String],
+            default: uni.$u.props.checkboxGroup.labelColor
+        },
+        // 是否禁止点击文本操作
+        labelDisabled: {
+            type: Boolean,
+            default: uni.$u.props.checkboxGroup.labelDisabled
+        },
+        // 图标颜色
+        iconColor: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.iconColor
+        },
+        // 图标的大小,单位px
+        iconSize: {
+            type: [String, Number],
+            default: uni.$u.props.checkboxGroup.iconSize
+        },
+        // 勾选图标的对齐方式,left-左边,right-右边
+        iconPlacement: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.iconPlacement
+        },
+        // 竖向配列时,是否显示下划线
+        borderBottom: {
+            type: Boolean,
+            default: uni.$u.props.checkboxGroup.borderBottom
+        }
+
+    }
+}

+ 103 - 0
uni_modules/uview-ui/components/u-checkbox-group/u-checkbox-group.vue

@@ -0,0 +1,103 @@
+<template>
+	<view
+	    class="u-checkbox-group"
+	    :class="bemClass"
+	>
+		<slot></slot>
+	</view>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * checkboxGroup 复选框组
+	 * @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便
+	 * @tutorial https://www.uviewui.com/components/checkbox.html
+	 * @property {String}			name			标识符 
+	 * @property {Array}			value			绑定的值
+	 * @property {String}			shape			形状,circle-圆形,square-方形 (默认 'square' )
+	 * @property {Boolean}			disabled		是否禁用全部checkbox (默认 false )
+	 * @property {String}			activeColor		选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值 (默认 '#2979ff' )
+	 * @property {String}			inactiveColor	未选中的颜色 (默认 '#c8c9cc' )
+	 * @property {String | Number}	size			整个组件的尺寸 单位px (默认 18 )
+	 * @property {String}			placement		布局方式,row-横向,column-纵向 (默认 'row' )
+	 * @property {String | Number}	labelSize		label的字体大小,px单位  (默认 14 )
+	 * @property {String}			labelColor		label的字体颜色 (默认 '#303133' )
+	 * @property {Boolean}			labelDisabled	是否禁止点击文本操作 (默认 false )
+	 * @property {String}			iconColor		图标颜色 (默认 '#ffffff' )
+	 * @property {String | Number}	iconSize		图标的大小,单位px (默认 12 )
+	 * @property {String}			iconPlacement	勾选图标的对齐方式,left-左边,right-右边  (默认 'left' )
+	 * @property {Boolean}			borderBottom	placement为row时,是否显示下边框 (默认 false )
+	 * @event {Function}	change	任一个checkbox状态发生变化时触发,回调为一个对象
+	 * @event {Function}	input	修改通过v-model绑定的值时触发,回调为一个对象
+	 * @example <u-checkbox-group></u-checkbox-group>
+	 */
+	export default {
+		name: 'u-checkbox-group',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
+		computed: {
+			// 这里computed的变量,都是子组件u-checkbox需要用到的,由于头条小程序的兼容性差异,子组件无法实时监听父组件参数的变化
+			// 所以需要手动通知子组件,这里返回一个parentData变量,供watch监听,在其中去通知每一个子组件重新从父组件(u-checkbox-group)
+			// 拉取父组件新的变化后的参数
+			parentData() {
+				return [this.value, this.disabled, this.inactiveColor, this.activeColor, this.size, this.labelDisabled, this.shape,
+					this.iconSize, this.borderBottom, this.placement
+				]
+			},
+			bemClass() {
+				// this.bem为一个computed变量,在mixin中
+				return this.bem('checkbox-group', ['placement'])
+			},
+		},
+		watch: {
+			// 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件
+			parentData() {
+				if (this.children.length) {
+					this.children.map(child => {
+						// 判断子组件(u-checkbox)如果有init方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
+						typeof(child.init) === 'function' && child.init()
+					})
+				}
+			},
+		},
+		data() {
+			return {
+
+			}
+		},
+		created() {
+			this.children = []
+		},
+		methods: {
+			// 将其他的checkbox设置为未选中的状态
+			unCheckedOther(childInstance) {
+				const values = []
+				this.children.map(child => {
+					// 将被选中的checkbox,放到数组中返回
+					if (child.isChecked) {
+						values.push(child.name)
+					}
+				})
+				// 发出事件
+				this.$emit('change', values)
+				// 修改通过v-model绑定的值
+				this.$emit('input', values)
+			},
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+
+	.u-checkbox-group {
+
+		&--row {
+			@include flex;
+		}
+
+		&--column {
+			@include flex(column);
+		}
+	}
+</style>

+ 69 - 0
uni_modules/uview-ui/components/u-checkbox/props.js

@@ -0,0 +1,69 @@
+export default {
+    props: {
+        // checkbox的名称
+        name: {
+            type: [String, Number, Boolean],
+            default: uni.$u.props.checkbox.name
+        },
+        // 形状,square为方形,circle为圆型
+        shape: {
+            type: String,
+            default: uni.$u.props.checkbox.shape
+        },
+        // 整体的大小
+        size: {
+            type: [String, Number],
+            default: uni.$u.props.checkbox.size
+        },
+        // 是否默认选中
+        checked: {
+            type: Boolean,
+            default: uni.$u.props.checkbox.checked
+        },
+        // 是否禁用
+        disabled: {
+            type: [String, Boolean],
+            default: uni.$u.props.checkbox.disabled
+        },
+        // 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值
+        activeColor: {
+            type: String,
+            default: uni.$u.props.checkbox.activeColor
+        },
+        // 未选中的颜色
+        inactiveColor: {
+            type: String,
+            default: uni.$u.props.checkbox.inactiveColor
+        },
+        // 图标的大小,单位px
+        iconSize: {
+            type: [String, Number],
+            default: uni.$u.props.checkbox.iconSize
+        },
+        // 图标颜色
+        iconColor: {
+            type: String,
+            default: uni.$u.props.checkbox.iconColor
+        },
+        // label提示文字,因为nvue下,直接slot进来的文字,由于特殊的结构,无法修改样式
+        label: {
+            type: [String, Number],
+            default: uni.$u.props.checkbox.label
+        },
+        // label的字体大小,px单位
+        labelSize: {
+            type: [String, Number],
+            default: uni.$u.props.checkbox.labelSize
+        },
+        // label的颜色
+        labelColor: {
+            type: String,
+            default: uni.$u.props.checkbox.labelColor
+        },
+        // 是否禁止点击提示语选中复选框
+        labelDisabled: {
+            type: [String, Boolean],
+            default: uni.$u.props.checkbox.labelDisabled
+        }
+    }
+}

+ 344 - 0
uni_modules/uview-ui/components/u-checkbox/u-checkbox.vue

@@ -0,0 +1,344 @@
+<template>
+	<view
+	    class="u-checkbox"
+	    :style="[checkboxStyle]"
+	    @tap.stop="wrapperClickHandler"
+	    :class="[`u-checkbox-label--${parentData.iconPlacement}`, parentData.borderBottom && parentData.placement === 'column' && 'u-border-bottom']"
+	>
+		<view
+		    class="u-checkbox__icon-wrap"
+		    @tap.stop="iconClickHandler"
+		    :class="iconClasses"
+		    :style="[iconWrapStyle]"
+		>
+			<slot name="icon">
+				<u-icon
+				    class="u-checkbox__icon-wrap__icon"
+				    name="checkbox-mark"
+				    :size="elIconSize"
+				    :color="elIconColor"
+				/>
+			</slot>
+		</view>
+		<text
+		    @tap.stop="labelClickHandler"
+		    :style="{
+				color: elDisabled ? elInactiveColor : elLabelColor,
+				fontSize: elLabelSize,
+				lineHeight: elLabelSize
+			}"
+		>{{label}}</text>
+	</view>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * checkbox  复选框
+	 * @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便
+	 * @tutorial https://uviewui.com/components/checkbox.html
+	 * @property {String | Number | Boolean}	name			checkbox组件的标示符
+	 * @property {String}						shape			形状,square为方形,circle为圆型
+	 * @property {String | Number}				size			整体的大小
+	 * @property {Boolean}						checked			是否默认选中
+	 * @property {String | Boolean}				disabled		是否禁用
+	 * @property {String}						activeColor		选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值
+	 * @property {String}						inactiveColor	未选中的颜色
+	 * @property {String | Number}				iconSize		图标的大小,单位px
+	 * @property {String}						iconColor		图标颜色
+	 * @property {String | Number}				label			label提示文字,因为nvue下,直接slot进来的文字,由于特殊的结构,无法修改样式
+	 * @property {String}						labelColor 		label的颜色
+	 * @property {String | Number}				labelSize		label的字体大小,px单位
+	 * @property {String | Boolean}				labelDisabled	是否禁止点击提示语选中复选框
+	 * @property {Object}						customStyle		定义需要用到的外部样式
+	 * 
+	 * @event {Function}	change	任一个checkbox状态发生变化时触发,回调为一个对象
+	 * @example <u-checkbox v-model="checked" :disabled="false">天涯</u-checkbox>
+	 */
+	export default {
+		name: "u-checkbox",
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
+		data() {
+			return {
+				isChecked: false,
+				// 父组件的默认值,因为头条小程序不支持在computed中使用this.parent.shape的形式
+				// 故只能使用如此方法
+				parentData: {
+					iconSize: 12,
+					labelDisabled: null,
+					disabled: null,
+					shape: 'square',
+					activeColor: null,
+					inactiveColor: null,
+					size: 18,
+					value: null,
+					iconColor: null,
+					placement: 'row',
+					borderBottom: false,
+					iconPlacement: 'left'
+				}
+			}
+		},
+		computed: {
+			// 是否禁用,如果父组件u-raios-group禁用的话,将会忽略子组件的配置
+			elDisabled() {
+				return this.disabled !== '' ? this.disabled : this.parentData.disabled !== null ? this.parentData.disabled : false;
+			},
+			// 是否禁用label点击
+			elLabelDisabled() {
+				return this.labelDisabled !== '' ? this.labelDisabled : this.parentData.labelDisabled !== null ? this.parentData.labelDisabled :
+					false;
+			},
+			// 组件尺寸,对应size的值,默认值为21px
+			elSize() {
+				return this.size ? this.size : (this.parentData.size ? this.parentData.size : 21);
+			},
+			// 组件的勾选图标的尺寸,默认12px
+			elIconSize() {
+				return this.iconSize ? this.iconSize : (this.parentData.iconSize ? this.parentData.iconSize : 12);
+			},
+			// 组件选中激活时的颜色
+			elActiveColor() {
+				return this.activeColor ? this.activeColor : (this.parentData.activeColor ? this.parentData.activeColor : '#2979ff');
+			},
+			// 组件选未中激活时的颜色
+			elInactiveColor() {
+				return this.inactiveColor ? this.inactiveColor : (this.parentData.inactiveColor ? this.parentData.inactiveColor :
+					'#c8c9cc');
+			},
+			// label的颜色
+			elLabelColor() {
+				return this.labelColor ? this.labelColor : (this.parentData.labelColor ? this.parentData.labelColor : '#606266')
+			},
+			// 组件的形状
+			elShape() {
+				return this.shape ? this.shape : (this.parentData.shape ? this.parentData.shape : 'circle');
+			},
+			// label大小
+			elLabelSize() {
+				return uni.$u.addUnit(this.labelSize ? this.labelSize : (this.parentData.labelSize ? this.parentData.labelSize :
+					'15'))
+			},
+			elIconColor() {
+				const iconColor = this.iconColor ? this.iconColor : (this.parentData.iconColor ? this.parentData.iconColor :
+					'#ffffff');
+				// 图标的颜色
+				if (this.elDisabled) {
+					// disabled状态下,已勾选的checkbox图标改为elInactiveColor
+					return this.isChecked ? this.elInactiveColor : 'transparent'
+				} else {
+					return this.isChecked ? iconColor : 'transparent'
+				}
+			},
+			iconClasses() {
+				let classes = []
+				// 组件的形状
+				classes.push('u-checkbox__icon-wrap--' + this.elShape)
+				if (this.elDisabled) {
+					classes.push('u-checkbox__icon-wrap--disabled')
+				}
+				if (this.isChecked && this.elDisabled) {
+					classes.push('u-checkbox__icon-wrap--disabled--checked')
+				}
+				// 支付宝,头条小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效
+				// #ifdef MP-ALIPAY || MP-TOUTIAO
+				classes = classes.join(' ')
+				// #endif
+				return classes
+			},
+			iconWrapStyle() {
+				// checkbox的整体样式
+				const style = {}
+				style.backgroundColor = this.isChecked && !this.elDisabled ? this.elActiveColor : '#ffffff'
+				style.borderColor = this.isChecked && !this.elDisabled ? this.elActiveColor : this.elInactiveColor
+				style.width = uni.$u.addUnit(this.elSize)
+				style.height = uni.$u.addUnit(this.elSize)
+				// 如果是图标在右边的话,移除它的右边距
+				if (this.parentData.iconPlacement === 'right') {
+					style.marginRight = 0
+				}
+				return style
+			},
+			checkboxStyle() {
+				const style = {}
+				if (this.parentData.borderBottom && this.parentData.placement === 'row') {
+					uni.$u.error('检测到您将borderBottom设置为true,需要同时将u-checkbox-group的placement设置为column才有效')
+				}
+				// 当父组件设置了显示下边框并且排列形式为纵向时,给内容和边框之间加上一定间隔
+				if (this.parentData.borderBottom && this.parentData.placement === 'column') {
+					style.paddingBottom = '8px'
+				}
+				return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))
+			}
+		},
+		mounted() {
+			this.init()
+		},
+		methods: {
+			init() {
+				// 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用
+				this.updateParentData()
+				if (!this.parent) {
+					uni.$u.error('u-checkbox必须搭配u-checkbox-group组件使用')
+				}
+				// 设置初始化时,是否默认选中的状态,父组件u-checkbox-group的value可能是array,所以额外判断
+				if (this.checked) {
+					this.isChecked = true
+				} else if (uni.$u.test.array(this.parentData.value)) {
+					// 查找数组是是否存在this.name元素值
+					this.isChecked = this.parentData.value.some(item => {
+						return item === this.name
+					})
+				}
+			},
+			updateParentData() {
+				this.getParentData('u-checkbox-group')
+			},
+			// 横向两端排列时,点击组件即可触发选中事件
+			wrapperClickHandler(e) {
+				this.parentData.iconPlacement === 'right' && this.iconClickHandler(e)
+			},
+			// 点击图标
+			iconClickHandler(e) {
+				this.preventEvent(e)
+				// 如果整体被禁用,不允许被点击
+				if (!this.elDisabled) {
+					this.setRadioCheckedStatus()
+				}
+			},
+			// 点击label
+			labelClickHandler(e) {
+				this.preventEvent(e)
+				// 如果按钮整体被禁用或者label被禁用,则不允许点击文字修改状态
+				if (!this.elLabelDisabled && !this.elDisabled) {
+					this.setRadioCheckedStatus()
+				}
+			},
+			emitEvent() {
+				this.$emit('change', this.isChecked)
+				// 尝试调用u-form的验证方法,进行一定延迟,否则微信小程序更新可能会不及时
+				this.$nextTick(() => {
+					uni.$u.formValidate(this, 'change')
+				})
+			},
+			// 改变组件选中状态
+			// 这里的改变的依据是,更改本组件的checked值为true,同时通过父组件遍历所有u-checkbox实例
+			// 将本组件外的其他u-checkbox的checked都设置为false(都被取消选中状态),因而只剩下一个为选中状态
+			setRadioCheckedStatus() {
+				// 将本组件标记为与原来相反的状态
+				this.isChecked = !this.isChecked
+				this.emitEvent()
+				typeof this.parent.unCheckedOther === 'function' && this.parent.unCheckedOther(this)
+			}
+		},
+		watch:{
+			checked(){
+				this.isChecked = this.checked
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+	$u-checkbox-icon-wrap-margin-right:6px !default;
+	$u-checkbox-icon-wrap-font-size:6px !default;
+	$u-checkbox-icon-wrap-border-width:1px !default;
+	$u-checkbox-icon-wrap-border-color:#c8c9cc !default;
+	$u-checkbox-icon-wrap-icon-line-height:0 !default;
+	$u-checkbox-icon-wrap-circle-border-radius:100% !default;
+	$u-checkbox-icon-wrap-square-border-radius:3px !default;
+	$u-checkbox-icon-wrap-checked-color:#fff !default;
+	$u-checkbox-icon-wrap-checked-background-color:red !default;
+	$u-checkbox-icon-wrap-checked-border-color:#2979ff !default;
+	$u-checkbox-icon-wrap-disabled-background-color:#ebedf0 !default;
+	$u-checkbox-icon-wrap-disabled-checked-color:#c8c9cc !default;
+	$u-checkbox-label-margin-left:5px !default;
+	$u-checkbox-label-margin-right:12px !default;
+	$u-checkbox-label-color:$u-content-color !default;
+	$u-checkbox-label-font-size:15px !default;
+	$u-checkbox-label-disabled-color:#c8c9cc !default;
+
+	.u-checkbox {
+		/* #ifndef APP-NVUE */
+		@include flex(row);
+		/* #endif */
+		overflow: hidden;
+		flex-direction: row;
+		align-items: center;
+
+		&-label--left {
+			flex-direction: row
+		}
+
+		&-label--right {
+			flex-direction: row-reverse;
+			justify-content: space-between
+		}
+
+		&__icon-wrap {
+			/* #ifndef APP-NVUE */
+			box-sizing: border-box;
+			// nvue下,border-color过渡有问题
+			transition-property: border-color, background-color, color;
+			transition-duration: 0.2s;
+			/* #endif */
+			color: $u-content-color;
+			@include flex;
+			align-items: center;
+			justify-content: center;
+			color: transparent;
+			text-align: center;
+			margin-right: $u-checkbox-icon-wrap-margin-right;
+
+			font-size: $u-checkbox-icon-wrap-font-size;
+			border-width: $u-checkbox-icon-wrap-border-width;
+			border-color: $u-checkbox-icon-wrap-border-color;
+			border-style: solid;
+
+			/* #ifdef MP-TOUTIAO */
+			// 头条小程序兼容性问题,需要设置行高为0,否则图标偏下
+			&__icon {
+				line-height: $u-checkbox-icon-wrap-icon-line-height;
+			}
+
+			/* #endif */
+
+			&--circle {
+				border-radius: $u-checkbox-icon-wrap-circle-border-radius;
+			}
+
+			&--square {
+				border-radius: $u-checkbox-icon-wrap-square-border-radius;
+			}
+
+			&--checked {
+				color: $u-checkbox-icon-wrap-checked-color;
+				background-color: $u-checkbox-icon-wrap-checked-background-color;
+				border-color: $u-checkbox-icon-wrap-checked-border-color;
+			}
+
+			&--disabled {
+				background-color: $u-checkbox-icon-wrap-disabled-background-color !important;
+			}
+
+			&--disabled--checked {
+				color: $u-checkbox-icon-wrap-disabled-checked-color !important;
+			}
+		}
+
+		&__label {
+			/* #ifndef APP-NVUE */
+			word-wrap: break-word;
+			/* #endif */
+			margin-left: $u-checkbox-label-margin-left;
+			margin-right: $u-checkbox-label-margin-right;
+			color: $u-checkbox-label-color;
+			font-size: $u-checkbox-label-font-size;
+
+			&--disabled {
+				color: $u-checkbox-label-disabled-color;
+			}
+		}
+	}
+</style>

+ 8 - 0
uni_modules/uview-ui/components/u-circle-progress/props.js

@@ -0,0 +1,8 @@
+export default {
+    props: {
+        percentage: {
+            type: [String, Number],
+            default: uni.$u.props.circleProgress.percentage
+        }
+    }
+}

+ 198 - 0
uni_modules/uview-ui/components/u-circle-progress/u-circle-progress.vue

@@ -0,0 +1,198 @@
+<template>
+	<view class="u-circle-progress">
+		<view class="u-circle-progress__left">
+			<view
+			    class="u-circle-progress__left__circle"
+			    :style="[leftSyle]"
+			    ref="left-circle"
+			>
+
+			</view>
+		</view>
+		<view
+		    class="u-circle-progress__right"
+		>
+			<view
+			    class="u-circle-progress__right__circle"
+			    ref="right-circle"
+				:style="[rightSyle]"
+			>
+
+			</view>
+		</view>
+		<view class="u-circle-progress__circle">
+
+		</view>
+	</view>
+</template>
+
+<script>
+	import props from './props.js';
+	// #ifdef APP-NVUE
+	const animation = uni.requireNativePlugin('animation')
+	// #endif
+	/**
+	 * CircleProgress 圆形进度条 TODO: 待完善 
+	 * @description 展示操作或任务的当前进度,比如上传文件,是一个圆形的进度环。
+	 * @tutorial https://www.uviewui.com/components/circleProgress.html
+	 * @property {String | Number}	percentage	圆环进度百分比值,为数值类型,0-100 (默认 30 )
+	 * @example
+	 */
+	export default {
+		name: 'u-circle-progress',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
+		data() {
+			return {
+				leftBorderColor: 'rgb(200, 200, 200)',
+				rightBorderColor: 'rgb(200, 200, 200)',
+			}
+		},
+		computed: {
+			leftSyle() {
+				const style = {}
+				style.borderTopColor = this.leftBorderColor
+				style.borderRightColor = this.leftBorderColor
+				return style
+			},
+			rightSyle() {
+				const style = {}
+				style.borderLeftColor = this.rightBorderColor
+				style.borderBottomColor = this.rightBorderColor
+				return style
+			}
+		},
+		mounted() {
+			uni.$u.sleep().then(() => {
+				this.rightBorderColor = 'rgb(66, 185, 131)'
+				// this.init()
+			})
+		},
+		methods: {
+			init() {
+				animation.transition(this.$refs['right-circle'].ref, {
+					styles: {
+						transform: 'rotate(45deg)',
+						transformOrigin: 'center center'
+					},
+				}, () => {
+					this.rightBorderColor = 'rgb(66, 185, 131)'
+					// animation.transition(this.$refs['right-circle'].ref, {
+					// 	styles: {
+					// 		transform: 'rotate(225deg)',
+					// 		transformOrigin: 'center center'
+					// 	},
+					// 	duration: 3000,
+					// }, () => {
+					// 	animation.transition(this.$refs['left-circle'].ref, {
+					// 		styles: {
+					// 			transform: 'rotate(45deg)',
+					// 			transformOrigin: 'center center'
+					// 		},
+					// 	}, () => {
+					// 		this.leftBorderColor = 'rgb(66, 185, 131)'
+					// 		animation.transition(this.$refs['left-circle'].ref, {
+					// 			styles: {
+					// 				transform: 'rotate(225deg)',
+					// 				transformOrigin: 'center center'
+					// 			},
+					// 			duration: 1500,
+					// 		}, () => {
+
+					// 		})
+					// 	})
+					// })
+				})
+
+			}
+		},
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+
+	.u-circle-progress {
+		@include flex(row);
+		position: relative;
+		border-radius: 100px;
+		height: 100px;
+		width: 100px;
+		// transform: rotate(0deg);
+		// background-color: rgb(66, 185, 131);
+		background-color: rgb(200, 200, 200);
+		overflow: hidden;
+		justify-content: space-between;
+
+		&__circle {
+			border-radius: 100px;
+			height: 90px;
+			width: 90px;
+			transform: translate(-50%, -50%);
+			background-color: rgb(255, 255, 255);
+			left: 50px;
+			top: 50px;
+			position: absolute;
+		}
+
+		&__left {
+			position: absolute;
+			left: 0;
+			width: 50px;
+			height: 100px;
+			overflow: hidden;
+			box-sizing: border-box;
+			// background-color: rgb(66, 185, 131);
+			// background-color: rgb(200, 200, 200);
+			// transform-origin: left center;
+
+			&__circle {
+				box-sizing: border-box;
+				// background-color: red;
+				border-left-color: transparent;
+				border-bottom-color: transparent;
+				border-top-left-radius: 50px;
+				border-top-right-radius: 50px;
+				border-bottom-right-radius: 50px;
+				// border-left-color: rgb(66, 185, 131);
+				// border-bottom-color: rgb(66, 185, 131);
+				border-top-color: rgb(66, 185, 131);
+				border-right-color: rgb(66, 185, 131);
+				border-width: 5px;
+				width: 100px;
+				height: 100px;
+				transform: rotate(225deg);
+				// border-radius: 100px;
+			}
+		}
+
+		&__right {
+			position: absolute;
+			right: 0;
+			width: 50px;
+			height: 100px;
+			overflow: hidden;
+
+			&__circle {
+				position: absolute;
+				right: 0;
+				box-sizing: border-box;
+				// background-color: red;
+				border-top-color: transparent;
+				border-right-color: transparent;
+				border-top-left-radius: 50px;
+				border-bottom-left-radius: 50px;
+				border-bottom-right-radius: 50px;
+				// border-left-color: rgb(66, 185, 131);
+				// border-bottom-color: rgb(66, 185, 131);
+				border-left-color: rgb(200, 200, 200);
+				border-bottom-color: rgb(200, 200, 200);
+				border-width: 5px;
+				width: 100px;
+				height: 100px;
+				transform: rotate(45deg);
+				transform-origin: center center;
+				// border-radius: 100px;
+			}
+		}
+	}
+</style>

+ 79 - 0
uni_modules/uview-ui/components/u-code-input/props.js

@@ -0,0 +1,79 @@
+export default {
+    props: {
+		// 键盘弹起时,是否自动上推页面
+		adjustPosition: {
+			type: Boolean,
+            default: uni.$u.props.codeInput.adjustPosition
+		},
+        // 最大输入长度
+        maxlength: {
+            type: [String, Number],
+            default: uni.$u.props.codeInput.maxlength
+        },
+        // 是否用圆点填充
+        dot: {
+            type: Boolean,
+            default: uni.$u.props.codeInput.dot
+        },
+        // 显示模式,box-盒子模式,line-底部横线模式
+        mode: {
+            type: String,
+            default: uni.$u.props.codeInput.mode
+        },
+        // 是否细边框
+        hairline: {
+            type: Boolean,
+            default: uni.$u.props.codeInput.hairline
+        },
+        // 字符间的距离
+        space: {
+            type: [String, Number],
+            default: uni.$u.props.codeInput.space
+        },
+        // 预置值
+        value: {
+            type: [String, Number],
+            default: uni.$u.props.codeInput.value
+        },
+        // 是否自动获取焦点
+        focus: {
+            type: Boolean,
+            default: uni.$u.props.codeInput.focus
+        },
+        // 字体是否加粗
+        bold: {
+            type: Boolean,
+            default: uni.$u.props.codeInput.bold
+        },
+        // 字体颜色
+        color: {
+            type: String,
+            default: uni.$u.props.codeInput.color
+        },
+        // 字体大小
+        fontSize: {
+            type: [String, Number],
+            default: uni.$u.props.codeInput.fontSize
+        },
+        // 输入框的大小,宽等于高
+        size: {
+            type: [String, Number],
+            default: uni.$u.props.codeInput.size
+        },
+        // 是否隐藏原生键盘,如果想用自定义键盘的话,需设置此参数为true
+        disabledKeyboard: {
+            type: Boolean,
+            default: uni.$u.props.codeInput.disabledKeyboard
+        },
+        // 边框和线条颜色
+        borderColor: {
+            type: String,
+            default: uni.$u.props.codeInput.borderColor
+        },
+		// 是否禁止输入"."符号
+		disabledDot: {
+			type: Boolean,
+			default: uni.$u.props.codeInput.disabledDot
+		}
+    }
+}

+ 252 - 0
uni_modules/uview-ui/components/u-code-input/u-code-input.vue

@@ -0,0 +1,252 @@
+<template>
+	<view class="u-code-input">
+		<view
+			class="u-code-input__item"
+			:style="[itemStyle(index)]"
+			v-for="(item, index) in codeLength"
+			:key="index"
+		>
+			<view
+				class="u-code-input__item__dot"
+				v-if="dot && codeArray.length > index"
+			></view>
+			<text
+				v-else
+				:style="{
+					fontSize: $u.addUnit(fontSize),
+					fontWeight: bold ? 'bold' : 'normal',
+					color: color
+				}"
+			>{{codeArray[index]}}</text>
+			<view
+				class="u-code-input__item__line"
+				v-if="mode === 'line'"
+				:style="[lineStyle]"
+			></view>
+			<!-- #ifndef APP-PLUS -->
+			<view v-if="isFocus && codeArray.length === index" :style="{backgroundColor: color}" class="u-code-input__item__cursor"></view>
+			<!-- #endif -->
+		</view>
+		<input
+			:disabled="disabledKeyboard"
+			type="number"
+			:focus="focus"
+			:value="inputValue"
+			:maxlength="maxlength"
+			:adjustPosition="adjustPosition"
+			class="u-code-input__input"
+			@input="inputHandler"
+			:style="{
+				height: $u.addUnit(size) 
+			}"
+			@focus="isFocus = true"
+			@blur="isFocus = false"
+		/>
+	</view>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * CodeInput 验证码输入
+	 * @description 该组件一般用于验证用户短信验证码的场景,也可以结合uView的键盘组件使用
+	 * @tutorial https://www.uviewui.com/components/codeInput.html
+	 * @property {String | Number}	maxlength			最大输入长度 (默认 6 )
+	 * @property {Boolean}			dot					是否用圆点填充 (默认 false )
+	 * @property {String}			mode				显示模式,box-盒子模式,line-底部横线模式 (默认 'box' )
+	 * @property {Boolean}			hairline			是否细边框 (默认 false )
+	 * @property {String | Number}	space				字符间的距离 (默认 10 )
+	 * @property {String | Number}	value				预置值
+	 * @property {Boolean}			focus				是否自动获取焦点 (默认 false )
+	 * @property {Boolean}			bold				字体和输入横线是否加粗 (默认 false )
+	 * @property {String}			color				字体颜色 (默认 '#606266' )
+	 * @property {String | Number}	fontSize			字体大小,单位px (默认 18 )
+	 * @property {String | Number}	size				输入框的大小,宽等于高 (默认 35 )
+	 * @property {Boolean}			disabledKeyboard	是否隐藏原生键盘,如果想用自定义键盘的话,需设置此参数为true (默认 false )
+	 * @property {String}			borderColor			边框和线条颜色 (默认 '#c9cacc' )
+	 * @property {Boolean}			disabledDot			是否禁止输入"."符号 (默认 true )
+	 * 
+	 * @event {Function}	change	输入内容发生改变时触发,具体见上方说明			value:当前输入的值
+	 * @event {Function}	finish	输入字符个数达maxlength值时触发,见上方说明	value:当前输入的值
+	 * @example	<u-code-input v-model="value4" :focus="true"></u-code-input>
+	 */
+	export default {
+		name: 'u-code-input',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+		data() {
+			return {
+				inputValue: '',
+				isFocus: this.focus
+			}
+		},
+		watch: {
+			value: {
+				immediate: true,
+				handler(val) {
+					// 转为字符串,超出部分截掉
+					this.inputValue = String(val).substring(0, this.maxlength)
+				}
+			},
+		},
+		computed: {
+			// 根据长度,循环输入框的个数,因为头条小程序数值不能用于v-for
+			codeLength() {
+				return new Array(Number(this.maxlength))
+			},
+			// 循环item的样式
+			itemStyle() {
+				return index => {
+					const addUnit = uni.$u.addUnit
+					const style = {
+						width: addUnit(this.size),
+						height: addUnit(this.size)
+					}
+					// 盒子模式下,需要额外进行处理
+					if (this.mode === 'box') {
+						// 设置盒子的边框,如果是细边框,则设置为0.5px宽度
+						style.border = `${this.hairline ? 0.5 : 1}px solid ${this.borderColor}`
+						// 如果盒子间距为0的话
+						if (uni.$u.getPx(this.space) === 0) {
+							// 给第一和最后一个盒子设置圆角
+							if (index === 0) {
+								style.borderTopLeftRadius = '3px'
+								style.borderBottomLeftRadius = '3px'
+							}
+							if (index === this.codeLength.length - 1) {
+								style.borderTopRightRadius = '3px'
+								style.borderBottomRightRadius = '3px'
+							}
+							// 最后一个盒子的右边框需要保留
+							if (index !== this.codeLength.length - 1) {
+								style.borderRight = 'none'
+							}
+						}
+					}
+					if (index !== this.codeLength.length - 1) {
+						// 设置验证码字符之间的距离,通过margin-right设置,最后一个字符,无需右边框
+						style.marginRight = addUnit(this.space)
+					} else {
+						// 最后一个盒子的有边框需要保留
+						style.marginRight = 0
+					}
+
+					return style
+				}
+			},
+			// 将输入的值,转为数组,给item历遍时,根据当前的索引显示数组的元素
+			codeArray() {
+				return String(this.inputValue).split('')
+			},
+			// 下划线模式下,横线的样式
+			lineStyle() {
+				const style = {}
+				style.height = this.hairline ? '2px' : '4px'
+				style.width = uni.$u.addUnit(this.size)
+				// 线条模式下,背景色即为边框颜色
+				style.backgroundColor = this.borderColor
+				return style
+			}
+		},
+		methods: {
+			// 监听输入框的值发生变化
+			inputHandler(e) {
+				const value = e.detail.value
+				this.inputValue = value
+				// 是否允许输入“.”符号
+				if(this.disabledDot) {
+					this.$nextTick(() => {
+						this.inputValue = value.replace('.', '')
+					})
+				}
+				// 未达到maxlength之前,发送change事件,达到后发送finish事件
+				this.$emit('change', value)
+				// 修改通过v-model双向绑定的值
+				this.$emit('input', value)
+				// 达到用户指定输入长度时,发出完成事件
+				if (String(value).length >= Number(this.maxlength)) {
+					this.$emit('finish', value)
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+	$u-code-input-cursor-width: 1px;
+	$u-code-input-cursor-height: 40%;
+	$u-code-input-cursor-animation-duration: 1s;
+	$u-code-input-cursor-animation-name: u-cursor-flicker;
+
+	.u-code-input {
+		@include flex;
+		position: relative;
+		overflow: hidden;
+
+		&__item {
+			@include flex;
+			justify-content: center;
+			align-items: center;
+			position: relative;
+
+			&__text {
+				font-size: 15px;
+				color: $u-content-color;
+			}
+
+			&__dot {
+				width: 7px;
+				height: 7px;
+				border-radius: 100px;
+				background-color: $u-content-color;
+			}
+
+			&__line {
+				position: absolute;
+				bottom: 0;
+				height: 4px;
+				border-radius: 100px;
+				width: 40px;
+				background-color: $u-content-color;
+			}
+			/* #ifndef APP-PLUS */
+			&__cursor {
+				position: absolute;
+				top: 50%;
+				left: 50%;
+				transform: translate(-50%,-50%);
+				width: $u-code-input-cursor-width;
+				height: $u-code-input-cursor-height;
+				animation: $u-code-input-cursor-animation-duration u-cursor-flicker infinite;
+			}
+			/* #endif */
+			
+		}
+
+		&__input {
+			// 之所以需要input输入框,是因为有它才能唤起键盘
+			// 这里将它设置为两倍的屏幕宽度,再将左边的一半移出屏幕,为了不让用户看到输入的内容
+			position: absolute;
+			left: -750rpx;
+			width: 1500rpx;
+			top: 0;
+			background-color: transparent;
+			text-align: left;
+		}
+	}
+	
+	/* #ifndef APP-PLUS */
+	@keyframes u-cursor-flicker {
+		0% {
+		    opacity: 0;
+		}
+		50% {
+		    opacity: 1;
+		}
+		100% {
+		    opacity: 0;
+		}
+	}
+	/* #endif */
+
+</style>

+ 34 - 0
uni_modules/uview-ui/components/u-code/props.js

@@ -0,0 +1,34 @@
+export default {
+    props: {
+        // 倒计时总秒数
+        seconds: {
+            type: [String, Number],
+            default: uni.$u.props.code.seconds
+        },
+        // 尚未开始时提示
+        startText: {
+            type: String,
+            default: uni.$u.props.code.startText
+        },
+        // 正在倒计时中的提示
+        changeText: {
+            type: String,
+            default: uni.$u.props.code.changeText
+        },
+        // 倒计时结束时的提示
+        endText: {
+            type: String,
+            default: uni.$u.props.code.endText
+        },
+        // 是否在H5刷新或各端返回再进入时继续倒计时
+        keepRunning: {
+            type: Boolean,
+            default: uni.$u.props.code.keepRunning
+        },
+        // 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了
+        uniqueKey: {
+            type: String,
+            default: uni.$u.props.code.uniqueKey
+        }
+    }
+}

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff