From 7d2f48fd1cd8cba220f47e59615640bdd413fa28 Mon Sep 17 00:00:00 2001 From: queenwuli <942534046@qq.com> Date: Thu, 10 Dec 2020 15:13:49 +0800 Subject: [PATCH] 项目初始化 --- hive-app/App.vue | 17 hive-app/components/uni-notice-bar/uni-notice-bar.vue | 395 + hive-app/pages.json | 17 hive-app/components/uni-drawer/uni-drawer.vue | 170 hive-app/components/uni-forms-item/uni-forms-item.vue | 437 + hive-app/uni.scss | 76 hive-app/components/uni-swipe-action-item/index.wxs | 266 hive-app/components/uni-goods-nav/uni-goods-nav.vue | 225 hive-app/components/uni-countdown/uni-countdown.vue | 211 hive-app/components/uni-easyinput/common.js | 56 hive-app/components/uni-list-ad/uni-list-ad.vue | 107 hive-app/components/uni-forms/validate.js | 442 + hive-app/components/uni-search-bar/uni-search-bar.vue | 206 hive-app/components/uni-forms/uni-forms.vue | 420 + hive-app/components/uni-fav/uni-fav.vue | 140 hive-app/components/uni-pagination/uni-pagination.vue | 204 hive-app/components/uni-swipe-action-item/mpwxs.js | 116 hive-app/manifest.json | 58 hive-app/components/uni-status-bar/uni-status-bar.vue | 25 hive-app/components/uni-section/uni-section.vue | 136 hive-app/components/uni-table/uni-table.vue | 263 hive-app/components/uni-badge/uni-badge.vue | 153 hive-app/components/uni-tr/uni-tr.vue | 117 hive-app/components/uni-group/uni-group.vue | 127 hive-app/components/uni-list-item/uni-list-item.vue | 440 + hive-app/components/uni-swipe-action/uni-swipe-action.vue | 42 hive-app/main.js | 11 hive-app/components/uni-link/uni-link.vue | 78 hive-app/components/uni-list-chat/uni-list-chat.vue | 533 + hive-app/components/uni-transition/uni-transition.vue | 279 hive-app/components/uni-swipe-action-item/mpother.js | 252 hive-app/components/uni-segmented-control/uni-segmented-control.vue | 138 hive-app/components/uni-calendar/util.js | 352 + hive-app/components/uni-icons/uni-icons.vue | 71 hive-app/components/uni-list/uni-refresh.vue | 65 hive-app/components/uni-nav-bar/uni-nav-bar.vue | 242 hive-app/components/uni-calendar/uni-calendar.vue | 505 + hive-app/components/uni-card/uni-card.vue | 403 + hive-app/components/uni-popup/message.js | 22 hive-app/pages/index/index.vue | 29 hive-app/components/uni-calendar/uni-calendar-item.vue | 170 hive-app/components/uni-datetime-picker/uni-datetime-picker.vue | 355 + hive-app/components/uni-dateformat/uni-dateformat.vue | 90 hive-app/components/uni-title/uni-title.vue | 170 hive-app/components/uni-icons/icons.js | 132 hive-app/components/uni-number-box/uni-number-box.vue | 200 hive-app/components/uni-calendar/calendar.js | 546 + hive-app/components/uni-list/uni-list.vue | 106 hive-app/components/uni-rate/uni-rate.vue | 296 + hive-app/components/uni-popup/popup.js | 25 hive-app/components/uni-popup-message/uni-popup-message.vue | 116 hive-app/components/uni-test/uni-test.vue | 19 hive-app/components/uni-swipe-action-item/bindingx.js | 292 + hive-app/components/uni-combox/uni-combox.vue | 213 hive-app/components/uni-list/uni-refresh.wxs | 87 hive-app/static/uni.ttf | 0 hive-app/components/uni-dateformat/date-format.js | 191 hive-app/components/uni-data-checkbox/clientdb.js | 368 + hive-app/components/uni-fab/uni-fab.vue | 433 + hive-app/components/uni-indexed-list/uni-indexed-list.vue | 318 + hive-app/components/uni-popup/share.js | 16 hive-app/components/uni-grid-item/uni-grid-item.vue | 124 hive-app/components/uni-icons/uni.ttf | 0 hive-app/components/uni-swipe-action-item/uni-swipe-action-item.vue | 365 + hive-app/components/uni-popup/uni-popup.vue | 297 + hive-app/components/uni-steps/uni-steps.vue | 256 hive-app/components/uni-grid/uni-grid.vue | 141 hive-app/components/uni-popup-share/uni-popup-share.vue | 165 hive-app/components/uni-th/uni-th.vue | 78 hive-app/components/uni-list-chat/uni-list-chat.scss | 58 hive-app/components/uni-td/uni-td.vue | 74 hive-app/components/uni-load-more/uni-load-more.vue | 359 + hive-app/components/uni-popup-dialog/uni-popup-dialog.vue | 243 hive-app/components/uni-tag/uni-tag.vue | 230 hive-app/components/uni-data-checkbox/uni-data-checkbox.vue | 766 ++ hive-app/components/uni-easyinput/uni-easyinput.vue | 384 + hive-app/components/uni-swipe-action-item/mpalipay.js | 207 hive-app/components/uni-swiper-dot/uni-swiper-dot.vue | 201 hive-app/components/uni-collapse-item/uni-collapse-item.vue | 218 hive-app/components/uni-collapse/uni-collapse.vue | 59 hive-app/components/uni-indexed-list/uni-indexed-list-item.vue | 142 hive-app/components/uni-field/uni-field.vue | 686 ++ 82 files changed, 17,042 insertions(+), 0 deletions(-) diff --git a/hive-app/App.vue b/hive-app/App.vue new file mode 100644 index 0000000..8c2b732 --- /dev/null +++ b/hive-app/App.vue @@ -0,0 +1,17 @@ +<script> + export default { + onLaunch: function() { + console.log('App Launch') + }, + onShow: function() { + console.log('App Show') + }, + onHide: function() { + console.log('App Hide') + } + } +</script> + +<style> + /*每个页面公共css */ +</style> diff --git a/hive-app/components/uni-badge/uni-badge.vue b/hive-app/components/uni-badge/uni-badge.vue new file mode 100644 index 0000000..4291fbc --- /dev/null +++ b/hive-app/components/uni-badge/uni-badge.vue @@ -0,0 +1,153 @@ +<template> + <text v-if="text" :class="inverted ? 'uni-badge--' + type + ' uni-badge--' + size + ' uni-badge--' + type + '-inverted' : 'uni-badge--' + type + ' uni-badge--' + size" + :style="badgeStyle" class="uni-badge" @click="onClick()">{{ text }}</text> +</template> + +<script> + /** + * Badge 数字角标 + * @description 数字角标一般和其它控件(列表、9宫格等)配合使用,用于进行数量提示,默认为实心灰色背景 + * @tutorial https://ext.dcloud.net.cn/plugin?id=21 + * @property {String} text 角标内容 + * @property {String} type = [default|primary|success|warning|error] 颜色类型 + * @value default 灰色 + * @value primary 蓝色 + * @value success 绿色 + * @value warning 黄色 + * @value error 红色 + * @property {String} size = [normal|small] Badge 大小 + * @value normal 一般尺寸 + * @value small 小尺寸 + * @property {String} inverted = [true|false] 是否无需背景颜色 + * @event {Function} click 点击 Badge 触发事件 + * @example <uni-badge text="1"></uni-badge> + */ + export default { + name: 'UniBadge', + props: { + type: { + type: String, + default: 'default' + }, + inverted: { + type: Boolean, + default: false + }, + text: { + type: [String, Number], + default: '' + }, + size: { + type: String, + default: 'normal' + } + }, + data() { + return { + badgeStyle: '' + }; + }, + watch: { + text() { + this.setStyle() + } + }, + mounted() { + this.setStyle() + }, + methods: { + setStyle() { + this.badgeStyle = `width: ${String(this.text).length * 8 + 12}px` + }, + onClick() { + this.$emit('click'); + } + } + }; +</script> + +<style lang="scss" scoped> + $bage-size: 12px; + $bage-small: scale(0.8); + $bage-height: 20px; + + .uni-badge { + /* #ifndef APP-PLUS */ + display: flex; + box-sizing: border-box; + overflow: hidden; + /* #endif */ + justify-content: center; + flex-direction: row; + height: $bage-height; + line-height: $bage-height; + color: $uni-text-color; + border-radius: 100px; + background-color: $uni-bg-color-hover; + background-color: transparent; + text-align: center; + font-family: 'Helvetica Neue', Helvetica, sans-serif; + font-size: $bage-size; + padding: 0px 6px; + } + + .uni-badge--inverted { + padding: 0 5px 0 0; + color: $uni-bg-color-hover; + } + + .uni-badge--default { + color: $uni-text-color; + background-color: $uni-bg-color-hover; + } + + .uni-badge--default-inverted { + color: $uni-text-color-grey; + background-color: transparent; + } + + .uni-badge--primary { + color: $uni-text-color-inverse; + background-color: $uni-color-primary; + } + + .uni-badge--primary-inverted { + color: $uni-color-primary; + background-color: transparent; + } + + .uni-badge--success { + color: $uni-text-color-inverse; + background-color: $uni-color-success; + } + + .uni-badge--success-inverted { + color: $uni-color-success; + background-color: transparent; + } + + .uni-badge--warning { + color: $uni-text-color-inverse; + background-color: $uni-color-warning; + } + + .uni-badge--warning-inverted { + color: $uni-color-warning; + background-color: transparent; + } + + .uni-badge--error { + color: $uni-text-color-inverse; + background-color: $uni-color-error; + } + + .uni-badge--error-inverted { + color: $uni-color-error; + background-color: transparent; + } + + .uni-badge--small { + transform: $bage-small; + transform-origin: center center; + } +</style> diff --git a/hive-app/components/uni-calendar/calendar.js b/hive-app/components/uni-calendar/calendar.js new file mode 100644 index 0000000..b8d7d6f --- /dev/null +++ b/hive-app/components/uni-calendar/calendar.js @@ -0,0 +1,546 @@ +/** +* @1900-2100区间内的公历、农历互转 +* @charset UTF-8 +* @github https://github.com/jjonline/calendar.js +* @Author Jea杨(JJonline@JJonline.Cn) +* @Time 2014-7-21 +* @Time 2016-8-13 Fixed 2033hex、Attribution Annals +* @Time 2016-9-25 Fixed lunar LeapMonth Param Bug +* @Time 2017-7-24 Fixed use getTerm Func Param Error.use solar year,NOT lunar year +* @Version 1.0.3 +* @公历转农历:calendar.solar2lunar(1987,11,01); //[you can ignore params of prefix 0] +* @农历转公历:calendar.lunar2solar(1987,09,10); //[you can ignore params of prefix 0] +*/ +/* eslint-disable */ +var calendar = { + + /** + * 农历1900-2100的润大小信息表 + * @Array Of Property + * @return Hex + */ + lunarInfo: [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909 + 0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910-1919 + 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920-1929 + 0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930-1939 + 0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940-1949 + 0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950-1959 + 0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960-1969 + 0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, // 1970-1979 + 0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980-1989 + 0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999 + 0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009 + 0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010-2019 + 0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029 + 0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030-2039 + 0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040-2049 + /** Add By JJonline@JJonline.Cn**/ + 0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050-2059 + 0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060-2069 + 0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070-2079 + 0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089 + 0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090-2099 + 0x0d520], // 2100 + + /** + * 公历每个月份的天数普通表 + * @Array Of Property + * @return Number + */ + solarMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], + + /** + * 天干地支之天干速查表 + * @Array Of Property trans["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"] + * @return Cn string + */ + Gan: ['\u7532', '\u4e59', '\u4e19', '\u4e01', '\u620a', '\u5df1', '\u5e9a', '\u8f9b', '\u58ec', '\u7678'], + + /** + * 天干地支之地支速查表 + * @Array Of Property + * @trans["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"] + * @return Cn string + */ + Zhi: ['\u5b50', '\u4e11', '\u5bc5', '\u536f', '\u8fb0', '\u5df3', '\u5348', '\u672a', '\u7533', '\u9149', '\u620c', '\u4ea5'], + + /** + * 天干地支之地支速查表<=>生肖 + * @Array Of Property + * @trans["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"] + * @return Cn string + */ + Animals: ['\u9f20', '\u725b', '\u864e', '\u5154', '\u9f99', '\u86c7', '\u9a6c', '\u7f8a', '\u7334', '\u9e21', '\u72d7', '\u732a'], + + /** + * 24节气速查表 + * @Array Of Property + * @trans["小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"] + * @return Cn string + */ + solarTerm: ['\u5c0f\u5bd2', '\u5927\u5bd2', '\u7acb\u6625', '\u96e8\u6c34', '\u60ca\u86f0', '\u6625\u5206', '\u6e05\u660e', '\u8c37\u96e8', '\u7acb\u590f', '\u5c0f\u6ee1', '\u8292\u79cd', '\u590f\u81f3', '\u5c0f\u6691', '\u5927\u6691', '\u7acb\u79cb', '\u5904\u6691', '\u767d\u9732', '\u79cb\u5206', '\u5bd2\u9732', '\u971c\u964d', '\u7acb\u51ac', '\u5c0f\u96ea', '\u5927\u96ea', '\u51ac\u81f3'], + + /** + * 1900-2100各年的24节气日期速查表 + * @Array Of Property + * @return 0x string For splice + */ + sTermInfo: ['9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', + '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', + '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', + 'b027097bd097c36b0b6fc9274c91aa', '9778397bd19801ec9210c965cc920e', '97b6b97bd19801ec95f8c965cc920f', + '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd197c36c9210c9274c91aa', + '97b6b97bd19801ec95f8c965cc920e', '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', + '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e', '97bcf97c3598082c95f8e1cfcc920f', + '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', + '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', + '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', + '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', + '97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd097bd07f595b0b6fc920fb0722', + '9778397bd097c36b0b6fc9210c8dc2', '9778397bd19801ec9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f', + '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e', + '97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', + '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bd07f1487f595b0b0bc920fb0722', + '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', + '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', + '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722', + '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', + '97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', + '9778397bd097c36b0b6fc9210c91aa', '97b6b97bd197c36c9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', + '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e', + '97b6b7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', + '9778397bd097c36b0b70c9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722', + '7f0e397bd097c35b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', + '7f0e27f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', + '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', + '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', + '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', + '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9274c91aa', + '97b6b7f0e47f531b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', + '9778397bd097c36b0b6fc9210c91aa', '97b6b7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', + '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '977837f0e37f149b0723b0787b0721', + '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c35b0b6fc9210c8dc2', + '977837f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722', + '7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', + '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd', + '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', + '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', + '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', + '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', + '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', + '977837f0e37f14998082b0723b06bd', '7f07e7f0e37f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', + '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721', + '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f595b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5', + '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f531b0b0bb0b6fb0722', + '7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', + '7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd', + '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', + '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', + '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721', + '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd', + '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35', + '7ec967f0e37f14998082b0723b06bd', '7f07e7f0e37f14998083b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', + '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14898082b0723b02d5', '7f07e7f0e37f14998082b0787b0721', + '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66aa89801e9808297c35', '665f67f0e37f14898082b0723b02d5', + '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66a449801e9808297c35', + '665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', + '7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd', + '7f07e7f0e47f531b0723b0b6fb0721', '7f0e26665b66a449801e9808297c35', '665f67f0e37f1489801eb072297c35', + '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722'], + + /** + * 数字转中文速查表 + * @Array Of Property + * @trans ['日','一','二','三','四','五','六','七','八','九','十'] + * @return Cn string + */ + nStr1: ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341'], + + /** + * 日期转农历称呼速查表 + * @Array Of Property + * @trans ['初','十','廿','卅'] + * @return Cn string + */ + nStr2: ['\u521d', '\u5341', '\u5eff', '\u5345'], + + /** + * 月份转农历称呼速查表 + * @Array Of Property + * @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊'] + * @return Cn string + */ + nStr3: ['\u6b63', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341', '\u51ac', '\u814a'], + + /** + * 返回农历y年一整年的总天数 + * @param lunar Year + * @return Number + * @eg:var count = calendar.lYearDays(1987) ;//count=387 + */ + lYearDays: function (y) { + var i; var sum = 348 + for (i = 0x8000; i > 0x8; i >>= 1) { sum += (this.lunarInfo[y - 1900] & i) ? 1 : 0 } + return (sum + this.leapDays(y)) + }, + + /** + * 返回农历y年闰月是哪个月;若y年没有闰月 则返回0 + * @param lunar Year + * @return Number (0-12) + * @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6 + */ + leapMonth: function (y) { // 闰字编码 \u95f0 + return (this.lunarInfo[y - 1900] & 0xf) + }, + + /** + * 返回农历y年闰月的天数 若该年没有闰月则返回0 + * @param lunar Year + * @return Number (0、29、30) + * @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29 + */ + leapDays: function (y) { + if (this.leapMonth(y)) { + return ((this.lunarInfo[y - 1900] & 0x10000) ? 30 : 29) + } + return (0) + }, + + /** + * 返回农历y年m月(非闰月)的总天数,计算m为闰月时的天数请使用leapDays方法 + * @param lunar Year + * @return Number (-1、29、30) + * @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29 + */ + monthDays: function (y, m) { + if (m > 12 || m < 1) { return -1 }// 月份参数从1至12,参数错误返回-1 + return ((this.lunarInfo[y - 1900] & (0x10000 >> m)) ? 30 : 29) + }, + + /** + * 返回公历(!)y年m月的天数 + * @param solar Year + * @return Number (-1、28、29、30、31) + * @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30 + */ + solarDays: function (y, m) { + if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1 + var ms = m - 1 + if (ms == 1) { // 2月份的闰平规律测算后确认返回28或29 + return (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) ? 29 : 28) + } else { + return (this.solarMonth[ms]) + } + }, + + /** + * 农历年份转换为干支纪年 + * @param lYear 农历年的年份数 + * @return Cn string + */ + toGanZhiYear: function (lYear) { + var ganKey = (lYear - 3) % 10 + var zhiKey = (lYear - 3) % 12 + if (ganKey == 0) ganKey = 10// 如果余数为0则为最后一个天干 + if (zhiKey == 0) zhiKey = 12// 如果余数为0则为最后一个地支 + return this.Gan[ganKey - 1] + this.Zhi[zhiKey - 1] + }, + + /** + * 公历月、日判断所属星座 + * @param cMonth [description] + * @param cDay [description] + * @return Cn string + */ + toAstro: function (cMonth, cDay) { + var s = '\u9b54\u7faf\u6c34\u74f6\u53cc\u9c7c\u767d\u7f8a\u91d1\u725b\u53cc\u5b50\u5de8\u87f9\u72ee\u5b50\u5904\u5973\u5929\u79e4\u5929\u874e\u5c04\u624b\u9b54\u7faf' + var arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22] + return s.substr(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2) + '\u5ea7'// 座 + }, + + /** + * 传入offset偏移量返回干支 + * @param offset 相对甲子的偏移量 + * @return Cn string + */ + toGanZhi: function (offset) { + return this.Gan[offset % 10] + this.Zhi[offset % 12] + }, + + /** + * 传入公历(!)y年获得该年第n个节气的公历日期 + * @param y公历年(1900-2100);n二十四节气中的第几个节气(1~24);从n=1(小寒)算起 + * @return day Number + * @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春 + */ + getTerm: function (y, n) { + if (y < 1900 || y > 2100) { return -1 } + if (n < 1 || n > 24) { return -1 } + var _table = this.sTermInfo[y - 1900] + var _info = [ + parseInt('0x' + _table.substr(0, 5)).toString(), + parseInt('0x' + _table.substr(5, 5)).toString(), + parseInt('0x' + _table.substr(10, 5)).toString(), + parseInt('0x' + _table.substr(15, 5)).toString(), + parseInt('0x' + _table.substr(20, 5)).toString(), + parseInt('0x' + _table.substr(25, 5)).toString() + ] + var _calday = [ + _info[0].substr(0, 1), + _info[0].substr(1, 2), + _info[0].substr(3, 1), + _info[0].substr(4, 2), + + _info[1].substr(0, 1), + _info[1].substr(1, 2), + _info[1].substr(3, 1), + _info[1].substr(4, 2), + + _info[2].substr(0, 1), + _info[2].substr(1, 2), + _info[2].substr(3, 1), + _info[2].substr(4, 2), + + _info[3].substr(0, 1), + _info[3].substr(1, 2), + _info[3].substr(3, 1), + _info[3].substr(4, 2), + + _info[4].substr(0, 1), + _info[4].substr(1, 2), + _info[4].substr(3, 1), + _info[4].substr(4, 2), + + _info[5].substr(0, 1), + _info[5].substr(1, 2), + _info[5].substr(3, 1), + _info[5].substr(4, 2) + ] + return parseInt(_calday[n - 1]) + }, + + /** + * 传入农历数字月份返回汉语通俗表示法 + * @param lunar month + * @return Cn string + * @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月' + */ + toChinaMonth: function (m) { // 月 => \u6708 + if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1 + var s = this.nStr3[m - 1] + s += '\u6708'// 加上月字 + return s + }, + + /** + * 传入农历日期数字返回汉字表示法 + * @param lunar day + * @return Cn string + * @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一' + */ + toChinaDay: function (d) { // 日 => \u65e5 + var s + switch (d) { + case 10: + s = '\u521d\u5341'; break + case 20: + s = '\u4e8c\u5341'; break + break + case 30: + s = '\u4e09\u5341'; break + break + default : + s = this.nStr2[Math.floor(d / 10)] + s += this.nStr1[d % 10] + } + return (s) + }, + + /** + * 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春” + * @param y year + * @return Cn string + * @eg:var animal = calendar.getAnimal(1987) ;//animal='兔' + */ + getAnimal: function (y) { + return this.Animals[(y - 4) % 12] + }, + + /** + * 传入阳历年月日获得详细的公历、农历object信息 <=>JSON + * @param y solar year + * @param m solar month + * @param d solar day + * @return JSON object + * @eg:console.log(calendar.solar2lunar(1987,11,01)); + */ + solar2lunar: function (y, m, d) { // 参数区间1900.1.31~2100.12.31 + // 年份限定、上限 + if (y < 1900 || y > 2100) { + return -1// undefined转换为数字变为NaN + } + // 公历传参最下限 + if (y == 1900 && m == 1 && d < 31) { + return -1 + } + // 未传参 获得当天 + if (!y) { + var objDate = new Date() + } else { + var objDate = new Date(y, parseInt(m) - 1, d) + } + var i; var leap = 0; var temp = 0 + // 修正ymd参数 + var y = objDate.getFullYear() + var m = objDate.getMonth() + 1 + var d = objDate.getDate() + var offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 31)) / 86400000 + for (i = 1900; i < 2101 && offset > 0; i++) { + temp = this.lYearDays(i) + offset -= temp + } + if (offset < 0) { + offset += temp; i-- + } + + // 是否今天 + var isTodayObj = new Date() + var isToday = false + if (isTodayObj.getFullYear() == y && isTodayObj.getMonth() + 1 == m && isTodayObj.getDate() == d) { + isToday = true + } + // 星期几 + var nWeek = objDate.getDay() + var cWeek = this.nStr1[nWeek] + // 数字表示周几顺应天朝周一开始的惯例 + if (nWeek == 0) { + nWeek = 7 + } + // 农历年 + var year = i + var leap = this.leapMonth(i) // 闰哪个月 + var isLeap = false + + // 效验闰月 + for (i = 1; i < 13 && offset > 0; i++) { + // 闰月 + if (leap > 0 && i == (leap + 1) && isLeap == false) { + --i + isLeap = true; temp = this.leapDays(year) // 计算农历闰月天数 + } else { + temp = this.monthDays(year, i)// 计算农历普通月天数 + } + // 解除闰月 + if (isLeap == true && i == (leap + 1)) { isLeap = false } + offset -= temp + } + // 闰月导致数组下标重叠取反 + if (offset == 0 && leap > 0 && i == leap + 1) { + if (isLeap) { + isLeap = false + } else { + isLeap = true; --i + } + } + if (offset < 0) { + offset += temp; --i + } + // 农历月 + var month = i + // 农历日 + var day = offset + 1 + // 天干地支处理 + var sm = m - 1 + var gzY = this.toGanZhiYear(year) + + // 当月的两个节气 + // bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year` + var firstNode = this.getTerm(y, (m * 2 - 1))// 返回当月「节」为几日开始 + var secondNode = this.getTerm(y, (m * 2))// 返回当月「节」为几日开始 + + // 依据12节气修正干支月 + var gzM = this.toGanZhi((y - 1900) * 12 + m + 11) + if (d >= firstNode) { + gzM = this.toGanZhi((y - 1900) * 12 + m + 12) + } + + // 传入的日期的节气与否 + var isTerm = false + var Term = null + if (firstNode == d) { + isTerm = true + Term = this.solarTerm[m * 2 - 2] + } + if (secondNode == d) { + isTerm = true + Term = this.solarTerm[m * 2 - 1] + } + // 日柱 当月一日与 1900/1/1 相差天数 + var dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10 + var gzD = this.toGanZhi(dayCyclical + d - 1) + // 该日期所属的星座 + var astro = this.toAstro(m, d) + + return { 'lYear': year, 'lMonth': month, 'lDay': day, 'Animal': this.getAnimal(year), 'IMonthCn': (isLeap ? '\u95f0' : '') + this.toChinaMonth(month), 'IDayCn': this.toChinaDay(day), 'cYear': y, 'cMonth': m, 'cDay': d, 'gzYear': gzY, 'gzMonth': gzM, 'gzDay': gzD, 'isToday': isToday, 'isLeap': isLeap, 'nWeek': nWeek, 'ncWeek': '\u661f\u671f' + cWeek, 'isTerm': isTerm, 'Term': Term, 'astro': astro } + }, + + /** + * 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON + * @param y lunar year + * @param m lunar month + * @param d lunar day + * @param isLeapMonth lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可] + * @return JSON object + * @eg:console.log(calendar.lunar2solar(1987,9,10)); + */ + lunar2solar: function (y, m, d, isLeapMonth) { // 参数区间1900.1.31~2100.12.1 + var isLeapMonth = !!isLeapMonth + var leapOffset = 0 + var leapMonth = this.leapMonth(y) + var leapDay = this.leapDays(y) + if (isLeapMonth && (leapMonth != m)) { return -1 }// 传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同 + if (y == 2100 && m == 12 && d > 1 || y == 1900 && m == 1 && d < 31) { return -1 }// 超出了最大极限值 + var day = this.monthDays(y, m) + var _day = day + // bugFix 2016-9-25 + // if month is leap, _day use leapDays method + if (isLeapMonth) { + _day = this.leapDays(y, m) + } + if (y < 1900 || y > 2100 || d > _day) { return -1 }// 参数合法性效验 + + // 计算农历的时间差 + var offset = 0 + for (var i = 1900; i < y; i++) { + offset += this.lYearDays(i) + } + var leap = 0; var isAdd = false + for (var i = 1; i < m; i++) { + leap = this.leapMonth(y) + if (!isAdd) { // 处理闰月 + if (leap <= i && leap > 0) { + offset += this.leapDays(y); isAdd = true + } + } + offset += this.monthDays(y, i) + } + // 转换闰月农历 需补充该年闰月的前一个月的时差 + if (isLeapMonth) { offset += day } + // 1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点) + var stmap = Date.UTC(1900, 1, 30, 0, 0, 0) + var calObj = new Date((offset + d - 31) * 86400000 + stmap) + var cY = calObj.getUTCFullYear() + var cM = calObj.getUTCMonth() + 1 + var cD = calObj.getUTCDate() + + return this.solar2lunar(cY, cM, cD) + } +} + +export default calendar diff --git a/hive-app/components/uni-calendar/uni-calendar-item.vue b/hive-app/components/uni-calendar/uni-calendar-item.vue new file mode 100644 index 0000000..c66a58d --- /dev/null +++ b/hive-app/components/uni-calendar/uni-calendar-item.vue @@ -0,0 +1,170 @@ +<template> + <view class="uni-calendar-item__weeks-box" :class="{ + 'uni-calendar-item--disable':weeks.disable, + 'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay, + 'uni-calendar-item--checked':(calendar.fullDate === weeks.fullDate && !weeks.isDay) , + 'uni-calendar-item--before-checked':weeks.beforeMultiple, + 'uni-calendar-item--multiple': weeks.multiple, + 'uni-calendar-item--after-checked':weeks.afterMultiple, + }" + @click="choiceDate(weeks)"> + <view class="uni-calendar-item__weeks-box-item"> + <text v-if="selected&&weeks.extraInfo" class="uni-calendar-item__weeks-box-circle"></text> + <text class="uni-calendar-item__weeks-box-text" :class="{ + 'uni-calendar-item--isDay-text': weeks.isDay, + 'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay, + 'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay, + 'uni-calendar-item--before-checked':weeks.beforeMultiple, + 'uni-calendar-item--multiple': weeks.multiple, + 'uni-calendar-item--after-checked':weeks.afterMultiple, + 'uni-calendar-item--disable':weeks.disable, + }">{{weeks.date}}</text> + <text v-if="!lunar&&!weeks.extraInfo && weeks.isDay" class="uni-calendar-item__weeks-lunar-text" :class="{ + 'uni-calendar-item--isDay-text':weeks.isDay, + 'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay, + 'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay, + 'uni-calendar-item--before-checked':weeks.beforeMultiple, + 'uni-calendar-item--multiple': weeks.multiple, + 'uni-calendar-item--after-checked':weeks.afterMultiple, + }">今天</text> + <text v-if="lunar&&!weeks.extraInfo" class="uni-calendar-item__weeks-lunar-text" :class="{ + 'uni-calendar-item--isDay-text':weeks.isDay, + 'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay, + 'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay, + 'uni-calendar-item--before-checked':weeks.beforeMultiple, + 'uni-calendar-item--multiple': weeks.multiple, + 'uni-calendar-item--after-checked':weeks.afterMultiple, + 'uni-calendar-item--disable':weeks.disable, + }">{{weeks.isDay?'今天': (weeks.lunar.IDayCn === '初一'?weeks.lunar.IMonthCn:weeks.lunar.IDayCn)}}</text> + <text v-if="weeks.extraInfo&&weeks.extraInfo.info" class="uni-calendar-item__weeks-lunar-text" :class="{ + 'uni-calendar-item--extra':weeks.extraInfo.info, + 'uni-calendar-item--isDay-text':weeks.isDay, + 'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay, + 'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay, + 'uni-calendar-item--before-checked':weeks.beforeMultiple, + 'uni-calendar-item--multiple': weeks.multiple, + 'uni-calendar-item--after-checked':weeks.afterMultiple, + 'uni-calendar-item--disable':weeks.disable, + }">{{weeks.extraInfo.info}}</text> + </view> + </view> +</template> + +<script> + export default { + props: { + weeks: { + type: Object, + default () { + return {} + } + }, + calendar: { + type: Object, + default: () => { + return {} + } + }, + selected: { + type: Array, + default: () => { + return [] + } + }, + lunar: { + type: Boolean, + default: false + } + }, + methods: { + choiceDate(weeks) { + this.$emit('change', weeks) + } + } + } +</script> + +<style lang="scss" scoped> + .uni-calendar-item__weeks-box { + flex: 1; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; + justify-content: center; + align-items: center; + } + + .uni-calendar-item__weeks-box-text { + font-size: $uni-font-size-base; + color: $uni-text-color; + } + + .uni-calendar-item__weeks-lunar-text { + font-size: $uni-font-size-sm; + color: $uni-text-color; + } + + .uni-calendar-item__weeks-box-item { + position: relative; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; + justify-content: center; + align-items: center; + width: 100rpx; + height: 100rpx; + } + + .uni-calendar-item__weeks-box-circle { + position: absolute; + top: 5px; + right: 5px; + width: 8px; + height: 8px; + border-radius: 8px; + background-color: $uni-color-error; + + } + + .uni-calendar-item--disable { + background-color: rgba(249, 249, 249, $uni-opacity-disabled); + color: $uni-text-color-disable; + } + + .uni-calendar-item--isDay-text { + color: $uni-color-primary; + } + + .uni-calendar-item--isDay { + background-color: $uni-color-primary; + opacity: 0.8; + color: #fff; + } + + .uni-calendar-item--extra { + color: $uni-color-error; + opacity: 0.8; + } + + .uni-calendar-item--checked { + background-color: $uni-color-primary; + color: #fff; + opacity: 0.8; + } + + .uni-calendar-item--multiple { + background-color: $uni-color-primary; + color: #fff; + opacity: 0.8; + } + .uni-calendar-item--before-checked { + background-color: #ff5a5f; + color: #fff; + } + .uni-calendar-item--after-checked { + background-color: #ff5a5f; + color: #fff; + } +</style> diff --git a/hive-app/components/uni-calendar/uni-calendar.vue b/hive-app/components/uni-calendar/uni-calendar.vue new file mode 100644 index 0000000..9c856cc --- /dev/null +++ b/hive-app/components/uni-calendar/uni-calendar.vue @@ -0,0 +1,505 @@ +<template> + <view class="uni-calendar"> + <view v-if="!insert&&show" class="uni-calendar__mask" :class="{'uni-calendar--mask-show':aniMaskShow}" @click="clean"></view> + <view v-if="insert || show" class="uni-calendar__content" :class="{'uni-calendar--fixed':!insert,'uni-calendar--ani-show':aniMaskShow}"> + <view v-if="!insert" class="uni-calendar__header uni-calendar--fixed-top"> + <view class="uni-calendar__header-btn-box" @click="close"> + <text class="uni-calendar__header-text uni-calendar--fixed-width">取消</text> + </view> + <view class="uni-calendar__header-btn-box" @click="confirm"> + <text class="uni-calendar__header-text uni-calendar--fixed-width">确定</text> + </view> + </view> + <view class="uni-calendar__header"> + <view class="uni-calendar__header-btn-box" @click.stop="pre"> + <view class="uni-calendar__header-btn uni-calendar--left"></view> + </view> + <picker mode="date" :value="date" fields="month" @change="bindDateChange"> + <text class="uni-calendar__header-text">{{ (nowDate.year||'') +'年'+( nowDate.month||'') +'月'}}</text> + </picker> + <view class="uni-calendar__header-btn-box" @click.stop="next"> + <view class="uni-calendar__header-btn uni-calendar--right"></view> + </view> + <text class="uni-calendar__backtoday" @click="backtoday">回到今天</text> + + </view> + <view class="uni-calendar__box"> + <view v-if="showMonth" class="uni-calendar__box-bg"> + <text class="uni-calendar__box-bg-text">{{nowDate.month}}</text> + </view> + <view class="uni-calendar__weeks"> + <view class="uni-calendar__weeks-day"> + <text class="uni-calendar__weeks-day-text">日</text> + </view> + <view class="uni-calendar__weeks-day"> + <text class="uni-calendar__weeks-day-text">一</text> + </view> + <view class="uni-calendar__weeks-day"> + <text class="uni-calendar__weeks-day-text">二</text> + </view> + <view class="uni-calendar__weeks-day"> + <text class="uni-calendar__weeks-day-text">三</text> + </view> + <view class="uni-calendar__weeks-day"> + <text class="uni-calendar__weeks-day-text">四</text> + </view> + <view class="uni-calendar__weeks-day"> + <text class="uni-calendar__weeks-day-text">五</text> + </view> + <view class="uni-calendar__weeks-day"> + <text class="uni-calendar__weeks-day-text">六</text> + </view> + </view> + <view class="uni-calendar__weeks" v-for="(item,weekIndex) in weeks" :key="weekIndex"> + <view class="uni-calendar__weeks-item" v-for="(weeks,weeksIndex) in item" :key="weeksIndex"> + <calendar-item class="uni-calendar-item--hook" :weeks="weeks" :calendar="calendar" :selected="selected" :lunar="lunar" @change="choiceDate"></calendar-item> + </view> + </view> + </view> + </view> + </view> +</template> + +<script> + import Calendar from './util.js'; + import calendarItem from './uni-calendar-item.vue' + /** + * Calendar 日历 + * @description 日历组件可以查看日期,选择任意范围内的日期,打点操作。常用场景如:酒店日期预订、火车机票选择购买日期、上下班打卡等 + * @tutorial https://ext.dcloud.net.cn/plugin?id=56 + * @property {String} date 自定义当前时间,默认为今天 + * @property {Boolean} lunar 显示农历 + * @property {String} startDate 日期选择范围-开始日期 + * @property {String} endDate 日期选择范围-结束日期 + * @property {Boolean} range 范围选择 + * @property {Boolean} insert = [true|false] 插入模式,默认为false + * @value true 弹窗模式 + * @value false 插入模式 + * @property {Boolean} clearDate = [true|false] 弹窗模式是否清空上次选择内容 + * @property {Array} selected 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}] + * @property {Boolean} showMonth 是否选择月份为背景 + * @event {Function} change 日期改变,`insert :ture` 时生效 + * @event {Function} confirm 确认选择`insert :false` 时生效 + * @event {Function} monthSwitch 切换月份时触发 + * @example <uni-calendar :insert="true":lunar="true" :start-date="'2019-3-2'":end-date="'2019-5-20'"@change="change" /> + */ + export default { + components: { + calendarItem + }, + props: { + date: { + type: String, + default: '' + }, + selected: { + type: Array, + default () { + return [] + } + }, + lunar: { + type: Boolean, + default: false + }, + startDate: { + type: String, + default: '' + }, + endDate: { + type: String, + default: '' + }, + range: { + type: Boolean, + default: false + }, + insert: { + type: Boolean, + default: true + }, + showMonth: { + type: Boolean, + default: true + }, + clearDate: { + type: Boolean, + default: true + } + }, + data() { + return { + show: false, + weeks: [], + calendar: {}, + nowDate: '', + aniMaskShow: false + } + }, + watch: { + date(newVal) { + // this.cale.setDate(newVal) + this.init(newVal) + }, + startDate(val){ + this.cale.resetSatrtDate(val) + }, + endDate(val){ + this.cale.resetEndDate(val) + }, + selected(newVal) { + this.cale.setSelectInfo(this.nowDate.fullDate, newVal) + this.weeks = this.cale.weeks + } + }, + created() { + // 获取日历方法实例 + this.cale = new Calendar({ + // date: new Date(), + selected: this.selected, + startDate: this.startDate, + endDate: this.endDate, + range: this.range, + }) + // 选中某一天 + // this.cale.setDate(this.date) + this.init(this.date) + // this.setDay + }, + methods: { + // 取消穿透 + clean() {}, + bindDateChange(e) { + const value = e.detail.value + '-1' + console.log(this.cale.getDate(value)); + this.init(value) + }, + /** + * 初始化日期显示 + * @param {Object} date + */ + init(date) { + this.cale.setDate(date) + this.weeks = this.cale.weeks + this.nowDate = this.calendar = this.cale.getInfo(date) + }, + /** + * 打开日历弹窗 + */ + open() { + // 弹窗模式并且清理数据 + if (this.clearDate && !this.insert) { + this.cale.cleanMultipleStatus() + // this.cale.setDate(this.date) + this.init(this.date) + } + this.show = true + this.$nextTick(() => { + setTimeout(() => { + this.aniMaskShow = true + }, 50) + }) + }, + /** + * 关闭日历弹窗 + */ + close() { + this.aniMaskShow = false + this.$nextTick(() => { + setTimeout(() => { + this.show = false + this.$emit('close') + }, 300) + }) + }, + /** + * 确认按钮 + */ + confirm() { + this.setEmit('confirm') + this.close() + }, + /** + * 变化触发 + */ + change() { + if (!this.insert) return + this.setEmit('change') + }, + /** + * 选择月份触发 + */ + monthSwitch() { + let { + year, + month + } = this.nowDate + this.$emit('monthSwitch', { + year, + month: Number(month) + }) + }, + /** + * 派发事件 + * @param {Object} name + */ + setEmit(name) { + let { + year, + month, + date, + fullDate, + lunar, + extraInfo + } = this.calendar + this.$emit(name, { + range: this.cale.multipleStatus, + year, + month, + date, + fulldate: fullDate, + lunar, + extraInfo: extraInfo || {} + }) + }, + /** + * 选择天触发 + * @param {Object} weeks + */ + choiceDate(weeks) { + if (weeks.disable) return + this.calendar = weeks + // 设置多选 + this.cale.setMultiple(this.calendar.fullDate) + this.weeks = this.cale.weeks + this.change() + }, + /** + * 回到今天 + */ + backtoday() { + console.log(this.cale.getDate(new Date()).fullDate); + let date = this.cale.getDate(new Date()).fullDate + // this.cale.setDate(date) + this.init(date) + this.change() + }, + /** + * 上个月 + */ + pre() { + const preDate = this.cale.getDate(this.nowDate.fullDate, -1, 'month').fullDate + this.setDate(preDate) + this.monthSwitch() + + }, + /** + * 下个月 + */ + next() { + const nextDate = this.cale.getDate(this.nowDate.fullDate, +1, 'month').fullDate + this.setDate(nextDate) + this.monthSwitch() + }, + /** + * 设置日期 + * @param {Object} date + */ + setDate(date) { + this.cale.setDate(date) + this.weeks = this.cale.weeks + this.nowDate = this.cale.getInfo(date) + } + } + } +</script> + +<style lang="scss" scoped> + .uni-calendar { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; + } + + .uni-calendar__mask { + position: fixed; + bottom: 0; + top: 0; + left: 0; + right: 0; + background-color: $uni-bg-color-mask; + transition-property: opacity; + transition-duration: 0.3s; + opacity: 0; + /* #ifndef APP-NVUE */ + z-index: 99; + /* #endif */ + } + + .uni-calendar--mask-show { + opacity: 1 + } + + .uni-calendar--fixed { + position: fixed; + bottom: 0; + left: 0; + right: 0; + transition-property: transform; + transition-duration: 0.3s; + transform: translateY(460px); + /* #ifndef APP-NVUE */ + z-index: 99; + /* #endif */ + } + + .uni-calendar--ani-show { + transform: translateY(0); + } + + .uni-calendar__content { + background-color: #fff; + } + + .uni-calendar__header { + position: relative; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + justify-content: center; + align-items: center; + height: 50px; + border-bottom-color: $uni-border-color; + border-bottom-style: solid; + border-bottom-width: 1px; + } + + .uni-calendar--fixed-top { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + justify-content: space-between; + border-top-color: $uni-border-color; + border-top-style: solid; + border-top-width: 1px; + } + + .uni-calendar--fixed-width { + width: 50px; + // padding: 0 15px; + } + + .uni-calendar__backtoday { + position: absolute; + right: 0; + top: 25rpx; + padding: 0 5px; + padding-left: 10px; + height: 25px; + line-height: 25px; + font-size: 12px; + border-top-left-radius: 25px; + border-bottom-left-radius: 25px; + color: $uni-text-color; + background-color: $uni-bg-color-hover; + } + + .uni-calendar__header-text { + text-align: center; + width: 100px; + font-size: $uni-font-size-base; + color: $uni-text-color; + } + + .uni-calendar__header-btn-box { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + align-items: center; + justify-content: center; + width: 50px; + height: 50px; + } + + .uni-calendar__header-btn { + width: 10px; + height: 10px; + border-left-color: $uni-text-color-placeholder; + border-left-style: solid; + border-left-width: 2px; + border-top-color: $uni-color-subtitle; + border-top-style: solid; + border-top-width: 2px; + } + + .uni-calendar--left { + transform: rotate(-45deg); + } + + .uni-calendar--right { + transform: rotate(135deg); + } + + + .uni-calendar__weeks { + position: relative; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + } + + .uni-calendar__weeks-item { + flex: 1; + } + + .uni-calendar__weeks-day { + flex: 1; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; + justify-content: center; + align-items: center; + height: 45px; + border-bottom-color: #F5F5F5; + border-bottom-style: solid; + border-bottom-width: 1px; + } + + .uni-calendar__weeks-day-text { + font-size: 14px; + } + + .uni-calendar__box { + position: relative; + } + + .uni-calendar__box-bg { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + justify-content: center; + align-items: center; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + } + + .uni-calendar__box-bg-text { + font-size: 200px; + font-weight: bold; + color: $uni-text-color-grey; + opacity: 0.1; + text-align: center; + /* #ifndef APP-NVUE */ + line-height: 1; + /* #endif */ + } +</style> diff --git a/hive-app/components/uni-calendar/util.js b/hive-app/components/uni-calendar/util.js new file mode 100644 index 0000000..37f4432 --- /dev/null +++ b/hive-app/components/uni-calendar/util.js @@ -0,0 +1,352 @@ +import CALENDAR from './calendar.js' + +class Calendar { + constructor({ + date, + selected, + startDate, + endDate, + range + } = {}) { + // 当前日期 + this.date = this.getDate(new Date()) // 当前初入日期 + // 打点信息 + this.selected = selected || []; + // 范围开始 + this.startDate = startDate + // 范围结束 + this.endDate = endDate + this.range = range + // 多选状态 + this.cleanMultipleStatus() + // 每周日期 + this.weeks = {} + // this._getWeek(this.date.fullDate) + } + /** + * 设置日期 + * @param {Object} date + */ + setDate(date) { + this.selectDate = this.getDate(date) + this._getWeek(this.selectDate.fullDate) + } + + /** + * 清理多选状态 + */ + cleanMultipleStatus() { + this.multipleStatus = { + before: '', + after: '', + data: [] + } + } + + /** + * 重置开始日期 + */ + resetSatrtDate(startDate) { + // 范围开始 + this.startDate = startDate + + } + + /** + * 重置结束日期 + */ + resetEndDate(endDate) { + // 范围结束 + this.endDate = endDate + } + + /** + * 获取任意时间 + */ + getDate(date, AddDayCount = 0, str = 'day') { + if (!date) { + date = new Date() + } + if (typeof date !== 'object') { + date = date.replace(/-/g, '/') + } + const dd = new Date(date) + switch (str) { + case 'day': + dd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期 + break + case 'month': + if (dd.getDate() === 31) { + dd.setDate(dd.getDate() + AddDayCount) + } else { + dd.setMonth(dd.getMonth() + AddDayCount) // 获取AddDayCount天后的日期 + } + break + case 'year': + dd.setFullYear(dd.getFullYear() + AddDayCount) // 获取AddDayCount天后的日期 + break + } + const y = dd.getFullYear() + const m = dd.getMonth() + 1 < 10 ? '0' + (dd.getMonth() + 1) : dd.getMonth() + 1 // 获取当前月份的日期,不足10补0 + const d = dd.getDate() < 10 ? '0' + dd.getDate() : dd.getDate() // 获取当前几号,不足10补0 + return { + fullDate: y + '-' + m + '-' + d, + year: y, + month: m, + date: d, + day: dd.getDay() + } + } + + + /** + * 获取上月剩余天数 + */ + _getLastMonthDays(firstDay, full) { + let dateArr = [] + for (let i = firstDay; i > 0; i--) { + const beforeDate = new Date(full.year, full.month - 1, -i + 1).getDate() + dateArr.push({ + date: beforeDate, + month: full.month - 1, + lunar: this.getlunar(full.year, full.month - 1, beforeDate), + disable: true + }) + } + return dateArr + } + /** + * 获取本月天数 + */ + _currentMonthDys(dateData, full) { + let dateArr = [] + let fullDate = this.date.fullDate + for (let i = 1; i <= dateData; i++) { + let isinfo = false + let nowDate = full.year + '-' + (full.month < 10 ? + full.month : full.month) + '-' + (i < 10 ? + '0' + i : i) + // 是否今天 + let isDay = fullDate === nowDate + // 获取打点信息 + let info = this.selected && this.selected.find((item) => { + if (this.dateEqual(nowDate, item.date)) { + return item + } + }) + + // 日期禁用 + let disableBefore = true + let disableAfter = true + if (this.startDate) { + let dateCompBefore = this.dateCompare(this.startDate, fullDate) + disableBefore = this.dateCompare(dateCompBefore ? this.startDate : fullDate, nowDate) + } + + if (this.endDate) { + let dateCompAfter = this.dateCompare(fullDate, this.endDate) + disableAfter = this.dateCompare(nowDate, dateCompAfter ? this.endDate : fullDate) + } + let multiples = this.multipleStatus.data + let checked = false + let multiplesStatus = -1 + if (this.range) { + if (multiples) { + multiplesStatus = multiples.findIndex((item) => { + return this.dateEqual(item, nowDate) + }) + } + if (multiplesStatus !== -1) { + checked = true + } + } + let data = { + fullDate: nowDate, + year: full.year, + date: i, + multiple: this.range ? checked : false, + beforeMultiple: this.dateEqual(this.multipleStatus.before, nowDate), + afterMultiple: this.dateEqual(this.multipleStatus.after, nowDate), + month: full.month, + lunar: this.getlunar(full.year, full.month, i), + disable: !disableBefore || !disableAfter, + isDay + } + if (info) { + data.extraInfo = info + } + + dateArr.push(data) + } + return dateArr + } + /** + * 获取下月天数 + */ + _getNextMonthDays(surplus, full) { + let dateArr = [] + for (let i = 1; i < surplus + 1; i++) { + dateArr.push({ + date: i, + month: Number(full.month) + 1, + lunar: this.getlunar(full.year, Number(full.month) + 1, i), + disable: true + }) + } + return dateArr + } + + /** + * 获取当前日期详情 + * @param {Object} date + */ + getInfo(date) { + if (!date) { + date = new Date() + } + const dateInfo = this.canlender.find(item => item.fullDate === this.getDate(date).fullDate) + return dateInfo + } + + /** + * 比较时间大小 + */ + dateCompare(startDate, endDate) { + // 计算截止时间 + startDate = new Date(startDate.replace('-', '/').replace('-', '/')) + // 计算详细项的截止时间 + endDate = new Date(endDate.replace('-', '/').replace('-', '/')) + if (startDate <= endDate) { + return true + } else { + return false + } + } + + /** + * 比较时间是否相等 + */ + dateEqual(before, after) { + // 计算截止时间 + before = new Date(before.replace('-', '/').replace('-', '/')) + // 计算详细项的截止时间 + after = new Date(after.replace('-', '/').replace('-', '/')) + if (before.getTime() - after.getTime() === 0) { + return true + } else { + return false + } + } + + + /** + * 获取日期范围内所有日期 + * @param {Object} begin + * @param {Object} end + */ + geDateAll(begin, end) { + var arr = [] + var ab = begin.split('-') + var ae = end.split('-') + var db = new Date() + db.setFullYear(ab[0], ab[1] - 1, ab[2]) + var de = new Date() + de.setFullYear(ae[0], ae[1] - 1, ae[2]) + var unixDb = db.getTime() - 24 * 60 * 60 * 1000 + var unixDe = de.getTime() - 24 * 60 * 60 * 1000 + for (var k = unixDb; k <= unixDe;) { + k = k + 24 * 60 * 60 * 1000 + arr.push(this.getDate(new Date(parseInt(k))).fullDate) + } + return arr + } + /** + * 计算阴历日期显示 + */ + getlunar(year, month, date) { + return CALENDAR.solar2lunar(year, month, date) + } + /** + * 设置打点 + */ + setSelectInfo(data, value) { + this.selected = value + this._getWeek(data) + } + + /** + * 获取多选状态 + */ + setMultiple(fullDate) { + let { + before, + after + } = this.multipleStatus + + if (!this.range) return + if (before && after) { + this.multipleStatus.before = '' + this.multipleStatus.after = '' + this.multipleStatus.data = [] + } else { + if (!before) { + this.multipleStatus.before = fullDate + } else { + this.multipleStatus.after = fullDate + if (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) { + this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus.after); + } else { + this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus.before); + } + } + } + this._getWeek(fullDate) + } + + /** + * 获取每周数据 + * @param {Object} dateData + */ + _getWeek(dateData) { + const { + fullDate, + year, + month, + date, + day + } = this.getDate(dateData) + let firstDay = new Date(year, month - 1, 1).getDay() + let currentDay = new Date(year, month, 0).getDate() + let dates = { + lastMonthDays: this._getLastMonthDays(firstDay, this.getDate(dateData)), // 上个月末尾几天 + currentMonthDys: this._currentMonthDys(currentDay, this.getDate(dateData)), // 本月天数 + nextMonthDays: [], // 下个月开始几天 + weeks: [] + } + let canlender = [] + const surplus = 42 - (dates.lastMonthDays.length + dates.currentMonthDys.length) + dates.nextMonthDays = this._getNextMonthDays(surplus, this.getDate(dateData)) + canlender = canlender.concat(dates.lastMonthDays, dates.currentMonthDys, dates.nextMonthDays) + let weeks = {} + // 拼接数组 上个月开始几天 + 本月天数+ 下个月开始几天 + for (let i = 0; i < canlender.length; i++) { + if (i % 7 === 0) { + weeks[parseInt(i / 7)] = new Array(7) + } + weeks[parseInt(i / 7)][i % 7] = canlender[i] + } + this.canlender = canlender + this.weeks = weeks + } + + //静态方法 + // static init(date) { + // if (!this.instance) { + // this.instance = new Calendar(date); + // } + // return this.instance; + // } +} + + +export default Calendar diff --git a/hive-app/components/uni-card/uni-card.vue b/hive-app/components/uni-card/uni-card.vue new file mode 100644 index 0000000..fdebf7e --- /dev/null +++ b/hive-app/components/uni-card/uni-card.vue @@ -0,0 +1,403 @@ +<template> + <view class="uni-card uni-border" :class="{ 'uni-card--full': isFull === true || isFull === 'true', 'uni-card--shadow': isShadow === true || isShadow === 'true'}"> + <!-- 基础 --> + <view v-if="mode === 'basic' && title" class="uni-card__header uni-border-bottom" @click.stop="onClick"> + <view v-if="thumbnail" class="uni-card__header-extra-img-view"> + <image :src="thumbnail" class="uni-card__header-extra-img" /> + </view> + <text class="uni-card__header-title-text">{{ title }}</text> + <text v-if="extra" class="uni-card__header-extra-text">{{ extra }}</text> + </view> + <!-- 标题 --> + <view v-if="mode === 'title'" class="uni-card__title uni-border-bottom" @click.stop="onClick"> + <view class="uni-card__title-box"> + <view class="uni-card__title-header"> + <image class="uni-card__title-header-image" :src="thumbnail" mode="scaleToFill" /> + </view> + <view class="uni-card__title-content"> + <text class="uni-card__title-content-title uni-ellipsis">{{ title }}</text> + <text class="uni-card__title-content-extra uni-ellipsis">{{ subTitle }}</text> + </view> + </view> + <view v-if="extra"> + <text class="uni-card__header-extra-text">{{ extra }}</text> + </view> + </view> + <!-- 图文 --> + <view v-if="mode === 'style'" class="uni-card__thumbnailimage" @click.stop="onClick"> + <view class="uni-card__thumbnailimage-box"> + <image class="uni-card__thumbnailimage-image" :src="thumbnail" mode="aspectFill" /> + </view> + <view v-if="title" class="uni-card__thumbnailimage-title"><text class="uni-card__thumbnailimage-title-text">{{ title }}</text></view> + </view> + <!-- 内容 --> + <view class="uni-card__content uni-card__content--pd" @click.stop="onClick"> + <view v-if="mode === 'style' && extra" class=""><text class="uni-card__content-extra">{{ extra }}</text></view> + <slot /> + </view> + <!-- 底部 --> + <view v-if="note" class="uni-card__footer uni-border-top"> + <slot name="footer"> + <text class="uni-card__footer-text">{{ note }}</text> + </slot> + </view> + </view> +</template> + +<script> + /** + * Card 卡片 + * @description 卡片视图组件 + * @tutorial https://ext.dcloud.net.cn/plugin?id=22 + * @property {String} title 标题文字 + * @property {String} subTitle 副标题(仅仅mode=title下生效) + * @property {String} extra 标题额外信息 + * @property {String} note 标题左侧缩略图 + * @property {String} thumbnail 底部信息 + * @property {String} mode = [basic|style|title] 卡片模式 + * @value basic 基础卡片 + * @value style 图文卡片 + * @value title 标题卡片 + * @property {Boolean} isFull = [true | false] 卡片内容是否通栏,为 true 时将去除padding值 + * @property {Boolean} isShadow = [true | false] 卡片内容是否开启阴影 + * @event {Function} click 点击 Card 触发事件 + * @example <uni-card title="标题文字" thumbnail="https://img-cdn-qiniu.dcloud.net.cn/new-page/uni.png" extra="额外信息" note="Tips">内容主体,可自定义内容及样式</uni-card> + */ + export default { + name: 'UniCard', + props: { + title: { + type: String, + default: '' + }, + subTitle: { + type: String, + default: '' + }, + extra: { + type: String, + default: '' + }, + note: { + type: String, + default: '' + }, + thumbnail: { + type: String, + default: '' + }, + mode: { + type: String, + default: 'basic' + }, + isFull: { + // 内容区域是否通栏 + type: Boolean, + default: false + }, + isShadow: { + // 是否开启阴影 + type: [Boolean, String], + default: false + } + }, + methods: { + onClick() { + this.$emit('click') + } + } + } +</script> + +<style lang="scss" scoped> + .uni-card { + /* #ifndef APP-NVUE */ + display: flex; + flex: 1; + box-shadow: 0 0 0 rgba(0, 0, 0, 0); + /* #endif */ + margin: $uni-spacing-col-lg $uni-spacing-row-lg; + background-color: $uni-bg-color; + position: relative; + flex-direction: column; + border-radius: 5px; + overflow: hidden; + } + + + + .uni-border { + position: relative; + /* #ifdef APP-NVUE */ + border-color: $uni-border-color; + border-style: solid; + border-width: 0.5px; + /* #endif */ + z-index: 1; + } + + /* #ifndef APP-NVUE */ + .uni-border:after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + top: 0; + right: 0; + border: 1px solid $uni-border-color; + border-radius: 10px; + box-sizing: border-box; + width: 200%; + height: 200%; + transform: scale(0.5); + transform-origin: left top; + z-index: -1; + } + + /* #endif */ + + .uni-border-bottom { + position: relative; + /* #ifdef APP-NVUE */ + border-bottom-color: $uni-border-color; + border-bottom-style: solid; + border-bottom-width: 0.5px; + /* #endif */ + z-index: 1; + } + + /* #ifndef APP-NVUE */ + .uni-border-bottom:after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + top: 0; + right: 0; + border-bottom: 1px solid $uni-border-color; + box-sizing: border-box; + width: 200%; + height: 200%; + transform: scale(0.5); + transform-origin: left top; + z-index: -1; + } + + /* #endif */ + .uni-border-top { + position: relative; + /* #ifdef APP-NVUE */ + border-top-color: $uni-border-color; + border-top-style: solid; + border-top-width: 0.5px; + /* #endif */ + z-index: 1; + } + + /* #ifndef APP-NVUE */ + .uni-border-top:after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + top: 0; + right: 0; + border-top: 1px solid $uni-border-color; + box-sizing: border-box; + width: 200%; + height: 200%; + transform: scale(0.5); + transform-origin: left top; + z-index: -1; + } + + /* #endif */ + + .uni-card__thumbnailimage { + position: relative; + flex-direction: column; + justify-content: center; + height: 150px; + overflow: hidden; + } + + .uni-card__thumbnailimage-box { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex: 1; + flex-direction: row; + overflow: hidden; + } + + .uni-card__thumbnailimage-image { + flex: 1; + } + + .uni-card__thumbnailimage-title { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + position: absolute; + bottom: 0; + left: 0; + right: 0; + flex-direction: row; + padding: $uni-spacing-col-base $uni-spacing-col-lg; + background-color: $uni-bg-color-mask; + } + + .uni-card__thumbnailimage-title-text { + flex: 1; + font-size: $uni-font-size-base; + color: #fff; + } + + .uni-card__title { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + align-items: center; + padding: 10px; + + } + + .uni-card__title-box { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex: 1; + flex-direction: row; + align-items: center; + overflow: hidden; + } + + .uni-card__title-header { + width: 40px; + height: 40px; + overflow: hidden; + border-radius: 5px; + } + + .uni-card__title-header-image { + width: 40px; + height: 40px; + } + + .uni-card__title-content { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; + justify-content: center; + flex: 1; + padding-left: 10px; + height: 40px; + overflow: hidden; + } + + .uni-card__title-content-title { + font-size: $uni-font-size-base; + line-height: 22px; + } + + .uni-card__title-content-extra { + font-size: $uni-font-size-sm; + line-height: 27px; + color: $uni-text-color-grey; + } + + .uni-card__header { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + position: relative; + flex-direction: row; + padding: $uni-spacing-col-lg; + align-items: center; + } + + .uni-card__header-title { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + margin-right: $uni-spacing-col-base; + justify-content: flex-start; + align-items: center; + } + + .uni-card__header-title-text { + font-size: $uni-font-size-lg; + flex: 1; + color: #333; + } + + .uni-card__header-extra-img { + height: $uni-img-size-sm; + width: $uni-img-size-sm; + margin-right: $uni-spacing-col-base; + } + + .uni-card__header-extra-text { + flex: 1; + margin-left: $uni-spacing-col-base; + font-size: $uni-font-size-sm; + text-align: right; + color: $uni-text-color-grey; + } + + .uni-card__content { + color: $uni-text-color; + } + + .uni-card__content--pd { + padding: $uni-spacing-col-lg; + } + + .uni-card__content-extra { + font-size: $uni-font-size-base; + padding-bottom: 10px; + color: $uni-text-color-grey; + } + + .uni-card__footer { + justify-content: space-between; + padding: $uni-spacing-col-lg; + } + + .uni-card__footer-text { + color: $uni-text-color-grey; + font-size: $uni-font-size-sm; + } + + .uni-card--shadow { + position: relative; + /* #ifndef APP-NVUE */ + box-shadow: 0px 0px 5px 1px rgba(0, 0, 0, 0.1); + /* #endif */ + } + + .uni-card--full { + margin: 0; + border-radius: 0; + } + + /* #ifndef APP-NVUE */ + .uni-card--full:after { + border-radius: 0; + } + + /* #endif */ + .uni-ellipsis { + /* #ifndef APP-NVUE */ + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + /* #endif */ + /* #ifdef APP-NVUE */ + lines: 1; + /* #endif */ + } +</style> diff --git a/hive-app/components/uni-collapse-item/uni-collapse-item.vue b/hive-app/components/uni-collapse-item/uni-collapse-item.vue new file mode 100644 index 0000000..de0cdc5 --- /dev/null +++ b/hive-app/components/uni-collapse-item/uni-collapse-item.vue @@ -0,0 +1,218 @@ +<template> + <view :class="{ 'uni-collapse-cell--disabled': disabled,'uni-collapse-cell--notdisabled': !disabled, 'uni-collapse-cell--open': isOpen,'uni-collapse-cell--hide':!isOpen }" + class="uni-collapse-cell"> + <view class="uni-collapse-cell__title" @click="onClick"> + <image v-if="thumb" :src="thumb" class="uni-collapse-cell__title-img" /> + <text class="uni-collapse-cell__title-text">{{ title }}</text> + <!-- #ifdef MP-ALIPAY --> + <view :class="{ 'uni-collapse-cell__title-arrow-active': isOpen, 'uni-collapse-cell--animation': showAnimation === true }" + class="uni-collapse-cell__title-arrow"> + <uni-icons color="#bbb" size="20" type="arrowdown" /> + </view> + <!-- #endif --> + <!-- #ifndef MP-ALIPAY --> + <uni-icons :class="{ 'uni-collapse-cell__title-arrow-active': isOpen, 'uni-collapse-cell--animation': showAnimation === true }" + class="uni-collapse-cell__title-arrow" color="#bbb" size="20" type="arrowdown" /> + <!-- #endif --> + </view> + <view :class="{'uni-collapse-cell__content--hide':!isOpen}" class="uni-collapse-cell__content"> + <view :class="{ 'uni-collapse-cell--animation': showAnimation === true }" class="uni-collapse-cell__wrapper" :style="{'transform':isOpen?'translateY(0)':'translateY(-50%)','-webkit-transform':isOpen?'translateY(0)':'translateY(-50%)'}"> + <slot /> + </view> + </view> + </view> +</template> + +<script> + import uniIcons from '../uni-icons/uni-icons.vue' + /** + * CollapseItem 折叠面板子组件 + * @description 折叠面板子组件 + * @property {String} title 标题文字 + * @property {String} thumb 标题左侧缩略图 + * @property {Boolean} disabled = [true|false] 是否展开面板 + * @property {Boolean} showAnimation = [true|false] 开启动画 + */ + export default { + name: 'UniCollapseItem', + components: { + uniIcons + }, + props: { + title: { + // 列表标题 + type: String, + default: '' + }, + name: { + // 唯一标识符 + type: [Number, String], + default: 0 + }, + disabled: { + // 是否禁用 + type: Boolean, + default: false + }, + showAnimation: { + // 是否显示动画 + type: Boolean, + default: false + }, + open: { + // 是否展开 + type: Boolean, + default: false + }, + thumb: { + // 缩略图 + type: String, + default: '' + } + }, + data() { + return { + isOpen: false + } + }, + watch: { + open(val) { + this.isOpen = val + } + }, + inject: ['collapse'], + created() { + this.isOpen = this.open + this.nameSync = this.name ? this.name : this.collapse.childrens.length + this.collapse.childrens.push(this) + if (String(this.collapse.accordion) === 'true') { + if (this.isOpen) { + let lastEl = this.collapse.childrens[this.collapse.childrens.length - 2] + if (lastEl) { + this.collapse.childrens[this.collapse.childrens.length - 2].isOpen = false + } + } + } + }, + methods: { + onClick() { + if (this.disabled) { + return + } + if (String(this.collapse.accordion) === 'true') { + this.collapse.childrens.forEach(vm => { + if (vm === this) { + return + } + vm.isOpen = false + }) + } + this.isOpen = !this.isOpen + this.collapse.onChange && this.collapse.onChange() + this.$forceUpdate() + } + } + } +</script> + +<style lang="scss" scoped> + .uni-collapse-cell { + flex-direction: column; + border-color: $uni-border-color; + border-bottom-width: 1px; + border-bottom-style: solid; + } + + + .uni-collapse-cell--hover { + background-color: $uni-bg-color-hover; + } + + .uni-collapse-cell--open { + background-color: $uni-bg-color-hover; + } + + .uni-collapse-cell--disabled { + background-color: $uni-bg-color-hover; + // opacity: 0.3; + } + + + .uni-collapse-cell--hide { + height: 48px; + } + + .uni-collapse-cell--animation { + // transition: transform 0.3s ease; + transition-property: transform; + transition-duration: 0.3s; + transition-timing-function: ease; + } + + .uni-collapse-cell__title { + padding: 12px 12px; + position: relative; + /* #ifndef APP-NVUE */ + display: flex; + width: 100%; + box-sizing: border-box; + /* #endif */ + height: 48px; + line-height: 24px; + flex-direction: row; + justify-content: space-between; + align-items: center; + } + + .uni-collapse-cell__title:active { + background-color: $uni-bg-color-hover; + } + + .uni-collapse-cell__title-img { + height: $uni-img-size-base; + width: $uni-img-size-base; + margin-right: 10px; + } + + .uni-collapse-cell__title-arrow { + width: 20px; + height: 20px; + transform: rotate(0deg); + transform-origin: center center; + + } + + .uni-collapse-cell__title-arrow-active { + transform: rotate(180deg); + } + + .uni-collapse-cell__title-text { + flex: 1; + font-size: $uni-font-size-base; + /* #ifndef APP-NVUE */ + white-space: nowrap; + color: inherit; + /* #endif */ + /* #ifdef APP-NVUE */ + lines: 1; + /* #endif */ + overflow: hidden; + text-overflow: ellipsis; + } + + .uni-collapse-cell__content { + overflow: hidden; + } + + .uni-collapse-cell__wrapper { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; + } + + .uni-collapse-cell__content--hide { + height: 0px; + line-height: 0px; + } +</style> diff --git a/hive-app/components/uni-collapse/uni-collapse.vue b/hive-app/components/uni-collapse/uni-collapse.vue new file mode 100644 index 0000000..bb3745f --- /dev/null +++ b/hive-app/components/uni-collapse/uni-collapse.vue @@ -0,0 +1,59 @@ +<template> + <view class="uni-collapse"> + <slot /> + </view> +</template> +<script> + /** + * Collapse 折叠面板 + * @description 展示可以折叠 / 展开的内容区域 + * @tutorial https://ext.dcloud.net.cn/plugin?id=23 + * @property {Boolean} accordion = [true|false] 是否开启手风琴效果是否开启手风琴效果 + * @event {Function} change 切换面板时触发,activeNames(Array):展开状态的uniCollapseItem的 name 值 + */ + export default { + name: 'UniCollapse', + props: { + accordion: { + // 是否开启手风琴效果 + type: [Boolean, String], + default: false + } + }, + data() { + return {} + }, + provide() { + return { + collapse: this + } + }, + created() { + this.childrens = [] + }, + methods: { + onChange() { + let activeItem = [] + this.childrens.forEach((vm, index) => { + if (vm.isOpen) { + activeItem.push(vm.nameSync) + } + }) + this.$emit('change', activeItem) + } + } + } +</script> +<style lang="scss" scoped> + .uni-collapse { + /* #ifndef APP-NVUE */ + width: 100%; + display: flex; + /* #endif */ + /* #ifdef APP-NVUE */ + flex: 1; + /* #endif */ + flex-direction: column; + background-color: $uni-bg-color; + } +</style> diff --git a/hive-app/components/uni-combox/uni-combox.vue b/hive-app/components/uni-combox/uni-combox.vue new file mode 100644 index 0000000..5f5786d --- /dev/null +++ b/hive-app/components/uni-combox/uni-combox.vue @@ -0,0 +1,213 @@ +<template> + <view class="uni-combox"> + <view v-if="label" class="uni-combox__label" :style="labelStyle"> + <text>{{label}}</text> + </view> + <view class="uni-combox__input-box"> + <input class="uni-combox__input" type="text" :placeholder="placeholder" v-model="inputVal" @input="onInput" + @focus="onFocus" @blur="onBlur" /> + <uni-icons class="uni-combox__input-arrow" type="arrowdown" size="14" @click="toggleSelector"></uni-icons> + <view class="uni-combox__selector" v-if="showSelector"> + <scroll-view scroll-y="true" class="uni-combox__selector-scroll"> + <view class="uni-combox__selector-empty" v-if="filterCandidatesLength === 0"> + <text>{{emptyTips}}</text> + </view> + <view class="uni-combox__selector-item" v-for="(item,index) in filterCandidates" :key="index" @click="onSelectorClick(index)"> + <text>{{item}}</text> + </view> + </scroll-view> + </view> + </view> + </view> +</template> + +<script> + import uniIcons from '../uni-icons/uni-icons.vue' + /** + * Combox 组合输入框 + * @description 组合输入框一般用于既可以输入也可以选择的场景 + * @tutorial https://ext.dcloud.net.cn/plugin?id=1261 + * @property {String} label 左侧文字 + * @property {String} labelWidth 左侧内容宽度 + * @property {String} placeholder 输入框占位符 + * @property {Array} candidates 候选项列表 + * @property {String} emptyTips 筛选结果为空时显示的文字 + * @property {String} value 组合框的值 + */ + export default { + name: 'uniCombox', + components: { + uniIcons + }, + props: { + label: { + type: String, + default: '' + }, + labelWidth: { + type: String, + default: 'auto' + }, + placeholder: { + type: String, + default: '' + }, + candidates: { + type: Array, + default () { + return [] + } + }, + emptyTips: { + type: String, + default: '无匹配项' + }, + value: { + type: [String, Number], + default: '' + } + }, + data() { + return { + showSelector: false, + inputVal: '' + } + }, + computed: { + labelStyle() { + if (this.labelWidth === 'auto') { + return {} + } + return { + width: this.labelWidth + } + }, + filterCandidates() { + return this.candidates.filter((item) => { + return item.toString().indexOf(this.inputVal) > -1 + }) + }, + filterCandidatesLength() { + return this.filterCandidates.length + } + }, + watch: { + value: { + handler(newVal) { + this.inputVal = newVal + }, + immediate: true + } + }, + methods: { + toggleSelector() { + this.showSelector = !this.showSelector + }, + onFocus() { + this.showSelector = true + }, + onBlur() { + setTimeout(() => { + this.showSelector = false + },50) + }, + onSelectorClick(index) { + this.inputVal = this.filterCandidates[index] + this.showSelector = false + this.$emit('input', this.inputVal) + }, + onInput() { + setTimeout(() => { + this.$emit('input', this.inputVal) + }) + } + } + } +</script> + +<style lang="scss" scoped> + .uni-combox { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + height: 40px; + flex-direction: row; + align-items: center; + // border-bottom: solid 1px #DDDDDD; + } + + .uni-combox__label { + font-size: 16px; + line-height: 22px; + padding-right: 10px; + color: #999999; + } + + .uni-combox__input-box { + position: relative; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex: 1; + flex-direction: row; + align-items: center; + } + + .uni-combox__input { + flex: 1; + font-size: 16px; + height: 22px; + line-height: 22px; + } + + .uni-combox__input-arrow { + padding: 10px; + } + + .uni-combox__selector { + box-sizing: border-box; + position: absolute; + top: 42px; + left: 0; + width: 100%; + background-color: #FFFFFF; + border-radius: 6px; + box-shadow: #DDDDDD 4px 4px 8px, #DDDDDD -4px -4px 8px; + z-index: 2; + } + + .uni-combox__selector-scroll { + max-height: 200px; + box-sizing: border-box; + } + + .uni-combox__selector::before { + content: ''; + position: absolute; + width: 0; + height: 0; + border-bottom: solid 6px #FFFFFF; + border-right: solid 6px transparent; + border-left: solid 6px transparent; + left: 50%; + top: -6px; + margin-left: -6px; + } + + .uni-combox__selector-empty, + .uni-combox__selector-item { + /* #ifdef APP-NVUE */ + display: flex; + /* #endif */ + line-height: 36px; + font-size: 14px; + text-align: center; + border-bottom: solid 1px #DDDDDD; + margin: 0px 10px; + } + + .uni-combox__selector-empty:last-child, + .uni-combox__selector-item:last-child { + border-bottom: none; + } +</style> diff --git a/hive-app/components/uni-countdown/uni-countdown.vue b/hive-app/components/uni-countdown/uni-countdown.vue new file mode 100644 index 0000000..7e9c508 --- /dev/null +++ b/hive-app/components/uni-countdown/uni-countdown.vue @@ -0,0 +1,211 @@ +<template> + <view class="uni-countdown"> + <text v-if="showDay" :style="{ borderColor: borderColor, color: color, backgroundColor: backgroundColor }" class="uni-countdown__number">{{ d }}</text> + <text v-if="showDay" :style="{ color: splitorColor }" class="uni-countdown__splitor">天</text> + <text :style="{ borderColor: borderColor, color: color, backgroundColor: backgroundColor }" class="uni-countdown__number">{{ h }}</text> + <text :style="{ color: splitorColor }" class="uni-countdown__splitor">{{ showColon ? ':' : '时' }}</text> + <text :style="{ borderColor: borderColor, color: color, backgroundColor: backgroundColor }" class="uni-countdown__number">{{ i }}</text> + <text :style="{ color: splitorColor }" class="uni-countdown__splitor">{{ showColon ? ':' : '分' }}</text> + <text :style="{ borderColor: borderColor, color: color, backgroundColor: backgroundColor }" class="uni-countdown__number">{{ s }}</text> + <text v-if="!showColon" :style="{ color: splitorColor }" class="uni-countdown__splitor">秒</text> + </view> +</template> +<script> + /** + * Countdown 倒计时 + * @description 倒计时组件 + * @tutorial https://ext.dcloud.net.cn/plugin?id=25 + * @property {String} backgroundColor 背景色 + * @property {String} color 文字颜色 + * @property {Number} day 天数 + * @property {Number} hour 小时 + * @property {Number} minute 分钟 + * @property {Number} second 秒 + * @property {Number} timestamp 时间戳 + * @property {Boolean} showDay = [true|false] 是否显示天数 + * @property {Boolean} showColon = [true|false] 是否以冒号为分隔符 + * @property {String} splitorColor 分割符号颜色 + * @event {Function} timeup 倒计时时间到触发事件 + * @example <uni-countdown :day="1" :hour="1" :minute="12" :second="40"></uni-countdown> + */ + export default { + name: 'UniCountdown', + props: { + showDay: { + type: Boolean, + default: true + }, + showColon: { + type: Boolean, + default: true + }, + backgroundColor: { + type: String, + default: '#FFFFFF' + }, + borderColor: { + type: String, + default: '#000000' + }, + color: { + type: String, + default: '#000000' + }, + splitorColor: { + type: String, + default: '#000000' + }, + day: { + type: Number, + default: 0 + }, + hour: { + type: Number, + default: 0 + }, + minute: { + type: Number, + default: 0 + }, + second: { + type: Number, + default: 0 + }, + timestamp: { + type: Number, + default: 0 + } + }, + data() { + return { + timer: null, + syncFlag: false, + d: '00', + h: '00', + i: '00', + s: '00', + leftTime: 0, + seconds: 0 + } + }, + watch: { + day(val) { + this.changeFlag() + }, + hour(val) { + this.changeFlag() + }, + minute(val) { + this.changeFlag() + }, + second(val) { + this.changeFlag() + } + }, + created: function(e) { + this.startData(); + }, + beforeDestroy() { + clearInterval(this.timer) + }, + methods: { + toSeconds(timestamp, day, hours, minutes, seconds) { + if (timestamp) { + return timestamp - parseInt(new Date().getTime() / 1000, 10) + } + return day * 60 * 60 * 24 + hours * 60 * 60 + minutes * 60 + seconds + }, + timeUp() { + clearInterval(this.timer) + this.$emit('timeup') + }, + countDown() { + let seconds = this.seconds + let [day, hour, minute, second] = [0, 0, 0, 0] + if (seconds > 0) { + day = Math.floor(seconds / (60 * 60 * 24)) + hour = Math.floor(seconds / (60 * 60)) - (day * 24) + minute = Math.floor(seconds / 60) - (day * 24 * 60) - (hour * 60) + second = Math.floor(seconds) - (day * 24 * 60 * 60) - (hour * 60 * 60) - (minute * 60) + } else { + this.timeUp() + } + if (day < 10) { + day = '0' + day + } + if (hour < 10) { + hour = '0' + hour + } + if (minute < 10) { + minute = '0' + minute + } + if (second < 10) { + second = '0' + second + } + this.d = day + this.h = hour + this.i = minute + this.s = second + }, + startData() { + this.seconds = this.toSeconds(this.timestamp, this.day, this.hour, this.minute, this.second) + if (this.seconds <= 0) { + return + } + this.countDown() + this.timer = setInterval(() => { + this.seconds-- + if (this.seconds < 0) { + this.timeUp() + return + } + this.countDown() + }, 1000) + }, + changeFlag() { + if (!this.syncFlag) { + this.seconds = this.toSeconds(this.timestamp, this.day, this.hour, this.minute, this.second) + this.startData(); + this.syncFlag = true; + } + } + } + } +</script> +<style lang="scss" scoped> + $countdown-height: 48rpx; + $countdown-width: 52rpx; + + .uni-countdown { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + justify-content: flex-start; + padding: 2rpx 0; + } + + .uni-countdown__splitor { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + justify-content: center; + line-height: $countdown-height; + padding: 5rpx; + font-size: $uni-font-size-sm; + } + + .uni-countdown__number { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + justify-content: center; + align-items: center; + width: $countdown-width; + height: $countdown-height; + line-height: $countdown-height; + margin: 5rpx; + text-align: center; + font-size: $uni-font-size-sm; + } +</style> diff --git a/hive-app/components/uni-data-checkbox/clientdb.js b/hive-app/components/uni-data-checkbox/clientdb.js new file mode 100644 index 0000000..f494d62 --- /dev/null +++ b/hive-app/components/uni-data-checkbox/clientdb.js @@ -0,0 +1,368 @@ + +const events = { + load: 'load', + error: 'error' +} +const pageMode = { + add: 'add', + replace: 'replace' +} + +const attrs = [ + 'pageCurrent', + 'pageSize', + 'collection', + 'action', + 'field', + 'getcount', + 'orderby', + 'where' +] + +export default { + props: { + options: { + type: [Object, Array], + default () { + return {} + } + }, + collection: { + type: String, + default: '' + }, + action: { + type: String, + default: '' + }, + field: { + type: String, + default: '' + }, + pageData: { + type: String, + default: 'add' + }, + pageCurrent: { + type: Number, + default: 1 + }, + pageSize: { + type: Number, + default: 20 + }, + getcount: { + type: [Boolean, String], + default: false + }, + orderby: { + type: String, + default: '' + }, + where: { + type: [String, Object], + default: '' + }, + getone: { + type: [Boolean, String], + default: false + }, + manual: { + type: Boolean, + default: false + } + }, + data() { + return { + loading: false, + listData: this.getone ? {} : [], + paginationInternal: { + current: this.pageCurrent, + size: this.pageSize, + count: 0 + }, + errorMessage: '' + } + }, + created() { + let db = null; + let dbCmd = null; + + if(this.collection){ + this.db = uniCloud.database(); + this.dbCmd = this.db.command; + } + + this._isEnded = false + + this.$watch(() => { + var al = [] + attrs.forEach(key => { + al.push(this[key]) + }) + return al + }, (newValue, oldValue) => { + this.paginationInternal.pageSize = this.pageSize + + let needReset = false + for (let i = 2; i < newValue.length; i++) { + if (newValue[i] != oldValue[i]) { + needReset = true + break + } + } + if (needReset) { + this.clear() + this.reset() + } + if (newValue[0] != oldValue[0]) { + this.paginationInternal.current = this.pageCurrent + } + + this._execLoadData() + }) + + // #ifdef H5 + if (process.env.NODE_ENV === 'development') { + this._debugDataList = [] + if (!window.unidev) { + window.unidev = { + clientDB: { + data: [] + } + } + } + unidev.clientDB.data.push(this._debugDataList) + } + // #endif + + // #ifdef MP-TOUTIAO + let changeName + let events = this.$scope.dataset.eventOpts + for (var i = 0; i < events.length; i++) { + let event = events[i] + if (event[0].includes('^load')) { + changeName = event[1][0][0] + } + } + if (changeName) { + let parent = this.$parent + let maxDepth = 16 + this._changeDataFunction = null + while (parent && maxDepth > 0) { + let fun = parent[changeName] + if (fun && typeof fun === 'function') { + this._changeDataFunction = fun + maxDepth = 0 + break + } + parent = parent.$parent + maxDepth--; + } + } + // #endif + + // if (!this.manual) { + // this.loadData() + // } + }, + // #ifdef H5 + beforeDestroy() { + if (process.env.NODE_ENV === 'development' && window.unidev) { + var cd = this._debugDataList + var dl = unidev.clientDB.data + for (var i = dl.length - 1; i >= 0; i--) { + if (dl[i] === cd) { + dl.splice(i, 1) + break + } + } + } + }, + // #endif + methods: { + loadData(args1, args2) { + let callback = null + if (typeof args1 === 'object') { + if (args1.clear) { + this.clear() + this.reset() + } + if (args1.current !== undefined) { + this.paginationInternal.current = args1.current + } + if (typeof args2 === 'function') { + callback = args2 + } + } else if (typeof args1 === 'function') { + callback = args1 + } + + this._execLoadData(callback) + }, + loadMore() { + if (this._isEnded) { + return + } + this._execLoadData() + }, + refresh() { + this.clear() + this._execLoadData() + }, + clear() { + this._isEnded = false + this.listData = [] + }, + reset() { + this.paginationInternal.current = 1 + }, + remove(id, { + action, + callback, + confirmTitle, + confirmContent + } = {}) { + if (!id || !id.length) { + return + } + uni.showModal({ + title: confirmTitle || '提示', + content: confirmContent || '是否删除该数据', + showCancel: true, + success: (res) => { + if (!res.confirm) { + return + } + this._execRemove(id, action, callback) + } + }) + }, + _execLoadData(callback) { + if (this.loading) { + return + } + this.loading = true + this.errorMessage = '' + + this._getExec().then((res) => { + this.loading = false + const { + data, + count + } = res.result + this._isEnded = data.length < this.pageSize + + callback && callback(data, this._isEnded) + this._dispatchEvent(events.load, data) + + if (this.getone) { + this.listData = data.length ? data[0] : undefined + } else if (this.pageData === pageMode.add) { + this.listData.push(...data) + if (this.listData.length) { + this.paginationInternal.current++ + } + } else if (this.pageData === pageMode.replace) { + this.listData = data + this.paginationInternal.count = count + } + + // #ifdef H5 + if (process.env.NODE_ENV === 'development') { + this._debugDataList.length = 0 + this._debugDataList.push(...JSON.parse(JSON.stringify(this.listData))) + } + // #endif + }).catch((err) => { + this.loading = false + this.errorMessage = err + callback && callback() + this.$emit(events.error, err) + }) + }, + _getExec() { + let exec = this.db + if (this.action) { + exec = exec.action(this.action) + } + + exec = exec.collection(this.collection) + + if (!(!this.where || !Object.keys(this.where).length)) { + exec = exec.where(this.where) + } + if (this.field) { + exec = exec.field(this.field) + } + if (this.orderby) { + exec = exec.orderBy(this.orderby) + } + + const { + current, + size + } = this.paginationInternal + exec = exec.skip(size * (current - 1)).limit(size).get({ + getCount: this.getcount + }) + + return exec + }, + _execRemove(id, action, callback) { + if (!this.collection || !id) { + return + } + + const ids = Array.isArray(id) ? id : [id] + if (!ids.length) { + return + } + + uni.showLoading({ + mask: true + }) + + let exec = this.db + if (action) { + exec = exec.action(action) + } + + exec.collection(this.collection).where({ + _id: dbCmd.in(ids) + }).remove().then((res) => { + callback && callback(res.result) + if (this.pageData === pageMode.replace) { + this.refresh() + } else { + this.removeData(ids) + } + }).catch((err) => { + uni.showModal({ + content: err.message, + showCancel: false + }) + }).finally(() => { + uni.hideLoading() + }) + }, + removeData(ids) { + let il = ids.slice(0) + let dl = this.listData + for (let i = dl.length - 1; i >= 0; i--) { + let index = il.indexOf(dl[i]._id) + if (index >= 0) { + dl.splice(i, 1) + il.splice(index, 1) + } + } + }, + _dispatchEvent(type, data) { + if (this._changeDataFunction) { + this._changeDataFunction(data, this._isEnded) + } else { + this.$emit(type, data, this._isEnded) + } + } + } +} diff --git a/hive-app/components/uni-data-checkbox/uni-data-checkbox.vue b/hive-app/components/uni-data-checkbox/uni-data-checkbox.vue new file mode 100644 index 0000000..b340d2b --- /dev/null +++ b/hive-app/components/uni-data-checkbox/uni-data-checkbox.vue @@ -0,0 +1,766 @@ +<template> + <view class="uni-data-checklist"> + <template v-if="loading"> + <view class="uni-data-loading"> + <uni-load-more status="loading" iconType="snow" :iconSize="18" :content-text="contentText"></uni-load-more> + </view> + </template> + <template v-else> + <checkbox-group v-if="multiple" class="checklist-group" :class="{'is-list':mode==='list','is-wrap':wrap}" @change="chagne"> + <label class="checklist-box" :class="item.labelClass" :style="[item.styleBackgroud]" v-for="(item,index) in dataList" + :key="index"> + <checkbox hidden :disabled="!!item.disable" :value="item.value+''" :checked="item.selected" /> + <view v-if="(mode !=='tag' && mode !== 'list') || ( mode === 'list' && icon === 'left')" class="checkbox__inner" + :class="item.checkboxBgClass" :style="[item.styleIcon]"> + <view class="checkbox__inner-icon" :class="item.checkboxClass"></view> + </view> + <view class="checklist-content" :class="{'list-content':mode === 'list' && icon ==='left'}"> + <text class="checklist-text" :class="item.textClass" :style="[item.styleIconText]">{{item.text}}</text> + <view v-if="mode === 'list' && icon === 'right'" class="checkobx__list" :class="item.listClass" :style="[item.styleBackgroud]"></view> + </view> + </label> + </checkbox-group> + <radio-group v-else class="checklist-group" :class="{'is-list':mode==='list','is-wrap':wrap}" @change="chagne"> + <label class="checklist-box" :class="item.labelClass" :style="[item.styleBackgroud]" v-for="(item,index) in dataList" :key="index"> + <radio hidden :disabled="item.disable" :value="item.value+''" :checked="item.selected" /> + <view v-if="(mode !=='tag' && mode !== 'list') || ( mode === 'list' && icon === 'left')" class="radio__inner" + :class="item.checkboxBgClass" :style="[item.styleBackgroud]"> + <view class="radio__inner-icon" :class="item.checkboxClass" :style="[item.styleIcon]"></view> + </view> + <view class="checklist-content" :class="{'list-content':mode === 'list' && icon ==='left'}"> + <text class="checklist-text" :class="item.textClass" :style="[item.styleIconText]">{{item.text}}</text> + <view v-if="mode === 'list' && icon === 'right'" class="checkobx__list" :class="item.listClass" :style="[item.styleRightIcon]"></view> + </view> + </label> + </radio-group> + </template> + </view> +</template> + +<script> + /** + * DataChecklist 数据选择器 + * @description 通过数据渲染 checkbox 和 radio + * @tutorial https://ext.dcloud.net.cn/plugin?id=xxx + * @property {String} mode = [default| list | button | tag] 显示模式 + * @value default 默认横排模式 + * @value list 列表模式 + * @value button 按钮模式 + * @value tag 标签模式 + * @property {Boolean} multiple = [true|false] 是否多选 + * @property {Array|String|Number} value 默认值 + * @property {Array} localdata 本地数据 ,格式 [{text:'',value:''}] + * @property {Number|String} min 最小选择个数 ,multiple为true时生效 + * @property {Number|String} max 最大选择个数 ,multiple为true时生效 + * @property {Boolean} wrap 是否换行显示 + * @property {String} icon = [left|right] list 列表模式下icon显示位置 + * @property {Boolean} selectedColor 选中颜色 + * @property {Boolean} selectedTextColor 选中文本颜色,如不填写则自动显示 + * @value left 左侧显示 + * @value right 右侧显示 + * @event {Function} change 选中发生变化触发 + */ + + import clientdb from './clientdb.js' + export default { + name: 'uniDataChecklist', + mixins: [clientdb], + props: { + mode: { + type: String, + default: 'default' + }, + multiple: { + type: Boolean, + default: false + }, + value: { + type: [Array, String, Number], + default () { + return '' + } + }, + localdata: { + type: Array, + default () { + return [] + } + }, + min: { + type: [Number, String], + default: '' + }, + max: { + type: [Number, String], + default: '' + }, + wrap: { + type: Boolean, + default: false + }, + icon: { + type: String, + default: 'left' + }, + selectedColor:{ + type: String, + default: '' + }, + selectedTextColor:{ + type: String, + default: '' + } + }, + watch: { + localdata: { + handler(newVal) { + this.dataList = this.getDataList(this.getSelectedValue(newVal)) + }, + deep: true + }, + + listData(newVal) { + this.range = newVal + this.dataList = this.getDataList(this.getSelectedValue(newVal)) + // console.log('----listData', this.dataList); + }, + value(newVal) { + this.dataList = this.getDataList(newVal) + this.formItem && this.formItem.setValue(newVal) + } + }, + data() { + return { + dataList: [], + range: [], + contentText: { + contentdown: '查看更多', + contentrefresh: '加载中', + contentnomore: '没有更多' + }, + styles: { + selectedColor: '#007aff', + selectedTextColor: '#333', + } + }; + }, + created() { + this.form = this.getForm('uniForms') + this.formItem = this.getForm('uniFormsItem') + this.formItem && this.formItem.setValue(this.value) + this.styles = { + selectedColor: this.selectedColor, + selectedTextColor: this.selectedTextColor + } + + if (this.formItem) { + if (this.formItem.name) { + this.rename = this.formItem.name + this.form.inputChildrens.push(this) + } + } + + if (this.localdata && this.localdata.length !== 0) { + this.range = this.localdata + this.dataList = this.getDataList(this.getSelectedValue(this.range)) + } else { + if (this.collection) { + this.loadData() + } + } + }, + methods: { + init(range) {}, + /** + * 获取父元素实例 + */ + getForm(name = 'uniForms') { + let parent = this.$parent; + let parentName = parent.$options.name; + while (parentName !== name) { + parent = parent.$parent; + if (!parent) return false + parentName = parent.$options.name; + } + return parent; + }, + chagne(e) { + const values = e.detail.value + + let detail = { + value: [], + data: [] + } + + if (this.multiple) { + this.range.forEach(item => { + if (values.includes(item.value + '')) { + detail.value.push(item.value) + detail.data.push(item) + } + }) + } else { + const range = this.range.find(item => (item.value + '') === values) + if (range) { + detail = { + value: range.value, + data: range + } + } + } + this.formItem && this.formItem.setValue(detail.value) + this.$emit('input', detail.value) + this.$emit('change', { + detail + }) + if (this.multiple) { + // 如果 v-model 没有绑定 ,则走内部逻辑 + // if (this.value.length === 0) { + this.dataList = this.getDataList(detail.value, true) + // } + } else { + this.dataList = this.getDataList(detail.value) + } + }, + getLabelClass(item, index) { + let classes = [] + switch (this.mode) { + case 'default': + item.disable && classes.push('disabled-cursor') + break + case 'button': + classes.push(...['is-button', ...this.getClasses(item, 'button')]) + break + case 'list': + if (this.multiple) { + classes.push('is-list-multiple-box') + } else { + classes.push('is-list-box') + } + item.disable && classes.push('is-list-disabled') + index !== 0 && classes.push('is-list-border') + break + case 'tag': + classes.push(...['is-tag', ...this.getClasses(item, 'tag')]) + break + } + classes = classes.join(' ') + return classes + }, + getCheckboxClass(item, type = '') { + let classes = [] + if (this.multiple) { + classes.push(...this.getClasses(item, 'default-multiple', type)) + } else { + classes.push(...this.getClasses(item, 'default', type)) + } + classes = classes.join(' ') + return classes + }, + getTextClass(item) { + let classes = [] + switch (this.mode) { + case 'default': + classes.push(...this.getClasses(item, 'list')) + break + case 'button': + classes.push(...this.getClasses(item, 'list')) + break + case 'list': + classes.push(...this.getClasses(item, 'list')) + break + case 'tag': + classes.push(...['is-tag-text', ...this.getClasses(item, 'tag-text')]) + break + } + classes = classes.join(' ') + return classes + }, + + /** + * 获取渲染的新数组 + * @param {Object} value 选中内容 + */ + getDataList(value) { + // 解除引用关系,破坏原引用关系,避免污染源数据 + let dataList = JSON.parse(JSON.stringify(this.range)) + let list = [] + if (this.multiple) { + if (!Array.isArray(value)) { + value = [] + // console.error('props 类型错误'); + } + } + dataList.forEach((item, index) => { + if (this.multiple) { + if (value.length > 0) { + let have = value.find(val => val === item.value) + item.selected = have !== undefined + } else { + item.selected = false + } + } else { + item.selected = value === item.value + } + + list.push(item) + }) + return this.setRange(list) + }, + /** + * 处理最大最小值 + * @param {Object} list + */ + setRange(list) { + let selectList = list.filter(item => item.selected) + let min = Number(this.min) || 0 + let max = Number(this.max) || '' + list.forEach((item, index) => { + if (this.multiple) { + if (selectList.length <= min) { + let have = selectList.find(val => val.value === item.value) + if (have !== undefined) { + item.disable = true + } + } + + if (selectList.length >= max && max !== '') { + let have = selectList.find(val => val.value === item.value) + if (have === undefined) { + item.disable = true + } + } + } + this.setClass(item, index) + list[index] = item + }) + return list + }, + /** + * 设置 class + * @param {Object} item + * @param {Object} index + */ + setClass(item, index) { + // 设置 label 的 class + item.labelClass = this.getLabelClass(item, index) + // 设置 checkbox外层样式 + item.checkboxBgClass = this.getCheckboxClass(item, '-bg') + // 设置 checkbox 内层样式 + item.checkboxClass = this.getCheckboxClass(item) + // 设置文本样式 + item.textClass = this.getTextClass(item) + // 设置 list 对勾右侧样式 + item.listClass = this.getCheckboxClass(item, '-list') + + // 设置自定义样式 + item.styleBackgroud = this.setStyleBackgroud(item) + item.styleIcon = this.setStyleIcon(item) + item.styleIconText = this.setStyleIconText(item) + item.styleRightIcon = this.setStyleRightIcon(item) + }, + /** + * 获取 class + */ + getClasses(item, name, type = '') { + let classes = [] + item.disable && classes.push('is-' + name + '-disabled' + type) + item.selected && classes.push('is-' + name + '-checked' + type) + + if (this.mode !== 'button' || name === 'button') { + item.selected && item.disable && classes.push('is-' + name + '-disabled-checked' + type) + } + + return classes + }, + /** + * 获取选中值 + * @param {Object} range + */ + getSelectedValue(range) { + if (!this.multiple) return this.value + let selectedArr = [] + range.forEach((item) => { + if (item.selected) { + selectedArr.push(item.value) + } + }) + return this.value.length > 0 ? this.value : selectedArr + }, + + /** + * 设置背景样式 + */ + setStyleBackgroud(item) { + let styles = {} + + if (item.selected) { + if (this.mode !== 'list') { + styles.borderColor = this.styles.selectedColor + } + if (this.mode === 'tag') { + styles.backgroundColor = this.styles.selectedColor + } + } + return styles + }, + setStyleIcon(item) { + let styles = {} + + if (item.selected) { + styles.backgroundColor = this.styles.selectedColor + styles.borderColor = this.styles.selectedColor + } + return styles + }, + setStyleIconText(item) { + let styles = {} + if (item.selected) { + if (this.styles.selectedTextColor) { + styles.color = this.styles.selectedTextColor + } else { + if(this.mode === 'tag'){ + styles.color = '#fff' + }else{ + styles.color = this.styles.selectedColor + } + } + } + return styles + }, + setStyleRightIcon(item){ + let styles = {} + if (item.selected) { + if(this.mode === 'list'){ + styles.borderColor = this.styles.selectedColor + } + } + + return styles + } + } + } +</script> + +<style> + .uni-data-checklist { + /* min-height: 36px; */ + } + + .uni-data-loading { + display: flex; + align-items: center; + /* justify-content: center; */ + height: 36px; + padding-left: 10px; + } + + .checklist-group { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + flex-wrap: wrap; + } + + .checklist-box { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + align-items: center; + margin: 5px 0; + margin-right: 25px; + } + + .checklist-text { + font-size: 14px; + color: #333; + margin-left: 5px; + transition: color 0.2s; + } + + .is-button { + margin-right: 10px; + padding: 3px 15px; + border: 1px #DCDFE6 solid; + border-radius: 3px; + transition: border-color 0.2s; + } + + .is-list { + flex-direction: column; + } + + .is-list-box { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + padding: 10px 15px; + padding-left: 0; + margin: 0; + } + + .checklist-content { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex: 1; + flex-direction: row; + align-items: center; + justify-content: space-between; + } + + .list-content { + margin-left: 5px; + } + + .is-list-multiple-box { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + padding: 10px 15px; + padding-left: 0; + margin: 0; + } + + .is-list-border { + border-top: 1px #eee solid; + } + + .is-tag { + margin-right: 10px; + padding: 3px 10px; + border: 1px #eee solid; + border-radius: 3px; + background-color: #f5f5f5; + /* transition: border-color 0.1s; */ + } + + .is-tag-text { + margin: 0; + color: #666; + } + + .checkbox__inner { + flex-shrink: 0; + position: relative; + border: 1px solid #DCDFE6; + border-radius: 2px; + box-sizing: border-box; + width: 16px; + height: 16px; + background-color: #fff; + z-index: 1; + transition: border-color 0.1s; + } + + .checkbox__inner-icon { + border: 1px solid #fff; + border-left: 0; + border-top: 0; + height: 8px; + left: 5px; + position: absolute; + top: 1px; + width: 3px; + opacity: 0; + transition: transform .2s; + transform-origin: center; + transform: rotate(40deg) scaleY(0.4); + } + + .radio__inner { + flex-shrink: 0; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + justify-content: center; + align-items: center; + position: relative; + border: 1px solid #DCDFE6; + border-radius: 2px; + box-sizing: border-box; + width: 16px; + height: 16px; + border-radius: 16px; + background-color: #fff; + z-index: 1; + transition: border-color .3s; + } + + .radio__inner-icon { + width: 8px; + height: 8px; + border-radius: 10px; + opacity: 0; + transition: transform .3s; + } + + .checkobx__list { + border: 1px solid #fff; + border-left: 0; + border-top: 0; + height: 12px; + width: 6px; + transform-origin: center; + opacity: 0; + transition: all 0.3s; + transform: rotate(45deg); + } + + + /* 禁用样式 */ + .is-default-disabled-bg { + background-color: #F2F6FC; + border-color: #DCDFE6; + /* #ifdef H5 */ + cursor: not-allowed; + /* #endif */ + } + + .is-default-multiple-disabled-bg { + background-color: #F2F6FC; + border-color: #DCDFE6; + /* #ifdef H5 */ + cursor: not-allowed; + /* #endif */ + } + + .is-default-disabled { + border-color: #F2F6FC; + } + + .is-default-multiple-disabled { + border-color: #F2F6FC; + } + + .is-list-disabled { + /* #ifdef H5 */ + cursor: not-allowed; + /* #endif */ + color: #999; + } + + .is-list-disabled-checked { + color: #a1dcc1; + } + + + .is-button-disabled { + /* #ifdef H5 */ + cursor: not-allowed; + /* #endif */ + border-color: #EBEEF5; + } + + .is-button-text-disabled { + color: #C0C4CC; + } + + .is-button-disabled-checked { + border-color: #a1dcc1; + } + + .is-tag-disabled { + /* #ifdef H5 */ + cursor: not-allowed; + /* #endif */ + border-color: #e9e9eb; + background-color: #f4f4f5; + } + + .is-tag-text-disabled { + color: #bcbec2; + } + + /* 选中样式 */ + .is-default-checked-bg { + border-color: #007aff; + } + + .is-default-multiple-checked-bg { + border-color: #007aff; + background-color: #007aff; + } + + .is-default-checked { + opacity: 1; + background-color: #007aff; + transform: rotate(45deg) scaleY(1); + } + + .is-default-multiple-checked { + opacity: 1; + transform: rotate(45deg) scaleY(1); + } + + .is-default-disabled-checked-bg { + opacity: 0.4; + } + + .is-default-multiple-disabled-checked-bg { + opacity: 0.4; + } + + + .is-default-checked-list { + border-color: #007aff; + opacity: 1; + transform: rotate(45deg) scaleY(1); + } + + .is-default-multiple-checked-list { + border-color: #007aff; + opacity: 1; + transform: rotate(45deg) scaleY(1); + } + + .is-list-disabled-checked { + opacity: 0.4; + } + + .is-default-disabled-checked-list { + opacity: 0.4; + } + + .is-default-multiple-disabled-checked-list { + opacity: 0.4; + } + + .is-button-checked { + border-color: #007aff; + } + + .is-button-disabled-checked { + opacity: 0.4; + } + + .is-list-checked { + color: #007aff; + } + + .is-tag-checked { + border-color: #007aff; + background-color: #007aff; + } + + .is-tag-text-checked { + color: #fff; + } + + .is-tag-disabled-checked { + opacity: 0.4; + } + + + .disabled-cursor { + /* #ifdef H5 */ + cursor: not-allowed; + /* #endif */ + } + + .is-wrap { + flex-direction: column; + } +</style> diff --git a/hive-app/components/uni-dateformat/date-format.js b/hive-app/components/uni-dateformat/date-format.js new file mode 100644 index 0000000..1939135 --- /dev/null +++ b/hive-app/components/uni-dateformat/date-format.js @@ -0,0 +1,191 @@ +// yyyy-MM-dd hh:mm:ss.SSS 所有支持的类型 +function pad(str, length = 2) { + str += '' + while (str.length < length) { + str = '0' + str + } + return str.slice(-length) +} + +const parser = { + yyyy: (dateObj) => { + return pad(dateObj.year, 4) + }, + yy: (dateObj) => { + return pad(dateObj.year) + }, + MM: (dateObj) => { + return pad(dateObj.month) + }, + M: (dateObj) => { + return dateObj.month + }, + dd: (dateObj) => { + return pad(dateObj.day) + }, + d: (dateObj) => { + return dateObj.day + }, + hh: (dateObj) => { + return pad(dateObj.hour) + }, + h: (dateObj) => { + return dateObj.hour + }, + mm: (dateObj) => { + return pad(dateObj.minute) + }, + m: (dateObj) => { + return dateObj.minute + }, + ss: (dateObj) => { + return pad(dateObj.second) + }, + s: (dateObj) => { + return dateObj.second + }, + SSS: (dateObj) => { + return pad(dateObj.millisecond, 3) + }, + S: (dateObj) => { + return dateObj.millisecond + }, +} + +// 这都n年了iOS依然不认识2020-12-12,需要转换为2020/12/12 +function getDate(time) { + if (time instanceof Date) { + return time + } + switch (typeof time) { + case 'string': + return new Date(time.replace(/-/g, '/')) + default: + return new Date(time) + } +} + +export function formatDate(date, format = 'yyyy/MM/dd hh:mm:ss') { + if (!date && date !== 0) { + return '-' + } + date = getDate(date) + const dateObj = { + year: date.getFullYear(), + month: date.getMonth() + 1, + day: date.getDate(), + hour: date.getHours(), + minute: date.getMinutes(), + second: date.getSeconds(), + millisecond: date.getMilliseconds() + } + const tokenRegExp = /yyyy|yy|MM|M|dd|d|hh|h|mm|m|ss|s|SSS|SS|S/ + let flag = true + let result = format + while (flag) { + flag = false + result = result.replace(tokenRegExp, function(matched) { + flag = true + return parser[matched](dateObj) + }) + } + return result +} + +export function friendlyDate(time, { + locale = 'zh', + threshold = [60000, 3600000], + format = 'yyyy/MM/dd hh:mm:ss' +}) { + if (!time && time !== 0) { + return '-' + } + const localeText = { + zh: { + year: '年', + month: '月', + day: '天', + hour: '小时', + minute: '分钟', + second: '秒', + ago: '前', + later: '后', + justNow: '刚刚', + soon: '马上', + template: '{num}{unit}{suffix}' + }, + en: { + year: 'year', + month: 'month', + day: 'day', + hour: 'hour', + minute: 'minute', + second: 'second', + ago: 'ago', + later: 'later', + justNow: 'just now', + soon: 'soon', + template: '{num} {unit} {suffix}' + } + } + const text = localeText[locale] || localeText.zh + let date = getDate(time) + let ms = date.getTime() - Date.now() + let absMs = Math.abs(ms) + if (absMs < threshold[0]) { + return ms < 0 ? text.justNow : text.soon + } + if (absMs >= threshold[1]) { + return formatDate(date, format) + } + let num + let unit + let suffix = text.later + if (ms < 0) { + suffix = text.ago + ms = -ms + } + const seconds = Math.floor((ms) / 1000) + const minutes = Math.floor(seconds / 60) + const hours = Math.floor(minutes / 60) + const days = Math.floor(hours / 24) + const months = Math.floor(days / 30) + const years = Math.floor(months / 12) + switch (true) { + case years > 0: + num = years + unit = text.year + break + case months > 0: + num = months + unit = text.month + break + case days > 0: + num = days + unit = text.day + break + case hours > 0: + num = hours + unit = text.hour + break + case minutes > 0: + num = minutes + unit = text.minute + break + default: + num = seconds + unit = text.second + break + } + + if (locale === 'en') { + if (num === 1) { + num = 'a' + } else { + unit += 's' + } + } + + return text.template.replace(/{\s*num\s*}/g, num + '').replace(/{\s*unit\s*}/g, unit).replace(/{\s*suffix\s*}/g, + suffix) +} diff --git a/hive-app/components/uni-dateformat/uni-dateformat.vue b/hive-app/components/uni-dateformat/uni-dateformat.vue new file mode 100644 index 0000000..487908c --- /dev/null +++ b/hive-app/components/uni-dateformat/uni-dateformat.vue @@ -0,0 +1,90 @@ +<template> + <text>{{dateShow}}</text> +</template> + +<script> + /** + * Dateformat 日期格式化 + * @description 日期格式化组件 + * @tutorial https://ext.dcloud.net.cn/plugin?id=xxx + * @property {Object|String|Number} date 日期对象/日期字符串/时间戳 + * @property {String} locale 格式化使用的语言 + * @value zh 中文 + * @value en 英文 + * @property {Array} threshold 应用不同类型格式化的阈值 + * @property {String} format 输出日期字符串时的格式 + */ + import { + friendlyDate + } from './date-format.js' + export default { + name: 'uniDateformat', + props: { + date: { + type: [Object, String, Number], + default () { + return Date.now() + } + }, + locale: { + type: String, + default: 'zh', + }, + threshold: { + type: Array, + default () { + return [0, 0] + } + }, + format: { + type: String, + default: 'yyyy/MM/dd hh:mm:ss' + }, + // refreshRate使用不当可能导致性能问题,谨慎使用 + refreshRate: { + type: [Number, String], + default: 0 + } + }, + data() { + return { + refreshMark: 0 + } + }, + computed: { + dateShow() { + this.refreshMark + return friendlyDate(this.date, { + locale: this.locale, + threshold: this.threshold, + format: this.format + }) + } + }, + watch: { + refreshRate: { + handler() { + this.setAutoRefresh() + }, + immediate: true + } + }, + methods: { + refresh() { + this.refreshMark++ + }, + setAutoRefresh() { + clearInterval(this.refreshInterval) + if (this.refreshRate) { + this.refreshInterval = setInterval(() => { + this.refresh() + }, parseInt(this.refreshRate)) + } + } + } + } +</script> + +<style> + +</style> diff --git a/hive-app/components/uni-datetime-picker/uni-datetime-picker.vue b/hive-app/components/uni-datetime-picker/uni-datetime-picker.vue new file mode 100644 index 0000000..fd735e2 --- /dev/null +++ b/hive-app/components/uni-datetime-picker/uni-datetime-picker.vue @@ -0,0 +1,355 @@ +<template> + <view class="uni-datetime-picker"> + <view @click="tiggerTimePicker"> + <slot> + <view class="uni-datetime-picker-timebox uni-datetime-picker-flex"> + {{time}}<view v-if="!time" class="uni-datetime-picker-time">选择日期时间</view> + <view class="uni-datetime-picker-down-arrow"></view> + </view> + </slot> + </view> + <view v-if="visible" class="uni-datetime-picker-mask" @click="initTimePicker"></view> + <view v-if="visible" class="uni-datetime-picker-popup"> + <view class="uni-title"> + 设置日期和时间 + </view> + <picker-view class="uni-datetime-picker-view" :indicator-style="indicatorStyle" :value="ymd" @change="bindDateChange"> + <picker-view-column class="uni-datetime-picker-hyphen"> + <view class="uni-datetime-picker-item" v-for="(item,index) in years" :key="index">{{item}}</view> + </picker-view-column> + <picker-view-column class="uni-datetime-picker-hyphen"> + <view class="uni-datetime-picker-item" v-for="(item,index) in months" :key="index">{{item < 10 ? '0' + item : item}}</view> + </picker-view-column> + <picker-view-column> + <view class="uni-datetime-picker-item" v-for="(item,index) in days" :key="index">{{item < 10 ? '0' + item : item}}</view> + </picker-view-column> + </picker-view> + <picker-view class="uni-datetime-picker-view" :indicator-style="indicatorStyle" :value="hms" @change="bindTimeChange"> + <picker-view-column class="uni-datetime-picker-colon"> + <view class="uni-datetime-picker-item" v-for="(item,index) in hours" :key="index">{{item < 10 ? '0' + item : item}}</view> + </picker-view-column> + <picker-view-column class="uni-datetime-picker-colon"> + <view class="uni-datetime-picker-item" v-for="(item,index) in minutes" :key="index">{{item < 10 ? '0' + item : item}}</view> + </picker-view-column> + <picker-view-column> + <view class="uni-datetime-picker-item" v-for="(item,index) in seconds" :key="index">{{item < 10 ? '0' + item : item}}</view> + </picker-view-column> + </picker-view> + <view class="uni-datetime-picker-btn"> + <view class="" @click="clearTime">重置</view> + <view class="uni-datetime-picker-btn-group"> + <view class="uni-datetime-picker-cancel" @click="tiggerTimePicker">取消</view> + <view class="" @click="setTime">确定</view> + </view> + </view> + </view> + </view> +</template> + +<script> + export default { + data() { + return { + visible: false, + time: '', + years: [], + months: [], + days: [], + hours: [], + minutes: [], + seconds: [], + year: 1900, + month: 0, + day: 0, + hour: 0, + minute: 0, + second: 0, + indicatorStyle: `height: 50px;`, + } + }, + props: { + type: { + type: String, + default: 'datetime-local' + }, + timestamp: { + type: Boolean, + default: false + }, + value: { + type: [String, Number], + default: '' + }, + maxYear: { + type: [Number, String], + default: 2100 + }, + minYear: { + type: [Number, String], + default: 1900 + } + }, + computed: { + ymd() { + return [this.year - this.minYear, this.month - 1, this.day - 1] + }, + hms() { + return [this.hour, this.minute, this.second] + } + }, + watch: { + value(newValue) { + this.parseValue(this.value) + this.initTime() + } + }, + created() { + this.form = this.getForm('uniForms') + this.formItem = this.getForm('uniFormsItem') + + if (this.formItem) { + if (this.formItem.name) { + this.rename = this.formItem.name + this.form.inputChildrens.push(this) + } + } + }, + mounted() { + const date = new Date() + for (let i = this.minYear; i <= this.maxYear; i++) { + this.years.push(i) + } + + for (let i = 1; i <= 12; i++) { + this.months.push(i) + } + + for (let i = 1; i <= 31; i++) { + this.days.push(i) + } + + for (let i = 0; i <= 23; i++) { + this.hours.push(i) + } + + for (let i = 0; i <= 59; i++) { + this.minutes.push(i) + } + + for (let i = 0; i <= 59; i++) { + this.seconds.push(i) + } + this.parseValue(this.value) + if (this.value) { + this.initTime() + } + + }, + methods: { + /** + * 获取父元素实例 + */ + getForm(name = 'uniForms') { + let parent = this.$parent; + let parentName = parent.$options.name; + while (parentName !== name) { + parent = parent.$parent; + if (!parent) return false + parentName = parent.$options.name; + } + return parent; + }, + parseDateTime(datetime) { + let defaultDate = null + if (!datetime) { + defaultDate = new Date() + } else { + defaultDate = new Date(datetime) + } + this.year = defaultDate.getFullYear() + if (this.year < this.minYear || this.year > this.maxYear) { + const now = Date.now() + this.parseDateTime(now) + return + } + this.month = defaultDate.getMonth() + 1 + this.day = defaultDate.getDate() + this.hour = defaultDate.getHours() + this.minute = defaultDate.getMinutes() + this.second = defaultDate.getSeconds() + }, + parseValue(defaultTime) { + if (Number(defaultTime)) { + defaultTime = parseInt(defaultTime) + } + this.parseDateTime(defaultTime) + }, + bindDateChange(e) { + const val = e.detail.value + this.year = this.years[val[0]] + this.month = this.months[val[1]] + this.day = this.days[val[2]] + }, + bindTimeChange(e) { + const val = e.detail.value + this.hour = this.hours[val[0]] + this.minute = this.minutes[val[1]] + this.second = this.seconds[val[2]] + }, + initTimePicker() { + // if (!this.time) { + // this.parseValue() + // } + this.parseValue(this.value) + this.visible = !this.visible + }, + tiggerTimePicker() { + this.visible = !this.visible + }, + clearTime() { + this.time = '' + this.tiggerTimePicker() + }, + initTime() { + this.time = this.createDomSting() + if (!this.timestamp) { + this.formItem && this.formItem.setValue(this.time) + this.$emit('change', this.time) + } else { + this.formItem && this.formItem.setValue(this.createTimeStamp(this.time)) + this.$emit('change', this.createTimeStamp(this.time)) + } + }, + setTime() { + this.initTime() + this.tiggerTimePicker() + }, + createTimeStamp(time) { + return Date.parse(new Date(time)) + }, + createDomSting() { + const yymmdd = this.year + + '-' + + (this.month < 10 ? '0' + this.month : this.month) + + '-' + + (this.day < 10 ? '0' + this.day : this.day) + + ' ' + + (this.hour < 10 ? '0' + this.hour : this.hour) + + ':' + + (this.minute < 10 ? '0' + this.minute : this.minute) + + ':' + + (this.second < 10 ? '0' + this.second : this.second) + + return yymmdd + } + } + } +</script> + +<style> + .uni-datetime-picker-view { + width: 100%; + height: 130px; + margin-top: 30px; + } + + .uni-datetime-picker-item { + line-height: 50px; + text-align: center; + } + + .uni-datetime-picker-btn { + margin-top: 60px; + display: flex; + justify-content: space-between; + color: blue; + cursor: pointer; + } + + .uni-datetime-picker-btn-group { + display: flex; + } + + .uni-datetime-picker-cancel { + margin-right: 30px; + } + + .uni-datetime-picker-mask { + position: fixed; + bottom: 0px; + top: 0px; + left: 0px; + right: 0px; + background-color: rgba(0, 0, 0, 0.4); + transition-duration: 0.3s; + z-index: 998; + } + + .uni-datetime-picker-popup { + border-radius: 8px; + padding: 30px; + width: 270px; + background-color: #fff; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + transition-duration: 0.3s; + z-index: 999; + } + + .uni-datetime-picker-time { + color: grey; + } + + .uni-datetime-picker-colon::after { + content: ':'; + position: absolute; + top: 53px; + right: 0; + } + + .uni-datetime-picker-hyphen::after { + content: '-'; + position: absolute; + top: 53px; + right: -2px; + } + + .uni-datetime-picker-timebox { + border: 1px solid #E5E5E5; + border-radius: 5px; + padding: 7px 10px; + box-sizing: border-box; + cursor: pointer; + } + + // 下箭头 + .uni-datetime-picker-down-arrow { + display :inline-block; + position: relative; + width: 20px; + height: 15px; + } + + .uni-datetime-picker-down-arrow::after { + display: inline-block; + content: " "; + height: 9px; + width: 9px; + border-width: 0 1px 1px 0; + border-color: #E5E5E5; + border-style: solid; + transform: matrix(0.71, 0.71, -0.71, 0.71, 0, 0); + transform-origin: center; + transition: transform .3s; + position: absolute; + top: 50%; + right: 5px; + margin-top: -5px; + } + .uni-datetime-picker-flex { + display: flex; + justify-content: space-between; + } +</style> diff --git a/hive-app/components/uni-drawer/uni-drawer.vue b/hive-app/components/uni-drawer/uni-drawer.vue new file mode 100644 index 0000000..ecf6b2f --- /dev/null +++ b/hive-app/components/uni-drawer/uni-drawer.vue @@ -0,0 +1,170 @@ +<template> + <view v-if="visibleSync" :class="{ 'uni-drawer--visible': showDrawer }" class="uni-drawer" @touchmove.stop.prevent="clear"> + <view class="uni-drawer__mask" :class="{ 'uni-drawer__mask--visible': showDrawer && mask }" @tap="close('mask')" /> + <view class="uni-drawer__content" :class="{'uni-drawer--right': rightMode,'uni-drawer--left': !rightMode, 'uni-drawer__content--visible': showDrawer}" :style="{width:drawerWidth+'px'}"> + <slot /> + </view> + </view> +</template> + +<script> + /** + * Drawer 抽屉 + * @description 抽屉侧滑菜单 + * @tutorial https://ext.dcloud.net.cn/plugin?id=26 + * @property {Boolean} mask = [true | false] 是否显示遮罩 + * @property {Boolean} maskClick = [true | false] 点击遮罩是否关闭 + * @property {Boolean} mode = [left | right] Drawer 滑出位置 + * @value left 从左侧滑出 + * @value right 从右侧侧滑出 + * @property {Number} width 抽屉的宽度 ,仅 vue 页面生效 + * @event {Function} close 组件关闭时触发事件 + */ + export default { + name: 'UniDrawer', + props: { + /** + * 显示模式(左、右),只在初始化生效 + */ + mode: { + type: String, + default: '' + }, + /** + * 蒙层显示状态 + */ + mask: { + type: Boolean, + default: true + }, + /** + * 遮罩是否可点击关闭 + */ + maskClick:{ + type: Boolean, + default: true + }, + /** + * 抽屉宽度 + */ + width: { + type: Number, + default: 220 + } + }, + data() { + return { + visibleSync: false, + showDrawer: false, + rightMode: false, + watchTimer: null, + drawerWidth: 220 + } + }, + created() { + // #ifndef APP-NVUE + this.drawerWidth = this.width + // #endif + this.rightMode = this.mode === 'right' + }, + methods: { + clear(){}, + close(type) { + // fixed by mehaotian 抽屉尚未完全关闭或遮罩禁止点击时不触发以下逻辑 + if((type === 'mask' && !this.maskClick) || !this.visibleSync) return + this._change('showDrawer', 'visibleSync', false) + }, + open() { + // fixed by mehaotian 处理重复点击打开的事件 + if(this.visibleSync) return + this._change('visibleSync', 'showDrawer', true) + }, + _change(param1, param2, status) { + this[param1] = status + if (this.watchTimer) { + clearTimeout(this.watchTimer) + } + this.watchTimer = setTimeout(() => { + this[param2] = status + this.$emit('change',status) + }, status ? 50 : 300) + } + } + } +</script> + +<style lang="scss" scoped> + // 抽屉宽度 + $drawer-width: 220px; + + .uni-drawer { + /* #ifndef APP-NVUE */ + display: block; + /* #endif */ + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + overflow: hidden; + z-index: 999; + } + + .uni-drawer__content { + /* #ifndef APP-NVUE */ + display: block; + /* #endif */ + position: absolute; + top: 0; + width: $drawer-width; + bottom: 0; + background-color: $uni-bg-color; + transition: transform 0.3s ease; + } + + .uni-drawer--left { + left: 0; + /* #ifdef APP-NVUE */ + transform: translateX(-$drawer-width); + /* #endif */ + /* #ifndef APP-NVUE */ + transform: translateX(-100%); + /* #endif */ + } + + .uni-drawer--right { + right: 0; + /* #ifdef APP-NVUE */ + transform: translateX($drawer-width); + /* #endif */ + /* #ifndef APP-NVUE */ + transform: translateX(100%); + /* #endif */ + } + + .uni-drawer__content--visible { + transform: translateX(0px); + } + + + .uni-drawer__mask { + /* #ifndef APP-NVUE */ + display: block; + /* #endif */ + opacity: 0; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background-color: $uni-bg-color-mask; + transition: opacity 0.3s; + } + + .uni-drawer__mask--visible { + /* #ifndef APP-NVUE */ + display: block; + /* #endif */ + opacity: 1; + } +</style> diff --git a/hive-app/components/uni-easyinput/common.js b/hive-app/components/uni-easyinput/common.js new file mode 100644 index 0000000..df9abe1 --- /dev/null +++ b/hive-app/components/uni-easyinput/common.js @@ -0,0 +1,56 @@ +/** + * @desc 函数防抖 + * @param func 目标函数 + * @param wait 延迟执行毫秒数 + * @param immediate true - 立即执行, false - 延迟执行 + */ +export const debounce = function(func, wait = 1000, immediate = true) { + let timer; + console.log(1); + return function() { + console.log(123); + let context = this, + args = arguments; + if (timer) clearTimeout(timer); + if (immediate) { + let callNow = !timer; + timer = setTimeout(() => { + timer = null; + }, wait); + if (callNow) func.apply(context, args); + } else { + timer = setTimeout(() => { + func.apply(context, args); + }, wait) + } + } +} +/** + * @desc 函数节流 + * @param func 函数 + * @param wait 延迟执行毫秒数 + * @param type 1 使用表时间戳,在时间段开始的时候触发 2 使用表定时器,在时间段结束的时候触发 + */ +export const throttle = (func, wait = 1000, type = 1) => { + let previous = 0; + let timeout; + return function() { + let context = this; + let args = arguments; + if (type === 1) { + let now = Date.now(); + + if (now - previous > wait) { + func.apply(context, args); + previous = now; + } + } else if (type === 2) { + if (!timeout) { + timeout = setTimeout(() => { + timeout = null; + func.apply(context, args) + }, wait) + } + } + } +} diff --git a/hive-app/components/uni-easyinput/uni-easyinput.vue b/hive-app/components/uni-easyinput/uni-easyinput.vue new file mode 100644 index 0000000..26bd7da --- /dev/null +++ b/hive-app/components/uni-easyinput/uni-easyinput.vue @@ -0,0 +1,384 @@ +<template> + <view class="uni-easyinput" :class="{'uni-easyinput-error':msg}"> + <view class="uni-easyinput__content" :class="{'is-input-border':inputBorder ,'is-input-error-border':inputBorder && msg,'is-textarea':type==='textarea','is-disabled':disabled}"> + <uni-icons v-if="prefixIcon" class="content-clear-icon" :type="prefixIcon" color="#c0c4cc"></uni-icons> + <textarea v-if="type === 'textarea'" class="uni-easyinput__content-textarea" :class="{'input-padding':inputBorder}" + :name="name" :value="val" :placeholder="placeholder" :placeholderStyle="placeholderStyle" :disabled="disabled" + :maxlength="inputMaxlength" :focus="focused" :autoHeight="autoHeight" @input="onInput" @blur="onBlur" @focus="onFocus" + @confirm="onConfirm"></textarea> + <input v-else :type="type === 'password'?'text':type" class="uni-easyinput__content-input" :style="{ + 'padding-right':type === 'password' ||clearable || prefixIcon?'':'10px', + 'padding-left':prefixIcon?'':'10px', + 'color':msg?'#dd524d':'' + }" + :name="name" :value="val" :password="!showPassword && type === 'password'" :placeholder="placeholder" + :placeholderStyle="placeholderStyle" :disabled="disabled" :maxlength="inputMaxlength" :focus="focused" @focus="onFocus" + @blur="onBlur" @input="onInput" @confirm="onConfirm" /> + <template v-if="type === 'password'"> + <uni-icons v-if="val != '' " class="content-clear-icon" :class="{'is-textarea-icon':type==='textarea'}" :type="showPassword?'eye-slash-filled':'eye-filled'" + :size="18" color="#c0c4cc" @click="onEyes"></uni-icons> + </template> + <template v-else-if="suffixIcon"> + <uni-icons v-if="suffixIcon" class="content-clear-icon" :type="suffixIcon" color="#c0c4cc"></uni-icons> + </template> + <template v-else> + <uni-icons class="content-clear-icon" :class="{'is-textarea-icon':type==='textarea'}" type="clear" :size="clearSize" + v-if="clearable && focused && val " color="#c0c4cc" @click="onClear"></uni-icons> + </template> + </view> + </view> +</template> + +<script> + /** + * Field 输入框 + * @description 此组件可以实现表单的输入与校验,包括 "text" 和 "textarea" 类型。 + * @tutorial https://ext.dcloud.net.cn/plugin?id=21001 + * @property {String| Number} value 输入内容 + * @property {String } type 输入框的类型(默认text) password/text/textarea/.. + * @value text 文本输入键盘 + * @value textarea 多行文本输入键盘 + * @value password 密码输入键盘 + * @value number 数字输入键盘,注意iOS上app-vue弹出的数字键盘并非9宫格方式 + * @value idcard 身份证输入键盘,信、支付宝、百度、QQ小程序 + * @value digit 带小数点的数字键盘 ,App的nvue页面、微信、支付宝、百度、头条、QQ小程序支持 + * @property {Boolean} clearable 是否显示右侧清空内容的图标控件(输入框有内容,且获得焦点时才显示),点击可清空输入框内容(默认true) + * @property {Boolean} autoHeight 是否自动增高输入区域,type为textarea时有效(默认true) + * @property {String } placeholder 输入框的提示文字 + * @property {String } placeholderStyle placeholder的样式(内联样式,字符串),如"color: #ddd" + * @property {Boolean} focus 是否自动获得焦点(默认false) + * @property {Boolean} disabled 是否不可输入(默认false) + * @property {Number } maxlength 最大输入长度,设置为 -1 的时候不限制最大长度(默认140) + * @property {String } confirmType 设置键盘右下角按钮的文字,仅在type="text"时生效(默认done) + * @property {Number } clearSize 清除图标的大小,单位px(默认15) + * @property {String} prefixIcon 输入框头部图标 + * @property {String} suffixIcon 输入框尾部图标 + * @property {Boolean} trim 是否自动去除两端的空格 + * @property {Boolean} inputBorder 是否显示input输入框的边框(默认false) + * @event {Function} input 输入框内容发生变化时触发 + * @event {Function} focus 输入框获得焦点时触发 + * @event {Function} blur 输入框失去焦点时触发 + * @event {Function} confirm 点击完成按钮时触发 + * @example <uni-easyinput v-model="mobile"></uni-easyinput> + */ + + import { + debounce, + throttle + } from './common.js' + + export default { + name: 'uni-easyinput', + props: { + name: String, + value: [Number, String], + type: { + type: String, + default: 'text' + }, + clearable: { + type: Boolean, + default: true + }, + autoHeight: { + type: Boolean, + default: false + }, + placeholder: String, + placeholderStyle: String, + focus: { + type: Boolean, + default: false + }, + disabled: { + type: Boolean, + default: false + }, + maxlength: { + type: [Number, String], + default: 140 + }, + confirmType: { + type: String, + default: 'done' + }, + // 清除按钮的大小 + clearSize: { + type: [Number, String], + default: 15 + }, + // 是否显示 input 边框 + inputBorder: { + type: Boolean, + default: true + }, + prefixIcon: { + type: String, + default: '' + }, + suffixIcon: { + type: String, + default: '' + }, + // 是否自动去除两端的空格 + trim: { + type: Boolean, + default: true + } + }, + data() { + return { + focused: false, + errMsg: '', + val: '', + showMsg: '', + border: false, + isFirstBorder: false, + showClearIcon: false, + showPassword: false + }; + }, + computed: { + msg() { + return this.errorMessage || this.errMsg; + }, + // 因为uniapp的input组件的maxlength组件必须要数值,这里转为数值,给用户可以传入字符串数值 + inputMaxlength() { + return Number(this.maxlength); + }, + }, + watch: { + value(newVal) { + this.val = newVal + if (this.formItem) { + this.formItem.setValue(newVal) + } + }, + focus(newVal) { + this.$nextTick(() => { + this.focused = this.focus + }) + } + }, + created() { + this.val = this.value + this.form = this.getForm('uniForms') + this.formItem = this.getForm('uniFormsItem') + if (this.formItem) { + if (this.formItem.name) { + this.rename = this.formItem.name + this.form.inputChildrens.push(this) + } + } + + }, + mounted() { + // this.onInput = throttle(this.input, 500) + this.$nextTick(() => { + this.focused = this.focus + }) + }, + methods: { + /** + * 初始化变量值 + */ + init() { + + }, + /** + * 获取父元素实例 + */ + getForm(name = 'uniForms') { + let parent = this.$parent; + let parentName = parent.$options.name; + while (parentName !== name) { + parent = parent.$parent; + if (!parent) return false; + parentName = parent.$options.name; + } + return parent; + }, + + onEyes() { + this.showPassword = !this.showPassword + }, + onInput(event) { + let value = event.detail.value; + // 判断是否去除空格 + if (this.trim) value = this.trimStr(value); + if (this.errMsg) this.errMsg = '' + this.val = value + this.$emit('input', value); + }, + + onFocus(event) { + this.focused = true; + this.$emit('focus', event); + }, + onBlur(event) { + let value = event.detail.value; + // 最开始使用的是监听图标@touchstart事件,自从hx2.8.4后,此方法在微信小程序出错 + // 这里改为监听点击事件,手点击清除图标时,同时也发生了@blur事件,导致图标消失而无法点击,这里做一个延时 + setTimeout(() => { + this.focused = false; + }, 100); + this.$emit('blur', event); + }, + onConfirm(e) { + this.$emit('confirm', e.detail.value); + }, + onClear(event) { + this.val = ''; + this.$emit('input', ''); + }, + fieldClick() { + this.$emit('click'); + }, + trimStr(str, pos = 'both') { + if (pos == 'both') { + return str.replace(/^\s+|\s+$/g, ''); + } else if (pos == 'left') { + return str.replace(/^\s*/, ''); + } else if (pos == 'right') { + return str.replace(/(\s*$)/g, ''); + } else if (pos == 'all') { + return str.replace(/\s+/g, ''); + } else { + return str; + } + } + } + }; +</script> + +<style lang="scss" scoped> + .uni-easyinput { + /* #ifndef APP-NVUE */ + width: 100%; + /* #endif */ + flex: 1; + position: relative; + // padding: 16px 14px; + text-align: left; + color: #333; + font-size: 14px; + } + + .uni-easyinput__content { + flex: 1; + /* #ifndef APP-NVUE */ + width: 100%; + display: flex; + /* #endif */ + flex-direction: row; + align-items: center; + box-sizing: border-box; + min-height: 36px; + } + + .uni-easyinput__content-input { + position: relative; + overflow: hidden; + flex: 1; + width: auto; + line-height: 2; + font-size: 14px; + // padding-right: 10px; + } + + .is-textarea { + align-items: flex-start; + } + + .is-textarea-icon { + margin-top: 5px; + } + + .uni-easyinput__content-textarea { + position: relative; + overflow: hidden; + flex: 1; + width: auto; + line-height: 1.5; + font-size: 14px; + // padding-right: 10px; + padding-top: 6px; + padding-bottom: 10px; + // box-sizing: border-box; + min-height: 80px; + height: 80px; + } + + .input-padding { + padding-left: 10px; + } + + .content-clear-icon { + padding: 0 5px; + } + + .label-icon { + margin-right: 5px; + margin-top: -1px; + } + + // 显示边框 + .is-input-border { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + align-items: center; + border: 1px solid $uni-border-color; + border-radius: 4px; + box-sizing: border-box; + } + + .uni-easyinput__right { + // margin-left: 5px; + } + + // 必填 + .is-required { + color: $uni-color-error; + } + + .uni-error-message { + position: absolute; + bottom: -17px; + left: 0; + line-height: 12px; + color: $uni-color-error; + font-size: 12px; + text-align: left; + } + + .uni-error-msg--boeder { + position: relative; + bottom: 0; + line-height: 22px; + } + + .is-input-error-border { + border-color: $uni-color-error; + } + + .uni-easyinput--border { + margin-bottom: 0; + padding: 10px 15px; + // padding-bottom: 0; + border-top: 1px #eee solid; + } + + .uni-easyinput-error { + padding-bottom: 0; + } + + .is-first-border { + border: none; + } + + .is-disabled { + background-color: #eee; + } +</style> diff --git a/hive-app/components/uni-fab/uni-fab.vue b/hive-app/components/uni-fab/uni-fab.vue new file mode 100644 index 0000000..c702895 --- /dev/null +++ b/hive-app/components/uni-fab/uni-fab.vue @@ -0,0 +1,433 @@ +<template> + <view> + <view v-if="popMenu && (leftBottom||rightBottom||leftTop||rightTop) && content.length > 0" :class="{ + 'uni-fab--leftBottom': leftBottom, + 'uni-fab--rightBottom': rightBottom, + 'uni-fab--leftTop': leftTop, + 'uni-fab--rightTop': rightTop + }" + class="uni-fab"> + <view :class="{ + 'uni-fab__content--left': horizontal === 'left', + 'uni-fab__content--right': horizontal === 'right', + 'uni-fab__content--flexDirection': direction === 'vertical', + 'uni-fab__content--flexDirectionStart': flexDirectionStart, + 'uni-fab__content--flexDirectionEnd': flexDirectionEnd, + 'uni-fab__content--other-platform': !isAndroidNvue + }" + :style="{ width: boxWidth, height: boxHeight, backgroundColor: styles.backgroundColor }" class="uni-fab__content" + elevation="5"> + <view v-if="flexDirectionStart || horizontalLeft" class="uni-fab__item uni-fab__item--first" /> + <view v-for="(item, index) in content" :key="index" :class="{ 'uni-fab__item--active': isShow }" class="uni-fab__item" + @click="_onItemClick(index, item)"> + <image :src="item.active ? item.selectedIconPath : item.iconPath" class="uni-fab__item-image" mode="widthFix" /> + <text class="uni-fab__item-text" :style="{ color: item.active ? styles.selectedColor : styles.color }">{{ item.text }}</text> + </view> + <view v-if="flexDirectionEnd || horizontalRight" class="uni-fab__item uni-fab__item--first" /> + </view> + </view> + <view :class="{ + 'uni-fab__circle--leftBottom': leftBottom, + 'uni-fab__circle--rightBottom': rightBottom, + 'uni-fab__circle--leftTop': leftTop, + 'uni-fab__circle--rightTop': rightTop, + 'uni-fab__content--other-platform': !isAndroidNvue + }" + class="uni-fab__circle uni-fab__plus" :style="{ 'background-color': styles.buttonColor }" @click="_onClick"> + <view class="fab-circle-v" :class="{'uni-fab__plus--active': isShow && content.length > 0}"></view> + <view class="fab-circle-h" :class="{'uni-fab__plus--active': isShow && content.length > 0}"></view> + </view> + </view> +</template> + +<script> + let platform = 'other' + // #ifdef APP-NVUE + platform = uni.getSystemInfoSync().platform + // #endif + + /** + * Fab 悬浮按钮 + * @description 点击可展开一个图形按钮菜单 + * @tutorial https://ext.dcloud.net.cn/plugin?id=144 + * @property {Object} pattern 可选样式配置项 + * @property {Object} horizontal = [left | right] 水平对齐方式 + * @value left 左对齐 + * @value right 右对齐 + * @property {Object} vertical = [bottom | top] 垂直对齐方式 + * @value bottom 下对齐 + * @value top 上对齐 + * @property {Object} direction = [horizontal | vertical] 展开菜单显示方式 + * @value horizontal 水平显示 + * @value vertical 垂直显示 + * @property {Array} content 展开菜单内容配置项 + * @property {Boolean} popMenu 是否使用弹出菜单 + * @event {Function} trigger 展开菜单点击事件,返回点击信息 + * @event {Function} fabClick 悬浮按钮点击事件 + */ + export default { + name: 'UniFab', + props: { + pattern: { + type: Object, + default () { + return {} + } + }, + horizontal: { + type: String, + default: 'left' + }, + vertical: { + type: String, + default: 'bottom' + }, + direction: { + type: String, + default: 'horizontal' + }, + content: { + type: Array, + default () { + return [] + } + }, + show: { + type: Boolean, + default: false + }, + popMenu: { + type: Boolean, + default: true + } + }, + data() { + return { + fabShow: false, + isShow: false, + isAndroidNvue: platform === 'android', + styles: { + color: '#3c3e49', + selectedColor: '#007AFF', + backgroundColor: '#fff', + buttonColor: '#3c3e49' + } + } + }, + computed: { + contentWidth(e) { + return (this.content.length + 1) * 55 + 10 + 'px' + }, + contentWidthMin() { + return 55 + 'px' + }, + // 动态计算宽度 + boxWidth() { + return this.getPosition(3, 'horizontal') + }, + // 动态计算高度 + boxHeight() { + return this.getPosition(3, 'vertical') + }, + // 计算左下位置 + leftBottom() { + return this.getPosition(0, 'left', 'bottom') + }, + // 计算右下位置 + rightBottom() { + return this.getPosition(0, 'right', 'bottom') + }, + // 计算左上位置 + leftTop() { + return this.getPosition(0, 'left', 'top') + }, + rightTop() { + return this.getPosition(0, 'right', 'top') + }, + flexDirectionStart() { + return this.getPosition(1, 'vertical', 'top') + }, + flexDirectionEnd() { + return this.getPosition(1, 'vertical', 'bottom') + }, + horizontalLeft() { + return this.getPosition(2, 'horizontal', 'left') + }, + horizontalRight() { + return this.getPosition(2, 'horizontal', 'right') + } + }, + watch: { + pattern(newValue, oldValue) { + //console.log(JSON.stringify(newValue)) + this.styles = Object.assign({}, this.styles, newValue) + } + }, + created() { + this.isShow = this.show + if (this.top === 0) { + this.fabShow = true + } + // 初始化样式 + this.styles = Object.assign({}, this.styles, this.pattern) + }, + methods: { + _onClick() { + this.$emit('fabClick') + if (!this.popMenu) { + return + } + this.isShow = !this.isShow + }, + open() { + this.isShow = true + }, + close() { + this.isShow = false + }, + /** + * 按钮点击事件 + */ + _onItemClick(index, item) { + this.$emit('trigger', { + index, + item + }) + }, + /** + * 获取 位置信息 + */ + getPosition(types, paramA, paramB) { + if (types === 0) { + return this.horizontal === paramA && this.vertical === paramB + } else if (types === 1) { + return this.direction === paramA && this.vertical === paramB + } else if (types === 2) { + return this.direction === paramA && this.horizontal === paramB + } else { + return this.isShow && this.direction === paramA ? this.contentWidth : this.contentWidthMin + } + } + } + } +</script> + +<style lang="scss" scoped> + .uni-fab { + position: fixed; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + justify-content: center; + align-items: center; + z-index: 10; + } + + .uni-fab--active { + opacity: 1; + } + + .uni-fab--leftBottom { + left: 5px; + bottom: 20px; + /* #ifdef H5 */ + bottom: calc(20px + var(--window-bottom)); + /* #endif */ + padding: 10px; + } + + .uni-fab--leftTop { + left: 5px; + top: 30px; + /* #ifdef H5 */ + top: calc(30px + var(--window-top)); + /* #endif */ + padding: 10px; + } + + .uni-fab--rightBottom { + right: 5px; + bottom: 20px; + /* #ifdef H5 */ + bottom: calc(20px + var(--window-bottom)); + /* #endif */ + padding: 10px; + } + + .uni-fab--rightTop { + right: 5px; + top: 30px; + /* #ifdef H5 */ + top: calc(30px + var(--window-top)); + /* #endif */ + padding: 10px; + } + + .uni-fab__circle { + position: fixed; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + justify-content: center; + align-items: center; + width: 55px; + height: 55px; + background-color: #3c3e49; + border-radius: 55px; + z-index: 11; + } + + .uni-fab__circle--leftBottom { + left: 15px; + bottom: 30px; + /* #ifdef H5 */ + bottom: calc(30px + var(--window-bottom)); + /* #endif */ + } + + .uni-fab__circle--leftTop { + left: 15px; + top: 40px; + /* #ifdef H5 */ + top: calc(40px + var(--window-top)); + /* #endif */ + } + + .uni-fab__circle--rightBottom { + right: 15px; + bottom: 30px; + /* #ifdef H5 */ + bottom: calc(30px + var(--window-bottom)); + /* #endif */ + } + + .uni-fab__circle--rightTop { + right: 15px; + top: 40px; + /* #ifdef H5 */ + top: calc(40px + var(--window-top)); + /* #endif */ + } + + .uni-fab__circle--left { + left: 0; + } + + .uni-fab__circle--right { + right: 0; + } + + .uni-fab__circle--top { + top: 0; + } + + .uni-fab__circle--bottom { + bottom: 0; + } + + .uni-fab__plus { + font-weight: bold; + } + + .fab-circle-v { + position: absolute; + width: 3px; + height: 31px; + left: 26px; + top: 12px; + background-color: white; + transform: rotate(0deg); + transition: transform 0.3s; + } + + .fab-circle-h { + position: absolute; + width: 31px; + height: 3px; + left: 12px; + top: 26px; + background-color: white; + transform: rotate(0deg); + transition: transform 0.3s; + } + + .uni-fab__plus--active { + transform: rotate(135deg); + } + + .uni-fab__content { + /* #ifndef APP-NVUE */ + box-sizing: border-box; + display: flex; + /* #endif */ + flex-direction: row; + border-radius: 55px; + overflow: hidden; + transition-property: width, height; + transition-duration: 0.2s; + width: 55px; + border-color: #DDDDDD; + border-width: 1rpx; + border-style: solid; + } + + .uni-fab__content--other-platform { + border-width: 0px; + box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.2); + } + + .uni-fab__content--left { + justify-content: flex-start; + } + + .uni-fab__content--right { + justify-content: flex-end; + } + + .uni-fab__content--flexDirection { + flex-direction: column; + justify-content: flex-end; + } + + .uni-fab__content--flexDirectionStart { + flex-direction: column; + justify-content: flex-start; + } + + .uni-fab__content--flexDirectionEnd { + flex-direction: column; + justify-content: flex-end; + } + + .uni-fab__item { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; + justify-content: center; + align-items: center; + width: 55px; + height: 55px; + opacity: 0; + transition: opacity 0.2s; + } + + .uni-fab__item--active { + opacity: 1; + } + + .uni-fab__item-image { + width: 25px; + height: 25px; + margin-bottom: 3px; + } + + .uni-fab__item-text { + color: #FFFFFF; + font-size: 12px; + } + + .uni-fab__item--first { + width: 55px; + } +</style> diff --git a/hive-app/components/uni-fav/uni-fav.vue b/hive-app/components/uni-fav/uni-fav.vue new file mode 100644 index 0000000..46a5ab8 --- /dev/null +++ b/hive-app/components/uni-fav/uni-fav.vue @@ -0,0 +1,140 @@ +<template> + <view :class="[circle === true || circle === 'true' ? 'uni-fav--circle' : '']" :style="[{ backgroundColor: checked ? bgColorChecked : bgColor }]" + @click="onClick" class="uni-fav"> + <!-- #ifdef MP-ALIPAY --> + <view class="uni-fav-star" v-if="!checked && (star === true || star === 'true')"> + <uni-icons :color="fgColor" :style="{color: checked ? fgColorChecked : fgColor}" size="14" type="star-filled" /> + </view> + <!-- #endif --> + <!-- #ifndef MP-ALIPAY --> + <uni-icons :color="fgColor" :style="{color: checked ? fgColorChecked : fgColor}" class="uni-fav-star" size="14" type="star-filled" + v-if="!checked && (star === true || star === 'true')" /> + <!-- #endif --> + <text :style="{color: checked ? fgColorChecked : fgColor}" class="uni-fav-text">{{ checked ? contentText.contentFav : contentText.contentDefault }}</text> + </view> +</template> + +<script> + import uniIcons from "../uni-icons/uni-icons.vue"; + + /** + * Fav 收藏按钮 + * @description 用于收藏功能,可点击切换选中、不选中的状态 + * @tutorial https://ext.dcloud.net.cn/plugin?id=864 + * @property {Boolean} star = [true|false] 按钮是否带星星 + * @property {String} bgColor 未收藏时的背景色 + * @property {String} bgColorChecked 已收藏时的背景色 + * @property {String} fgColor 未收藏时的文字颜色 + * @property {String} fgColorChecked 已收藏时的文字颜色 + * @property {Boolean} circle = [true|false] 是否为圆角 + * @property {Boolean} checked = [true|false] 是否为已收藏 + * @property {Object} contentText = [true|false] 收藏按钮文字 + * @event {Function} click 点击 fav按钮触发事件 + * @example <uni-fav :checked="true"/> + */ + export default { + name: "UniFav", + components: { + uniIcons + }, + props: { + star: { + type: [Boolean, String], + default: true + }, + bgColor: { + type: String, + default: "#eeeeee" + }, + fgColor: { + type: String, + default: "#666666" + }, + bgColorChecked: { + type: String, + default: "#007aff" + }, + fgColorChecked: { + type: String, + default: "#FFFFFF" + }, + circle: { + type: [Boolean, String], + default: false + }, + checked: { + type: Boolean, + default: false + }, + contentText: { + type: Object, + default () { + return { + contentDefault: "收藏", + contentFav: "已收藏" + }; + } + } + }, + watch: { + checked() { + if (uni.report) { + if (this.checked) { + uni.report("收藏", "收藏"); + } else { + uni.report("取消收藏", "取消收藏"); + } + } + } + }, + methods: { + onClick() { + this.$emit("click"); + } + } + }; +</script> + +<style lang="scss" scoped> + $fav-height: 25px; + + .uni-fav { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + align-items: center; + justify-content: center; + width: 60px; + height: $fav-height; + line-height: $fav-height; + text-align: center; + border-radius: 3px; + } + + .uni-fav--circle { + border-radius: 30px; + } + + .uni-fav-star { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + height: $fav-height; + line-height: 24px; + margin-right: 3px; + align-items: center; + justify-content: center; + } + + .uni-fav-text { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + height: $fav-height; + line-height: $fav-height; + align-items: center; + justify-content: center; + font-size: $uni-font-size-base; + } +</style> diff --git a/hive-app/components/uni-field/uni-field.vue b/hive-app/components/uni-field/uni-field.vue new file mode 100644 index 0000000..3884356 --- /dev/null +++ b/hive-app/components/uni-field/uni-field.vue @@ -0,0 +1,686 @@ +<template> + <view class="uni-field" :class="{ 'uni-border-top': borderTop, 'uni-border-bottom': borderBottom }" :style="[fieldStyle]"> + <view class="uni-field-inner" :class="[type == 'textarea' ? 'uni-textarea-inner' : '', 'uni-label-postion-' + labelPos]"> + <view :class="errorTop ? 'uni-error-in-label' : ''"> + <view class="uni-field-label" :class="[required ? 'uni-required' : '']" :style="{ + justifyContent: justifyContent, + width: labelWid + 'px', + marginBottom: labelMarginBottom + }"> + <view class="uni-icon-wrap" v-if="leftIcon"> + <uni-icons size="16" :type="leftIcon" :color="iconColor" /> + </view> + <slot name="leftIcon"></slot> + <text class="uni-label-text" :class="[leftIcon ? 'uni-label-left-gap' : '']">{{ label }}</text> + </view> + <view v-if="errorTop" class="uni-error-message" :style="{ paddingLeft: '4px' }">{{ msg }}</view> + </view> + <view class="fild-body" :class="[inputBorder ? 'uni-input-border' : '']" :style="[borderEixstTextareaStyle]"> + <view class="uni-flex-1 uni-flex" :style="[inputWrapStyle]"> + <textarea v-if="type == 'textarea'" class="uni-flex-1 uni-textarea-class" :name="name" :value="value" :placeholder="placeholder" + :placeholderStyle="placeholderStyle" :disabled="disabled" :maxlength="inputMaxlength" :focus="focus" :autoHeight="autoHeight" + @input="onInput" @blur="onBlur" @focus="onFocus" @confirm="onConfirm" @tap="fieldClick" /> + <input + v-else + :type="type" + class="uni-flex-1 uni-field__input-wrap" + :name="name" + :value="value" + :password="password || this.type === 'password'" + :placeholder="placeholder" + :placeholderStyle="placeholderStyle" + :disabled="disabled" + :maxlength="inputMaxlength" + :focus="focus" + :confirmType="confirmType" + @focus="onFocus" + @blur="onBlur" + @input="onInput" + @confirm="onConfirm" + @tap="fieldClick" + /> + <uni-icons :size="clearSize" v-if="clearable && value != ''" type="clear" color="#c0c4cc" @click="onClear" class="uni-clear-icon" /> + </view> + <view class="uni-button-wrap"><slot name="right" /></view> + <uni-icons v-if="rightIcon" size="16" @click="rightIconClick" :type="rightIcon" color="#c0c4cc" :style="[rightIconStyle]" /> + </view> + </view> + <view + v-if="errorBottom" + class="uni-error-message" + :style="{ + paddingLeft: Number(labelWid) + 4 + 'px' + }" + > + {{ msg }} + </view> + </view> +</template> + +<script> +/** + * Field 输入框 + * @description 此组件可以实现表单的输入与校验,包括 "text" 和 "textarea" 类型。 + * @tutorial https://ext.dcloud.net.cn/plugin?id=21001 + * @property {String } type 输入框的类型(默认text) + * @property {Boolean} required 是否必填,左边您显示红色"*"号(默认false) + * @property {String } leftIcon label左边的图标,限uni-ui的图标名称 + * @property {String } iconColor 左边通过icon配置的图标的颜色(默认#606266) + * @property {Boolean} rightIcon 输入框右边的图标名称,限uni-ui的图标名称(默认false) + * @property {String } label 输入框左边的文字提示 + * @property {Number } labelWidth label的宽度,单位px(默认65) + * @property {String } labelAlign label的文字对齐方式(默认left) + * @property {String } labelPosition label的文字的位置(默认left) + * @property {Boolean} clearable 是否显示右侧清空内容的图标控件(输入框有内容,且获得焦点时才显示),点击可清空输入框内容(默认true) + * @property {String } placeholder 输入框的提示文字 + * @property {String } placeholderStyle placeholder的样式(内联样式,字符串),如"color: #ddd" + * @property {Boolean} password 是否密码输入方式(用点替换文字),type为text时有效(默认false) + * @property {Boolean} focus 是否自动获得焦点(默认false) + * @property {Boolean} disabled 是否不可输入(默认false) + * @property {Number } maxlength 最大输入长度,设置为 -1 的时候不限制最大长度(默认140) + * @property {String } confirmType 设置键盘右下角按钮的文字,仅在type="text"时生效(默认done) + * @property {String } errorMessage 显示的错误提示内容,如果为空字符串或者false,则不显示错误信息 + * @property {Number } clearSize 清除图标的大小,单位px(默认15) + * @property {Boolean} trim 是否自动去除两端的空格 + * @property {String } name 表单域的属性名,在使用校验规则时必填 + * @property {Array } rules 单行表单验证规则,接受一个数组 + * @property {Boolean} inputBorder 是否显示input输入框的边框(默认false) + * @property {Boolean} border-bottom 是否显示field的下边框(默认true) + * @property {Boolean} border-top 是否显示field的上边框(默认false) + * @property {Boolean} auto-height 是否自动增高输入区域,type为textarea时有效(默认true) + * @event {Function} input 输入框内容发生变化时触发 + * @event {Function} focus 输入框获得焦点时触发 + * @event {Function} blur 输入框失去焦点时触发 + * @event {Function} confirm 点击完成按钮时触发 + * @event {Function} right-icon-click 通过right-icon生成的图标被点击时触发 + * @event {Function} click 输入框被点击或者通过right-icon生成的图标被点击时触发,这样设计是考虑到传递右边的图标,一般都为需要弹出"picker"等操作时的场景,点击倒三角图标,理应发出此事件,见上方说明 + * @example <uni-field v-model="mobile" label="手机号" required :error-message="errorMessage"></uni-field> + */ +export default { + name: 'uni-field', + props: { + // rules:{ + // type:Array, + // default(){ + // return [] + // } + // }, + trigger: { + type: String, + default: '' + }, + leftIcon: String, + rightIcon: String, + required: Boolean, + label: String, + password: Boolean, + clearable: { + type: Boolean, + default: true + }, + // 左边标题的宽度单位px + labelWidth: { + type: [Number, String], + default: '' + }, + // 对齐方式,left|center|right + labelAlign: { + type: String, + default: '' + }, + iconColor: { + type: String, + default: '#606266' + }, + autoHeight: { + type: Boolean, + default: true + }, + errorMessage: { + type: [String, Boolean], + default: '' + }, + placeholder: String, + placeholderStyle: String, + focus: Boolean, + name: String, + value: [Number, String], + type: { + type: String, + default: 'text' + }, + disabled: { + type: Boolean, + default: false + }, + maxlength: { + type: [Number, String], + default: 140 + }, + confirmType: { + type: String, + default: 'done' + }, + // lable的位置,可选为 left-左边,top-上边 + labelPosition: { + type: String, + default: '' + }, + // 清除按钮的大小 + clearSize: { + type: [Number, String], + default: 15 + }, + // 是否显示 input 边框 + inputBorder: { + type: Boolean, + default: false + }, + // 是否显示上边框 + borderTop: { + type: Boolean, + default: false + }, + // 是否显示下边框 + borderBottom: { + type: Boolean, + default: true + }, + // 是否自动去除两端的空格 + trim: { + type: Boolean, + default: true + } + }, + data() { + return { + focused: false, + itemIndex: 0, + errorTop: false, + errorBottom: false, + labelMarginBottom: '', + errorWidth: '', + errMsg: '', + errorBorderColor: false, + val: '', + labelPos: '', + labelWid: '', + labelAli: '' + }; + }, + computed: { + msg() { + return this.errorMessage || this.errMsg; + }, + fieldStyle() { + let style = {}; + if (this.labelPos === 'top') { + style.padding = '10px 14px'; + this.labelMarginBottom = '6px'; + } + if (this.labelPos === 'left' && this.msg !== false && this.msg !== '') { + style.paddingBottom = '0px'; + this.errorBottom = true; + this.errorTop = false; + } else if (this.labelPos === 'top' && this.msg !== false && this.msg !== '') { + this.errorBottom = false; + this.errorTop = true; + } else { + // style.paddingBottom = '' + this.errorTop = false; + this.errorBottom = false; + } + return style; + }, + + borderEixstTextareaStyle() { + let style = {}; + if (this.inputBorder) { + if (this.type === 'textarea') { + style.minHeight = '60px'; + } + if (this.msg !== false && this.msg != '') { + style.borderColor = '#dd524d'; + } + } + return style; + }, + + inputWrapStyle() { + let style = {}; + // 判断lable的位置,如果是left的话,让input左边两边有间隙 + if (this.labelPos == 'left') { + style.margin = `0 4px`; + } else { + // 如果lable是top的,input的左边就没必要有间隙了 + style.marginRight = `4px`; + // this.fieldStyle.style.padding = '10px 14px' + } + return style; + }, + rightIconStyle() { + let style = {}; + if (this.arrowDirection == 'top') style.transform = 'roate(-90deg)'; + if (this.arrowDirection == 'bottom') style.transform = 'roate(90deg)'; + else style.transform = 'roate(0deg)'; + return style; + }, + labelStyle() { + let style = {}; + if (this.labelAli == 'left') style.justifyContent = 'flext-start'; + if (this.labelAli == 'center') style.justifyContent = 'center'; + if (this.labelAli == 'right') style.justifyContent = 'flext-end'; + return style; + }, + // uni不支持在computed中写style.justifyContent = 'center'的形式,故用此方法 + justifyContent() { + if (this.labelAli == 'left') return 'flex-start'; + if (this.labelAli == 'center') return 'center'; + if (this.labelAli == 'right') return 'flex-end'; + }, + // 因为uniapp的input组件的maxlength组件必须要数值,这里转为数值,给用户可以传入字符串数值 + inputMaxlength() { + return Number(this.maxlength); + }, + // label的位置 + fieldInnerStyle() { + let style = {}; + if (this.labelPos == 'left') { + style.flexDirection = 'row'; + } else { + style.flexDirection = 'column'; + } + + return style; + } + }, + watch: { + trigger(trigger) { + this.formTrigger = trigger; + } + }, + created() { + this.form = this.getForm(); + this.formRules = []; + this.formTrigger = this.trigger; + this.init(); + }, + methods: { + /** + * 初始化变量值 + */ + init() { + if (this.form) { + this.form.childrens.push(this); + this.labelPos = this.labelPosition ? this.labelPosition : this.form.labelPosition; + this.labelWid = this.labelWidth ? this.labelWidth : this.form.labelWidth; + this.labelAli = this.labelAlign ? this.labelAlign : this.form.labelAlign; + + if (this.form.formRules) { + this.formRules = this.form.formRules[this.name]; + } + this.validator = this.form.validator; + if(this.name){ + this.form.formData[this.name] = this.value || ''; + } + } else { + this.labelPos = this.labelPosition || 'left'; + this.labelWid = this.labelWidth || 65; + this.labelAli = this.labelAlign || 'left'; + } + }, + /** + * 获取父元素实例 + */ + getForm() { + let parent = this.$parent; + let parentName = parent.$options.name; + while (parentName !== 'uniForms') { + parent = parent.$parent; + if (!parent) return false; + parentName = parent.$options.name; + } + return parent; + }, + + /** + * 移除该表单项的校验结果 + */ + clearValidate() { + this.errMsg = ''; + }, + /** + * 父组件处理函数 + * @param {Object} callback + */ + parentVal(callback) { + if (this.type === 'number') { + this.val = this.val === '' ? this.val : Number(this.val); + } + typeof callback === 'function' && + callback( + { + [this.name]: this.val + }, + this.name + ); + }, + /** + * 触发校验 + * @param {Object} trigger + * @param {Object} value + */ + triggerValidator(trigger, value) { + let isValid = false; + // 如果 name 不存在,则不开启校验 + this.formRules && + this.formRules.rules && + this.formRules.rules.forEach(item => { + item.trigger = this.isTrigger(this.form.formTrigger, this.formTrigger, item.trigger); + if (item.trigger !== trigger || item.trigger === 'submit') return; + isValid = true; + }); + + isValid && this.triggerCheck(value); + }, + /** + * 校验规则 + * @param {Object} value + */ + triggerCheck(value, item) { + // 输入值为 number + if (this.type === 'number') { + value = value === '' ? value : Number(value); + } + const result = this.validator.validateUpdate({ + [this.name]: value + }); + this.errMsg = !result ? '' : result.errorMessage; + this.form.validateCheck(result); + }, + /** + * 触发时机 + * @param {Object} event + */ + isTrigger(parentRule, itemRlue, rule) { + let rl = 'none'; + if (rule) { + rl = rule; + } else if (itemRlue) { + rl = itemRlue; + } else if (parentRule) { + rl = parentRule; + } else { + rl = 'blur'; + } + return rl; + }, + + onInput(event) { + let value = event.detail.value; + // 判断是否去除空格 + if (this.trim) value = this.trimStr(value); + this.form.formData[this.name] = value || ''; + this.val = value; + this.$emit('input', value); + // 校验输入 + this.triggerValidator('change', value); + }, + + onFocus(event) { + this.focused = true; + this.$emit('focus', event); + }, + onBlur(event) { + let value = event.detail.value; + // 最开始使用的是监听图标@touchstart事件,自从hx2.8.4后,此方法在微信小程序出错 + // 这里改为监听点击事件,手点击清除图标时,同时也发生了@blur事件,导致图标消失而无法点击,这里做一个延时 + setTimeout(() => { + this.focused = false; + }, 100); + this.$emit('blur', event); + + // 校验输入 + this.triggerValidator('blur', value); + }, + onConfirm(e) { + this.$emit('confirm', e.detail.value); + }, + onClear(event) { + this.val = ''; + this.$emit('input', ''); + this.clearValidate(); + }, + rightIconClick() { + this.$emit('right-icon-click'); + this.$emit('click'); + }, + fieldClick() { + this.$emit('click'); + }, + trimStr(str, pos = 'both') { + if (pos == 'both') { + return str.replace(/^\s+|\s+$/g, ''); + } else if (pos == 'left') { + return str.replace(/^\s*/, ''); + } else if (pos == 'right') { + return str.replace(/(\s*$)/g, ''); + } else if (pos == 'all') { + return str.replace(/\s+/g, ''); + } else { + return str; + } + } + } +}; +</script> + +<style lang="scss" scoped> +.uni-field { + padding: 16px 14px; + text-align: left; + color: #333; + font-size: 14px; + background-color: #fff; +} + +.uni-field-inner { + display: flex; + align-items: center; +} + +.uni-textarea-inner { + align-items: flex-start; +} + +.uni-textarea-class { + min-height: 48px; + width: auto; + font-size: 14px; +} + +.fild-body { + width: 100%; + display: flex; + flex: 1; + align-items: center; +} + +.uni-arror-right { + margin-left: 4px; +} + +.uni-label-text { + display: inline-block; +} + +.uni-label-left-gap { + margin-left: 3px; +} + +.uni-label-postion-top { + flex-direction: column; + align-items: flex-start; + flex: 1; +} + +.uni-field-label { + width: 65px; + flex: 1 1 65px; + text-align: left; + position: relative; + display: flex; + align-items: center; +} + +.uni-required::before { + content: '*'; + position: absolute; + left: -8px; + font-size: 14px; + color: $uni-color-error; + height: 9px; + line-height: 1; +} + +.uni-field__input-wrap { + position: relative; + overflow: hidden; + font-size: 14px; + height: 24px; + flex: 1; + width: auto; +} + +.uni-clear-icon { + display: flex; + align-items: center; +} + +.uni-error-message { + line-height: 12px; + padding-top: 2px; + padding-bottom: 2px; + color: $uni-color-error; + font-size: 12px; + text-align: left; +} + +.uni-input-error-border { + border-color: $uni-color-error; +} + +.placeholder-style { + color: rgb(150, 151, 153); +} + +.uni-input-class { + font-size: 14px; +} + +.uni-button-wrap { + margin-left: 4px; +} + +/* start--Retina 屏幕下的 1px 边框--start */ +.uni-border, +.uni-border-bottom, +.uni-border-left, +.uni-border-right, +.uni-border-top, +.uni-border-top-bottom { + position: relative; +} + +.uni-border-bottom:after, +.uni-border-left:after, +.uni-border-right:after, +.uni-border-top-bottom:after, +.uni-border-top:after, +.uni-border:after { + /* #ifndef APP-NVUE */ + content: ' '; + /* #endif */ + position: absolute; + left: 0; + top: 0; + pointer-events: none; + box-sizing: border-box; + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + // 多加0.1%,能解决有时候边框缺失的问题 + width: 199.8%; + height: 199.7%; + transform: scale(0.5, 0.5); + border: 0 solid $uni-border-color; + z-index: 2; +} + +.uni-input-border { + min-height: 34px; + padding-left: 4px; + border: 1px solid $uni-border-color; + border-radius: 6px; + box-sizing: border-box; +} + +.uni-border-top:after { + border-top-width: 1px; +} + +.uni-border-left:after { + border-left-width: 1px; +} + +.uni-border-right:after { + border-right-width: 1px; +} + +.uni-border-bottom:after { + border-bottom-width: 1px; +} + +.uni-border-top-bottom:after { + border-width: 1px 0; +} + +.uni-border:after { + border-width: 1px; +} +/* end--Retina 屏幕下的 1px 边框--end */ + +.uni-icon-wrap { + padding-left: 3px; + padding-right: 3px; + display: flex; + align-items: center; + justify-content: center; +} + +.uni-button-wrap { + display: flex; + align-items: right; + justify-content: center; +} +.uni-clear-icon { + display: flex; + align-items: center; + margin-left: 4px; +} + +.uni-flex { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + align-items: center; +} + +.uni-flex-1 { + flex: 1; +} +.uni-error-in-label { + display: flex; + flex-direction: row; +} +</style> diff --git a/hive-app/components/uni-forms-item/uni-forms-item.vue b/hive-app/components/uni-forms-item/uni-forms-item.vue new file mode 100644 index 0000000..c78ce5c --- /dev/null +++ b/hive-app/components/uni-forms-item/uni-forms-item.vue @@ -0,0 +1,437 @@ +<template> + <view class="uni-forms-item" :class="{'uni-forms-item--border':border,'is-first-border':border&&isFirstBorder,'uni-forms-item-error':msg}"> + <view class="uni-forms-item__inner" :class="['is-direction-'+labelPos,]"> + <view v-if="label" class="uni-forms-item__label" :style="{width:labelWid+'px',justifyContent: justifyContent}"> + <slot name="left"> + <uni-icons v-if="leftIcon" class="label-icon" size="16" :type="leftIcon" :color="iconColor" /> + <text>{{label}}</text> + <text v-if="required" class="is-required">*</text> + </slot> + </view> + <view class="uni-forms-item__content" :class="{'is-input-error-border': msg}"> + <slot></slot> + </view> + </view> + <view class="uni-error-message" :class="{'uni-error-msg--boeder':border}" :style="{ + paddingLeft: (labelPos === 'left'? Number(labelWid)+5:5) + 'px' + }">{{ showMsg === 'undertext' ? msg:'' }}</view> + </view> +</template> + +<script> + /** + * Field 输入框 + * @description 此组件可以实现表单的输入与校验,包括 "text" 和 "textarea" 类型。 + * @tutorial https://ext.dcloud.net.cn/plugin?id=21001 + * @property {Boolean} required 是否必填,左边显示红色"*"号(默认false) + * @property {String} validateTrigger = [bind|submit] 校验触发器方式 默认 submit 可选 + * @value bind 发生变化时触发 + * @value submit 提交时触发 + * @property {String } leftIcon label左边的图标,限 uni-ui 的图标名称 + * @property {String } iconColor 左边通过icon配置的图标的颜色(默认#606266) + * @property {String } label 输入框左边的文字提示 + * @property {Number } labelWidth label的宽度,单位px(默认65) + * @property {String } labelAlign = [left|center|right] label的文字对齐方式(默认left) + * @value left label 左侧显示 + * @value center label 居中 + * @value right label 右侧对齐 + * @property {String } labelPosition = [top|left] label的文字的位置(默认left) + * @value top 顶部显示 label + * @value left 左侧显示 label + * @property {String } errorMessage 显示的错误提示内容,如果为空字符串或者false,则不显示错误信息 + * @property {String } name 表单域的属性名,在使用校验规则时必填 + */ + + + + export default { + name: "uniFormsItem", + props: { + // 自定义内容 + custom: { + type: Boolean, + default: false + }, + // 是否显示报错信息 + showMessage: { + type: Boolean, + default: true + }, + name: String, + required: Boolean, + validateTrigger: { + type: String, + default: '' + }, + leftIcon: String, + iconColor: { + type: String, + default: '#606266' + }, + label: String, + // 左边标题的宽度单位px + labelWidth: { + type: [Number, String], + default: '' + }, + // 对齐方式,left|center|right + labelAlign: { + type: String, + default: '' + }, + // lable的位置,可选为 left-左边,top-上边 + labelPosition: { + type: String, + default: '' + }, + errorMessage: { + type: [String, Boolean], + default: '' + } + }, + data() { + return { + errorTop: false, + errorBottom: false, + labelMarginBottom: '', + errorWidth: '', + errMsg: '', + val: '', + labelPos: '', + labelWid: '', + labelAli: '', + showMsg: 'undertext', + border: false, + isFirstBorder: false + }; + }, + computed: { + msg() { + return this.errorMessage || this.errMsg; + }, + fieldStyle() { + let style = {} + if (this.labelPos == 'top') { + style.padding = '0 0' + this.labelMarginBottom = '6px' + } + if (this.labelPos == 'left' && this.msg !== false && this.msg != '') { + style.paddingBottom = '0px' + this.errorBottom = true + this.errorTop = false + } else if (this.labelPos == 'top' && this.msg !== false && this.msg != '') { + this.errorBottom = false + this.errorTop = true + } else { + // style.paddingBottom = '' + this.errorTop = false + this.errorBottom = false + } + return style + }, + + // uni不支持在computed中写style.justifyContent = 'center'的形式,故用此方法 + justifyContent() { + if (this.labelAli === 'left') return 'flex-start'; + if (this.labelAli === 'center') return 'center'; + if (this.labelAli === 'right') return 'flex-end'; + } + + }, + watch: { + validateTrigger(trigger) { + this.formTrigger = trigger + } + }, + created() { + this.form = this.getForm() + this.group = this.getForm('uniGroup') + this.formRules = [] + this.formTrigger = this.validateTrigger + // if (this.form) { + this.form.childrens.push(this) + // } + this.init() + }, + destroyed() { + if (this.form) { + this.form.childrens.forEach((item, index) => { + if (item === this) { + this.form.childrens.splice(index, 1) + } + }) + } + }, + methods: { + init() { + if (this.form) { + let { + formRules, + validator, + formData, + value, + labelPosition, + labelWidth, + labelAlign, + errShowType + } = this.form + + this.labelPos = this.labelPosition ? this.labelPosition : labelPosition + this.labelWid = this.label ? (this.labelWidth ? this.labelWidth : labelWidth):0 + this.labelAli = this.labelAlign ? this.labelAlign : labelAlign + console.log(this.labelWid); + // 判断第一个 item + if (!this.form.isFirstBorder) { + this.form.isFirstBorder = true + this.isFirstBorder = true + } + // 判断 group 里的第一个 item + if (this.group) { + if (!this.group.isFirstBorder) { + this.group.isFirstBorder = true + this.isFirstBorder = true + } + } + + this.border = this.form.border + this.showMsg = errShowType + + if (formRules) { + this.formRules = formRules[this.name] || {} + } + + this.validator = validator + + if (this.name) { + formData[this.name] = value.hasOwnProperty(this.name) ? value[this.name] : this.form._getValue(this, '') + } + } else { + this.labelPos = this.labelPosition || 'left' + this.labelWid = this.labelWidth || 65 + this.labelAli = this.labelAlign || 'left' + } + }, + /** + * 获取父元素实例 + */ + getForm(name = 'uniForms') { + let parent = this.$parent; + let parentName = parent.$options.name; + while (parentName !== name) { + parent = parent.$parent; + if (!parent) return false + parentName = parent.$options.name; + } + return parent; + }, + /** + * 移除该表单项的校验结果 + */ + clearValidate() { + this.errMsg = '' + }, + setValue(value){ + if (this.name) { + if(this.errMsg) this.errMsg = '' + this.form.formData[this.name] = this.form._getValue(this, value) + } + }, + /** + * 校验规则 + * @param {Object} value + */ + async triggerCheck(value, callback) { + let promise = null; + this.errMsg = '' + // if no callback, return promise + if (callback && typeof callback !== 'function' && Promise) { + promise = new Promise((resolve, reject) => { + callback = function(valid) { + !valid ? resolve(valid) : reject(valid) + }; + }); + } + + if (!this.validator) { + typeof callback === 'function' && callback(null); + if (promise) return promise + } + + const isNoField = this.isRequired(this.formRules.rules || []) + + + let isTrigger = this.isTrigger(this.formRules.validateTrigger, this.validateTrigger, this.form.validateTrigger) + + let result = null + + if (!(!isTrigger)) { + result = this.validator && (await this.validator.validateUpdate({ + [this.name]: value + }, this.form.formData)) + } + // 判断是否必填 + if (!isNoField && !value) { + result = null + } + + if (isTrigger && result && result.errorMessage) { + if (this.form.errShowType === 'toast') { + uni.showToast({ + title: result.errorMessage || '校验错误', + icon: 'none' + }) + } + if (this.form.errShowType === 'modal') { + uni.showModal({ + title: '提示', + content: result.errorMessage || '校验错误' + }) + } + } + + this.errMsg = !result ? '' : result.errorMessage + this.form.validateCheck(result ? result : null) + typeof callback === 'function' && callback(result ? result : null); + if (promise) return promise + + }, + /** + * 触发时机 + * @param {Object} event + */ + isTrigger(rule, itemRlue, parentRule) { + let rl = true; + // bind submit + if (rule === 'submit' || !rule) { + if (rule === undefined) { + if (itemRlue !== 'bind') { + if (!itemRlue) { + return parentRule === 'bind' ? true : false + } + return false + } + return true + } + return false + } + return true; + }, + // 是否有必填字段 + isRequired(rules) { + let isNoField = false + for (let i = 0; i < rules.length; i++) { + const ruleData = rules[i] + if (ruleData.required) { + isNoField = true + break + } + } + return isNoField + } + } + }; +</script> + +<style lang="scss" scoped> + .uni-forms-item { + position: relative; + // padding: 16px 14px; + text-align: left; + color: #333; + font-size: 14px; + margin-bottom: 22px; + background-color: #fff; + } + + .uni-forms-item__inner { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + // flex-direction: row; + // align-items: center; + } + + .is-direction-left { + flex-direction: row; + } + + .is-direction-top { + flex-direction: column; + } + + .uni-forms-item__label { + /* #ifndef APP-NVUE */ + display: flex; + flex-shrink: 0; + /* #endif */ + flex-direction: row; + align-items: center; + font-size: 14px; + color: #333; + width: 65px; + // line-height: 2; + // margin-top: 3px; + padding: 5px 0; + box-sizing: border-box; + height: 36px; + margin-right: 5px; + } + + .uni-forms-item__content { + /* #ifndef APP-NVUE */ + width: 100%; + // display: flex; + /* #endif */ + // flex: 1; + // flex-direction: row; + // align-items: center; + box-sizing: border-box; + min-height: 36px; + } + + + .label-icon { + margin-right: 5px; + margin-top: -1px; + } + + // 必填 + .is-required { + color: $uni-color-error; + } + + .uni-error-message { + position: absolute; + bottom: -17px; + left: 0; + line-height: 12px; + color: $uni-color-error; + font-size: 12px; + text-align: left; + } + + .uni-error-msg--boeder { + position: relative; + bottom: 0; + line-height: 22px; + } + + .is-input-error-border { + border-color: $uni-color-error; + } + + .uni-forms-item--border { + margin-bottom: 0; + padding: 10px 15px; + // padding-bottom: 0; + border-top: 1px #eee solid; + } + + .uni-forms-item-error { + padding-bottom: 0; + } + + .is-first-border { + border: none; + } +</style> diff --git a/hive-app/components/uni-forms/uni-forms.vue b/hive-app/components/uni-forms/uni-forms.vue new file mode 100644 index 0000000..5065cde --- /dev/null +++ b/hive-app/components/uni-forms/uni-forms.vue @@ -0,0 +1,420 @@ +<template> + <!-- --> + <view class="uni-forms" :class="{'uni-forms--top':!border}"> + <form @submit.stop="submitForm" @reset="resetForm"> + <slot></slot> + </form> + </view> +</template> + +<script> + /** + * Forms 表单 + * @description 由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据 + * @tutorial https://ext.dcloud.net.cn/plugin?id=2773 + * @property {Object} rules 表单校验规则 + * @property {String} validateTrigger = [bind|submit] 校验触发器方式 默认 submit 可选 + * @value bind 发生变化时触发 + * @value submit 提交时触发 + * @property {String} labelPosition = [top|left] label 位置 默认 left 可选 + * @value top 顶部显示 label + * @value left 左侧显示 label + * @property {String} labelWidth label 宽度,默认 65px + * @property {String} labelAlign = [left|center|right] label 居中方式 默认 left 可选 + * @value left label 左侧显示 + * @value center label 居中 + * @value right label 右侧对齐 + * @property {String} errShowType = [undertext|toast|modal] 校验错误信息提示方式 + * @value undertext 错误信息在底部显示 + * @value toast 错误信息toast显示 + * @value modal 错误信息modal显示 + * @event {Function} submit 提交时触发 + */ + import Vue from 'vue' + Vue.prototype.binddata = function(name, value, formName) { + if (formName) { + this.$refs[formName].setValue(name, value) + } else { + let formVm + for (let i in this.$refs) { + const vm = this.$refs[i] + if (vm && vm.$options && vm.$options.name === 'uniForms') { + formVm = vm + break + } + } + if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性') + formVm.setValue(name, value) + } + } + + import Validator from './validate.js' + + export default { + name: 'uniForms', + props: { + value: { + type: Object, + default () { + return {} + } + }, + // 表单校验规则 + rules: { + type: Object, + default () { + return {} + } + }, + // 校验触发器方式,默认 关闭 + validateTrigger: { + type: String, + default: '' + }, + // label 位置,可选值 top/left + labelPosition: { + type: String, + default: 'left' + }, + // label 宽度,单位 px + labelWidth: { + type: [String, Number], + default: 65 + }, + // label 居中方式,可选值 left/center/right + labelAlign: { + type: String, + default: 'left' + }, + errShowType: { + type: String, + default: 'undertext' + }, + border: { + type: Boolean, + default: false + } + }, + data() { + return { + formData: {} + }; + }, + watch: { + rules(newVal) { + this.init(newVal) + }, + trigger(trigger) { + this.formTrigger = trigger + }, + value: { + handler(newVal) { + if (this.isChildEdit) { + this.isChildEdit = false + return + } + this.childrens.forEach((item) => { + if (item.name) { + const formDataValue = newVal.hasOwnProperty(item.name) ? newVal[item.name] : null + this.formData[item.name] = this._getValue(item, formDataValue) + } + }) + }, + deep: true + } + }, + created() { + let _this = this + this.childrens = [] + this.inputChildrens = [] + this.checkboxChildrens = [] + this.formRules = [] + this.init(this.rules) + }, + methods: { + init(formRules) { + if (Object.keys(formRules).length > 0) { + this.formTrigger = this.trigger + this.formRules = formRules + if (!this.validator) { + this.validator = new Validator(formRules) + } + } + this.childrens.forEach((item) => { + item.init() + }) + }, + /** + * 设置校验规则 + * @param {Object} formRules + */ + setRules(formRules) { + this.init(formRules) + }, + /** + * 公开给用户使用 + * 设置自定义表单组件 value 值 + * @param {String} name 字段名称 + * @param {String} value 字段值 + */ + setValue(name, value, callback) { + let example = this.childrens.find(child => child.name === name) + if (!example) return null + this.isChildEdit = true + value = this._getValue(example, value) + this.formData[name] = value + example.val = value + this.$emit('input', Object.assign({}, this.value, this.formData)) + return example.triggerCheck(value, callback) + }, + + /** + * TODO 表单提交, 小程序暂不支持这种用法 + * @param {Object} event + */ + submitForm(event) { + const value = event.detail.value + return this.validateAll(value || this.formData, 'submit') + }, + /** + * 表单重置 + * @param {Object} event + */ + resetForm(event) { + this.childrens.forEach(item => { + item.errMsg = '' + const inputComp = this.inputChildrens.find(child => child.rename === item.name) + if (inputComp) { + inputComp.errMsg = '' + inputComp.$emit('input', inputComp.multiple?[]:'') + } + }) + + this.isChildEdit = true + this.childrens.forEach((item) => { + if (item.name) { + this.formData[item.name] = this._getValue(item, '') + } + }) + + this.$emit('input', this.formData) + this.$emit('reset', event) + }, + + /** + * 触发表单校验,通过 @validate 获取 + * @param {Object} validate + */ + validateCheck(validate) { + if (validate === null) validate = null + this.$emit('validate', validate) + }, + /** + * 校验所有或者部分表单 + */ + async validateAll(invalidFields, type, callback) { + + this.childrens.forEach(item => { + item.errMsg = '' + }) + + let promise; + if (!callback && typeof callback !== 'function' && Promise) { + promise = new Promise((resolve, reject) => { + callback = function(valid, invalidFields) { + !valid ? resolve(invalidFields) : reject(valid); + }; + }); + } + + let fieldsValue = {} + let tempInvalidFields = Object.assign({}, invalidFields) + + Object.keys(this.formRules).forEach(item => { + const values = this.formRules[item] + const rules = (values && values.rules) || [] + let isNoField = false + for (let i = 0; i < rules.length; i++) { + const rule = rules[i] + if (rule.required) { + isNoField = true + break + } + } + + // 如果存在 required 才会将内容插入校验对象 + if (!isNoField && (!tempInvalidFields[item] && tempInvalidFields[item] !== false)) { + delete tempInvalidFields[item] + } + + }) + // 循环字段是否存在于校验规则中 + for (let i in this.formRules) { + for (let j in tempInvalidFields) { + if (i === j) { + fieldsValue[i] = tempInvalidFields[i] + } + } + } + let result = [] + let example = null + if (this.validator) { + for (let i in fieldsValue) { + const resultData = await this.validator.validateUpdate({ + [i]: fieldsValue[i] + }, this.formData) + if (resultData) { + example = this.childrens.find(child => child.name === resultData.key) + const inputComp = this.inputChildrens.find(child => child.rename === example.name) + if (inputComp) { + inputComp.errMsg = resultData.errorMessage + } + result.push(resultData) + if (this.errShowType === 'undertext') { + if (example) example.errMsg = resultData.errorMessage + } else { + if (this.errShowType === 'toast') { + uni.showToast({ + title: resultData.errorMessage || '校验错误', + icon: 'none' + }) + break + } else if (this.errShowType === 'modal') { + uni.showModal({ + title: '提示', + content: resultData.errorMessage || '校验错误' + }) + break + } else { + if (example) example.errMsg = resultData.errorMessage + } + } + } + } + } + + if (Array.isArray(result)) { + if (result.length === 0) result = null + } + if (type === 'submit') { + this.$emit('submit', { + detail: { + value: invalidFields, + errors: result + } + }) + } else { + this.$emit('validate', result) + } + callback && typeof callback === 'function' && callback(result, invalidFields) + if (promise && callback) { + return promise + } else { + return null + } + }, + + /** + * 外部调用方法 + * 手动提交校验表单 + * 对整个表单进行校验的方法,参数为一个回调函数。 + */ + submit(callback) { + // Object.assign(this.formData,formData) + return this.validateAll(this.formData, 'submit', callback) + }, + + /** + * 外部调用方法 + * 校验表单 + * 对整个表单进行校验的方法,参数为一个回调函数。 + */ + validate(callback) { + return this.validateAll(this.formData, '', callback) + }, + + /** + * 部分表单校验 + * @param {Object} props + * @param {Object} cb + */ + validateField(props, callback) { + props = [].concat(props); + let invalidFields = {} + this.childrens.forEach(item => { + // item.parentVal((val, name) => { + if (props.indexOf(item.name) !== -1) { + invalidFields = Object.assign({}, invalidFields, { + [item.name]: this.formData[item.name] + }) + } + // }) + + }) + return this.validateAll(invalidFields, '', callback) + }, + + /** + * 对整个表单进行重置,将所有字段值重置为初始值并移除校验结果 + */ + resetFields() { + this.resetForm() + }, + + /** + * 移除表单项的校验结果。传入待移除的表单项的 prop 属性或者 prop 组成的数组,如不传则移除整个表单的校验结果 + */ + clearValidate(props) { + props = [].concat(props); + this.childrens.forEach(item => { + const inputComp = this.inputChildrens.find(child => child.rename === item.name) + if (props.length === 0) { + item.errMsg = '' + if (inputComp) { + inputComp.errMsg = '' + } + } else { + if (props.indexOf(item.name) !== -1) { + item.errMsg = '' + if (inputComp) { + inputComp.errMsg = '' + } + } + } + }) + }, + // 把 value 转换成指定的类型 + _getValue(item, value) { + const rules = item.formRules.rules || [] + const isRuleNum = rules.find(val => val.format && this.type_filter(val.format)) + const isRuleBool = rules.find(val => val.format && val.format === 'boolean' || val.format === 'bool') + // 输入值为 number + if (isRuleNum) { + value = value === '' || value === null ? null : Number(value) + } + // 简单判断真假值 + if (isRuleBool) { + value = !value ? false : true + } + return value + }, + // 过滤数字类型 + type_filter(format) { + return format === 'int' || format === 'double' || format === 'number' + } + } + } +</script> + +<style lang="scss" scoped> + .uni-forms { + overflow: hidden; + // padding: 10px 15px; + // background-color: #fff; + } + + .uni-forms--top { + padding: 10px 15px; + // padding-top: 22px; + } +</style> diff --git a/hive-app/components/uni-forms/validate.js b/hive-app/components/uni-forms/validate.js new file mode 100644 index 0000000..e369abb --- /dev/null +++ b/hive-app/components/uni-forms/validate.js @@ -0,0 +1,442 @@ + +var pattern = { + email: /^\S+?@\S+?\.\S+?$/, + url: new RegExp("^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$", 'i') +}; + +const FORMAT_MAPPING = { + "int": 'number', + "bool": 'boolean', + "double": 'number', + "long": 'number', + "password": 'string' +} + +function formatMessage(args, resources) { + var defaultMessage = ['label'] + defaultMessage.forEach((item) => { + if (args[item] === undefined) { + args[item] = '' + } + }) + + let str = resources + for (let key in args) { + let reg = new RegExp('{' + key + '}') + str = str.replace(reg, args[key]) + } + return str +} + +function isEmptyValue(value, type) { + if (value === undefined || value === null) { + return true; + } + + if (typeof value === 'string' && !value) { + return true; + } + + if (Array.isArray(value) && !value.length) { + return true; + } + + if (type === 'object' && !Object.keys(value).length) { + return true; + } + + return false; +} + +const types = { + integer(value) { + return types.number(value) && parseInt(value, 10) === value; + }, + string(value) { + return typeof value === 'string'; + }, + number(value) { + if (isNaN(value)) { + return false; + } + return typeof value === 'number'; + }, + "boolean": function (value) { + return typeof value === 'boolean'; + }, + "float": function (value) { + return types.number(value) && !types.integer(value); + }, + array(value) { + return Array.isArray(value); + }, + object(value) { + return typeof value === 'object' && !types.array(value); + }, + date(value) { + var v + if (value instanceof Date) { + v = value; + } else { + v = new Date(value); + } + return typeof v.getTime === 'function' && typeof v.getMonth === 'function' && typeof v.getYear === 'function' && !isNaN(v.getTime()); + }, + timestamp(value) { + if (!this.integer(value) || Math.abs(value).toString().length > 16) { + return false + } + + return this.date(value); + }, + email(value) { + return typeof value === 'string' && !!value.match(pattern.email) && value.length < 255; + }, + url(value) { + return typeof value === 'string' && !!value.match(pattern.url); + }, + pattern(reg, value) { + try { + return new RegExp(reg).test(value); + } catch (e) { + return false; + } + }, + method(value) { + return typeof value === 'function'; + } +} + +class RuleValidator { + + constructor(message) { + this._message = message + } + + async validateRule(key, value, data, allData) { + var result = null + + let rules = key.rules + + let hasRequired = rules.findIndex((item) => { + return item.required + }) + if (hasRequired < 0) { + if (value === null || value === undefined) { + return result + } + if (typeof value === 'string' && !value.length) { + return result + } + } + + var message = this._message + + if (rules === undefined) { + return message['default'] + } + + for (var i = 0; i < rules.length; i++) { + let rule = rules[i] + let vt = this._getValidateType(rule) + + if (key.label !== undefined) { + Object.assign(rule, { + label: key.label + }) + } + + if (RuleValidatorHelper[vt]) { + result = RuleValidatorHelper[vt](rule, value, message) + if (result != null) { + break + } + } + + if (rule.validateExpr) { + let now = Date.now() + let resultExpr = rule.validateExpr(value, allData, now) + if (resultExpr === false) { + result = this._getMessage(rule, rule.errorMessage || this._message['default']) + break + } + } + + if (rule.validateFunction) { + result = await this.validateFunction(rule, value, data, allData, vt) + if (result !== null) { + break + } + } + } + + return result + } + + async validateFunction(rule, value, data, allData, vt) { + let result = null + try { + let callbackMessage = null + const res = await rule.validateFunction(rule, value, allData || data, (message) => { + callbackMessage = message + }) + if (callbackMessage || (typeof res === 'string' && res) || res === false) { + result = this._getMessage(rule, callbackMessage || res, vt) + } + } catch (e) { + result = this._getMessage(rule, e.message, vt) + } + return result + } + + _getMessage(rule, message, vt) { + return formatMessage(rule, message || rule.errorMessage || this._message[vt] || message['default']) + } + + _getValidateType(rule) { + // TODO + var result = '' + if (rule.required) { + result = 'required' + } else if (rule.format) { + result = 'format' + } else if (rule.range) { + result = 'range' + } else if (rule.maximum || rule.minimum) { + result = 'rangeNumber' + } else if (rule.maxLength || rule.minLength) { + result = 'rangeLength' + } else if (rule.pattern) { + result = 'pattern' + } + return result + } +} + +const RuleValidatorHelper = { + required(rule, value, message) { + if (rule.required && isEmptyValue(value, rule.format || typeof value)) { + return formatMessage(rule, rule.errorMessage || message.required); + } + + return null + }, + + range(rule, value, message) { + const { range, errorMessage } = rule; + + let list = new Array(range.length); + for (let i = 0; i < range.length; i++) { + const item = range[i]; + if (types.object(item) && item.value !== undefined) { + list[i] = item.value; + } else { + list[i] = item; + } + } + + let result = false + if (Array.isArray(value)) { + result = (new Set(value.concat(list)).size === list.length); + } else { + if (list.indexOf(value) > -1) { + result = true; + } + } + + if (!result) { + return formatMessage(rule, errorMessage || message['enum']); + } + + return null + }, + + rangeNumber(rule, value, message) { + if (!types.number(value)) { + return formatMessage(rule, rule.errorMessage || message.pattern.mismatch); + } + + let { minimum, maximum, exclusiveMinimum, exclusiveMaximum } = rule; + let min = exclusiveMinimum ? value <= minimum : value < minimum; + let max = exclusiveMaximum ? value >= maximum : value > maximum; + + if (minimum !== undefined && min) { + return formatMessage(rule, rule.errorMessage || message['number'].min) + } else if (maximum !== undefined && max) { + return formatMessage(rule, rule.errorMessage || message['number'].max) + } else if (minimum !== undefined && maximum !== undefined && (min || max)) { + return formatMessage(rule, rule.errorMessage || message['number'].range) + } + + return null + }, + + rangeLength(rule, value, message) { + if (!types.string(value) && !types.array(value)) { + return formatMessage(rule, rule.errorMessage || message.pattern.mismatch); + } + + let min = rule.minLength; + let max = rule.maxLength; + let val = value.length; + + if (min !== undefined && val < min) { + return formatMessage(rule, rule.errorMessage || message['length'].min) + } else if (max !== undefined && val > max) { + return formatMessage(rule, rule.errorMessage || message['length'].max) + } else if (min !== undefined && max !== undefined && (val < min || val > max)) { + return formatMessage(rule, rule.errorMessage || message['length'].range) + } + + return null + }, + + pattern(rule, value, message) { + if (!types['pattern'](rule.pattern, value)) { + return formatMessage(rule, rule.errorMessage || message.pattern.mismatch); + } + + return null + }, + + format(rule, value, message) { + var customTypes = Object.keys(types); + var format = FORMAT_MAPPING[rule.format] ? FORMAT_MAPPING[rule.format] : rule.format; + + if (customTypes.indexOf(format) > -1) { + if (!types[format](value)) { + return formatMessage(rule, rule.errorMessage || message.types[format]); + } + } + + return null + } +} + +class SchemaValidator extends RuleValidator { + + constructor(schema, options) { + super(SchemaValidator.message); + + this._schema = schema + this._options = options || null + } + + updateSchema(schema) { + this._schema = schema + } + + async validate(data, allData) { + let result = this._checkFieldInSchema(data) + if (!result) { + result = await this.invokeValidate(data, false, allData) + } + return result.length ? result[0] : null + } + + async validateAll(data, allData) { + let result = this._checkFieldInSchema(data) + if (!result) { + result = await this.invokeValidate(data, true, allData) + } + return result + } + + async validateUpdate(data, allData) { + let result = this._checkFieldInSchema(data) + if (!result) { + result = await this.invokeValidateUpdate(data, false, allData) + } + return result.length ? result[0] : null + } + + async invokeValidate(data, all, allData) { + let result = [] + let schema = this._schema + for (let key in schema) { + let value = schema[key] + let errorMessage = await this.validateRule(value, data[key], data, allData) + if (errorMessage != null) { + result.push({ + key, + errorMessage + }) + if (!all) break + } + } + return result + } + + async invokeValidateUpdate(data, all, allData) { + let result = [] + for (let key in data) { + let errorMessage = await this.validateRule(this._schema[key], data[key], data, allData) + if (errorMessage != null) { + result.push({ + key, + errorMessage + }) + if (!all) break + } + } + return result + } + + _checkFieldInSchema(data) { + var keys = Object.keys(data) + var keys2 = Object.keys(this._schema) + if (new Set(keys.concat(keys2)).size === keys2.length) { + return '' + } + return [{ + key: 'invalid', + errorMessage: SchemaValidator.message['defaultInvalid'] + }] + } +} + +function Message() { + return { + default: '验证错误', + defaultInvalid: '字段超出范围', + required: '{label}必填', + 'enum': '{label}超出范围', + whitespace: '{label}不能为空', + date: { + format: '{label}日期{value}格式无效', + parse: '{label}日期无法解析,{value}无效', + invalid: '{label}日期{value}无效' + }, + types: { + string: '{label}类型无效', + array: '{label}类型无效', + object: '{label}类型无效', + number: '{label}类型无效', + date: '{label}类型无效', + boolean: '{label}类型无效', + integer: '{label}类型无效', + float: '{label}类型无效', + regexp: '{label}无效', + email: '{label}类型无效', + url: '{label}类型无效' + }, + length: { + min: '{label}长度不能少于{minLength}', + max: '{label}长度不能超过{maxLength}', + range: '{label}必须介于{minLength}和{maxLength}之间' + }, + number: { + min: '{label}不能小于{minimum}', + max: '{label}不能大于{maximum}', + range: '{label}必须介于{minimum}and{maximum}之间' + }, + pattern: { + mismatch: '{label}格式不匹配' + } + }; +} + + +SchemaValidator.message = new Message(); + +export default SchemaValidator diff --git a/hive-app/components/uni-goods-nav/uni-goods-nav.vue b/hive-app/components/uni-goods-nav/uni-goods-nav.vue new file mode 100644 index 0000000..ea90c9a --- /dev/null +++ b/hive-app/components/uni-goods-nav/uni-goods-nav.vue @@ -0,0 +1,225 @@ +<template> + <view class="uni-goods-nav"> + <!-- 底部占位 --> + <view class="uni-tab__seat" /> + <view class="uni-tab__cart-box flex"> + <view class="flex uni-tab__cart-sub-left"> + <view v-for="(item,index) in options" :key="index" class="flex uni-tab__cart-button-left uni-tab__shop-cart" @click="onClick(index,item)"> + <view class="uni-tab__icon"> + <uni-icons :type="item.icon" size="20" color="#646566"></uni-icons> + <!-- <image class="image" :src="item.icon" mode="widthFix" /> --> + </view> + <text class="uni-tab__text">{{ item.text }}</text> + <view class="flex uni-tab__dot-box"> + <text v-if="item.info" :class="{ 'uni-tab__dots': item.info > 9 }" class="uni-tab__dot " :style="{'backgroundColor':item.infoBackgroundColor?item.infoBackgroundColor:'#ff0000', + color:item.infoColor?item.infoColor:'#fff' + }">{{ item.info }}</text> + </view> + </view> + </view> + <view :class="{'uni-tab__right':fill}" class="flex uni-tab__cart-sub-right "> + <view v-for="(item,index) in buttonGroup" :key="index" :style="{backgroundColor:item.backgroundColor,color:item.color}" + class="flex uni-tab__cart-button-right" @click="buttonClick(index,item)"><text :style="{color:item.color}" class="uni-tab__cart-button-right-text">{{ item.text }}</text></view> + </view> + </view> + </view> +</template> + +<script> + import uniIcons from '../uni-icons/uni-icons.vue' + /** + * GoodsNav 商品导航 + * @description 商品加入购物车、立即购买等 + * @tutorial https://ext.dcloud.net.cn/plugin?id=865 + * @property {Array} options 组件参数 + * @property {Array} buttonGroup 组件按钮组参数 + * @property {Boolean} fill = [true | false] 组件按钮组参数 + * @event {Function} click 左侧点击事件 + * @event {Function} buttonClick 右侧按钮组点击事件 + * @example <uni-goods-nav :fill="true" options="" buttonGroup="buttonGroup" @click="" @buttonClick="" /> + */ + export default { + name: 'UniGoodsNav', + components: { + uniIcons + }, + props: { + options: { + type: Array, + default () { + return [{ + icon: 'shop', + text: '店铺', + }, { + icon: 'cart', + text: '购物车' + }] + } + }, + buttonGroup: { + type: Array, + default () { + return [{ + text: '加入购物车', + backgroundColor: '#ffa200', + color: '#fff' + }, + { + text: '立即购买', + backgroundColor: '#ff0000', + color: '#fff' + } + ] + } + }, + fill: { + type: Boolean, + default: false + } + }, + methods: { + onClick(index, item) { + this.$emit('click', { + index, + content: item, + + }) + }, + buttonClick(index, item) { + if (uni.report) { + uni.report(item.text, item.text) + } + this.$emit('buttonClick', { + index, + content: item + }) + } + } + } +</script> + +<style lang="scss" scoped> + .flex { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + } + + .uni-goods-nav { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex: 1; + flex-direction: row; + } + + .uni-tab__cart-box { + flex: 1; + height: 50px; + background-color: #fff; + z-index: 900; + } + + .uni-tab__cart-sub-left { + padding: 0 5px; + } + + .uni-tab__cart-sub-right { + flex: 1; + } + + .uni-tab__right { + margin: 5px 0; + margin-right: 10px; + border-radius: 100px; + overflow: hidden; + } + + .uni-tab__cart-button-left { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + // flex: 1; + position: relative; + justify-content: center; + align-items: center; + flex-direction: column; + margin: 0 10px; + } + + .uni-tab__icon { + width: 18px; + height: 18px; + } + + .image { + width: 18px; + height: 18px; + } + + .uni-tab__text { + margin-top: 3px; + font-size: $uni-font-size-sm; + color: #646566; + } + + .uni-tab__cart-button-right { + /* #ifndef APP-NVUE */ + display: flex; + flex-direction: column; + /* #endif */ + flex: 1; + justify-content: center; + align-items: center; + } + + .uni-tab__cart-button-right-text { + font-size: $uni-font-size-base; + color: #fff; + } + + .uni-tab__cart-button-right:active { + opacity: 0.7; + } + + .uni-tab__dot-box { + /* #ifndef APP-NVUE */ + display: flex; + flex-direction: column; + /* #endif */ + position: absolute; + right: -2px; + top: 2px; + justify-content: center; + align-items: center; + // width: 0; + // height: 0; + } + + .uni-tab__dot { + // width: 30rpx; + // height: 30rpx; + padding: 0 4px; + line-height: 15px; + color: #ffffff; + text-align: center; + font-size: 12px; + background-color: #ff0000; + border-radius: 15px; + } + + .uni-tab__dots { + padding: 0 4px; + // width: auto; + border-radius: 15px; + } + + .uni-tab__color-y { + background-color: #ffa200; + } + + .uni-tab__color-r { + background-color: #ff0000; + } +</style> diff --git a/hive-app/components/uni-grid-item/uni-grid-item.vue b/hive-app/components/uni-grid-item/uni-grid-item.vue new file mode 100644 index 0000000..bb65143 --- /dev/null +++ b/hive-app/components/uni-grid-item/uni-grid-item.vue @@ -0,0 +1,124 @@ +<template> + <view v-if="width" :style="'width:'+width+';'+(square?'height:'+width:'')" class="uni-grid-item"> + <view :class="{ 'uni-grid-item--border': showBorder, 'uni-grid-item--border-top': showBorder && index < column, 'uni-highlight': highlight }" + :style="{'border-right-color': borderColor ,'border-bottom-color': borderColor ,'border-top-color': borderColor }" + class="uni-grid-item__box" @click="_onClick"> + <slot /> + </view> + </view> +</template> + +<script> + /** + * GridItem 宫格 + * @description 宫格组件 + * @tutorial https://ext.dcloud.net.cn/plugin?id=27 + * @property {Number} index 子组件的唯一标识 ,点击gird会返回当前的标识 + */ + export default { + name: 'UniGridItem', + inject: ['grid'], + props: { + index: { + type: Number, + default: 0 + } + }, + data() { + return { + column: 0, + showBorder: true, + square: true, + highlight: true, + left: 0, + top: 0, + openNum: 2, + width: 0, + borderColor: '#e5e5e5' + } + }, + created() { + this.column = this.grid.column + this.showBorder = this.grid.showBorder + this.square = this.grid.square + this.highlight = this.grid.highlight + this.top = this.hor === 0 ? this.grid.hor : this.hor + this.left = this.ver === 0 ? this.grid.ver : this.ver + this.borderColor = this.grid.borderColor + this.grid.children.push(this) + // this.grid.init() + this.width = this.grid.width + }, + beforeDestroy() { + this.grid.children.forEach((item, index) => { + if (item === this) { + this.grid.children.splice(index, 1) + } + }) + }, + methods: { + _onClick() { + this.grid.change({ + detail: { + index: this.index + } + }) + } + } + } +</script> + +<style lang="scss" scoped> + .uni-grid-item { + /* #ifndef APP-NVUE */ + height: 100%; + display: flex; + /* #endif */ + } + + .uni-grid-item__box { + /* #ifndef APP-NVUE */ + display: flex; + width: 100%; + /* #endif */ + position: relative; + flex: 1; + flex-direction: column; + // justify-content: center; + // align-items: center; + } + + .uni-grid-item--border { + position: relative; + /* #ifdef APP-NVUE */ + border-bottom-color: $uni-border-color; + border-bottom-style: solid; + border-bottom-width: 0.5px; + border-right-color: $uni-border-color; + border-right-style: solid; + border-right-width: 0.5px; + /* #endif */ + /* #ifndef APP-NVUE */ + z-index: 0; + border-bottom: 1px $uni-border-color solid; + border-right: 1px $uni-border-color solid; + /* #endif */ + } + .uni-grid-item--border-top { + position: relative; + /* #ifdef APP-NVUE */ + border-top-color: $uni-border-color; + border-top-style: solid; + border-top-width: 0.5px; + /* #endif */ + /* #ifndef APP-NVUE */ + border-top: 1px $uni-border-color solid; + z-index: 0; + /* #endif */ + } + + + .uni-highlight:active { + background-color: $uni-bg-color-hover; + } +</style> diff --git a/hive-app/components/uni-grid/uni-grid.vue b/hive-app/components/uni-grid/uni-grid.vue new file mode 100644 index 0000000..f0322c0 --- /dev/null +++ b/hive-app/components/uni-grid/uni-grid.vue @@ -0,0 +1,141 @@ +<template> + <view class="uni-grid-wrap"> + <view :id="elId" ref="uni-grid" class="uni-grid" :class="{ 'uni-grid--border': showBorder }" :style="{ 'border-left-color':borderColor}"> + <slot /> + </view> + </view> +</template> + +<script> + // #ifdef APP-NVUE + const dom = uni.requireNativePlugin('dom'); + // #endif + + /** + * Grid 宫格 + * @description 宫格组件 + * @tutorial https://ext.dcloud.net.cn/plugin?id=27 + * @property {Number} column 每列显示个数 + * @property {String} borderColor 边框颜色 + * @property {Boolean} showBorder 是否显示边框 + * @property {Boolean} square 是否方形显示 + * @property {Boolean} Boolean 点击背景是否高亮 + * @event {Function} change 点击 grid 触发,e={detail:{index:0}},index 为当前点击 gird 下标 + */ + export default { + name: 'UniGrid', + props: { + // 每列显示个数 + column: { + type: Number, + default: 3 + }, + // 是否显示边框 + showBorder: { + type: Boolean, + default: true + }, + // 边框颜色 + borderColor: { + type: String, + default: '#e5e5e5' + }, + // 是否正方形显示,默认为 true + square: { + type: Boolean, + default: true + }, + highlight: { + type: Boolean, + default: true + } + }, + provide() { + return { + grid: this + } + }, + data() { + const elId = `Uni_${Math.ceil(Math.random() * 10e5).toString(36)}` + return { + elId, + width: 0 + } + }, + created() { + this.children = [] + }, + mounted() { + this.$nextTick(()=>{ + this.init() + }) + }, + methods: { + init() { + setTimeout(() => { + this._getSize((width) => { + this.children.forEach((item, index) => { + item.width = width + }) + }) + }, 50) + }, + change(e) { + this.$emit('change', e) + }, + _getSize(fn) { + // #ifndef APP-NVUE + uni.createSelectorQuery() + .in(this) + .select(`#${this.elId}`) + .boundingClientRect() + .exec(ret => { + this.width = parseInt((ret[0].width - 1) / this.column) + 'px' + fn(this.width) + }) + // #endif + // #ifdef APP-NVUE + dom.getComponentRect(this.$refs['uni-grid'], (ret) => { + this.width = parseInt((ret.size.width - 1) / this.column) + 'px' + fn(this.width) + }) + // #endif + } + } + } +</script> + +<style lang="scss" scoped> + .uni-grid-wrap { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex: 1; + flex-direction: column; + /* #ifdef H5 */ + width: 100%; + /* #endif */ + } + + .uni-grid { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + // flex: 1; + flex-direction: row; + flex-wrap: wrap; + } + + .uni-grid--border { + position: relative; + /* #ifdef APP-NVUE */ + border-left-color: $uni-border-color; + border-left-style: solid; + border-left-width: 0.5px; + /* #endif */ + /* #ifndef APP-NVUE */ + z-index: 1; + border-left: 1px $uni-border-color solid; + /* #endif */ + } +</style> diff --git a/hive-app/components/uni-group/uni-group.vue b/hive-app/components/uni-group/uni-group.vue new file mode 100644 index 0000000..7b8f2b7 --- /dev/null +++ b/hive-app/components/uni-group/uni-group.vue @@ -0,0 +1,127 @@ +<template> + <view class="uni-group" :class="['uni-group--'+mode ,margin?'group-margin':'']" :style="{marginTop: `${top}px` }"> + <slot name="title"> + <view v-if="title" class="uni-group__title" :style="{'padding-left':border?'30px':'15px'}"> + <text class="uni-group__title-text">{{ title }}</text> + </view> + </slot> + <view class="uni-group__content" :class="{'group-conent-padding':border}"> + <slot /> + </view> + </view> +</template> + +<script> + /** + * Group 分组 + * @description 表单字段分组 + * @tutorial https://ext.dcloud.net.cn/plugin?id=21002 + * @property {String} title 主标题 + * @property {Number} top 分组间隔 + */ + export default { + name: 'uniGroup', + props: { + title: { + type: String, + default: '' + }, + top: { + type: [Number, String], + default: 10 + }, + mode: { + type: String, + default: 'default' + } + }, + data() { + return { + margin: false, + border: false + } + }, + watch: { + title(newVal) { + if (uni.report && newVal !== '') { + uni.report('title', newVal) + } + } + }, + created() { + this.form = this.getForm() + if (this.form) { + this.margin = true + this.border = this.form.border + } + }, + methods: { + /** + * 获取父元素实例 + */ + getForm() { + let parent = this.$parent; + let parentName = parent.$options.name; + while (parentName !== 'uniForms') { + parent = parent.$parent; + if (!parent) return false + parentName = parent.$options.name; + } + return parent; + }, + onClick() { + this.$emit('click') + } + } + } +</script> +<style lang="scss" scoped> + .uni-group { + background: #fff; + margin-top: 10px; + // border: 1px red solid; + } + + .group-margin { + margin: 0 -15px; + } + + .uni-group__title { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + align-items: center; + padding-left: 15px; + height: 40px; + background-color: $uni-bg-color-grey; + font-weight: normal; + color: $uni-text-color; + } + + .uni-group__content { + padding: 15px; + // padding-bottom: 5px; + background-color: #FFF; + } + + .group-conent-padding { + padding: 0 15px; + } + + .uni-group__title-text { + font-size: $uni-font-size-base; + color: $uni-text-color; + } + + .distraction { + flex-direction: row; + align-items: center; + } + + .uni-group--card { + margin: 10px; + border-radius: 5px; + overflow: hidden; + box-shadow: 0 0 5px 1px rgba($color: #000000, $alpha: 0.08); + } +</style> diff --git a/hive-app/components/uni-icons/icons.js b/hive-app/components/uni-icons/icons.js new file mode 100644 index 0000000..60b7332 --- /dev/null +++ b/hive-app/components/uni-icons/icons.js @@ -0,0 +1,132 @@ +export default { + "pulldown": "\ue588", + "refreshempty": "\ue461", + "back": "\ue471", + "forward": "\ue470", + "more": "\ue507", + "more-filled": "\ue537", + "scan": "\ue612", + "qq": "\ue264", + "weibo": "\ue260", + "weixin": "\ue261", + "pengyouquan": "\ue262", + "loop": "\ue565", + "refresh": "\ue407", + "refresh-filled": "\ue437", + "arrowthindown": "\ue585", + "arrowthinleft": "\ue586", + "arrowthinright": "\ue587", + "arrowthinup": "\ue584", + "undo-filled": "\ue7d6", + "undo": "\ue406", + "redo": "\ue405", + "redo-filled": "\ue7d9", + "bars": "\ue563", + "chatboxes": "\ue203", + "camera": "\ue301", + "chatboxes-filled": "\ue233", + "camera-filled": "\ue7ef", + "cart-filled": "\ue7f4", + "cart": "\ue7f5", + "checkbox-filled": "\ue442", + "checkbox": "\ue7fa", + "arrowleft": "\ue582", + "arrowdown": "\ue581", + "arrowright": "\ue583", + "smallcircle-filled": "\ue801", + "arrowup": "\ue580", + "circle": "\ue411", + "eye-filled": "\ue568", + "eye-slash-filled": "\ue822", + "eye-slash": "\ue823", + "eye": "\ue824", + "flag-filled": "\ue825", + "flag": "\ue508", + "gear-filled": "\ue532", + "reload": "\ue462", + "gear": "\ue502", + "hand-thumbsdown-filled": "\ue83b", + "hand-thumbsdown": "\ue83c", + "hand-thumbsup-filled": "\ue83d", + "heart-filled": "\ue83e", + "hand-thumbsup": "\ue83f", + "heart": "\ue840", + "home": "\ue500", + "info": "\ue504", + "home-filled": "\ue530", + "info-filled": "\ue534", + "circle-filled": "\ue441", + "chat-filled": "\ue847", + "chat": "\ue263", + "mail-open-filled": "\ue84d", + "email-filled": "\ue231", + "mail-open": "\ue84e", + "email": "\ue201", + "checkmarkempty": "\ue472", + "list": "\ue562", + "locked-filled": "\ue856", + "locked": "\ue506", + "map-filled": "\ue85c", + "map-pin": "\ue85e", + "map-pin-ellipse": "\ue864", + "map": "\ue364", + "minus-filled": "\ue440", + "mic-filled": "\ue332", + "minus": "\ue410", + "micoff": "\ue360", + "mic": "\ue302", + "clear": "\ue434", + "smallcircle": "\ue868", + "close": "\ue404", + "closeempty": "\ue460", + "paperclip": "\ue567", + "paperplane": "\ue503", + "paperplane-filled": "\ue86e", + "person-filled": "\ue131", + "contact-filled": "\ue130", + "person": "\ue101", + "contact": "\ue100", + "images-filled": "\ue87a", + "phone": "\ue200", + "images": "\ue87b", + "image": "\ue363", + "image-filled": "\ue877", + "location-filled": "\ue333", + "location": "\ue303", + "plus-filled": "\ue439", + "plus": "\ue409", + "plusempty": "\ue468", + "help-filled": "\ue535", + "help": "\ue505", + "navigate-filled": "\ue884", + "navigate": "\ue501", + "mic-slash-filled": "\ue892", + "search": "\ue466", + "settings": "\ue560", + "sound": "\ue590", + "sound-filled": "\ue8a1", + "spinner-cycle": "\ue465", + "download-filled": "\ue8a4", + "personadd-filled": "\ue132", + "videocam-filled": "\ue8af", + "personadd": "\ue102", + "upload": "\ue402", + "upload-filled": "\ue8b1", + "starhalf": "\ue463", + "star-filled": "\ue438", + "star": "\ue408", + "trash": "\ue401", + "phone-filled": "\ue230", + "compose": "\ue400", + "videocam": "\ue300", + "trash-filled": "\ue8dc", + "download": "\ue403", + "chatbubble-filled": "\ue232", + "chatbubble": "\ue202", + "cloud-download": "\ue8e4", + "cloud-upload-filled": "\ue8e5", + "cloud-upload": "\ue8e6", + "cloud-download-filled": "\ue8e9", + "headphones":"\ue8bf", + "shop":"\ue609" +} diff --git a/hive-app/components/uni-icons/uni-icons.vue b/hive-app/components/uni-icons/uni-icons.vue new file mode 100644 index 0000000..6319565 --- /dev/null +++ b/hive-app/components/uni-icons/uni-icons.vue @@ -0,0 +1,71 @@ +<template> + <text :style="{ color: color, 'font-size': size + 'px' }" class="uni-icons" :class="[customIcons,customIcons?type:'']" @click="_onClick">{{icons[type]}}</text> +</template> + +<script> + import icons from './icons.js'; + // #ifdef APP-NVUE + var domModule = weex.requireModule('dom'); + domModule.addRule('fontFace', { + 'fontFamily': "uniicons", + 'src': "url('data:font/truetype;charset=utf-8;base64,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AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMHdW5pMDAwMAdjb250YWN0BnBlcnNvbglwZXJzb25hZGQNY29udGFjdGZpbGxlZAxwZXJzb25maWxsZWQPcGVyc29uYWRkZmlsbGVkBXBob25lBWVtYWlsCmNoYXRidWJibGUJY2hhdGJveGVzC3Bob25lZmlsbGVkC2VtYWlsZmlsbGVkEGNoYXRidWJibGVmaWxsZWQPY2hhdGJveGVzZmlsbGVkBXdlaWJvBndlaXhpbgtwZW5neW91cXVhbgRjaGF0AnFxCHZpZGVvY2FtBmNhbWVyYQNtaWMIbG9jYXRpb24JbWljZmlsbGVkDmxvY2F0aW9uZmlsbGVkBm1pY29mZgVpbWFnZQNtYXAHY29tcG9zZQV0cmFzaAZ1cGxvYWQIZG93bmxvYWQFY2xvc2UEcmVkbwR1bmRvB3JlZnJlc2gEc3Rhcgt3aGl0ZWNpcmNsZQVjbGVhcg1yZWZyZXNoZmlsbGVkCnN0YXJmaWxsZWQKcGx1c2ZpbGxlZAttaW51c2ZpbGxlZAxjaXJjbGVmaWxsZWQOY2hlY2tib3hmaWxsZWQKY2xvc2VlbXB0eQxyZWZyZXNoZW1wdHkGcmVsb2FkCHN0YXJoYWxmDHNwaW5uZXJjeWNsZQZzZWFyY2gJcGx1c2VtcHR5B2ZvcndhcmQEYmFjaw5jaGVja21hcmtlbXB0eQRob21lCG5hdmlnYXRlBGdlYXIKcGFwZXJwbGFuZQRpbmZvBGhlbHAGbG9ja2VkBG1vcmUEZmxhZwpob21lZmlsbGVkCmdlYXJmaWxsZWQKaW5mb2ZpbGxlZApoZWxwZmlsbGVkCm1vcmVmaWxsZWQIc2V0dGluZ3MEbGlzdARiYXJzBGxvb3AJcGFwZXJjbGlwCWV5ZWZpbGxlZAx1cHdhcmRzYXJyb3cOZG93bndhcmRzYXJyb3cObGVmdHdhcmRzYXJyb3cPcmlnaHR3YXJkc2Fycm93C2Fycm93dGhpbnVwDWFycm93dGhpbmRvd24NYXJyb3d0aGlubGVmdA5hcnJvd3RoaW5yaWdodAhwdWxsZG93bgVzb3VuZARzaG9wBHNjYW4KdW5kb2ZpbGxlZApyZWRvZmlsbGVkDGNhbWVyYWZpbGxlZApjYXJ0ZmlsbGVkBGNhcnQIY2hlY2tib3gRc21hbGxjaXJjbGVmaWxsZWQOZXllc2xhc2hmaWxsZWQIZXllc2xhc2gDZXllCmZsYWdmaWxsZWQVaGFuZHRodW1ic2Rvd25fZmlsbGVkDmhhbmR0aHVtYnNkb3duEmhhbmR0aHVtYnN1cGZpbGxlZAtoZWFydGZpbGxlZAxoYW5kdGh1bWJzdXAOYmxhY2toZWFydHN1aXQKY2hhdGZpbGxlZA5tYWlsb3BlbmZpbGxlZAhtYWlsb3Blbgxsb2NrZWRmaWxsZWQJbWFwZmlsbGVkBm1hcHBpbg1tYXBwaW5lbGxpcHNlC3NtYWxsY2lyY2xlEHBhcGVycGxhbmVmaWxsZWQLaW1hZ2VmaWxsZWQMaW1hZ2VzZmlsbGVkBmltYWdlcw5uYXZpZ2F0ZWZpbGxlZA5taWNzbGFzaGZpbGxlZAtzb3VuZGZpbGxlZA5kb3dubG9hZGZpbGxlZA52aWRlb2NhbWZpbGxlZAx1cGxvYWRmaWxsZWQKaGVhZHBob25lcwt0cmFzaGZpbGxlZA1jbG91ZGRvd25sb2FkEWNsb3VkdXBsb2FkZmlsbGVkC2Nsb3VkdXBsb2FkE2Nsb3VkZG93bmxvYWRmaWxsZWQHdW5pMDAwOQAAAAAB//8AAgABAAAADAAAABYAAAACAAEAAQCFAAEABAAAAAIAAAAAAAAAAQAAAADVpCcIAAAAANoxE3MAAAAA2jSpUA==')" + }); + // #endif + + /** + * Icons 图标 + * @description 用于展示 icons 图标 + * @tutorial https://ext.dcloud.net.cn/plugin?id=28 + * @property {Number} size 图标大小 + * @property {String} type 图标图案,参考示例 + * @property {String} color 图标颜色 + * @event {Function} click 点击 Icon 触发事件 + */ + export default { + name: 'UniIcons', + props: { + type: { + type: String, + default: '' + }, + color: { + type: String, + default: '#333333' + }, + size: { + type: [Number, String], + default: 16 + }, + customIcons:{ + type: String, + default: '' + } + }, + data() { + return { + icons: icons + } + }, + methods: { + _onClick() { + this.$emit('click') + } + } + } +</script> + +<style lang="scss" scoped> + /* #ifndef APP-NVUE */ + @font-face { + font-family: uniicons; + src: url('./uni.ttf') format('truetype'); + } + + /* #endif */ + + .uni-icons { + font-family: uniicons; + text-decoration: none; + text-align: center; + } +</style> diff --git a/hive-app/components/uni-icons/uni.ttf b/hive-app/components/uni-icons/uni.ttf new file mode 100644 index 0000000..60a1968 --- /dev/null +++ b/hive-app/components/uni-icons/uni.ttf Binary files differ diff --git a/hive-app/components/uni-indexed-list/uni-indexed-list-item.vue b/hive-app/components/uni-indexed-list/uni-indexed-list-item.vue new file mode 100644 index 0000000..37b8e3c --- /dev/null +++ b/hive-app/components/uni-indexed-list/uni-indexed-list-item.vue @@ -0,0 +1,142 @@ +<template> + <view> + <view v-if="loaded || list.itemIndex < 15" class="uni-indexed-list__title-wrapper"> + <text v-if="list.items && list.items.length > 0" class="uni-indexed-list__title">{{ list.key }}</text> + </view> + <view v-if="(loaded || list.itemIndex < 15) && list.items && list.items.length > 0" class="uni-indexed-list__list"> + <view v-for="(item, index) in list.items" :key="index" class="uni-indexed-list__item" hover-class="uni-indexed-list__item--hover"> + <view class="uni-indexed-list__item-container" @click="onClick(idx, index)"> + <view class="uni-indexed-list__item-border" :class="{'uni-indexed-list__item-border--last':index===list.items.length-1}"> + <view v-if="showSelect" style="margin-right: 20rpx;"> + <uni-icons :type="item.checked ? 'checkbox-filled' : 'circle'" :color="item.checked ? '#007aff' : '#aaa'" size="24" /> + </view> + <text class="uni-indexed-list__item-content">{{ item.name }}</text> + </view> + </view> + </view> + </view> + </view> +</template> + +<script> + import uniIcons from '../uni-icons/uni-icons.vue' + export default { + name: 'UniIndexedList', + components: { + uniIcons + }, + props: { + loaded: { + type: Boolean, + default: false + }, + idx: { + type: Number, + default: 0 + }, + list: { + type: Object, + default () { + return {} + } + }, + showSelect: { + type: Boolean, + default: false + } + }, + methods: { + onClick(idx, index) { + this.$emit("itemClick", { + idx, + index + }) + } + } + } +</script> + +<style lang="scss" scoped> + .uni-indexed-list__list { + background-color: $uni-bg-color; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; + border-top-style: solid; + border-top-width: 1px; + border-top-color: $uni-border-color; + } + + .uni-indexed-list__item { + font-size: $uni-font-size-lg; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex: 1; + flex-direction: row; + justify-content: space-between; + align-items: center; + } + + .uni-indexed-list__item-container { + padding-left: $uni-spacing-row-lg; + flex: 1; + position: relative; + /* #ifndef APP-NVUE */ + display: flex; + box-sizing: border-box; + /* #endif */ + flex-direction: row; + justify-content: space-between; + align-items: center; + } + + .uni-indexed-list__item-border { + flex: 1; + position: relative; + /* #ifndef APP-NVUE */ + display: flex; + box-sizing: border-box; + /* #endif */ + flex-direction: row; + justify-content: space-between; + align-items: center; + height: 50px; + padding: $uni-spacing-row-lg; + padding-left: 0; + border-bottom-style: solid; + border-bottom-width: 1px; + border-bottom-color: $uni-border-color; + } + + .uni-indexed-list__item-border--last { + border-bottom-width: 0px; + } + + .uni-indexed-list__item-content { + flex: 1; + font-size: 14px; + } + + .uni-indexed-list { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + } + + .uni-indexed-list__title-wrapper { + /* #ifndef APP-NVUE */ + display: flex; + width: 100%; + /* #endif */ + background-color: #f7f7f7; + } + + .uni-indexed-list__title { + padding: 6px 12px; + line-height: 24px; + font-size: $uni-font-size-sm; + } +</style> diff --git a/hive-app/components/uni-indexed-list/uni-indexed-list.vue b/hive-app/components/uni-indexed-list/uni-indexed-list.vue new file mode 100644 index 0000000..f94e7b5 --- /dev/null +++ b/hive-app/components/uni-indexed-list/uni-indexed-list.vue @@ -0,0 +1,318 @@ +<template> + <view class="uni-indexed-list" ref="list" id="list"> + <!-- #ifdef APP-NVUE --> + <list class="uni-indexed-list__scroll" scrollable="true" show-scrollbar="false"> + <cell v-for="(list, idx) in lists" :key="idx" :ref="'uni-indexed-list-' + idx"> + <!-- #endif --> + <!-- #ifndef APP-NVUE --> + <scroll-view :scroll-into-view="scrollViewId" class="uni-indexed-list__scroll" scroll-y> + <view v-for="(list, idx) in lists" :key="idx" :id="'uni-indexed-list-' + idx"> + <!-- #endif --> + <uni-indexed-list-item :list="list" :loaded="loaded" :idx="idx" :showSelect="showSelect" @itemClick="onClick"></uni-indexed-list-item> + <!-- #ifndef APP-NVUE --> + </view> + </scroll-view> + <!-- #endif --> + <!-- #ifdef APP-NVUE --> + </cell> + </list> + <!-- #endif --> + <view :class="touchmove ? 'uni-indexed-list__menu--active' : ''" @touchstart="touchStart" @touchmove.stop.prevent="touchMove" + @touchend="touchEnd" class="uni-indexed-list__menu"> + <view v-for="(list, key) in lists" :key="key" class="uni-indexed-list__menu-item"> + <text class="uni-indexed-list__menu-text" :class="touchmoveIndex == key ? 'uni-indexed-list__menu-text--active' : ''">{{ list.key }}</text> + </view> + </view> + <view v-if="touchmove" class="uni-indexed-list__alert-wrapper"> + <text class="uni-indexed-list__alert">{{ lists[touchmoveIndex].key }}</text> + </view> + </view> +</template> +<script> + import uniIcons from '../uni-icons/uni-icons.vue' + import uniIndexedListItem from './uni-indexed-list-item.vue' + // #ifdef APP-NVUE + const dom = weex.requireModule('dom'); + // #endif + // #ifdef APP-PLUS + function throttle(func, delay) { + var prev = Date.now(); + return function() { + var context = this; + var args = arguments; + var now = Date.now(); + if (now - prev >= delay) { + func.apply(context, args); + prev = Date.now(); + } + } + } + + function touchMove(e) { + let pageY = e.touches[0].pageY + let index = Math.floor((pageY - this.winOffsetY) / this.itemHeight) + if (this.touchmoveIndex === index) { + return false + } + let item = this.lists[index] + if (item) { + // #ifndef APP-NVUE + this.scrollViewId = 'uni-indexed-list-' + index + this.touchmoveIndex = index + // #endif + // #ifdef APP-NVUE + dom.scrollToElement(this.$refs['uni-indexed-list-' + index][0], { + animated: false + }) + this.touchmoveIndex = index + // #endif + } + } + const throttleTouchMove = throttle(touchMove, 40) + // #endif + + /** + * IndexedList 索引列表 + * @description 用于展示索引列表 + * @tutorial https://ext.dcloud.net.cn/plugin?id=375 + * @property {Boolean} showSelect = [true|false] 展示模式 + * @value true 展示模式 + * @value false 选择模式 + * @property {Object} options 索引列表需要的数据对象 + * @event {Function} click 点击列表事件 ,返回当前选择项的事件对象 + * @example <uni-indexed-list options="" showSelect="false" @click=""></uni-indexed-list> + */ + export default { + name: 'UniIndexedList', + components: { + uniIcons, + uniIndexedListItem + }, + props: { + options: { + type: Array, + default () { + return [] + } + }, + showSelect: { + type: Boolean, + default: false + } + }, + data() { + return { + lists: [], + winHeight: 0, + itemHeight: 0, + winOffsetY: 0, + touchmove: false, + touchmoveIndex: -1, + scrollViewId: '', + touchmoveTimeout: '', + loaded: false + } + }, + watch: { + options: { + handler: function() { + this.setList() + }, + deep: true + } + }, + mounted() { + setTimeout(() => { + this.setList() + }, 50) + setTimeout(() => { + this.loaded = true + }, 300); + }, + methods: { + setList() { + let index = 0; + this.lists = [] + this.options.forEach((value, index) => { + if (value.data.length === 0) { + return + } + let indexBefore = index + let items = value.data.map(item => { + let obj = {} + obj['key'] = value.letter + obj['name'] = item + obj['itemIndex'] = index + index++ + obj.checked = item.checked ? item.checked : false + return obj + }) + this.lists.push({ + title: value.letter, + key: value.letter, + items: items, + itemIndex: indexBefore + }) + }) + // #ifndef APP-NVUE + uni.createSelectorQuery() + .in(this) + .select('#list') + .boundingClientRect() + .exec(ret => { + this.winOffsetY = ret[0].top + this.winHeight = ret[0].height + this.itemHeight = this.winHeight / this.lists.length + }) + // #endif + // #ifdef APP-NVUE + dom.getComponentRect(this.$refs['list'], (res) => { + this.winOffsetY = res.size.top + this.winHeight = res.size.height + this.itemHeight = this.winHeight / this.lists.length + }) + // #endif + }, + touchStart(e) { + this.touchmove = true + let pageY = e.touches[0].pageY + let index = Math.floor((pageY - this.winOffsetY) / this.itemHeight) + let item = this.lists[index] + if (item) { + this.scrollViewId = 'uni-indexed-list-' + index + this.touchmoveIndex = index + // #ifdef APP-NVUE + dom.scrollToElement(this.$refs['uni-indexed-list-' + index][0], { + animated: false + }) + // #endif + } + }, + touchMove(e) { + // #ifndef APP-PLUS + let pageY = e.touches[0].pageY + let index = Math.floor((pageY - this.winOffsetY) / this.itemHeight) + if (this.touchmoveIndex === index) { + return false + } + let item = this.lists[index] + if (item) { + this.scrollViewId = 'uni-indexed-list-' + index + this.touchmoveIndex = index + } + // #endif + // #ifdef APP-PLUS + throttleTouchMove.call(this, e) + // #endif + }, + touchEnd() { + this.touchmove = false + this.touchmoveIndex = -1 + }, + onClick(e) { + let { + idx, + index + } = e + let obj = {} + for (let key in this.lists[idx].items[index]) { + obj[key] = this.lists[idx].items[index][key] + } + let select = [] + if (this.showSelect) { + this.lists[idx].items[index].checked = !this.lists[idx].items[index].checked + this.lists.forEach((value, idx) => { + value.items.forEach((item, index) => { + if (item.checked) { + let obj = {} + for (let key in this.lists[idx].items[index]) { + obj[key] = this.lists[idx].items[index][key] + } + select.push(obj) + } + }) + }) + } + this.$emit('click', { + item: obj, + select: select + }) + } + } + } +</script> +<style lang="scss" scoped> + .uni-indexed-list { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + } + + .uni-indexed-list__scroll { + flex: 1; + } + + .uni-indexed-list__menu { + width: 24px; + background-color: lightgrey; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; + } + + .uni-indexed-list__menu-item { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex: 1; + align-items: center; + justify-content: center; + } + + .uni-indexed-list__menu-text { + line-height: 20px; + font-size: 12px; + text-align: center; + color: #aaa; + } + + .uni-indexed-list__menu--active { + background-color: rgb(200, 200, 200); + } + + .uni-indexed-list__menu-text--active { + color: #007aff; + } + + .uni-indexed-list__alert-wrapper { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + align-items: center; + justify-content: center; + } + + .uni-indexed-list__alert { + width: 80px; + height: 80px; + border-radius: 80px; + text-align: center; + line-height: 80px; + font-size: 35px; + color: #fff; + background-color: rgba(0, 0, 0, 0.5); + } +</style> diff --git a/hive-app/components/uni-link/uni-link.vue b/hive-app/components/uni-link/uni-link.vue new file mode 100644 index 0000000..8ad5639 --- /dev/null +++ b/hive-app/components/uni-link/uni-link.vue @@ -0,0 +1,78 @@ +<template> + <text class="uni-link" :class="{'uni-link--withline':showUnderLine===true||showUnderLine==='true'}" :style="{color,fontSize:fontSize+'px'}" + @click="openURL">{{text}}</text> +</template> + +<script> + /** + * Link 外部网页超链接组件 + * @description uni-link是一个外部网页超链接组件,在小程序内复制url,在app内打开外部浏览器,在h5端打开新网页 + * @tutorial https://ext.dcloud.net.cn/plugin?id=1182 + * @property {String} href 点击后打开的外部网页url + * @property {String} text 显示的文字 + * @property {Boolean} showUnderLine 是否显示下划线 + * @property {String} copyTips 在小程序端复制链接时显示的提示语 + * @property {String} color 链接文字颜色 + * @property {String} fontSize 链接文字大小 + * @example * <uni-link href="https://ext.dcloud.net.cn" text="https://ext.dcloud.net.cn"></uni-link> + */ + export default { + name: 'uniLink', + props: { + href: { + type: String, + default: '' + }, + text: { + type: String, + default: '' + }, + showUnderLine: { + type: [Boolean, String], + default: true + }, + copyTips: { + type: String, + default: '已自动复制网址,请在手机浏览器里粘贴该网址' + }, + color: { + type: String, + default: '#999999' + }, + fontSize: { + type: [Number, String], + default: 14 + } + }, + methods: { + openURL() { + // #ifdef APP-PLUS + plus.runtime.openURL(this.href) + // #endif + // #ifdef H5 + window.open(this.href) + // #endif + // #ifdef MP + uni.setClipboardData({ + data: this.href + }); + uni.showModal({ + content: this.copyTips, + showCancel: false + }); + // #endif + } + } + } +</script> + +<style> + /* #ifndef APP-NVUE */ + .uni-link { + cursor: pointer; + } + /* #endif */ + .uni-link--withline { + text-decoration: underline; + } +</style> diff --git a/hive-app/components/uni-list-ad/uni-list-ad.vue b/hive-app/components/uni-list-ad/uni-list-ad.vue new file mode 100644 index 0000000..e256e4c --- /dev/null +++ b/hive-app/components/uni-list-ad/uni-list-ad.vue @@ -0,0 +1,107 @@ +<template> + <!-- #ifdef APP-NVUE --> + <cell> + <!-- #endif --> + <view class="uni-list-ad"> + <view v-if="borderShow" :class="{'uni-list--border':border,'uni-list-item--first':isFirstChild}"></view> + <ad style="width: 200px;height: 300px;border-width: 1px;border-color: red;border-style: solid;" adpid="1111111111" + unit-id="" appid="" apid="" type="feed" @error="aderror" @close="closeAd"></ad> + </view> + <!-- #ifdef APP-NVUE --> + </cell> + <!-- #endif --> + +</template> + +<script> + // #ifdef APP-NVUE + const dom = uni.requireNativePlugin('dom'); + // #endif + export default { + name: 'UniListAd', + props: { + title: { + type: String, + default: '', + + } + }, + // inject: ['list'], + data() { + return { + isFirstChild: false, + border: false, + borderShow: true, + } + }, + + mounted() { + this.list = this.getForm() + if (this.list) { + if (!this.list.firstChildAppend) { + this.list.firstChildAppend = true + this.isFirstChild = true + } + this.border = this.list.border + } + }, + methods: { + /** + * 获取父元素实例 + */ + getForm(name = 'uniList') { + let parent = this.$parent; + let parentName = parent.$options.name; + while (parentName !== name) { + parent = parent.$parent; + if (!parent) return false + parentName = parent.$options.name; + } + return parent; + }, + aderror(e) { + console.log("aderror: " + JSON.stringify(e.detail)); + }, + closeAd(e) { + this.borderShow = false + } + } + } +</script> + +<style lang="scss" scoped> + .uni-list-ad { + position: relative; + border: 1px red solid; + } + + .uni-list--border { + position: relative; + padding-bottom: 1px; + /* #ifdef APP-PLUS */ + border-top-color: $uni-border-color; + border-top-style: solid; + border-top-width: 0.5px; + /* #endif */ + margin-left: $uni-spacing-row-lg; + } + + /* #ifndef APP-NVUE */ + .uni-list--border:after { + position: absolute; + top: 0; + right: 0; + left: 0; + height: 1px; + content: ''; + -webkit-transform: scaleY(.5); + transform: scaleY(.5); + background-color: $uni-border-color; + } + + .uni-list-item--first:after { + height: 0px; + } + + /* #endif */ +</style> diff --git a/hive-app/components/uni-list-chat/uni-list-chat.scss b/hive-app/components/uni-list-chat/uni-list-chat.scss new file mode 100644 index 0000000..311f8d9 --- /dev/null +++ b/hive-app/components/uni-list-chat/uni-list-chat.scss @@ -0,0 +1,58 @@ +/** + * 这里是 uni-list 组件内置的常用样式变量 + * 如果需要覆盖样式,这里提供了基本的组件样式变量,您可以尝试修改这里的变量,去完成样式替换,而不用去修改源码 + * + */ + +// 背景色 +$background-color : #fff; +// 分割线颜色 +$divide-line-color : #e5e5e5; + +// 默认头像大小,如需要修改此值,注意同步修改 js 中的值 const avatarWidth = xx ,目前只支持方形头像 +// nvue 页面不支持修改头像大小 +$avatar-width : 45px ; + +// 头像边框 +$avatar-border-radius: 5px; +$avatar-border-color: #eee; +$avatar-border-width: 1px; + +// 标题文字样式 +$title-size : 16px; +$title-color : #3b4144; +$title-weight : normal; + +// 描述文字样式 +$note-size : 12px; +$note-color : #999; +$note-weight : normal; + +// 右侧额外内容默认样式 +$right-text-size : 12px; +$right-text-color : #999; +$right-text-weight : normal; + +// 角标样式 +// nvue 页面不支持修改圆点位置以及大小 +// 角标在左侧时,角标的位置,默认为 0 ,负数左/下移动,正数右/上移动 +$badge-left: 0px; +$badge-top: 0px; + +// 显示圆点时,圆点大小 +$dot-width: 10px; +$dot-height: 10px; + +// 显示角标时,角标大小和字体大小 +$badge-size : 18px; +$badge-font : 12px; +// 显示角标时,角标前景色 +$badge-color : #fff; +// 显示角标时,角标背景色 +$badge-background-color : #ff5a5f; +// 显示角标时,角标左右间距 +$badge-space : 6px; + +// 状态样式 +// 选中颜色 +$hover : #f5f5f5; diff --git a/hive-app/components/uni-list-chat/uni-list-chat.vue b/hive-app/components/uni-list-chat/uni-list-chat.vue new file mode 100644 index 0000000..a2de186 --- /dev/null +++ b/hive-app/components/uni-list-chat/uni-list-chat.vue @@ -0,0 +1,533 @@ +<template> + <!-- #ifdef APP-NVUE --> + <cell> + <!-- #endif --> + <view :hover-class="!clickable && !link ? '' : 'uni-list-chat--hover'" class="uni-list-chat" @click.stop="onClick"> + <view :class="{ 'uni-list--border': border, 'uni-list-chat--first': isFirstChild }"></view> + <view class="uni-list-chat__container"> + <view class="uni-list-chat__header-warp"> + <view v-if="avatarCircle || avatarList.length === 0" class="uni-list-chat__header" :class="{ 'header--circle': avatarCircle }"> + <image class="uni-list-chat__header-image" :src="avatar" mode="aspectFill"></image> + </view> + <!-- 头像组 --> + <view v-else class="uni-list-chat__header"> + <view v-for="(item, index) in avatarList" :key="index" class="uni-list-chat__header-box" :class="computedAvatar" + :style="{ width: imageWidth + 'px', height: imageWidth + 'px' }"> + <image class="uni-list-chat__header-image" :style="{ width: imageWidth + 'px', height: imageWidth + 'px' }" :src="item.url" + mode="aspectFill"></image> + </view> + </view> + </view> + <view v-if="badgeText && badgePositon === 'left'" class="uni-list-chat__badge uni-list-chat__badge-pos" :class="[isSingle]"> + <text class="uni-list-chat__badge-text">{{ badgeText === 'dot' ? '' : badgeText }}</text> + </view> + <view class="uni-list-chat__content"> + <view class="uni-list-chat__content-main"> + <text class="uni-list-chat__content-title uni-ellipsis">{{ title }}</text> + <text class="uni-list-chat__content-note uni-ellipsis">{{ note }}</text> + </view> + <view class="uni-list-chat__content-extra"> + <slot> + <text class="uni-list-chat__content-extra-text">{{ time }}</text> + <view v-if="badgeText && badgePositon === 'right'" class="uni-list-chat__badge" :class="[isSingle, badgePositon === 'right' ? 'uni-list-chat--right' : '']"> + <text class="uni-list-chat__badge-text">{{ badgeText === 'dot' ? '' : badgeText }}</text> + </view> + </slot> + </view> + </view> + </view> + </view> + <!-- #ifdef APP-NVUE --> + </cell> + <!-- #endif --> +</template> + +<script> + // 头像大小 + const avatarWidth = 45; + + /** + * ListChat 聊天列表 + * @description 聊天列表,用于创建聊天类列表 + * @tutorial https://ext.dcloud.net.cn/plugin?id=24 + * @property {String} title 标题 + * @property {String} note 描述 + * @property {Boolean} clickable = [true|false] 是否开启点击反馈,默认为false + * @property {String} badgeText 数字角标内容 + * @property {String} badgePositon = [left|right] 角标位置,默认为 right + * @property {String} link = [false|navigateTo|redirectTo|reLaunch|switchTab] 是否展示右侧箭头并开启点击反馈,默认为false + * @value false 不开启 + * @value navigateTo 同 uni.navigateTo() + * @value redirectTo 同 uni.redirectTo() + * @value reLaunch 同 uni.reLaunch() + * @value switchTab 同 uni.switchTab() + * @property {String | PageURIString} to 跳转目标页面 + * @property {String} time 右侧时间显示 + * @property {Boolean} avatarCircle = [true|false] 是否显示圆形头像,默认为false + * @property {String} avatar 头像地址,avatarCircle 不填时生效 + * @property {Array} avatarList 头像组,格式为 [{url:''}] + * @event {Function} click 点击 uniListChat 触发事件 + */ + export default { + name: 'UniListChat', + props: { + title: { + type: String, + default: '' + }, + note: { + type: String, + default: '' + }, + clickable: { + type: Boolean, + default: false + }, + link: { + type: [Boolean, String], + default: false + }, + to: { + type: String, + default: '' + }, + badgeText: { + type: [String, Number], + default: '' + }, + badgePositon: { + type: String, + default: 'right' + }, + time: { + type: String, + default: '' + }, + avatarCircle: { + type: Boolean, + default: false + }, + avatar: { + type: String, + default: '' + }, + avatarList: { + type: Array, + default () { + return []; + } + } + }, + // inject: ['list'], + computed: { + isSingle() { + if (this.badgeText === 'dot') { + return 'uni-badge--dot'; + } else { + const badgeText = this.badgeText.toString(); + if (badgeText.length > 1) { + return 'uni-badge--complex'; + } else { + return 'uni-badge--single'; + } + } + }, + computedAvatar() { + if (this.avatarList.length > 4) { + this.imageWidth = avatarWidth * 0.31; + return 'avatarItem--3'; + } else if (this.avatarList.length > 1) { + this.imageWidth = avatarWidth * 0.47; + return 'avatarItem--2'; + } else { + this.imageWidth = avatarWidth; + return 'avatarItem--1'; + } + } + }, + data() { + return { + isFirstChild: false, + border: true, + // avatarList: 3, + imageWidth: 50 + }; + }, + mounted() { + this.list = this.getForm() + if (this.list) { + if (!this.list.firstChildAppend) { + this.list.firstChildAppend = true; + this.isFirstChild = true; + } + this.border = this.list.border; + } + }, + methods: { + /** + * 获取父元素实例 + */ + getForm(name = 'uniList') { + let parent = this.$parent; + let parentName = parent.$options.name; + while (parentName !== name) { + parent = parent.$parent; + if (!parent) return false + parentName = parent.$options.name; + } + return parent; + }, + onClick() { + if (this.to !== '') { + this.openPage(); + return; + } + + if (this.clickable || this.link) { + this.$emit('click', { + data: {} + }); + } + }, + openPage() { + if (['navigateTo', 'redirectTo', 'reLaunch', 'switchTab'].indexOf(this.link) !== -1) { + this.pageApi(this.link); + } else { + this.pageApi('navigateTo'); + } + }, + pageApi(api) { + uni[api]({ + url: this.to, + success: res => { + this.$emit('click', { + data: res + }); + }, + fail: err => { + this.$emit('click', { + data: err + }); + console.error(err.errMsg); + } + }); + } + } + }; +</script> + +<style lang="scss" scoped> + $background-color: #fff; + $divide-line-color: #e5e5e5; + $avatar-width: 45px; + $avatar-border-radius: 5px; + $avatar-border-color: #eee; + $avatar-border-width: 1px; + $title-size: 16px; + $title-color: #3b4144; + $title-weight: normal; + $note-size: 12px; + $note-color: #999; + $note-weight: normal; + $right-text-size: 12px; + $right-text-color: #999; + $right-text-weight: normal; + $badge-left: 0px; + $badge-top: 0px; + $dot-width: 10px; + $dot-height: 10px; + $badge-size: 18px; + $badge-font: 12px; + $badge-color: #fff; + $badge-background-color: #ff5a5f; + $badge-space: 6px; + $hover: #f5f5f5; + + .uni-list-chat { + font-size: $uni-font-size-lg; + position: relative; + flex-direction: column; + justify-content: space-between; + background-color: $background-color; + } + + // .uni-list-chat--disabled { + // opacity: 0.3; + // } + + .uni-list-chat--hover { + background-color: $hover; + } + + .uni-list--border { + position: relative; + margin-left: $uni-spacing-row-lg; + /* #ifdef APP-PLUS */ + border-top-color: $divide-line-color; + border-top-style: solid; + border-top-width: 0.5px; + /* #endif */ + } + + /* #ifndef APP-NVUE */ + .uni-list--border:after { + position: absolute; + top: 0; + right: 0; + left: 0; + height: 1px; + content: ''; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); + background-color: $divide-line-color; + } + + .uni-list-item--first:after { + height: 0px; + } + + /* #endif */ + + .uni-list-chat--first { + border-top-width: 0px; + } + + .uni-ellipsis { + /* #ifndef APP-NVUE */ + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + /* #endif */ + /* #ifdef APP-NVUE */ + lines: 1; + /* #endif */ + } + + .uni-ellipsis-2 { + /* #ifndef APP-NVUE */ + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + /* #endif */ + + /* #ifdef APP-NVUE */ + lines: 2; + /* #endif */ + } + + .uni-list-chat__container { + position: relative; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + flex: 1; + padding: $uni-spacing-row-base $uni-spacing-row-lg; + position: relative; + overflow: hidden; + } + + .uni-list-chat__header-warp { + position: relative; + } + + .uni-list-chat__header { + /* #ifndef APP-NVUE */ + display: flex; + align-content: center; + /* #endif */ + flex-direction: row; + justify-content: center; + align-items: center; + flex-wrap: wrap-reverse; + /* #ifdef APP-NVUE */ + width: 50px; + height: 50px; + /* #endif */ + /* #ifndef APP-NVUE */ + width: $avatar-width; + height: $avatar-width; + /* #endif */ + + border-radius: $avatar-border-radius; + border-color: $avatar-border-color; + border-width: $avatar-border-width; + border-style: solid; + overflow: hidden; + } + + .uni-list-chat__header-box { + /* #ifndef APP-PLUS */ + box-sizing: border-box; + display: flex; + width: $avatar-width; + height: $avatar-width; + /* #endif */ + /* #ifdef APP-NVUE */ + width: 50px; + height: 50px; + /* #endif */ + overflow: hidden; + border-radius: 2px; + } + + .uni-list-chat__header-image { + margin: 1px; + /* #ifdef APP-NVUE */ + width: 50px; + height: 50px; + /* #endif */ + /* #ifndef APP-NVUE */ + width: $avatar-width; + height: $avatar-width; + /* #endif */ + } + + /* #ifndef APP-NVUE */ + .uni-list-chat__header-image { + display: block; + width: 100%; + height: 100%; + } + + .avatarItem--1 { + width: 100%; + height: 100%; + } + + .avatarItem--2 { + width: 47%; + height: 47%; + } + + .avatarItem--3 { + width: 32%; + height: 32%; + } + + /* #endif */ + .header--circle { + border-radius: 50%; + } + + .uni-list-chat__content { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + flex: 1; + overflow: hidden; + padding: 2px 0; + } + + .uni-list-chat__content-main { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; + justify-content: space-between; + padding-left: $uni-spacing-row-base; + flex: 1; + overflow: hidden; + } + + .uni-list-chat__content-title { + font-size: $title-size; + color: $title-color; + font-weight: $title-weight; + overflow: hidden; + } + + .uni-list-chat__content-note { + margin-top: 3px; + color: $note-color; + font-size: $note-size; + font-weight: $title-weight; + overflow: hidden; + } + + .uni-list-chat__content-extra { + /* #ifndef APP-NVUE */ + flex-shrink: 0; + display: flex; + /* #endif */ + flex-direction: column; + justify-content: space-between; + align-items: flex-end; + margin-left: 5px; + } + + .uni-list-chat__content-extra-text { + color: $right-text-color; + font-size: $right-text-size; + font-weight: $right-text-weight; + overflow: hidden; + } + + .uni-list-chat__badge-pos { + position: absolute; + /* #ifdef APP-NVUE */ + left: 55px; + top: 3px; + /* #endif */ + /* #ifndef APP-NVUE */ + left: calc(#{$avatar-width} + 10px - #{$badge-space} + #{$badge-left}); + top: calc(#{$uni-spacing-row-base}/ 2 + 1px + #{$badge-top}); + /* #endif */ + } + + .uni-list-chat__badge { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + justify-content: center; + align-items: center; + border-radius: 100px; + background-color: $badge-background-color; + } + + .uni-list-chat__badge-text { + color: $badge-color; + font-size: $badge-font; + } + + .uni-badge--single { + /* #ifndef APP-NVUE */ + // left: calc(#{$avatar-width} + 7px + #{$badge-left}); + /* #endif */ + width: $badge-size; + height: $badge-size; + } + + .uni-badge--complex { + /* #ifdef APP-NVUE */ + left: 50px; + /* #endif */ + /* #ifndef APP-NVUE */ + width: auto; + /* #endif */ + height: $badge-size; + padding: 0 $badge-space; + } + + .uni-badge--dot { + /* #ifdef APP-NVUE */ + left: 60px; + top: 6px; + /* #endif */ + /* #ifndef APP-NVUE */ + left: calc(#{$avatar-width} + 15px - #{$dot-width}/ 2 + 1px + #{$badge-left}); + /* #endif */ + width: $dot-width; + height: $dot-height; + padding: 0; + } + + .uni-list-chat--right { + /* #ifdef APP-NVUE */ + left: 0; + /* #endif */ + } +</style> diff --git a/hive-app/components/uni-list-item/uni-list-item.vue b/hive-app/components/uni-list-item/uni-list-item.vue new file mode 100644 index 0000000..7688e64 --- /dev/null +++ b/hive-app/components/uni-list-item/uni-list-item.vue @@ -0,0 +1,440 @@ +<template> + <!-- #ifdef APP-NVUE --> + <cell> + <!-- #endif --> + + <view + :class="{ 'uni-list-item--disabled': disabled }" + :hover-class="(!clickable && !link) || disabled || showSwitch ? '' : 'uni-list-item--hover'" + class="uni-list-item" + @click.stop="onClick" + > + <view v-if="!isFirstChild" class="border--left" :class="{ 'uni-list--border': border }"></view> + <view class="uni-list-item__container" :class="{ 'container--right': showArrow || link, 'flex--direction': direction === 'column' }"> + <slot name="header"> + <view class="uni-list-item__header"> + <view v-if="thumb" class="uni-list-item__icon"><image :src="thumb" class="uni-list-item__icon-img" :class="['uni-list--' + thumbSize]" /></view> + <view v-else-if="showExtraIcon" class="uni-list-item__icon"><uni-icons :color="extraIcon.color" :size="extraIcon.size" :type="extraIcon.type" /></view> + </view> + </slot> + <slot name="body"> + <view class="uni-list-item__content" :class="{ 'uni-list-item__content--center': thumb || showExtraIcon || showBadge || showSwitch }"> + <text v-if="title" class="uni-list-item__content-title" :class="[ellipsis !== 0 && ellipsis <= 2 ? 'uni-ellipsis-' + ellipsis : '']">{{ title }}</text> + <text v-if="note" class="uni-list-item__content-note">{{ note }}</text> + </view> + </slot> + <slot name="footer"> + <view v-if="rightText || showBadge || showSwitch" class="uni-list-item__extra" :class="{ 'flex--justify': direction === 'column' }"> + <text v-if="rightText" class="uni-list-item__extra-text">{{ rightText }}</text> + <uni-badge v-if="showBadge" :type="badgeType" :text="badgeText" /> + <switch v-if="showSwitch" :disabled="disabled" :checked="switchChecked" @change="onSwitchChange" /> + </view> + </slot> + </view> + <uni-icons v-if="showArrow || link" :size="16" class="uni-icon-wrapper" color="#bbb" type="arrowright" /> + </view> + <!-- #ifdef APP-NVUE --> + </cell> + <!-- #endif --> +</template> + +<script> +import uniIcons from '../uni-icons/uni-icons.vue'; +import uniBadge from '../uni-badge/uni-badge.vue'; + +/** + * ListItem 列表子组件 + * @description 列表子组件 + * @tutorial https://ext.dcloud.net.cn/plugin?id=24 + * @property {String} title 标题 + * @property {String} note 描述 + * @property {String} thumb 左侧缩略图,若thumb有值,则不会显示扩展图标 + * @property {String} thumbSize = [lg|base|sm] 略缩图大小 + * @value lg 大图 + * @value base 一般 + * @value sm 小图 + * @property {String} badgeText 数字角标内容 + * @property {String} badgeType 数字角标类型,参考[uni-icons](https://ext.dcloud.net.cn/plugin?id=21) + * @property {String} rightText 右侧文字内容 + * @property {Boolean} disabled = [true|false] 是否禁用 + * @property {Boolean} clickable = [true|false] 是否开启点击反馈 + * @property {String} link = [navigateTo|redirectTo|reLaunch|switchTab] 是否展示右侧箭头并开启点击反馈 + * @value navigateTo 同 uni.navigateTo() + * @value redirectTo 同 uni.redirectTo() + * @value reLaunch 同 uni.reLaunch() + * @value switchTab 同 uni.switchTab() + * @property {String | PageURIString} to 跳转目标页面 + * @property {Boolean} showBadge = [true|false] 是否显示数字角标 + * @property {Boolean} showSwitch = [true|false] 是否显示Switch + * @property {Boolean} switchChecked = [true|false] Switch是否被选中 + * @property {Boolean} showExtraIcon = [true|false] 左侧是否显示扩展图标 + * @property {Object} extraIcon 扩展图标参数,格式为 {color: '#4cd964',size: '22',type: 'spinner'} + * @property {String} direction = [row|column] 排版方向 + * @value row 水平排列 + * @value column 垂直排列 + * @event {Function} click 点击 uniListItem 触发事件 + * @event {Function} switchChange 点击切换 Switch 时触发 + */ +export default { + name: 'UniListItem', + components: { + uniIcons, + uniBadge + }, + props: { + direction: { + type: String, + default: 'row' + }, + title: { + type: String, + default: '' + }, + note: { + type: String, + default: '' + }, + ellipsis: { + type: [Number], + default: 0 + }, + disabled: { + type: [Boolean, String], + default: false + }, + clickable: { + type: Boolean, + default: false + }, + showArrow: { + type: [Boolean, String], + default: false + }, + link: { + type: [Boolean, String], + default: false + }, + to: { + type: String, + default: '' + }, + showBadge: { + type: [Boolean, String], + default: false + }, + showSwitch: { + type: [Boolean, String], + default: false + }, + switchChecked: { + type: [Boolean, String], + default: false + }, + badgeText: { + type: String, + default: '' + }, + badgeType: { + type: String, + default: 'success' + }, + rightText: { + type: String, + default: '' + }, + thumb: { + type: String, + default: '' + }, + thumbSize: { + type: String, + default: 'base' + }, + showExtraIcon: { + type: [Boolean, String], + default: false + }, + extraIcon: { + type: Object, + default() { + return { + type: 'contact', + color: '#000000', + size: 20 + }; + } + }, + border: { + type: Boolean, + default: true + } + }, + // inject: ['list'], + data() { + return { + isFirstChild: false + }; + }, + mounted() { + this.list = this.getForm() + // 判断是否存在 uni-list 组件 + if(this.list){ + if (!this.list.firstChildAppend) { + this.list.firstChildAppend = true; + this.isFirstChild = true; + } + } + }, + methods: { + /** + * 获取父元素实例 + */ + getForm(name = 'uniList') { + let parent = this.$parent; + let parentName = parent.$options.name; + while (parentName !== name) { + parent = parent.$parent; + if (!parent) return false + parentName = parent.$options.name; + } + return parent; + }, + onClick() { + if (this.to !== '') { + this.openPage(); + return; + } + if (this.clickable || this.link) { + this.$emit('click', { + data: {} + }); + } + }, + onSwitchChange(e) { + this.$emit('switchChange', e.detail); + }, + openPage() { + if (['navigateTo', 'redirectTo', 'reLaunch', 'switchTab'].indexOf(this.link) !== -1) { + this.pageApi(this.link); + } else { + this.pageApi('navigateTo'); + } + }, + pageApi(api) { + uni[api]({ + url: this.to, + success: res => { + this.$emit('click', { + data: res + }); + }, + fail: err => { + this.$emit('click', { + data: err + }); + console.error(err.errMsg); + } + }); + } + } +}; +</script> + +<style lang="scss"> +$list-item-pd: $uni-spacing-col-lg $uni-spacing-row-lg; + +.uni-list-item { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + font-size: $uni-font-size-lg; + position: relative; + justify-content: space-between; + background-color: #fff; + flex-direction: row; +} + +.uni-list-item--disabled { + opacity: 0.3; +} + +.uni-list-item--hover { + background-color: $uni-bg-color-hover; +} + +.uni-list-item__container { + position: relative; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + padding: $list-item-pd; + padding-left: $uni-spacing-row-lg; + flex: 1; + overflow: hidden; + // align-items: center; +} + +.container--right { + padding-right: 0; +} + +// .border--left { +// margin-left: $uni-spacing-row-lg; +// } + +.uni-list--border { + position: absolute; + top: 0; + right: 0; + left: 0; + /* #ifdef APP-NVUE */ + border-top-color: $uni-border-color; + border-top-style: solid; + border-top-width: 0.5px; + /* #endif */ +} + +/* #ifndef APP-NVUE */ +.uni-list--border:after { + position: absolute; + top: 0; + right: 0; + left: 0; + height: 1px; + content: ''; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); + background-color: $uni-border-color; +} + +/* #endif */ + +.uni-list-item__content { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + padding-right: 8px; + flex: 1; + color: #3b4144; + // overflow: hidden; + flex-direction: column; + justify-content: space-between; + overflow: hidden; +} + +.uni-list-item__content--center { + justify-content: center; +} + +.uni-list-item__content-title { + font-size: $uni-font-size-base; + color: #3b4144; + overflow: hidden; +} + +.uni-list-item__content-note { + margin-top: 6rpx; + color: $uni-text-color-grey; + font-size: $uni-font-size-sm; + overflow: hidden; +} + +.uni-list-item__extra { + // width: 25%; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + justify-content: flex-end; + align-items: center; +} + +.uni-list-item__header { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + align-items: center; +} + +.uni-list-item__icon { + margin-right: 18rpx; + flex-direction: row; + justify-content: center; + align-items: center; +} + +.uni-list-item__icon-img { + /* #ifndef APP-NVUE */ + display: block; + /* #endif */ + height: $uni-img-size-base; + width: $uni-img-size-base; +} + +.uni-icon-wrapper { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + align-items: center; + padding: 0 10px; +} + +.flex--direction { + flex-direction: column; + /* #ifndef APP-NVUE */ + align-items: initial; + /* #endif */ +} + +.flex--justify { + /* #ifndef APP-NVUE */ + justify-content: initial; + /* #endif */ +} + +.uni-list--lg { + height: $uni-img-size-lg; + width: $uni-img-size-lg; +} + +.uni-list--base { + height: $uni-img-size-base; + width: $uni-img-size-base; +} + +.uni-list--sm { + height: $uni-img-size-sm; + width: $uni-img-size-sm; +} + +.uni-list-item__extra-text { + color: $uni-text-color-grey; + font-size: $uni-font-size-sm; +} +.uni-ellipsis-1 { + /* #ifndef APP-NVUE */ + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + /* #endif */ + /* #ifdef APP-NVUE */ + lines: 1; + /* #endif */ +} + +.uni-ellipsis-2 { + /* #ifndef APP-NVUE */ + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + /* #endif */ + + /* #ifdef APP-NVUE */ + lines: 2; + /* #endif */ +} +</style> diff --git a/hive-app/components/uni-list/uni-list.vue b/hive-app/components/uni-list/uni-list.vue new file mode 100644 index 0000000..d478729 --- /dev/null +++ b/hive-app/components/uni-list/uni-list.vue @@ -0,0 +1,106 @@ +<template> + <!-- #ifndef APP-NVUE --> + <view class="uni-list uni-border-top-bottom"> + <view v-if="border" class="uni-list--border-top"></view> + <slot /> + <view v-if="border" class="uni-list--border-bottom"></view> + </view> + <!-- #endif --> + <!-- #ifdef APP-NVUE --> + <list class="uni-list" :class="{ 'uni-list--border': border }" :enableBackToTop="enableBackToTop" loadmoreoffset="15"><slot /></list> + <!-- #endif --> +</template> + +<script> +/** + * List 列表 + * @description 列表组件 + * @tutorial https://ext.dcloud.net.cn/plugin?id=24 + * @property {String} border = [true|false] 标题 + */ +export default { + name: 'uniList', + 'mp-weixin': { + options: { + multipleSlots: false + } + }, + props: { + enableBackToTop: { + type: [Boolean, String], + default: false + }, + scrollY: { + type: [Boolean, String], + default: false + }, + border: { + type: Boolean, + default: true + } + }, + // provide() { + // return { + // list: this + // }; + // }, + created() { + this.firstChildAppend = false; + }, + methods: { + loadMore(e) { + this.$emit('scrolltolower'); + } + } +}; +</script> +<style lang="scss"> +.uni-list { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + background-color: $uni-bg-color; + position: relative; + flex-direction: column; +} + +.uni-list--border { + position: relative; + /* #ifdef APP-NVUE */ + border-top-color: $uni-border-color; + border-top-style: solid; + border-top-width: 0.5px; + border-bottom-color: $uni-border-color; + border-bottom-style: solid; + border-bottom-width: 0.5px; + /* #endif */ + z-index: -1; +} + +/* #ifndef APP-NVUE */ + +.uni-list--border-top { + position: absolute; + top: 0; + right: 0; + left: 0; + height: 1px; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); + background-color: $uni-border-color; + z-index: 1; +} + +.uni-list--border-bottom { + position: absolute; + bottom: 0; + right: 0; + left: 0; + height: 1px; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); + background-color: $uni-border-color; +} + +/* #endif */ +</style> diff --git a/hive-app/components/uni-list/uni-refresh.vue b/hive-app/components/uni-list/uni-refresh.vue new file mode 100644 index 0000000..3b4c5a2 --- /dev/null +++ b/hive-app/components/uni-list/uni-refresh.vue @@ -0,0 +1,65 @@ +<template> + <!-- #ifdef APP-NVUE --> + <refresh :display="display" @refresh="onrefresh" @pullingdown="onpullingdown"> + <slot /> + </refresh> + <!-- #endif --> + <!-- #ifndef APP-NVUE --> + <view ref="uni-refresh" class="uni-refresh" v-show="isShow"> + <slot /> + </view> + <!-- #endif --> +</template> + +<script> + export default { + name: 'UniRefresh', + props: { + display: { + type: [String], + default: "hide" + } + }, + data() { + return { + pulling: false + } + }, + computed: { + isShow() { + if (this.display === "show" || this.pulling === true) { + return true; + } + return false; + } + }, + created() {}, + methods: { + onchange(value) { + this.pulling = value; + }, + onrefresh(e) { + this.$emit("refresh", e); + }, + onpullingdown(e) { + // #ifdef APP-NVUE + this.$emit("pullingdown", e); + // #endif + // #ifndef APP-NVUE + var detail = { + viewHeight: 90, + pullingDistance: e.height + } + this.$emit("pullingdown", detail); + // #endif + } + } + } +</script> + +<style> + .uni-refresh { + height: 0; + overflow: hidden; + } +</style> diff --git a/hive-app/components/uni-list/uni-refresh.wxs b/hive-app/components/uni-list/uni-refresh.wxs new file mode 100644 index 0000000..818a6b7 --- /dev/null +++ b/hive-app/components/uni-list/uni-refresh.wxs @@ -0,0 +1,87 @@ +var pullDown = { + threshold: 95, + maxHeight: 200, + callRefresh: 'onrefresh', + callPullingDown: 'onpullingdown', + refreshSelector: '.uni-refresh' +}; + +function ready(newValue, oldValue, ownerInstance, instance) { + var state = instance.getState() + state.canPullDown = newValue; + // console.log(newValue); +} + +function touchStart(e, instance) { + var state = instance.getState(); + state.refreshInstance = instance.selectComponent(pullDown.refreshSelector); + state.canPullDown = (state.refreshInstance != null && state.refreshInstance != undefined); + if (!state.canPullDown) { + return + } + + // console.log("touchStart"); + + state.height = 0; + state.touchStartY = e.touches[0].pageY || e.changedTouches[0].pageY; + state.refreshInstance.setStyle({ + 'height': 0 + }); + state.refreshInstance.callMethod("onchange", true); +} + +function touchMove(e, ownerInstance) { + var instance = e.instance; + var state = instance.getState(); + if (!state.canPullDown) { + return + } + + var oldHeight = state.height; + var endY = e.touches[0].pageY || e.changedTouches[0].pageY; + var height = endY - state.touchStartY; + if (height > pullDown.maxHeight) { + return; + } + + var refreshInstance = state.refreshInstance; + refreshInstance.setStyle({ + 'height': height + 'px' + }); + + height = height < pullDown.maxHeight ? height : pullDown.maxHeight; + state.height = height; + refreshInstance.callMethod(pullDown.callPullingDown, { + height: height + }); +} + +function touchEnd(e, ownerInstance) { + var state = e.instance.getState(); + if (!state.canPullDown) { + return + } + + state.refreshInstance.callMethod("onchange", false); + + var refreshInstance = state.refreshInstance; + if (state.height > pullDown.threshold) { + refreshInstance.callMethod(pullDown.callRefresh); + return; + } + + refreshInstance.setStyle({ + 'height': 0 + }); +} + +function propObserver(newValue, oldValue, instance) { + pullDown = newValue; +} + +module.exports = { + touchmove: touchMove, + touchstart: touchStart, + touchend: touchEnd, + propObserver: propObserver +} diff --git a/hive-app/components/uni-load-more/uni-load-more.vue b/hive-app/components/uni-load-more/uni-load-more.vue new file mode 100644 index 0000000..9430321 --- /dev/null +++ b/hive-app/components/uni-load-more/uni-load-more.vue @@ -0,0 +1,359 @@ +<template> + <view class="uni-load-more" @click="onClick"> + <!-- #ifdef APP-NVUE --> + <loading-indicator v-if="!webviewHide && status === 'loading' && showIcon" :style="{color: color,width:iconSize+'px',height:iconSize+'px'}" :animating="true" class="uni-load-more__img uni-load-more__img--nvue"></loading-indicator> + <!-- #endif --> + <!-- #ifdef H5 --> + <svg width="24" height="24" viewBox="25 25 50 50" v-if="!webviewHide && (iconType==='circle' || iconType==='auto' && platform === 'android') && status === 'loading' && showIcon" + :style="{width:iconSize+'px',height:iconSize+'px'}" class="uni-load-more__img uni-load-more__img--android-H5"> + <circle cx="50" cy="50" r="20" fill="none" :style="{color:color}" :stroke-width="3"></circle> + </svg> + <!-- #endif --> + <!-- #ifndef APP-NVUE || H5 --> + <view v-if="!webviewHide && (iconType==='circle' || iconType==='auto' && platform === 'android') && status === 'loading' && showIcon" + :style="{width:iconSize+'px',height:iconSize+'px'}" class="uni-load-more__img uni-load-more__img--android-MP"> + <view :style="{borderTopColor:color,borderTopWidth:iconSize/12}"></view> + <view :style="{borderTopColor:color,borderTopWidth:iconSize/12}"></view> + <view :style="{borderTopColor:color,borderTopWidth:iconSize/12}"></view> + </view> + <!-- #endif --> + <!-- #ifndef APP-NVUE --> + <view v-else-if="!webviewHide && status === 'loading' && showIcon" :style="{width:iconSize+'px',height:iconSize+'px'}" class="uni-load-more__img uni-load-more__img--ios-H5"> + <image src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QzlBMzU3OTlEOUM0MTFFOUI0NTZDNERBQURBQzI4RkUiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QzlBMzU3OUFEOUM0MTFFOUI0NTZDNERBQURBQzI4RkUiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpDOUEzNTc5N0Q5QzQxMUU5QjQ1NkM0REFBREFDMjhGRSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpDOUEzNTc5OEQ5QzQxMUU5QjQ1NkM0REFBREFDMjhGRSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pt+ALSwAAA6CSURBVHja1FsLkFZV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" + mode="widthFix"></image> + </view> + <!-- #endif --> + <text class="uni-load-more__text" :style="{color: color}">{{ status === 'more' ? contentText.contentdown : status === 'loading' ? contentText.contentrefresh : contentText.contentnomore }}</text> + </view> +</template> + +<script> + const platform = uni.getSystemInfoSync().platform + + /** + * LoadMore 加载更多 + * @description 用于列表中,做滚动加载使用,展示 loading 的各种状态 + * @tutorial https://ext.dcloud.net.cn/plugin?id=29 + * @property {String} status = [more|loading|noMore] loading 的状态 + * @value more loading前 + * @value loading loading中 + * @value noMore 没有更多了 + * @property {Number} iconSize 指定图标大小 + * @property {Boolean} iconSize = [true|false] 是否显示 loading 图标 + * @property {String} iconType = [snow|circle|auto] 指定图标样式 + * @value snow ios雪花加载样式 + * @value circle 安卓唤醒加载样式 + * @value auto 根据平台自动选择加载样式 + * @property {String} color 图标和文字颜色 + * @property {Object} contentText 各状态文字说明,值为:{contentdown: "上拉显示更多",contentrefresh: "正在加载...",contentnomore: "没有更多数据了"} + * @event {Function} clickLoadMore 点击加载更多时触发 + */ + export default { + name: 'UniLoadMore', + props: { + status: { + // 上拉的状态:more-loading前;loading-loading中;noMore-没有更多了 + type: String, + default: 'more' + }, + showIcon: { + type: Boolean, + default: true + }, + iconType: { + type: String, + default: 'auto' + }, + iconSize: { + type: Number, + default: 24 + }, + color: { + type: String, + default: '#777777' + }, + contentText: { + type: Object, + default () { + return { + contentdown: '上拉显示更多', + contentrefresh: '正在加载...', + contentnomore: '没有更多数据了' + } + } + } + }, + data() { + return { + webviewHide: false, + platform: platform + } + }, + // #ifndef APP-NVUE + computed:{ + iconSnowWidth(){ + return (Math.floor(this.iconSize/24)||1)*2 + } + }, + // #endif + mounted() { + // #ifdef APP-PLUS + var pages = getCurrentPages(); + var page = pages[pages.length - 1]; + var currentWebview = page.$getAppWebview(); + currentWebview.addEventListener('hide', () => { + this.webviewHide = true + }) + currentWebview.addEventListener('show', () => { + this.webviewHide = false + }) + // #endif + }, + methods: { + onClick() { + this.$emit('clickLoadMore', { + detail: { + status: this.status, + } + }) + } + } + } +</script> + +<style lang="scss" scoped> + + .uni-load-more { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + height: 40px; + align-items: center; + justify-content: center; + } + + .uni-load-more__text { + font-size: 15px; + } + + .uni-load-more__img { + width: 24px; + height: 24px; + margin-right: 8px; + } + + .uni-load-more__img--nvue { + color: #666666; + } + + .uni-load-more__img--android, + .uni-load-more__img--ios { + width: 24px; + height: 24px; + transform: rotate(0deg); + } + + /* #ifndef APP-NVUE */ + .uni-load-more__img--android { + animation: loading-ios 1s 0s linear infinite; + } + + @keyframes loading-android { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } + } + + .uni-load-more__img--ios-H5 { + position: relative; + animation: loading-ios-H5 1s 0s step-end infinite; + } + + .uni-load-more__img--ios-H5>image { + position: absolute; + width: 100%; + height: 100%; + left: 0; + top: 0; + } + + @keyframes loading-ios-H5 { + 0% { + transform: rotate(0deg); + } + + 8% { + transform: rotate(30deg); + } + + 16% { + transform: rotate(60deg); + } + + 24% { + transform: rotate(90deg); + } + + 32% { + transform: rotate(120deg); + } + + 40% { + transform: rotate(150deg); + } + + 48% { + transform: rotate(180deg); + } + + 56% { + transform: rotate(210deg); + } + + 64% { + transform: rotate(240deg); + } + + 73% { + transform: rotate(270deg); + } + + 82% { + transform: rotate(300deg); + } + + 91% { + transform: rotate(330deg); + } + + 100% { + transform: rotate(360deg); + } + } + + /* #endif */ + + /* #ifdef H5 */ + .uni-load-more__img--android-H5 { + animation: loading-android-H5-rotate 2s linear infinite; + transform-origin: center center; + } + + .uni-load-more__img--android-H5>circle { + display: inline-block; + animation: loading-android-H5-dash 1.5s ease-in-out infinite; + stroke: currentColor; + stroke-linecap: round; + } + + @keyframes loading-android-H5-rotate { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } + } + + @keyframes loading-android-H5-dash { + 0% { + stroke-dasharray: 1, 200; + stroke-dashoffset: 0; + } + + 50% { + stroke-dasharray: 90, 150; + stroke-dashoffset: -40; + } + + 100% { + stroke-dasharray: 90, 150; + stroke-dashoffset: -120; + } + } + + /* #endif */ + + /* #ifndef APP-NVUE || H5 */ + .uni-load-more__img--android-MP { + position: relative; + width: 24px; + height: 24px; + transform: rotate(0deg); + animation: loading-ios 1s 0s ease infinite; + } + + .uni-load-more__img--android-MP>view { + position: absolute; + box-sizing: border-box; + width: 100%; + height: 100%; + border-radius: 50%; + border: solid 2px transparent; + border-top: solid 2px #777777; + transform-origin: center; + } + + .uni-load-more__img--android-MP>view:nth-child(1){ + animation: loading-android-MP-1 1s 0s linear infinite; + } + + .uni-load-more__img--android-MP>view:nth-child(2){ + animation: loading-android-MP-2 1s 0s linear infinite; + } + + .uni-load-more__img--android-MP>view:nth-child(3){ + animation: loading-android-MP-3 1s 0s linear infinite; + } + + @keyframes loading-android { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } + } + + @keyframes loading-android-MP-1{ + 0%{ + transform: rotate(0deg); + } + 50%{ + transform: rotate(90deg); + } + 100%{ + transform: rotate(360deg); + } + } + @keyframes loading-android-MP-2{ + 0%{ + transform: rotate(0deg); + } + 50%{ + transform: rotate(180deg); + } + 100%{ + transform: rotate(360deg); + } + } + @keyframes loading-android-MP-3{ + 0%{ + transform: rotate(0deg); + } + 50%{ + transform: rotate(270deg); + } + 100%{ + transform: rotate(360deg); + } + } + /* #endif */ +</style> diff --git a/hive-app/components/uni-nav-bar/uni-nav-bar.vue b/hive-app/components/uni-nav-bar/uni-nav-bar.vue new file mode 100644 index 0000000..8286023 --- /dev/null +++ b/hive-app/components/uni-nav-bar/uni-nav-bar.vue @@ -0,0 +1,242 @@ +<template> + <view class="uni-navbar"> + <view :class="{ 'uni-navbar--fixed': fixed, 'uni-navbar--shadow': shadow, 'uni-navbar--border': border }" :style="{ 'background-color': backgroundColor }" + class="uni-navbar__content"> + <uni-status-bar v-if="statusBar" /> + <view :style="{ color: color,backgroundColor: backgroundColor }" class="uni-navbar__header uni-navbar__content_view"> + <view @tap="onClickLeft" class="uni-navbar__header-btns uni-navbar__header-btns-left uni-navbar__content_view"> + <view class="uni-navbar__content_view" v-if="leftIcon.length"> + <uni-icons :color="color" :type="leftIcon" size="24" /> + </view> + <view :class="{ 'uni-navbar-btn-icon-left': !leftIcon.length }" class="uni-navbar-btn-text uni-navbar__content_view" + v-if="leftText.length"> + <text :style="{ color: color, fontSize: '14px' }">{{ leftText }}</text> + </view> + <slot name="left" /> + </view> + <view class="uni-navbar__header-container uni-navbar__content_view" @tap="onClickTitle"> + <view class="uni-navbar__header-container-inner uni-navbar__content_view" v-if="title.length"> + <text class="uni-nav-bar-text" :style="{color: color }">{{ title }}</text> + </view> + <!-- 标题插槽 --> + <slot /> + </view> + <view :class="title.length ? 'uni-navbar__header-btns-right' : ''" @tap="onClickRight" class="uni-navbar__header-btns uni-navbar__content_view"> + <view class="uni-navbar__content_view" v-if="rightIcon.length"> + <uni-icons :color="color" :type="rightIcon" size="24" /> + </view> + <!-- 优先显示图标 --> + <view class="uni-navbar-btn-text uni-navbar__content_view" v-if="rightText.length && !rightIcon.length"> + <text class="uni-nav-bar-right-text">{{ rightText }}</text> + </view> + <slot name="right" /> + </view> + </view> + </view> + <view class="uni-navbar__placeholder" v-if="fixed"> + <uni-status-bar v-if="statusBar" /> + <view class="uni-navbar__placeholder-view" /> + </view> + </view> +</template> + +<script> + import uniStatusBar from "../uni-status-bar/uni-status-bar.vue"; + import uniIcons from "../uni-icons/uni-icons.vue"; + + /** + * NavBar 自定义导航栏 + * @description 导航栏组件,主要用于头部导航 + * @tutorial https://ext.dcloud.net.cn/plugin?id=52 + * @property {String} title 标题文字 + * @property {String} leftText 左侧按钮文本 + * @property {String} rightText 右侧按钮文本 + * @property {String} leftIcon 左侧按钮图标(图标类型参考 [Icon 图标](http://ext.dcloud.net.cn/plugin?id=28) type 属性) + * @property {String} rightIcon 右侧按钮图标(图标类型参考 [Icon 图标](http://ext.dcloud.net.cn/plugin?id=28) type 属性) + * @property {String} color 图标和文字颜色 + * @property {String} backgroundColor 导航栏背景颜色 + * @property {Boolean} fixed = [true|false] 是否固定顶部 + * @property {Boolean} statusBar = [true|false] 是否包含状态栏 + * @property {Boolean} shadow = [true|false] 导航栏下是否有阴影 + * @event {Function} clickLeft 左侧按钮点击时触发 + * @event {Function} clickRight 右侧按钮点击时触发 + * @event {Function} clickTitle 中间标题点击时触发 + */ + export default { + name: "UniNavBar", + components: { + uniStatusBar, + uniIcons + }, + props: { + title: { + type: String, + default: "" + }, + leftText: { + type: String, + default: "" + }, + rightText: { + type: String, + default: "" + }, + leftIcon: { + type: String, + default: "" + }, + rightIcon: { + type: String, + default: "" + }, + fixed: { + type: [Boolean, String], + default: false + }, + color: { + type: String, + default: "#000000" + }, + backgroundColor: { + type: String, + default: "#FFFFFF" + }, + statusBar: { + type: [Boolean, String], + default: false + }, + shadow: { + type: [Boolean, String], + default: false + }, + border: { + type: [Boolean, String], + default: true + } + }, + mounted() { + if(uni.report && this.title !== '') { + uni.report('title', this.title) + } + }, + methods: { + onClickLeft() { + this.$emit("clickLeft"); + }, + onClickRight() { + this.$emit("clickRight"); + }, + onClickTitle() { + this.$emit("clickTitle"); + } + } + }; +</script> + +<style lang="scss" scoped> + $nav-height: 44px; + + .uni-nav-bar-text { + /* #ifdef APP-PLUS */ + font-size: 34rpx; + /* #endif */ + /* #ifndef APP-PLUS */ + font-size: $uni-font-size-lg; + /* #endif */ + } + + .uni-nav-bar-right-text { + font-size: $uni-font-size-base; + } + + .uni-navbar__content { + position: relative; + background-color: $uni-bg-color; + overflow: hidden; + width: 750rpx; + } + + .uni-navbar__content_view { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + align-items: center; + flex-direction: row; + // background-color: #FFFFFF; + } + + .uni-navbar__header { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + height: $nav-height; + line-height: $nav-height; + font-size: 16px; + // background-color: #ffffff; + } + + .uni-navbar__header-btns { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-wrap: nowrap; + width: 120rpx; + padding: 0 6px; + justify-content: center; + align-items: center; + } + + .uni-navbar__header-btns-left { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + width: 150rpx; + justify-content: flex-start; + } + + .uni-navbar__header-btns-right { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + width: 150rpx; + padding-right: 30rpx; + justify-content: flex-end; + } + + .uni-navbar__header-container { + flex: 1; + } + + .uni-navbar__header-container-inner { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex: 1; + align-items: center; + justify-content: center; + font-size: $uni-font-size-base; + } + + + .uni-navbar__placeholder-view { + height: $nav-height; + } + + .uni-navbar--fixed { + position: fixed; + z-index: 998; + } + + .uni-navbar--shadow { + /* #ifndef APP-NVUE */ + box-shadow: 0 1px 6px #ccc; + /* #endif */ + } + + .uni-navbar--border { + border-bottom-width: 1rpx; + border-bottom-style: solid; + border-bottom-color: $uni-border-color; + } +</style> diff --git a/hive-app/components/uni-notice-bar/uni-notice-bar.vue b/hive-app/components/uni-notice-bar/uni-notice-bar.vue new file mode 100644 index 0000000..8399eca --- /dev/null +++ b/hive-app/components/uni-notice-bar/uni-notice-bar.vue @@ -0,0 +1,395 @@ +<template> + <view v-if="show" class="uni-noticebar" :style="{ backgroundColor: backgroundColor }" @click="onClick"> + <!-- #ifdef MP-ALIPAY --> + <view v-if="showClose === true || showClose === 'true'" class="uni-noticebar-close" @click="close"> + <uni-icons type="closeempty" :color="color" size="12" /> + </view> + <view v-if="showIcon === true || showIcon === 'true'" class="uni-noticebar-icon"> + <uni-icons type="sound" :color="color" size="14" /> + </view> + <!-- #endif --> + <!-- #ifndef MP-ALIPAY --> + <uni-icons v-if="showClose === true || showClose === 'true'" class="uni-noticebar-close" type="closeempty" :color="color" + size="12" @click="close" /> + <uni-icons v-if="showIcon === true || showIcon === 'true'" class="uni-noticebar-icon" type="sound" :color="color" + size="14" /> + <!-- #endif --> + <view ref="textBox" class="uni-noticebar__content-wrapper" :class="{'uni-noticebar__content-wrapper--scrollable':scrollable, 'uni-noticebar__content-wrapper--single':!scrollable && (single || moreText)}"> + <view :id="elIdBox" class="uni-noticebar__content" :class="{'uni-noticebar__content--scrollable':scrollable, 'uni-noticebar__content--single':!scrollable && (single || moreText)}"> + <text :id="elId" ref="animationEle" class="uni-noticebar__content-text" :class="{'uni-noticebar__content-text--scrollable':scrollable,'uni-noticebar__content-text--single':!scrollable && (single || moreText)}" + :style="{color:color, width:wrapWidth+'px', 'animationDuration': animationDuration, '-webkit-animationDuration': animationDuration ,animationPlayState: webviewHide?'paused':animationPlayState,'-webkit-animationPlayState':webviewHide?'paused':animationPlayState, animationDelay: animationDelay, '-webkit-animationDelay':animationDelay}">{{text}}</text> + </view> + </view> + <view v-if="showGetMore === true || showGetMore === 'true'" class="uni-noticebar__more" @click="clickMore"> + <text v-if="moreText" :style="{ color: moreColor }" class="uni-noticebar__more-text">{{ moreText }}</text> + <uni-icons type="arrowright" :color="moreColor" size="14" /> + </view> + </view> +</template> + +<script> + import uniIcons from '../uni-icons/uni-icons.vue' + // #ifdef APP-NVUE + const dom = weex.requireModule('dom'); + const animation = weex.requireModule('animation'); + // #endif + + /** + * NoticeBar 自定义导航栏 + * @description 通告栏组件 + * @tutorial https://ext.dcloud.net.cn/plugin?id=30 + * @property {Number} speed 文字滚动的速度,默认100px/秒 + * @property {String} text 显示文字 + * @property {String} backgroundColor 背景颜色 + * @property {String} color 文字颜色 + * @property {String} moreColor 查看更多文字的颜色 + * @property {String} moreText 设置“查看更多”的文本 + * @property {Boolean} single = [true|false] 是否单行 + * @property {Boolean} scrollable = [true|false] 是否滚动,为true时,NoticeBar为单行 + * @property {Boolean} showIcon = [true|false] 是否显示左侧喇叭图标 + * @property {Boolean} showClose = [true|false] 是否显示左侧关闭按钮 + * @property {Boolean} showGetMore = [true|false] 是否显示右侧查看更多图标,为true时,NoticeBar为单行 + * @event {Function} click 点击 NoticeBar 触发事件 + * @event {Function} close 关闭 NoticeBar 触发事件 + * @event {Function} getmore 点击”查看更多“时触发事件 + */ + + export default { + name: 'UniNoticeBar', + components: { + uniIcons + }, + props: { + text: { + type: String, + default: '' + }, + moreText: { + type: String, + default: '' + }, + backgroundColor: { + type: String, + default: '#fffbe8' + }, + speed: { + // 默认1s滚动100px + type: Number, + default: 100 + }, + color: { + type: String, + default: '#de8c17' + }, + moreColor: { + type: String, + default: '#999999' + }, + single: { + // 是否单行 + type: [Boolean, String], + default: false + }, + scrollable: { + // 是否滚动,添加后控制单行效果取消 + type: [Boolean, String], + default: false + }, + showIcon: { + // 是否显示左侧icon + type: [Boolean, String], + default: false + }, + showGetMore: { + // 是否显示右侧查看更多 + type: [Boolean, String], + default: false + }, + showClose: { + // 是否显示左侧关闭按钮 + type: [Boolean, String], + default: false + } + }, + data() { + const elId = `Uni_${Math.ceil(Math.random() * 10e5).toString(36)}` + const elIdBox = `Uni_${Math.ceil(Math.random() * 10e5).toString(36)}` + return { + textWidth: 0, + boxWidth: 0, + wrapWidth: '', + webviewHide: false, + // #ifdef APP-NVUE + stopAnimation: false, + // #endif + elId: elId, + elIdBox: elIdBox, + show: true, + animationDuration: 'none', + animationPlayState: 'paused', + animationDelay: '0s' + } + }, + mounted() { + // #ifdef APP-PLUS + var pages = getCurrentPages(); + var page = pages[pages.length - 1]; + var currentWebview = page.$getAppWebview(); + currentWebview.addEventListener('hide',()=>{ + this.webviewHide = true + }) + currentWebview.addEventListener('show',()=>{ + this.webviewHide = false + }) + // #endif + this.$nextTick(() => { + this.initSize() + }) + }, + // #ifdef APP-NVUE + beforeDestroy() { + this.stopAnimation = true + }, + // #endif + methods: { + initSize() { + if (this.scrollable) { + // #ifndef APP-NVUE + let query = [], + boxWidth = 0, + textWidth = 0; + let textQuery = new Promise((resolve, reject) => { + uni.createSelectorQuery() + // #ifndef MP-ALIPAY + .in(this) + // #endif + .select(`#${this.elId}`) + .boundingClientRect() + .exec(ret => { + this.textWidth = ret[0].width + resolve() + }) + }) + let boxQuery = new Promise((resolve, reject) => { + uni.createSelectorQuery() + // #ifndef MP-ALIPAY + .in(this) + // #endif + .select(`#${this.elIdBox}`) + .boundingClientRect() + .exec(ret => { + this.boxWidth = ret[0].width + resolve() + }) + }) + query.push(textQuery) + query.push(boxQuery) + Promise.all(query).then(() => { + this.animationDuration = `${this.textWidth / this.speed}s` + this.animationDelay = `-${this.boxWidth / this.speed}s` + setTimeout(() => { + this.animationPlayState = 'running' + }, 1000) + }) + // #endif + // #ifdef APP-NVUE + dom.getComponentRect(this.$refs['animationEle'], (res) => { + let winWidth = uni.getSystemInfoSync().windowWidth + this.textWidth = res.size.width + animation.transition(this.$refs['animationEle'], { + styles: { + transform: `translateX(-${winWidth}px)` + }, + duration: 0, + timingFunction: 'linear', + delay: 0 + }, () => { + if (!this.stopAnimation) { + animation.transition(this.$refs['animationEle'], { + styles: { + transform: `translateX(-${this.textWidth}px)` + }, + timingFunction: 'linear', + duration: (this.textWidth - winWidth) / this.speed * 1000, + delay: 1000 + }, () => { + if (!this.stopAnimation) { + this.loopAnimation() + } + }); + } + }); + }) + // #endif + } + // #ifdef APP-NVUE + if (!this.scrollable && (this.single || this.moreText)) { + dom.getComponentRect(this.$refs['textBox'], (res) => { + this.wrapWidth = res.size.width + }) + } + // #endif + }, + loopAnimation() { + // #ifdef APP-NVUE + animation.transition(this.$refs['animationEle'], { + styles: { + transform: `translateX(0px)` + }, + duration: 0 + }, () => { + if (!this.stopAnimation) { + animation.transition(this.$refs['animationEle'], { + styles: { + transform: `translateX(-${this.textWidth}px)` + }, + duration: this.textWidth / this.speed * 1000, + timingFunction: 'linear', + delay: 0 + }, () => { + if (!this.stopAnimation) { + this.loopAnimation() + } + }); + } + }); + // #endif + }, + clickMore() { + this.$emit('getmore') + }, + close() { + this.show = false; + this.$emit('close') + }, + onClick() { + this.$emit('click') + } + } + } +</script> + +<style lang="scss" scoped> + + .uni-noticebar { + /* #ifndef APP-NVUE */ + display: flex; + width: 100%; + box-sizing: border-box; + /* #endif */ + flex-direction: row; + align-items: center; + padding: 6px 12px; + margin-bottom: 10px; + } + + .uni-noticebar-close { + margin-right: 5px; + } + + .uni-noticebar-icon { + margin-right: 5px; + } + + .uni-noticebar__content-wrapper { + flex: 1; + flex-direction: column; + overflow: hidden; + } + + .uni-noticebar__content-wrapper--single { + /* #ifndef APP-NVUE */ + line-height: 18px; + /* #endif */ + } + + .uni-noticebar__content-wrapper--single, + .uni-noticebar__content-wrapper--scrollable { + flex-direction: row; + } + + /* #ifndef APP-NVUE */ + .uni-noticebar__content-wrapper--scrollable { + position: relative; + height: 18px; + } + /* #endif */ + + .uni-noticebar__content--scrollable { + /* #ifdef APP-NVUE */ + flex: 0; + /* #endif */ + /* #ifndef APP-NVUE */ + flex: 1; + display: block; + overflow: hidden; + /* #endif */ + } + + .uni-noticebar__content--single { + /* #ifndef APP-NVUE */ + display: flex; + flex: none; + width: 100%; + justify-content: center; + /* #endif */ + } + + .uni-noticebar__content-text { + font-size: 14px; + line-height: 18px; + /* #ifndef APP-NVUE */ + word-break: break-all; + /* #endif */ + } + + .uni-noticebar__content-text--single { + /* #ifdef APP-NVUE */ + lines: 1; + /* #endif */ + /* #ifndef APP-NVUE */ + display: block; + width: 100%; + white-space: nowrap; + /* #endif */ + overflow: hidden; + text-overflow: ellipsis; + } + + .uni-noticebar__content-text--scrollable { + /* #ifdef APP-NVUE */ + lines: 1; + padding-left: 750rpx; + /* #endif */ + /* #ifndef APP-NVUE */ + position: absolute; + display: block; + height: 18px; + line-height: 18px; + white-space: nowrap; + padding-left: 100%; + animation: notice 10s 0s linear infinite both; + animation-play-state: paused; + /* #endif */ + } + + .uni-noticebar__more { + /* #ifndef APP-NVUE */ + display: inline-flex; + /* #endif */ + flex-direction: row; + flex-wrap: nowrap; + align-items: center; + padding-left: 5px; + } + + .uni-noticebar__more-text { + font-size: 14px; + } + + @keyframes notice { + 100% { + transform: translate3d(-100%, 0, 0); + } + } +</style> diff --git a/hive-app/components/uni-number-box/uni-number-box.vue b/hive-app/components/uni-number-box/uni-number-box.vue new file mode 100644 index 0000000..fc43f1b --- /dev/null +++ b/hive-app/components/uni-number-box/uni-number-box.vue @@ -0,0 +1,200 @@ +<template> + <view class="uni-numbox"> + <view @click="_calcValue('minus')" class="uni-numbox__minus"> + <text class="uni-numbox--text" :class="{ 'uni-numbox--disabled': inputValue <= min || disabled }">-</text> + </view> + <input :disabled="disabled" @blur="_onBlur" class="uni-numbox__value" type="number" v-model="inputValue" /> + <view @click="_calcValue('plus')" class="uni-numbox__plus"> + <text class="uni-numbox--text" :class="{ 'uni-numbox--disabled': inputValue >= max || disabled }">+</text> + </view> + </view> +</template> +<script> + + /** + * NumberBox 数字输入框 + * @description 带加减按钮的数字输入框 + * @tutorial https://ext.dcloud.net.cn/plugin?id=31 + * @property {Number} value 输入框当前值 + * @property {Number} min 最小值 + * @property {Number} max 最大值 + * @property {Number} step 每次点击改变的间隔大小 + * @property {Boolean} disabled = [true|false] 是否为禁用状态 + * @event {Function} change 输入框值改变时触发的事件,参数为输入框当前的 value + */ + + export default { + name: "UniNumberBox", + props: { + value: { + type: [Number, String], + default: 1 + }, + min: { + type: Number, + default: 0 + }, + max: { + type: Number, + default: 100 + }, + step: { + type: Number, + default: 1 + }, + disabled: { + type: Boolean, + default: false + } + }, + data() { + return { + inputValue: 0 + }; + }, + watch: { + value(val) { + this.inputValue = +val; + }, + inputValue(newVal, oldVal) { + if (+newVal !== +oldVal) { + this.$emit("change", newVal); + } + } + }, + created() { + this.inputValue = +this.value; + }, + methods: { + _calcValue(type) { + if (this.disabled) { + return; + } + const scale = this._getDecimalScale(); + let value = this.inputValue * scale; + let step = this.step * scale; + if (type === "minus") { + value -= step; + if (value < (this.min * scale)) { + return; + } + if (value > (this.max * scale)) { + value = this.max * scale + } + } else if (type === "plus") { + value += step; + if (value > (this.max * scale)) { + return; + } + if (value < (this.min * scale)) { + value = this.min * scale + } + } + + this.inputValue = String(value / scale); + }, + _getDecimalScale() { + let scale = 1; + // 浮点型 + if (~~this.step !== this.step) { + scale = Math.pow(10, (this.step + "").split(".")[1].length); + } + return scale; + }, + _onBlur(event) { + let value = event.detail.value; + if (!value) { + // this.inputValue = 0; + return; + } + value = +value; + if (value > this.max) { + value = this.max; + } else if (value < this.min) { + value = this.min; + } + this.inputValue = value; + } + } + }; +</script> +<style lang="scss" scoped> + $box-height: 35px; + /* #ifdef APP-NVUE */ + $box-line-height: 35px; + /* #endif */ + $box-line-height: 26px; + $box-width: 35px; + + .uni-numbox { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + height: $box-height; + line-height: $box-height; + width: 120px; + } + + .uni-numbox__value { + background-color: $uni-bg-color; + width: 40px; + height: $box-height; + text-align: center; + font-size: $uni-font-size-lg; + border-width: 1rpx; + border-style: solid; + border-color: $uni-border-color; + border-left-width: 0; + border-right-width: 0; + } + + .uni-numbox__minus { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + align-items: center; + justify-content: center; + width: $box-width; + height: $box-height; + // line-height: $box-line-height; + // text-align: center; + font-size: 20px; + color: $uni-text-color; + background-color: $uni-bg-color-grey; + border-width: 1rpx; + border-style: solid; + border-color: $uni-border-color; + border-top-left-radius: $uni-border-radius-base; + border-bottom-left-radius: $uni-border-radius-base; + border-right-width: 0; + } + + .uni-numbox__plus { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + align-items: center; + justify-content: center; + width: $box-width; + height: $box-height; + border-width: 1rpx; + border-style: solid; + border-color: $uni-border-color; + border-top-right-radius: $uni-border-radius-base; + border-bottom-right-radius: $uni-border-radius-base; + background-color: $uni-bg-color-grey; + border-left-width: 0; + } + + .uni-numbox--text { + font-size: 40rpx; + color: $uni-text-color; + } + + .uni-numbox--disabled { + color: $uni-text-color-disable; + } +</style> diff --git a/hive-app/components/uni-pagination/uni-pagination.vue b/hive-app/components/uni-pagination/uni-pagination.vue new file mode 100644 index 0000000..5358a40 --- /dev/null +++ b/hive-app/components/uni-pagination/uni-pagination.vue @@ -0,0 +1,204 @@ +<template> + <view class="uni-pagination"> + <view class="uni-pagination__btn" :class="currentIndex === 1 ? 'uni-pagination--disabled' : 'uni-pagination--enabled'" + :hover-class="currentIndex === 1 ? '' : 'uni-pagination--hover'" :hover-start-time="20" :hover-stay-time="70" + @click="clickLeft"> + <template v-if="showIcon===true || showIcon === 'true'"> + <uni-icons color="#000" size="20" type="arrowleft" /> + </template> + <template v-else><text class="uni-pagination__child-btn">{{ prevText }}</text></template> + </view> + <view class="uni-pagination__num"> + <view class="uni-pagination__num-current"> + <text class="uni-pagination__num-current-text" style="color:#007aff">{{ currentIndex }}</text><text class="uni-pagination__num-current-text">/{{ maxPage || 0 }}</text> + </view> + </view> + <view class="uni-pagination__btn" :class="currentIndex === maxPage ? 'uni-pagination--disabled' : 'uni-pagination--enabled'" + :hover-class="currentIndex === maxPage ? '' : 'uni-pagination--hover'" :hover-start-time="20" :hover-stay-time="70" + @click="clickRight"> + <template v-if="showIcon===true || showIcon === 'true'"> + <uni-icons color="#000" size="20" type="arrowright" /> + </template> + <template v-else><text class="uni-pagination__child-btn">{{ nextText }}</text></template> + </view> + </view> +</template> + +<script> + import uniIcons from '../uni-icons/uni-icons.vue' + + /** + * Pagination 分页器 + * @description 分页器组件,用于展示页码、请求数据等 + * @tutorial https://ext.dcloud.net.cn/plugin?id=32 + * @property {String} prevText 左侧按钮文字 + * @property {String} nextText 右侧按钮文字 + * @property {Number} current 当前页 + * @property {Number} total 数据总量 + * @property {Number} pageSize 每页数据量 + * @property {Number} showIcon = [true|false] 是否以 icon 形式展示按钮 + * @event {Function} change 点击页码按钮时触发 ,e={type,current} current为当前页,type值为:next/prev,表示点击的是上一页还是下一个 + */ + + export default { + name: 'UniPagination', + components: { + uniIcons + }, + props: { + prevText: { + type: String, + default: '上一页' + }, + nextText: { + type: String, + default: '下一页' + }, + current: { + type: [Number, String], + default: 1 + }, + total: { // 数据总量 + type: [Number, String], + default: 0 + }, + pageSize: { // 每页数据量 + type: [Number, String], + default: 10 + }, + showIcon: { // 是否以 icon 形式展示按钮 + type: [Boolean, String], + default: false + } + }, + data() { + return { + currentIndex: 1 + } + }, + computed: { + maxPage() { + let maxPage = 1 + let total = Number(this.total) + let pageSize = Number(this.pageSize) + if (total && pageSize) { + maxPage = Math.ceil(total / pageSize) + } + return maxPage + } + }, + watch: { + current(val) { + this.currentIndex = +val + } + }, + created() { + this.currentIndex = +this.current + }, + methods: { + clickLeft() { + if (Number(this.currentIndex) === 1) { + return + } + this.currentIndex -= 1 + this.change('prev') + }, + clickRight() { + if (Number(this.currentIndex) === this.maxPage) { + return + } + this.currentIndex += 1 + this.change('next') + }, + change(e) { + this.$emit('change', { + type: e, + current: this.currentIndex + }) + } + } + } +</script> + +<style lang="scss" scoped> + .uni-pagination { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + position: relative; + overflow: hidden; + flex-direction: row; + justify-content: center; + align-items: center; + } + + .uni-pagination__btn { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + width: 60px; + height: 30px; + line-height: 30px; + font-size: $uni-font-size-base; + position: relative; + background-color: $uni-bg-color-grey; + flex-direction: row; + justify-content: center; + align-items: center; + text-align: center; + border-width: 1px; + border-style: solid; + border-color: $uni-border-color; + } + + .uni-pagination__child-btn { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + font-size: $uni-font-size-base; + position: relative; + flex-direction: row; + justify-content: center; + align-items: center; + text-align: center; + } + + .uni-pagination__num { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex: 1; + flex-direction: row; + justify-content: center; + align-items: center; + height: 30px; + line-height: 30px; + font-size: $uni-font-size-base; + color: $uni-text-color; + } + + .uni-pagination__num-current { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + } + + .uni-pagination__num-current-text { + font-size: 15px; + } + + .uni-pagination--enabled { + color: #333333; + opacity: 1; + } + + .uni-pagination--disabled { + opacity: 0.3; + } + + .uni-pagination--hover { + color: rgba(0, 0, 0, .6); + background-color: $uni-bg-color-hover; + } +</style> diff --git a/hive-app/components/uni-popup-dialog/uni-popup-dialog.vue b/hive-app/components/uni-popup-dialog/uni-popup-dialog.vue new file mode 100644 index 0000000..c91123c --- /dev/null +++ b/hive-app/components/uni-popup-dialog/uni-popup-dialog.vue @@ -0,0 +1,243 @@ +<template> + <view class="uni-popup-dialog"> + <view class="uni-dialog-title"> + <text class="uni-dialog-title-text" :class="['uni-popup__'+dialogType]">{{title}}</text> + </view> + <view class="uni-dialog-content"> + <text class="uni-dialog-content-text" v-if="mode === 'base'">{{content}}</text> + <input v-else class="uni-dialog-input" v-model="val" type="text" :placeholder="placeholder" :focus="focus" > + </view> + <view class="uni-dialog-button-group"> + <view class="uni-dialog-button" @click="close"> + <text class="uni-dialog-button-text">取消</text> + </view> + <view class="uni-dialog-button uni-border-left" @click="onOk"> + <text class="uni-dialog-button-text uni-button-color">确定</text> + </view> + </view> + + </view> +</template> + +<script> + /** + * PopUp 弹出层-对话框样式 + * @description 弹出层-对话框样式 + * @tutorial https://ext.dcloud.net.cn/plugin?id=329 + * @property {String} value input 模式下的默认值 + * @property {String} placeholder input 模式下输入提示 + * @property {String} type = [success|warning|info|error] 主题样式 + * @value success 成功 + * @value warning 提示 + * @value info 消息 + * @value error 错误 + * @property {String} mode = [base|input] 模式、 + * @value base 基础对话框 + * @value input 可输入对话框 + * @property {String} content 对话框内容 + * @property {Boolean} beforeClose 是否拦截取消事件 + * @event {Function} confirm 点击确认按钮触发 + * @event {Function} close 点击取消按钮触发 + */ + + export default { + name: "uniPopupDialog", + props: { + value: { + type: [String, Number], + default: '' + }, + placeholder: { + type: [String, Number], + default: '请输入内容' + }, + /** + * 对话框主题 success/warning/info/error 默认 success + */ + type: { + type: String, + default: 'error' + }, + /** + * 对话框模式 base/input + */ + mode: { + type: String, + default: 'base' + }, + /** + * 对话框标题 + */ + title: { + type: String, + default: '提示' + }, + /** + * 对话框内容 + */ + content: { + type: String, + default: '' + }, + /** + * 拦截取消事件 ,如果拦截取消事件,必须监听close事件,执行 done() + */ + beforeClose: { + type: Boolean, + default: false + } + }, + data() { + return { + dialogType: 'error', + focus: false, + val: "" + } + }, + inject: ['popup'], + watch: { + type(val) { + this.dialogType = val + }, + mode(val) { + if (val === 'input') { + this.dialogType = 'info' + } + }, + value(val) { + this.val = val + } + }, + created() { + // 对话框遮罩不可点击 + this.popup.mkclick = false + if (this.mode === 'input') { + this.dialogType = 'info' + this.val = this.value + } else { + this.dialogType = this.type + } + }, + mounted() { + this.focus = true + }, + methods: { + /** + * 点击确认按钮 + */ + onOk() { + this.$emit('confirm', () => { + this.popup.close() + if (this.mode === 'input') this.val = this.value + }, this.mode === 'input' ? this.val : '') + }, + /** + * 点击取消按钮 + */ + close() { + if (this.beforeClose) { + this.$emit('close', () => { + this.popup.close() + }) + return + } + this.popup.close() + } + } + } +</script> + +<style lang="scss" scoped> + .uni-popup-dialog { + width: 300px; + border-radius: 15px; + background-color: #fff; + } + + .uni-dialog-title { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + justify-content: center; + padding-top: 15px; + padding-bottom: 5px; + } + + .uni-dialog-title-text { + font-size: 16px; + font-weight: 500; + } + + .uni-dialog-content { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + justify-content: center; + align-items: center; + padding: 5px 15px 15px 15px; + } + + .uni-dialog-content-text { + font-size: 14px; + color: #6e6e6e; + } + + .uni-dialog-button-group { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + border-top-color: #f5f5f5; + border-top-style: solid; + border-top-width: 1px; + } + + .uni-dialog-button { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + + flex: 1; + flex-direction: row; + justify-content: center; + align-items: center; + height: 45px; + } + + .uni-border-left { + border-left-color: #f0f0f0; + border-left-style: solid; + border-left-width: 1px; + } + + .uni-dialog-button-text { + font-size: 14px; + } + + .uni-button-color { + color: $uni-color-primary; + } + + .uni-dialog-input { + flex: 1; + font-size: 14px; + } + + .uni-popup__success { + color: $uni-color-success; + } + + .uni-popup__warn { + color: $uni-color-warning; + } + + .uni-popup__error { + color: $uni-color-error; + } + + .uni-popup__info { + color: #909399; + } +</style> diff --git a/hive-app/components/uni-popup-message/uni-popup-message.vue b/hive-app/components/uni-popup-message/uni-popup-message.vue new file mode 100644 index 0000000..a32bd00 --- /dev/null +++ b/hive-app/components/uni-popup-message/uni-popup-message.vue @@ -0,0 +1,116 @@ +<template> + <view class="uni-popup-message" :class="'uni-popup__'+[type]"> + <text class="uni-popup-message-text" :class="'uni-popup__'+[type]+'-text'">{{message}}</text> + </view> +</template> + +<script> + + /** + * PopUp 弹出层-消息提示 + * @description 弹出层-消息提示 + * @tutorial https://ext.dcloud.net.cn/plugin?id=329 + * @property {String} type = [success|warning|info|error] 主题样式 + * @value success 成功 + * @value warning 提示 + * @value info 消息 + * @value error 错误 + * @property {String} message 消息提示文字 + * @property {String} duration 显示时间,设置为 0 则不会自动关闭 + */ + + export default { + name: 'UniPopupMessage', + props: { + /** + * 主题 success/warning/info/error 默认 success + */ + type: { + type: String, + default: 'success' + }, + /** + * 消息文字 + */ + message: { + type: String, + default: '' + }, + /** + * 显示时间,设置为 0 则不会自动关闭 + */ + duration: { + type: Number, + default: 3000 + } + }, + inject: ['popup'], + data() { + return {} + }, + created() { + this.popup.childrenMsg = this + }, + methods: { + open() { + if (this.duration === 0) return + clearTimeout(this.popuptimer) + this.popuptimer = setTimeout(() => { + this.popup.close() + }, this.duration) + }, + close() { + clearTimeout(this.popuptimer) + } + } + } +</script> +<style lang="scss" scoped> + .uni-popup-message { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + background-color: #e1f3d8; + padding: 10px 15px; + border-color: #eee; + border-style: solid; + border-width: 1px; + } + .uni-popup-message-text { + font-size: 14px; + padding: 0; + } + + .uni-popup__success { + background-color: #e1f3d8; + } + + .uni-popup__success-text { + color: #67C23A; + } + + .uni-popup__warn { + background-color: #faecd8; + } + + .uni-popup__warn-text { + color: #E6A23C; + } + + .uni-popup__error { + background-color: #fde2e2; + } + + .uni-popup__error-text { + color: #F56C6C; + } + + .uni-popup__info { + background-color: #F2F6FC; + } + + .uni-popup__info-text { + color: #909399; + } +</style> diff --git a/hive-app/components/uni-popup-share/uni-popup-share.vue b/hive-app/components/uni-popup-share/uni-popup-share.vue new file mode 100644 index 0000000..4c11a13 --- /dev/null +++ b/hive-app/components/uni-popup-share/uni-popup-share.vue @@ -0,0 +1,165 @@ +<template> + <view class="uni-popup-share"> + <view class="uni-share-title"><text class="uni-share-title-text">{{title}}</text></view> + <view class="uni-share-content"> + <view class="uni-share-content-box"> + <view class="uni-share-content-item" v-for="(item,index) in bottomData" :key="index" @click.stop="select(item,index)"> + <image class="uni-share-image" :src="item.icon" mode="aspectFill"></image> + <text class="uni-share-text">{{item.text}}</text> + </view> + + </view> + </view> + <view class="uni-share-button-box"> + <button class="uni-share-button" @click="close">取消</button> + </view> + </view> +</template> + +<script> + export default { + name: 'UniPopupShare', + props: { + title: { + type: String, + default: '分享到' + } + }, + inject: ['popup'], + data() { + return { + bottomData: [{ + text: '微信', + icon: 'https://img-cdn-qiniu.dcloud.net.cn/uni-ui/grid-2.png', + name: 'wx' + }, + { + text: '支付宝', + icon: 'https://img-cdn-qiniu.dcloud.net.cn/uni-ui/grid-8.png', + name: 'wx' + }, + { + text: 'QQ', + icon: 'https://img-cdn-qiniu.dcloud.net.cn/uni-ui/gird-3.png', + name: 'qq' + }, + { + text: '新浪', + icon: 'https://img-cdn-qiniu.dcloud.net.cn/uni-ui/grid-1.png', + name: 'sina' + }, + { + text: '百度', + icon: 'https://img-cdn-qiniu.dcloud.net.cn/uni-ui/grid-7.png', + name: 'copy' + }, + { + text: '其他', + icon: 'https://img-cdn-qiniu.dcloud.net.cn/uni-ui/grid-5.png', + name: 'more' + } + ] + } + }, + created() {}, + methods: { + /** + * 选择内容 + */ + select(item, index) { + this.$emit('select', { + item, + index + }, () => { + this.popup.close() + }) + }, + /** + * 关闭窗口 + */ + close() { + this.popup.close() + } + } + } +</script> +<style lang="scss" scoped> + .uni-popup-share { + background-color: #fff; + } + .uni-share-title { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + align-items: center; + justify-content: center; + height: 40px; + } + .uni-share-title-text { + font-size: 14px; + color: #666; + } + .uni-share-content { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + justify-content: center; + padding-top: 10px; + } + + .uni-share-content-box { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + flex-wrap: wrap; + width: 360px; + } + + .uni-share-content-item { + width: 90px; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; + justify-content: center; + padding: 10px 0; + align-items: center; + } + + .uni-share-content-item:active { + background-color: #f5f5f5; + } + + .uni-share-image { + width: 30px; + height: 30px; + } + + .uni-share-text { + margin-top: 10px; + font-size: 14px; + color: #3B4144; + } + + .uni-share-button-box { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + padding: 10px 15px; + } + + .uni-share-button { + flex: 1; + border-radius: 50px; + color: #666; + font-size: 16px; + } + + .uni-share-button::after { + border-radius: 50px; + } +</style> diff --git a/hive-app/components/uni-popup/message.js b/hive-app/components/uni-popup/message.js new file mode 100644 index 0000000..0ff9a02 --- /dev/null +++ b/hive-app/components/uni-popup/message.js @@ -0,0 +1,22 @@ +export default { + created() { + if (this.type === 'message') { + // 不显示遮罩 + this.maskShow = false + // 获取子组件对象 + this.childrenMsg = null + } + }, + methods: { + customOpen() { + if (this.childrenMsg) { + this.childrenMsg.open() + } + }, + customClose() { + if (this.childrenMsg) { + this.childrenMsg.close() + } + } + } +} diff --git a/hive-app/components/uni-popup/popup.js b/hive-app/components/uni-popup/popup.js new file mode 100644 index 0000000..14d4b77 --- /dev/null +++ b/hive-app/components/uni-popup/popup.js @@ -0,0 +1,25 @@ +import message from './message.js'; +// 定义 type 类型:弹出类型:top/bottom/center +const config = { + // 顶部弹出 + top:'top', + // 底部弹出 + bottom:'bottom', + // 居中弹出 + center:'center', + // 消息提示 + message:'top', + // 对话框 + dialog:'center', + // 分享 + share:'bottom', +} + +export default { + data(){ + return { + config:config + } + }, + mixins: [message] +} diff --git a/hive-app/components/uni-popup/share.js b/hive-app/components/uni-popup/share.js new file mode 100644 index 0000000..462bb83 --- /dev/null +++ b/hive-app/components/uni-popup/share.js @@ -0,0 +1,16 @@ +export default { + created() { + if (this.type === 'share') { + // 关闭点击 + this.mkclick = false + } + }, + methods: { + customOpen() { + console.log('share 打开了'); + }, + customClose() { + console.log('share 关闭了'); + } + } +} diff --git a/hive-app/components/uni-popup/uni-popup.vue b/hive-app/components/uni-popup/uni-popup.vue new file mode 100644 index 0000000..ae40d03 --- /dev/null +++ b/hive-app/components/uni-popup/uni-popup.vue @@ -0,0 +1,297 @@ +<template> + <view v-if="showPopup" class="uni-popup" :class="[popupstyle]" @touchmove.stop.prevent="clear"> + <uni-transition v-if="maskShow" class="uni-mask--hook" :mode-class="['fade']" :styles="maskClass" :duration="duration" :show="showTrans" + @click="onTap" /> + <uni-transition :mode-class="ani" :styles="transClass" :duration="duration" :show="showTrans" @click="onTap"> + <view class="uni-popup__wrapper-box" @click.stop="clear"> + <slot /> + </view> + </uni-transition> + </view> +</template> + +<script> + import uniTransition from '../uni-transition/uni-transition.vue' + import popup from './popup.js' + /** + * PopUp 弹出层 + * @description 弹出层组件,为了解决遮罩弹层的问题 + * @tutorial https://ext.dcloud.net.cn/plugin?id=329 + * @property {String} type = [top|center|bottom] 弹出方式 + * @value top 顶部弹出 + * @value center 中间弹出 + * @value bottom 底部弹出 + * @value message 消息提示 + * @value dialog 对话框 + * @value share 底部分享示例 + * @property {Boolean} animation = [ture|false] 是否开启动画 + * @property {Boolean} maskClick = [ture|false] 蒙版点击是否关闭弹窗 + * @event {Function} change 打开关闭弹窗触发,e={show: false} + */ + + export default { + name: 'UniPopup', + components: { + uniTransition + }, + props: { + // 开启动画 + animation: { + type: Boolean, + default: true + }, + // 弹出层类型,可选值,top: 顶部弹出层;bottom:底部弹出层;center:全屏弹出层 + // message: 消息提示 ; dialog : 对话框 + type: { + type: String, + default: 'center' + }, + // maskClick + maskClick: { + type: Boolean, + default: true + } + }, + provide() { + return { + popup: this + } + }, + mixins: [popup], + watch: { + /** + * 监听type类型 + */ + type: { + handler: function(newVal) { + this[this.config[newVal]]() + }, + immediate: true + }, + /** + * 监听遮罩是否可点击 + * @param {Object} val + */ + maskClick: { + handler: function(val) { + this.mkclick = val + }, + immediate: true + } + }, + data() { + return { + duration: 300, + ani: [], + showPopup: false, + showTrans: false, + maskClass: { + 'position': 'fixed', + 'bottom': 0, + 'top': 0, + 'left': 0, + 'right': 0, + 'backgroundColor': 'rgba(0, 0, 0, 0.4)' + }, + transClass: { + 'position': 'fixed', + 'left': 0, + 'right': 0, + }, + maskShow: true, + mkclick: true, + popupstyle: 'top' + } + }, + created() { + this.mkclick = this.maskClick + if (this.animation) { + this.duration = 300 + } else { + this.duration = 0 + } + }, + methods: { + clear(e) { + // TODO nvue 取消冒泡 + e.stopPropagation() + }, + open() { + this.showPopup = true + this.$nextTick(() => { + new Promise(resolve => { + clearTimeout(this.timer) + this.timer = setTimeout(() => { + this.showTrans = true + // fixed by mehaotian 兼容 app 端 + this.$nextTick(() => { + resolve(); + }) + }, 50); + }).then(res => { + // 自定义打开事件 + clearTimeout(this.msgtimer) + this.msgtimer = setTimeout(() => { + this.customOpen && this.customOpen() + }, 100) + this.$emit('change', { + show: true, + type: this.type + }) + }) + }) + }, + close(type) { + this.showTrans = false + this.$nextTick(() => { + this.$emit('change', { + show: false, + type: this.type + }) + clearTimeout(this.timer) + // 自定义关闭事件 + this.customOpen && this.customClose() + this.timer = setTimeout(() => { + this.showPopup = false + }, 300) + }) + }, + onTap() { + if (!this.mkclick) return + this.close() + }, + /** + * 顶部弹出样式处理 + */ + top() { + this.popupstyle = 'top' + this.ani = ['slide-top'] + this.transClass = { + 'position': 'fixed', + 'left': 0, + 'right': 0, + } + }, + /** + * 底部弹出样式处理 + */ + bottom() { + this.popupstyle = 'bottom' + this.ani = ['slide-bottom'] + this.transClass = { + 'position': 'fixed', + 'left': 0, + 'right': 0, + 'bottom': 0 + } + }, + /** + * 中间弹出样式处理 + */ + center() { + this.popupstyle = 'center' + this.ani = ['zoom-out', 'fade'] + this.transClass = { + 'position': 'fixed', + /* #ifndef APP-NVUE */ + 'display': 'flex', + 'flexDirection': 'column', + /* #endif */ + 'bottom': 0, + 'left': 0, + 'right': 0, + 'top': 0, + 'justifyContent': 'center', + 'alignItems': 'center' + } + } + } + } +</script> +<style lang="scss" scoped> + .uni-popup { + position: fixed; + /* #ifndef APP-NVUE */ + z-index: 99; + /* #endif */ + } + + .uni-popup__mask { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: $uni-bg-color-mask; + opacity: 0; + } + + .mask-ani { + transition-property: opacity; + transition-duration: 0.2s; + } + + .uni-top-mask { + opacity: 1; + } + + .uni-bottom-mask { + opacity: 1; + } + + .uni-center-mask { + opacity: 1; + } + + .uni-popup__wrapper { + /* #ifndef APP-NVUE */ + display: block; + /* #endif */ + position: absolute; + } + + .top { + /* #ifdef H5 */ + top: var(--window-top); + /* #endif */ + /* #ifndef H5 */ + top: 0; + /* #endif */ + } + + .bottom { + bottom: 0; + } + + .uni-popup__wrapper-box { + /* #ifndef APP-NVUE */ + display: block; + /* #endif */ + position: relative; + /* iphonex 等安全区设置,底部安全区适配 */ + /* #ifndef APP-NVUE */ + padding-bottom: constant(safe-area-inset-bottom); + padding-bottom: env(safe-area-inset-bottom); + /* #endif */ + } + + .content-ani { + // transition: transform 0.3s; + transition-property: transform, opacity; + transition-duration: 0.2s; + } + + + .uni-top-content { + transform: translateY(0); + } + + .uni-bottom-content { + transform: translateY(0); + } + + .uni-center-content { + transform: scale(1); + opacity: 1; + } +</style> diff --git a/hive-app/components/uni-rate/uni-rate.vue b/hive-app/components/uni-rate/uni-rate.vue new file mode 100644 index 0000000..a748321 --- /dev/null +++ b/hive-app/components/uni-rate/uni-rate.vue @@ -0,0 +1,296 @@ +<template> + <view> + <view + ref="uni-rate" + class="uni-rate" + > + <view + class="uni-rate__icon" + :style="{ 'margin-right': margin + 'px' }" + v-for="(star, index) in stars" + :key="index" + @touchstart.stop="touchstart" + @touchmove.stop="touchmove" + > + <uni-icons + :color="color" + :size="size" + :type="isFill ? 'star-filled' : 'star'" + /> + <!-- #ifdef APP-NVUE --> + <view + :style="{ width: star.activeWitch.replace('%','')*size/100+'px'}" + class="uni-rate__icon-on" + > + <uni-icons + style="text-align: left;" + :color="disabled?'#ccc':activeColor" + :size="size" + type="star-filled" + /> + </view> + <!-- #endif --> + <!-- #ifndef APP-NVUE --> + <view + :style="{ width: star.activeWitch}" + class="uni-rate__icon-on" + > + <uni-icons + :color="disabled?disabledColor:activeColor" + :size="size" + type="star-filled" + /> + </view> + <!-- #endif --> + </view> + </view> + </view> +</template> + +<script> + // #ifdef APP-NVUE + const dom = uni.requireNativePlugin('dom'); + // #endif + import uniIcons from "../uni-icons/uni-icons.vue"; + /** + * Rate 评分 + * @description 评分组件 + * @tutorial https://ext.dcloud.net.cn/plugin?id=33 + * @property {Boolean} isFill = [true|false] 星星的类型,是否为实心类型, 默认为实心 + * @property {String} color 未选中状态的星星颜色,默认为 "#ececec" + * @property {String} activeColor 选中状态的星星颜色,默认为 "#ffca3e" + * @property {String} disabledColor 禁用状态的星星颜色,默认为 "#c0c0c0" + * @property {Number} size 星星的大小 + * @property {Number} value/v-model 当前评分 + * @property {Number} max 最大评分评分数量,目前一分一颗星 + * @property {Number} margin 星星的间距,单位 px + * @property {Boolean} disabled = [true|false] 是否为禁用状态,默认为 false + * @property {Boolean} readonly = [true|false] 是否为只读状态,默认为 false + * @property {Boolean} allowHalf = [true|false] 是否实现半星,默认为 false + * @property {Boolean} touchable = [true|false] 是否支持滑动手势,默认为 true + * @event {Function} change uniRate 的 value 改变时触发事件,e={value:Number} + */ + + export default { + components: { + uniIcons + }, + name: "UniRate", + props: { + isFill: { + // 星星的类型,是否镂空 + type: [Boolean, String], + default: true + }, + color: { + // 星星未选中的颜色 + type: String, + default: "#ececec" + }, + activeColor: { + // 星星选中状态颜色 + type: String, + default: "#ffca3e" + }, + disabledColor: { + // 星星禁用状态颜色 + type: String, + default: "#c0c0c0" + }, + size: { + // 星星的大小 + type: [Number, String], + default: 24 + }, + value: { + // 当前评分 + type: [Number, String], + default: 1 + }, + max: { + // 最大评分 + type: [Number, String], + default: 5 + }, + margin: { + // 星星的间距 + type: [Number, String], + default: 0 + }, + disabled: { + // 是否可点击 + type: [Boolean, String], + default: false + }, + readonly: { + // 是否只读 + type: [Boolean, String], + default: false + }, + allowHalf: { + // 是否显示半星 + type: [Boolean, String], + default: false + }, + touchable: { + // 是否支持滑动手势 + type: [Boolean, String], + default: true + } + }, + data() { + return { + valueSync: "" + }; + }, + watch: { + value(newVal) { + this.valueSync = Number(newVal); + } + }, + computed: { + stars() { + const value = this.valueSync ? this.valueSync : 0; + const starList = []; + const floorValue = Math.floor(value); + const ceilValue = Math.ceil(value); + for (let i = 0; i < this.max; i++) { + if (floorValue > i) { + starList.push({ + activeWitch: "100%" + }); + } else if (ceilValue - 1 === i) { + starList.push({ + activeWitch: (value - floorValue) * 100 + "%" + }); + } else { + starList.push({ + activeWitch: "0" + }); + } + } + return starList; + } + }, + created() { + this.valueSync = Number(this.value); + this._rateBoxLeft = 0 + this._oldValue = null + }, + mounted() { + setTimeout(() => { + this._getSize() + }, 100) + }, + methods: { + touchstart(e) { + if (this.readonly || this.disabled) return + const { + clientX, + screenX + } = e.changedTouches[0] + // TODO 做一下兼容,只有 Nvue 下才有 screenX,其他平台式 clientX + this._getRateCount(clientX || screenX) + }, + touchmove(e) { + if (this.readonly || this.disabled || !this.touchable) return + const { + clientX, + screenX + } = e.changedTouches[0] + this._getRateCount(clientX || screenX) + }, + /** + * 获取星星个数 + */ + _getRateCount(clientX) { + const rateMoveRange = clientX - this._rateBoxLeft + let index = parseInt(rateMoveRange / (this.size + this.margin)) + index = index < 0 ? 0 : index; + index = index > this.max ? this.max : index; + const range = parseInt(rateMoveRange - (this.size + this.margin) * index); + let value = 0; + if (this._oldValue === index) return; + this._oldValue = index; + + if (this.allowHalf) { + if (range > (this.size / 2)) { + value = index + 1 + } else { + value = index + 0.5 + } + } else { + value = index + 1 + } + + value = Math.max(0.5, Math.min(value, this.max)) + this.valueSync = value + this._onChange() + }, + + /** + * 触发动态修改 + */ + _onChange() { + + this.$emit("input", this.valueSync); + this.$emit("change", { + value: this.valueSync + }); + }, + /** + * 获取星星距离屏幕左侧距离 + */ + _getSize() { + // #ifndef APP-NVUE + uni.createSelectorQuery() + .in(this) + .select('.uni-rate') + .boundingClientRect() + .exec(ret => { + if (ret) { + this._rateBoxLeft = ret[0].left + } + }) + // #endif + // #ifdef APP-NVUE + dom.getComponentRect(this.$refs['uni-rate'], (ret) => { + const size = ret.size + if (size) { + this._rateBoxLeft = size.left + } + }) + // #endif + } + } + }; +</script> + +<style + lang="scss" + scoped +> + .uni-rate { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + line-height: 1; + font-size: 0; + flex-direction: row; + } + + .uni-rate__icon { + position: relative; + line-height: 1; + font-size: 0; + } + + .uni-rate__icon-on { + overflow: hidden; + position: absolute; + top: 0; + left: 0; + line-height: 1; + text-align: left; + } +</style> diff --git a/hive-app/components/uni-search-bar/uni-search-bar.vue b/hive-app/components/uni-search-bar/uni-search-bar.vue new file mode 100644 index 0000000..edb6fef --- /dev/null +++ b/hive-app/components/uni-search-bar/uni-search-bar.vue @@ -0,0 +1,206 @@ +<template> + <view class="uni-searchbar"> + <view :style="{borderRadius:radius+'px',backgroundColor: bgColor}" class="uni-searchbar__box" @click="searchClick"> + <!-- #ifdef MP-ALIPAY --> + <view class="uni-searchbar__box-icon-search"> + <uni-icons color="#999999" size="18" type="search" /> + </view> + <!-- #endif --> + <!-- #ifndef MP-ALIPAY --> + <uni-icons color="#999999" class="uni-searchbar__box-icon-search" size="18" type="search" /> + <!-- #endif --> + <input v-if="show" :focus="showSync" :placeholder="placeholder" :maxlength="maxlength" @confirm="confirm" class="uni-searchbar__box-search-input" + confirm-type="search" type="text" v-model="searchVal" /> + <text v-else class="uni-searchbar__text-placeholder">{{ placeholder }}</text> + <view v-if="show && (clearButton==='always'||clearButton==='auto'&&searchVal!=='')" class="uni-searchbar__box-icon-clear" @click="clear"> + <uni-icons color="#999999" class="" size="24" type="clear" /> + </view> + </view> + <text @click="cancel" class="uni-searchbar__cancel" v-if="cancelButton ==='always' || show && cancelButton ==='auto'">{{cancelText}}</text> + </view> +</template> + +<script> + import uniIcons from "../uni-icons/uni-icons.vue"; + + /** + * SearchBar 搜索栏 + * @description 评分组件 + * @tutorial https://ext.dcloud.net.cn/plugin?id=866 + * @property {Number} radius 搜索栏圆角 + * @property {Number} maxlength 输入最大长度 + * @property {String} placeholder 搜索栏Placeholder + * @property {String} clearButton = [always|auto|none] 是否显示清除按钮 + * @value always 一直显示 + * @value auto 输入框不为空时显示 + * @value none 一直不显示 + * @property {String} cancelButton = [always|auto|none] 是否显示取消按钮 + * @value always 一直显示 + * @value auto 输入框不为空时显示 + * @value none 一直不显示 + * @property {String} cancelText 取消按钮的文字 + * @property {String} bgColor 输入框背景颜色 + * @event {Function} confirm uniSearchBar 的输入框 confirm 事件,返回参数为uniSearchBar的value,e={value:Number} + * @event {Function} input uniSearchBar 的 value 改变时触发事件,返回参数为uniSearchBar的value,e={value:Number} + * @event {Function} cancel 点击取消按钮时触发事件,返回参数为uniSearchBar的value,e={value:Number} + */ + + export default { + name: "UniSearchBar", + components: { + uniIcons + }, + props: { + placeholder: { + type: String, + default: "请输入搜索内容" + }, + radius: { + type: [Number, String], + default: 5 + }, + clearButton: { + type: String, + default: "auto" + }, + cancelButton: { + type: String, + default: "auto" + }, + cancelText: { + type: String, + default: '取消' + }, + bgColor: { + type: String, + default: "#F8F8F8" + }, + maxlength: { + type: [Number, String], + default: 100 + } + }, + data() { + return { + show: false, + showSync: false, + searchVal: "" + } + }, + watch: { + searchVal() { + this.$emit("input", { + value: this.searchVal + }) + } + }, + methods: { + searchClick() { + if (this.show) { + return + } + this.searchVal = "" + this.show = true; + this.$nextTick(() => { + this.showSync = true; + }) + }, + clear() { + this.searchVal = "" + }, + cancel() { + this.$emit("cancel", { + value: this.searchVal + }); + this.searchVal = "" + this.show = false + this.showSync = false + // #ifndef APP-PLUS + uni.hideKeyboard() + // #endif + // #ifdef APP-PLUS + plus.key.hideSoftKeybord() + // #endif + }, + confirm() { + // #ifndef APP-PLUS + uni.hideKeyboard(); + // #endif + // #ifdef APP-PLUS + plus.key.hideSoftKeybord() + // #endif + this.$emit("confirm", { + value: this.searchVal + }) + } + } + }; +</script> + +<style lang="scss" scoped> + $uni-searchbar-height: 36px; + + .uni-searchbar { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + position: relative; + padding: $uni-spacing-col-base; + background-color: $uni-bg-color; + } + + .uni-searchbar__box { + /* #ifndef APP-NVUE */ + display: flex; + box-sizing: border-box; + /* #endif */ + overflow: hidden; + position: relative; + flex: 1; + justify-content: center; + flex-direction: row; + align-items: center; + height: $uni-searchbar-height; + padding: 5px 8px 5px 0px; + border-width: 0.5px; + border-style: solid; + border-color: $uni-border-color; + } + + .uni-searchbar__box-icon-search { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + width: 32px; + justify-content: center; + align-items: center; + color: $uni-text-color-placeholder; + } + + .uni-searchbar__box-search-input { + flex: 1; + font-size: $uni-font-size-base; + color: $uni-text-color; + } + + .uni-searchbar__box-icon-clear { + align-items: center; + line-height: 24px; + padding-left: 5px; + } + + .uni-searchbar__text-placeholder { + font-size: $uni-font-size-base; + color: $uni-text-color-placeholder; + margin-left: 5px; + } + + .uni-searchbar__cancel { + padding-left: 10px; + line-height: $uni-searchbar-height; + font-size: 14px; + color: $uni-text-color; + } +</style> diff --git a/hive-app/components/uni-section/uni-section.vue b/hive-app/components/uni-section/uni-section.vue new file mode 100644 index 0000000..52a1567 --- /dev/null +++ b/hive-app/components/uni-section/uni-section.vue @@ -0,0 +1,136 @@ +<template> + <view class="uni-section" nvue> + <view v-if="type" class="uni-section__head"> + <view :class="type" class="uni-section__head-tag" /> + </view> + <view class="uni-section__content"> + <text :class="{'distraction':!subTitle}" class="uni-section__content-title">{{ title }}</text> + <text v-if="subTitle" class="uni-section__content-sub">{{ subTitle }}</text> + </view> + <slot /> + </view> +</template> + +<script> + + /** + * Section 标题栏 + * @description 标题栏 + * @property {String} type = [line|circle] 标题装饰类型 + * @value line 竖线 + * @value circle 圆形 + * @property {String} title 主标题 + * @property {String} subTitle 副标题 + */ + + export default { + name: 'UniSection', + props: { + type: { + type: String, + default: '' + }, + title: { + type: String, + default: '' + }, + subTitle: { + type: String, + default: '' + } + }, + data() { + return {} + }, + watch: { + title(newVal) { + if (uni.report && newVal !== '') { + uni.report('title', newVal) + } + } + }, + methods: { + onClick() { + this.$emit('click') + } + } + } +</script> +<style lang="scss" scoped> + .uni-section { + position: relative; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + margin-top: 10px; + flex-direction: row; + align-items: center; + padding: 0 10px; + height: 50px; + background-color: $uni-bg-color-grey; + /* #ifdef APP-NVUE */ + // border-bottom-color: $uni-border-color; + // border-bottom-style: solid; + // border-bottom-width: 0.5px; + /* #endif */ + font-weight: normal; + } + /* #ifndef APP-NVUE */ + // .uni-section:after { + // position: absolute; + // bottom: 0; + // right: 0; + // left: 0; + // height: 1px; + // content: ''; + // -webkit-transform: scaleY(.5); + // transform: scaleY(.5); + // background-color: $uni-border-color; + // } + /* #endif */ + + .uni-section__head { + flex-direction: row; + justify-content: center; + align-items: center; + margin-right: 10px; + } + + .line { + height: 15px; + background-color: $uni-text-color-disable; + border-radius: 5px; + width: 3px; + } + + .circle { + width: 8px; + height: 8px; + border-top-right-radius: 50px; + border-top-left-radius: 50px; + border-bottom-left-radius: 50px; + border-bottom-right-radius: 50px; + background-color: $uni-text-color-disable; + } + + .uni-section__content { + flex-direction: column; + flex: 1; + color: $uni-text-color; + } + + .uni-section__content-title { + font-size: $uni-font-size-base; + color: $uni-text-color; + } + + .distraction { + flex-direction: row; + align-items: center; + } + + .uni-section__content-sub { + font-size: $uni-font-size-sm; + color: $uni-text-color-grey; + } +</style> diff --git a/hive-app/components/uni-segmented-control/uni-segmented-control.vue b/hive-app/components/uni-segmented-control/uni-segmented-control.vue new file mode 100644 index 0000000..01b80da --- /dev/null +++ b/hive-app/components/uni-segmented-control/uni-segmented-control.vue @@ -0,0 +1,138 @@ +<template> + <view :class="[styleType === 'text'?'segmented-control--text' : 'segmented-control--button' ]" :style="{ borderColor: styleType === 'text' ? '' : activeColor }" + class="segmented-control"> + <view v-for="(item, index) in values" :class="[ styleType === 'text'?'segmented-control__item--text': 'segmented-control__item--button' , index === currentIndex&&styleType === 'button'?'segmented-control__item--button--active': '' , index === 0&&styleType === 'button'?'segmented-control__item--button--first': '',index === values.length - 1&&styleType === 'button'?'segmented-control__item--button--last': '' ]" + :key="index" :style="{ + backgroundColor: index === currentIndex && styleType === 'button' ? activeColor : '',borderColor: index === currentIndex&&styleType === 'text'||styleType === 'button'?activeColor:'transparent' + }" + class="segmented-control__item" @click="_onClick(index)"> + <text :style="{color: + index === currentIndex + ? styleType === 'text' + ? activeColor + : '#fff' + : styleType === 'text' + ? '#000' + : activeColor}" + class="segmented-control__text">{{ item }}</text> + </view> + </view> +</template> + +<script> + /** + * SegmentedControl 分段器 + * @description 用作不同视图的显示 + * @tutorial https://ext.dcloud.net.cn/plugin?id=54 + * @property {Number} current 当前选中的tab索引值,从0计数 + * @property {String} styleType = [button|text] 分段器样式类型 + * @value button 按钮类型 + * @value text 文字类型 + * @property {String} activeColor 选中的标签背景色与边框颜色 + * @property {Array} values 选项数组 + * @event {Function} clickItem 组件触发点击事件时触发,e={currentIndex} + */ + + export default { + name: 'UniSegmentedControl', + props: { + current: { + type: Number, + default: 0 + }, + values: { + type: Array, + default () { + return [] + } + }, + activeColor: { + type: String, + default: '#007aff' + }, + styleType: { + type: String, + default: 'button' + } + }, + data() { + return { + currentIndex: 0 + } + }, + watch: { + current(val) { + if (val !== this.currentIndex) { + this.currentIndex = val + } + } + }, + created() { + this.currentIndex = this.current + }, + methods: { + _onClick(index) { + if (this.currentIndex !== index) { + this.currentIndex = index + this.$emit('clickItem', { + currentIndex: index + }) + } + } + } + } +</script> + +<style lang="scss" scoped> + + .segmented-control { + /* #ifndef APP-NVUE */ + display: flex; + box-sizing: border-box; + /* #endif */ + flex-direction: row; + height: 36px; + overflow: hidden; + } + + .segmented-control__item { + /* #ifndef APP-NVUE */ + display: inline-flex; + box-sizing: border-box; + /* #endif */ + position: relative; + flex: 1; + justify-content: center; + align-items: center; + } + + .segmented-control__item--button { + border-style: solid; + border-top-width: 1px; + border-bottom-width: 1px; + border-right-width: 1px; + border-left-width: 0; + } + + .segmented-control__item--button--first { + border-left-width: 1px; + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; + } + + .segmented-control__item--button--last { + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; + } + + .segmented-control__item--text { + border-bottom-style: solid; + border-bottom-width: 3px; + } + + .segmented-control__text { + font-size: 16px; + line-height: 20px; + text-align: center; + } +</style> diff --git a/hive-app/components/uni-status-bar/uni-status-bar.vue b/hive-app/components/uni-status-bar/uni-status-bar.vue new file mode 100644 index 0000000..4d9fb30 --- /dev/null +++ b/hive-app/components/uni-status-bar/uni-status-bar.vue @@ -0,0 +1,25 @@ +<template> + <view :style="{ height: statusBarHeight }" class="uni-status-bar"> + <slot /> + </view> +</template> + +<script> + var statusBarHeight = uni.getSystemInfoSync().statusBarHeight + 'px' + export default { + name: 'UniStatusBar', + data() { + return { + statusBarHeight: statusBarHeight + } + } + } +</script> + +<style lang="scss" scoped> + .uni-status-bar { + width: 750rpx; + height: 20px; + // height: var(--status-bar-height); + } +</style> diff --git a/hive-app/components/uni-steps/uni-steps.vue b/hive-app/components/uni-steps/uni-steps.vue new file mode 100644 index 0000000..fc72a91 --- /dev/null +++ b/hive-app/components/uni-steps/uni-steps.vue @@ -0,0 +1,256 @@ +<template> + <view class="uni-steps"> + <view :class="[direction==='column'?'uni-steps__column':'uni-steps__row']"> + <view :class="[direction==='column'?'uni-steps__column-text-container':'uni-steps__row-text-container']"> + <view v-for="(item,index) in options" :key="index" :class="[direction==='column'?'uni-steps__column-text':'uni-steps__row-text']"> + <text :style="{color:index<=active?activeColor:deactiveColor}" :class="[direction==='column'?'uni-steps__column-title':'uni-steps__row-title']">{{item.title}}</text> + <text :style="{color:index<=active?activeColor:deactiveColor}" :class="[direction==='column'?'uni-steps__column-desc':'uni-steps__row-desc']">{{item.desc}}</text> + </view> + </view> + <view :class="[direction==='column'?'uni-steps__column-container':'uni-steps__row-container']"> + <view :class="[direction==='column'?'uni-steps__column-line-item':'uni-steps__row-line-item']" v-for="(item,index) in options" + :key="index"> + <view :class="[direction==='column'?'uni-steps__column-line':'uni-steps__row-line',direction==='column'?'uni-steps__column-line--before':'uni-steps__row-line--before']" + :style="{backgroundColor:index<=active&&index!==0?activeColor:index===0?'transparent':deactiveColor}"></view> + <view :class="[direction==='column'?'uni-steps__column-check':'uni-steps__row-check']" v-if="index === active"> + <uni-icons :color="activeColor" type="checkbox-filled" size="14"></uni-icons> + </view> + <view :class="[direction==='column'?'uni-steps__column-circle':'uni-steps__row-circle']" v-else :style="{backgroundColor:index<active?activeColor:deactiveColor}"></view> + <view :class="[direction==='column'?'uni-steps__column-line':'uni-steps__row-line',direction==='column'?'uni-steps__column-line--after':'uni-steps__row-line--after']" + :style="{backgroundColor:index<active&&index!==options.length-1?activeColor:index===options.length-1?'transparent':deactiveColor}"></view> + </view> + </view> + </view> + </view> +</template> + +<script> + import uniIcons from '../uni-icons/uni-icons.vue' + + /** + * Steps 步骤条 + * @description 评分组件 + * @tutorial https://ext.dcloud.net.cn/plugin?id=34 + * @property {Number} active 当前步骤 + * @property {String} direction = [row|column] 当前步骤 + * @value row 横向 + * @value column 纵向 + * @property {String} activeColor 选中状态的颜色 + * @property {Array} options 数据源,格式为:[{title:'xxx',desc:'xxx'},{title:'xxx',desc:'xxx'}] + */ + + export default { + name: 'UniSteps', + components: { + uniIcons + }, + props: { + direction: { + // 排列方向 row column + type: String, + default: 'row' + }, + activeColor: { + // 激活状态颜色 + type: String, + default: '#1aad19' + }, + deactiveColor: { + // 未激活状态颜色 + type: String, + default: '#999999' + }, + active: { + // 当前步骤 + type: Number, + default: 0 + }, + options: { + type: Array, + default () { + return [] + } + } // 数据 + }, + data() { + return {} + } + } +</script> + +<style lang="scss" scoped> + .uni-steps { + /* #ifndef APP-NVUE */ + display: flex; + width: 100%; + /* #endif */ + /* #ifdef APP-NVUE */ + flex: 1; + /* #endif */ + flex-direction: column; + } + + .uni-steps__row { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; + } + + .uni-steps__column { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row-reverse; + } + + .uni-steps__row-text-container { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + } + + .uni-steps__column-text-container { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; + flex: 1; + } + + .uni-steps__row-text { + /* #ifndef APP-NVUE */ + display: inline-flex; + /* #endif */ + flex: 1; + flex-direction: column; + } + + .uni-steps__column-text { + padding: 6px 0px; + border-bottom-style: solid; + border-bottom-width: 1px; + border-bottom-color: $uni-border-color; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; + } + + .uni-steps__row-title { + font-size: $uni-font-size-base; + line-height: 16px; + text-align: center; + } + + .uni-steps__column-title { + font-size: $uni-font-size-base; + text-align: left; + line-height: 18px; + } + + .uni-steps__row-desc { + font-size: 12px; + line-height: 14px; + text-align: center; + } + + .uni-steps__column-desc { + font-size: $uni-font-size-sm; + text-align: left; + line-height: 18px; + } + + .uni-steps__row-container { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + } + + .uni-steps__column-container { + /* #ifndef APP-NVUE */ + display: inline-flex; + /* #endif */ + width: 30px; + flex-direction: column; + } + + .uni-steps__row-line-item { + /* #ifndef APP-NVUE */ + display: inline-flex; + /* #endif */ + flex-direction: row; + flex: 1; + height: 14px; + line-height: 14px; + align-items: center; + justify-content: center; + } + + .uni-steps__column-line-item { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; + flex: 1; + align-items: center; + justify-content: center; + } + + .uni-steps__row-line { + flex: 1; + height: 1px; + background-color: $uni-text-color-grey; + } + + .uni-steps__column-line { + width: 1px; + background-color: $uni-text-color-grey; + } + + .uni-steps__row-line--after { + transform: translateX(1px); + } + + .uni-steps__column-line--after { + flex: 1; + transform: translate(0px, 1px); + } + + .uni-steps__row-line--before { + transform: translateX(-1px); + } + + .uni-steps__column-line--before { + height: 6px; + transform: translate(0px, -1px); + } + + .uni-steps__row-circle { + width: 5px; + height: 5px; + border-radius: 100px; + background-color: $uni-text-color-grey; + margin: 0px 3px; + } + + .uni-steps__column-circle { + width: 5px; + height: 5px; + border-radius: 100px; + background-color: $uni-text-color-grey; + margin: 4px 0px 5px 0px; + } + + .uni-steps__row-check { + margin: 0px 6px; + } + + .uni-steps__column-check { + height: 14px; + line-height: 14px; + margin: 2px 0px; + } +</style> diff --git a/hive-app/components/uni-swipe-action-item/bindingx.js b/hive-app/components/uni-swipe-action-item/bindingx.js new file mode 100644 index 0000000..2a3977f --- /dev/null +++ b/hive-app/components/uni-swipe-action-item/bindingx.js @@ -0,0 +1,292 @@ +const BindingX = uni.requireNativePlugin('bindingx'); +const dom = uni.requireNativePlugin('dom'); +const animation = uni.requireNativePlugin('animation'); + +export default { + data() { + return {} + }, + + watch: { + show(newVal) { + if (this.autoClose) return + if (this.stop) return + this.stop = true + if (newVal) { + this.open(newVal) + } else { + this.close() + } + }, + leftOptions() { + this.getSelectorQuery() + this.init() + }, + rightOptions(newVal) { + this.init() + } + }, + created() { + if (this.swipeaction.children !== undefined) { + this.swipeaction.children.push(this) + } + }, + mounted() { + this.box = this.getEl(this.$refs['selector-box--hock']) + this.selector = this.getEl(this.$refs['selector-content--hock']); + this.leftButton = this.getEl(this.$refs['selector-left-button--hock']); + this.rightButton = this.getEl(this.$refs['selector-right-button--hock']); + this.init() + }, + beforeDestroy() { + this.swipeaction.children.forEach((item, index) => { + if (item === this) { + this.swipeaction.children.splice(index, 1) + } + }) + }, + methods: { + init() { + this.$nextTick(() => { + this.x = 0 + this.button = { + show: false + } + setTimeout(() => { + this.getSelectorQuery() + }, 200) + }) + }, + onClick(index, item, position) { + this.$emit('click', { + content: item, + index, + position + }) + }, + touchstart(e) { + // 每次只触发一次,避免多次监听造成闪烁 + if (this.stop) return + this.stop = true + if (this.autoClose) { + this.swipeaction.closeOther(this) + } + + const leftWidth = this.button.left.width + const rightWidth = this.button.right.width + let expression = this.range(this.x, -rightWidth, leftWidth) + let leftExpression = this.range(this.x - leftWidth, -leftWidth, 0) + let rightExpression = this.range(this.x + rightWidth, 0, rightWidth) + + this.eventpan = BindingX.bind({ + anchor: this.box, + eventType: 'pan', + props: [{ + element: this.selector, + property: 'transform.translateX', + expression + }, { + element: this.leftButton, + property: 'transform.translateX', + expression: leftExpression + }, { + element: this.rightButton, + property: 'transform.translateX', + expression: rightExpression + }, ] + }, (e) => { + // nope + if (e.state === 'end') { + this.x = e.deltaX + this.x; + this.isclick = true + this.bindTiming(e.deltaX) + } + }); + }, + touchend(e) { + if (this.isopen !== 'none' && !this.isclick) { + this.open('none') + } + }, + bindTiming(x) { + const left = this.x + const leftWidth = this.button.left.width + const rightWidth = this.button.right.width + const threshold = this.threshold + if (!this.isopen || this.isopen === 'none') { + if (left > threshold) { + this.open('left') + } else if (left < -threshold) { + this.open('right') + } else { + this.open('none') + } + } else { + if ((x > -leftWidth && x < 0) || x > rightWidth) { + if ((x > -threshold && x < 0) || (x - rightWidth > threshold)) { + this.open('left') + } else { + this.open('none') + } + } else { + if ((x < threshold && x > 0) || (x + leftWidth < -threshold)) { + this.open('right') + } else { + this.open('none') + } + } + } + }, + + /** + * 移动范围 + * @param {Object} num + * @param {Object} mix + * @param {Object} max + */ + range(num, mix, max) { + return `min(max(x+${num}, ${mix}), ${max})` + }, + + /** + * 开启swipe + */ + open(type) { + this.animation(type) + }, + + /** + * 关闭swipe + */ + close() { + this.animation('none') + }, + + /** + * 开启关闭动画 + * @param {Object} type + */ + animation(type) { + const time = 300 + const leftWidth = this.button.left.width + const rightWidth = this.button.right.width + if (this.eventpan && this.eventpan.token) { + BindingX.unbind({ + token: this.eventpan.token, + eventType: 'pan' + }) + } + + switch (type) { + case 'left': + Promise.all([ + this.move(this.selector, leftWidth), + this.move(this.leftButton, 0), + this.move(this.rightButton, rightWidth * 2) + ]).then(() => { + this.setEmit(leftWidth, type) + }) + break + case 'right': + Promise.all([ + this.move(this.selector, -rightWidth), + this.move(this.leftButton, -leftWidth * 2), + this.move(this.rightButton, 0) + ]).then(() => { + this.setEmit(-rightWidth, type) + }) + break + default: + Promise.all([ + this.move(this.selector, 0), + this.move(this.leftButton, -leftWidth), + this.move(this.rightButton, rightWidth) + ]).then(() => { + this.setEmit(0, type) + }) + + } + }, + setEmit(x, type) { + const leftWidth = this.button.left.width + const rightWidth = this.button.right.width + this.isopen = this.isopen || 'none' + this.stop = false + this.isclick = false + // 只有状态不一致才会返回结果 + if (this.isopen !== type && this.x !== x) { + if (type === 'left' && leftWidth > 0) { + this.$emit('change', 'left') + } + if (type === 'right' && rightWidth > 0) { + this.$emit('change', 'right') + } + if (type === 'none') { + this.$emit('change', 'none') + } + } + this.x = x + this.isopen = type + }, + move(ref, value) { + return new Promise((resolve, reject) => { + animation.transition(ref, { + styles: { + transform: `translateX(${value})`, + }, + duration: 150, //ms + timingFunction: 'linear', + needLayout: false, + delay: 0 //ms + }, function(res) { + resolve(res) + }) + }) + + }, + + /** + * 获取ref + * @param {Object} el + */ + getEl(el) { + return el.ref + }, + /** + * 获取节点信息 + */ + getSelectorQuery() { + Promise.all([ + this.getDom('left'), + this.getDom('right'), + ]).then((data) => { + let show = 'none' + if (this.autoClose) { + show = 'none' + } else { + show = this.show + } + + if (show === 'none') { + // this.close() + } else { + this.open(show) + } + + }) + + }, + getDom(str) { + return new Promise((resolve, reject) => { + dom.getComponentRect(this.$refs[`selector-${str}-button--hock`], (data) => { + if (data) { + this.button[str] = data.size + resolve(data) + } else { + reject() + } + }) + }) + } + } +} diff --git a/hive-app/components/uni-swipe-action-item/index.wxs b/hive-app/components/uni-swipe-action-item/index.wxs new file mode 100644 index 0000000..fc64ae1 --- /dev/null +++ b/hive-app/components/uni-swipe-action-item/index.wxs @@ -0,0 +1,266 @@ +var MIN_DISTANCE = 10; + +/** + * 监听页面内值的变化,主要用于动态开关swipe-action + * @param {Object} newValue + * @param {Object} oldValue + * @param {Object} ownerInstance + * @param {Object} instance + */ +function sizeReady(newValue, oldValue, ownerInstance, instance) { + var state = instance.getState() + var buttonPositions = JSON.parse(newValue) + if (!buttonPositions || !buttonPositions.data || buttonPositions.data.length === 0) return + state.leftWidth = buttonPositions.data[0].width + state.rightWidth = buttonPositions.data[1].width + state.threshold = instance.getDataset().threshold + + if (buttonPositions.show && buttonPositions.show !== 'none') { + openState(buttonPositions.show, instance, ownerInstance) + return + } + + if (state.left) { + openState('none', instance, ownerInstance) + } + resetTouchStatus(instance) +} + +/** + * 开始触摸操作 + * @param {Object} e + * @param {Object} ins + */ +function touchstart(e, ins) { + var instance = e.instance; + var disabled = instance.getDataset().disabled + var state = instance.getState(); + // fix by mehaotian, TODO 兼容 app-vue 获取dataset为字符串 , h5 获取 为 undefined 的问题,待框架修复 + disabled = (typeof(disabled) === 'string' ? JSON.parse(disabled) : disabled) || false; + if (disabled) return + // 开始触摸时移除动画类 + instance.requestAnimationFrame(function(){ + instance.removeClass('ani'); + ins.callMethod('closeSwipe'); + }) + + // 记录上次的位置 + state.x = state.left || 0 + // 计算滑动开始位置 + stopTouchStart(e, ins) +} + +/** + * 开始滑动操作 + * @param {Object} e + * @param {Object} ownerInstance + */ +function touchmove(e, ownerInstance) { + + var instance = e.instance; + var disabled = instance.getDataset().disabled + var state = instance.getState() + // fix by mehaotian, TODO 兼容 app-vue 获取dataset为字符串 , h5 获取 为 undefined 的问题,待框架修复 + disabled = (typeof(disabled) === 'string' ? JSON.parse(disabled) : disabled) || false; + if (disabled) return + // 是否可以滑动页面 + stopTouchMove(e); + if (state.direction !== 'horizontal') { + return; + } + + if (e.preventDefault) { + // 阻止页面滚动 + e.preventDefault() + } + + move(state.x + state.deltaX, instance, ownerInstance) +} + +/** + * 结束触摸操作 + * @param {Object} e + * @param {Object} ownerInstance + */ +function touchend(e, ownerInstance) { + var instance = e.instance; + var disabled = instance.getDataset().disabled + var state = instance.getState() + // fix by mehaotian, TODO 兼容 app-vue 获取dataset为字符串 , h5 获取 为 undefined 的问题,待框架修复 + disabled = (typeof(disabled) === 'string' ? JSON.parse(disabled) : disabled) || false; + + if (disabled) return + // 滑动过程中触摸结束,通过阙值判断是开启还是关闭 + // fixed by mehaotian 定时器解决点击按钮,touchend 触发比 click 事件时机早的问题 ,主要是 ios13 + moveDirection(state.left, instance, ownerInstance) + +} + +/** + * 设置移动距离 + * @param {Object} value + * @param {Object} instance + * @param {Object} ownerInstance + */ +function move(value, instance, ownerInstance) { + value = value || 0 + var state = instance.getState() + var leftWidth = state.leftWidth + var rightWidth = state.rightWidth + // 获取可滑动范围 + state.left = range(value, -rightWidth, leftWidth); + instance.requestAnimationFrame(function(){ + instance.setStyle({ + transform: 'translateX(' + state.left + 'px)', + '-webkit-transform': 'translateX(' + state.left + 'px)' + }) + }) + +} + +/** + * 获取范围 + * @param {Object} num + * @param {Object} min + * @param {Object} max + */ +function range(num, min, max) { + return Math.min(Math.max(num, min), max); +} + + +/** + * 移动方向判断 + * @param {Object} left + * @param {Object} value + * @param {Object} ownerInstance + * @param {Object} ins + */ +function moveDirection(left, ins, ownerInstance) { + var state = ins.getState() + var threshold = state.threshold + var position = state.position + var isopen = state.isopen || 'none' + var leftWidth = state.leftWidth + var rightWidth = state.rightWidth + if (state.deltaX === 0) { + openState('none', ins, ownerInstance) + return + } + if ((isopen === 'none' && rightWidth > 0 && -left > threshold) || (isopen !== 'none' && rightWidth > 0 && rightWidth + + left < threshold)) { + // right + openState('right', ins, ownerInstance) + } else if ((isopen === 'none' && leftWidth > 0 && left > threshold) || (isopen !== 'none' && leftWidth > 0 && + leftWidth - left < threshold)) { + // left + openState('left', ins, ownerInstance) + } else { + // default + openState('none', ins, ownerInstance) + } +} + + +/** + * 开启状态 + * @param {Boolean} type + * @param {Object} ins + * @param {Object} ownerInstance + */ +function openState(type, ins, ownerInstance) { + var state = ins.getState() + var position = state.position + var leftWidth = state.leftWidth + var rightWidth = state.rightWidth + var left = '' + state.isopen = state.isopen ? state.isopen : 'none' + switch (type) { + case "left": + left = leftWidth + break + case "right": + left = -rightWidth + break + default: + left = 0 + } + + // && !state.throttle + + if (state.isopen !== type ) { + state.throttle = true + ownerInstance.callMethod('change', { + open: type + }) + + } + + state.isopen = type + // 添加动画类 + ins.requestAnimationFrame(function(){ + ins.addClass('ani'); + move(left, ins, ownerInstance) + }) + // 设置最终移动位置,理论上只要进入到这个函数,肯定是要打开的 +} + + +function getDirection(x, y) { + if (x > y && x > MIN_DISTANCE) { + return 'horizontal'; + } + if (y > x && y > MIN_DISTANCE) { + return 'vertical'; + } + return ''; +} + +/** + * 重置滑动状态 + * @param {Object} event + */ +function resetTouchStatus(instance) { + var state = instance.getState(); + state.direction = ''; + state.deltaX = 0; + state.deltaY = 0; + state.offsetX = 0; + state.offsetY = 0; +} + +/** + * 设置滑动开始位置 + * @param {Object} event + */ +function stopTouchStart(event) { + var instance = event.instance; + var state = instance.getState(); + resetTouchStatus(instance); + var touch = event.touches[0]; + state.startX = touch.clientX; + state.startY = touch.clientY; +} + +/** + * 滑动中,是否禁止打开 + * @param {Object} event + */ +function stopTouchMove(event) { + var instance = event.instance; + var state = instance.getState(); + var touch = event.touches[0]; + state.deltaX = touch.clientX - state.startX; + state.deltaY = touch.clientY - state.startY; + state.offsetX = Math.abs(state.deltaX); + state.offsetY = Math.abs(state.deltaY); + state.direction = state.direction || getDirection(state.offsetX, state.offsetY); +} + + +module.exports = { + sizeReady: sizeReady, + touchstart: touchstart, + touchmove: touchmove, + touchend: touchend +} diff --git a/hive-app/components/uni-swipe-action-item/mpalipay.js b/hive-app/components/uni-swipe-action-item/mpalipay.js new file mode 100644 index 0000000..0197200 --- /dev/null +++ b/hive-app/components/uni-swipe-action-item/mpalipay.js @@ -0,0 +1,207 @@ +export default { + data() { + return { + x: 0, + transition: false, + width: 0, + viewWidth: 0, + swipeShow: 0 + } + }, + watch: { + show(newVal) { + if (this.autoClose) return + if (newVal && newVal !== 'none' ) { + this.transition = true + this.open(newVal) + } else { + this.close() + } + } + }, + created() { + if (this.swipeaction.children !== undefined) { + this.swipeaction.children.push(this) + } + }, + + beforeDestroy() { + this.swipeaction.children.forEach((item, index) => { + if (item === this) { + this.swipeaction.children.splice(index, 1) + } + }) + }, + mounted() { + this.isopen = false + setTimeout(() => { + this.getQuerySelect() + }, 50) + }, + methods: { + appTouchStart(e) { + const { + clientX + } = e.changedTouches[0] + this.clientX = clientX + this.timestamp = new Date().getTime() + }, + appTouchEnd(e, index, item, position) { + const { + clientX + } = e.changedTouches[0] + // fixed by xxxx 模拟点击事件,解决 ios 13 点击区域错位的问题 + let diff = Math.abs(this.clientX - clientX) + let time = (new Date().getTime()) - this.timestamp + if (diff < 40 && time < 300) { + this.$emit('click', { + content: item, + index, + position + }) + } + }, + // onClick(index, item, position) { + // this.$emit('click', { + // content: item, + // index, + // position + // }) + // }, + /** + * 移动触发 + * @param {Object} e + */ + onChange(e) { + this.moveX = e.detail.x + this.isclose = false + }, + touchstart(e) { + this.transition = false + this.isclose = true + this.autoClose && this.swipeaction.closeOther(this) + }, + touchmove(e) {}, + touchend(e) { + // 0的位置什么都不执行 + if (this.isclose && this.isopen === 'none') return + if (this.isclose && this.isopen !== 'none') { + this.transition = true + this.close() + } else { + this.move(this.moveX + this.leftWidth) + } + }, + + /** + * 移动 + * @param {Object} moveX + */ + move(moveX) { + // 打开关闭的处理逻辑不太一样 + this.transition = true + // 未打开状态 + if (!this.isopen || this.isopen === 'none') { + if (moveX > this.threshold) { + this.open('left') + } else if (moveX < -this.threshold) { + this.open('right') + } else { + this.close() + } + } else { + if (moveX < 0 && moveX < this.rightWidth) { + const rightX = this.rightWidth + moveX + if (rightX < this.threshold) { + this.open('right') + } else { + this.close() + } + } else if (moveX > 0 && moveX < this.leftWidth) { + const leftX = this.leftWidth - moveX + if (leftX < this.threshold) { + this.open('left') + } else { + this.close() + } + } + + } + + }, + + /** + * 打开 + */ + open(type) { + this.x = this.moveX + this.animation(type) + }, + + /** + * 关闭 + */ + close() { + this.x = this.moveX + // TODO 解决 x 值不更新的问题,所以会多触发一次 nextTick ,待优化 + this.$nextTick(() => { + this.x = -this.leftWidth + if(this.isopen!=='none'){ + this.$emit('change', 'none') + } + this.isopen = 'none' + }) + }, + + /** + * 执行结束动画 + * @param {Object} type + */ + animation(type) { + this.$nextTick(() => { + if (type === 'left') { + this.x = 0 + } else { + this.x = -this.rightWidth - this.leftWidth + } + + if(this.isopen!==type){ + this.$emit('change', type) + } + this.isopen = type + }) + + }, + getSlide(x) {}, + getQuerySelect() { + const query = uni.createSelectorQuery().in(this); + query.selectAll('.movable-view--hock').boundingClientRect(data => { + this.leftWidth = data[1].width + this.rightWidth = data[2].width + this.width = data[0].width + this.viewWidth = this.width + this.rightWidth + this.leftWidth + if (this.leftWidth === 0) { + // TODO 疑似bug ,初始化的时候如果x 是0,会导致移动位置错误,所以让元素超出一点 + this.x = -0.1 + } else { + this.x = -this.leftWidth + } + this.moveX = this.x + this.$nextTick(() => { + this.swipeShow = 1 + }) + + if (!this.buttonWidth) { + this.disabledView = true + } + + if (this.autoClose) return + if (this.show !== 'none') { + this.transition = true + this.open(this.shows) + } + }).exec(); + + } + } +} diff --git a/hive-app/components/uni-swipe-action-item/mpother.js b/hive-app/components/uni-swipe-action-item/mpother.js new file mode 100644 index 0000000..9b44007 --- /dev/null +++ b/hive-app/components/uni-swipe-action-item/mpother.js @@ -0,0 +1,252 @@ +const MIN_DISTANCE = 10; +export default { + data() { + return { + uniShow: false, + left: 0, + buttonShow: 'none', + ani: false, + moveLeft:'' + } + }, + watch: { + show(newVal) { + if (this.autoClose) return + this.openState(newVal) + }, + left(){ + this.moveLeft = `translateX(${this.left}px)` + }, + buttonShow(newVal){ + if (this.autoClose) return + this.openState(newVal) + }, + leftOptions() { + this.init() + }, + rightOptions() { + this.init() + } + }, + mounted() { + // this.position = {} + if (this.swipeaction.children !== undefined) { + this.swipeaction.children.push(this) + } + this.init() + }, + beforeDestoy() { + this.swipeaction.children.forEach((item, index) => { + if (item === this) { + this.swipeaction.children.splice(index, 1) + } + }) + }, + methods: { + init(){ + clearTimeout(this.timer) + this.timer = setTimeout(() => { + this.getSelectorQuery() + }, 100) + // 移动距离 + this.left = 0 + this.x = 0 + }, + closeSwipe(e) { + if (!this.autoClose) return + this.swipeaction.closeOther(this) + }, + appTouchStart(e) { + const { + clientX + } = e.changedTouches[0] + this.clientX = clientX + this.timestamp = new Date().getTime() + }, + appTouchEnd(e, index, item, position) { + const { + clientX + } = e.changedTouches[0] + // fixed by xxxx 模拟点击事件,解决 ios 13 点击区域错位的问题 + let diff = Math.abs(this.clientX - clientX) + let time = (new Date().getTime()) - this.timestamp + if (diff < 40 && time < 300) { + this.$emit('click', { + content: item, + index, + position + }) + } + }, + touchstart(e) { + if (this.disabled) return + this.ani = false + this.x = this.left || 0 + this.stopTouchStart(e) + this.autoClose && this.closeSwipe() + }, + touchmove(e) { + if (this.disabled) return + // 是否可以滑动页面 + this.stopTouchMove(e); + if (this.direction !== 'horizontal') { + return; + } + + this.move(this.x + this.deltaX) + }, + touchend() { + if (this.disabled) return + this.moveDirection(this.left) + }, + /** + * 设置移动距离 + * @param {Object} value + */ + move(value) { + value = value || 0 + const leftWidth = this.leftWidth + const rightWidth = this.rightWidth + // 获取可滑动范围 + this.left = this.range(value, -rightWidth, leftWidth); + }, + + /** + * 获取范围 + * @param {Object} num + * @param {Object} min + * @param {Object} max + */ + range(num, min, max) { + return Math.min(Math.max(num, min), max); + }, + /** + * 移动方向判断 + * @param {Object} left + * @param {Object} value + */ + moveDirection(left) { + const threshold = this.threshold + const isopen = this.isopen || 'none' + const leftWidth = this.leftWidth + const rightWidth = this.rightWidth + if (this.deltaX === 0) { + this.openState('none') + return + } + if ((isopen === 'none' && rightWidth > 0 && -left > threshold) || (isopen !== 'none' && rightWidth > 0 && rightWidth + + left < threshold)) { + // right + this.openState('right') + } else if ((isopen === 'none' && leftWidth > 0 && left > threshold) || (isopen !== 'none' && leftWidth > 0 && + leftWidth - left < threshold)) { + // left + this.openState('left') + } else { + // default + this.openState('none') + } + }, + + /** + * 开启状态 + * @param {Boolean} type + */ + openState(type) { + const leftWidth = this.leftWidth + const rightWidth = this.rightWidth + let left = '' + this.isopen = this.isopen ? this.isopen : 'none' + switch (type) { + case "left": + left = leftWidth + break + case "right": + left = -rightWidth + break + default: + left = 0 + } + + + if (this.isopen !== type) { + this.throttle = true + this.$emit('change', type) + } + + this.isopen = type + // 添加动画类 + this.ani = true + this.$nextTick(() => { + this.move(left) + }) + // 设置最终移动位置,理论上只要进入到这个函数,肯定是要打开的 + }, + close() { + this.openState('none') + }, + getDirection(x, y) { + if (x > y && x > MIN_DISTANCE) { + return 'horizontal'; + } + if (y > x && y > MIN_DISTANCE) { + return 'vertical'; + } + return ''; + }, + + /** + * 重置滑动状态 + * @param {Object} event + */ + resetTouchStatus() { + this.direction = ''; + this.deltaX = 0; + this.deltaY = 0; + this.offsetX = 0; + this.offsetY = 0; + }, + + /** + * 设置滑动开始位置 + * @param {Object} event + */ + stopTouchStart(event) { + this.resetTouchStatus(); + const touch = event.touches[0]; + this.startX = touch.clientX; + this.startY = touch.clientY; + }, + + /** + * 滑动中,是否禁止打开 + * @param {Object} event + */ + stopTouchMove(event) { + const touch = event.touches[0]; + this.deltaX = touch.clientX - this.startX; + this.deltaY = touch.clientY - this.startY; + this.offsetX = Math.abs(this.deltaX); + this.offsetY = Math.abs(this.deltaY); + this.direction = this.direction || this.getDirection(this.offsetX, this.offsetY); + }, + + getSelectorQuery() { + const views = uni.createSelectorQuery().in(this) + views + .selectAll('.uni-swipe_button-group') + .boundingClientRect(data => { + let show = 'none' + if (this.autoClose) { + show = 'none' + } else { + show = this.show + } + this.leftWidth = data[0].width || 0 + this.rightWidth = data[1].width || 0 + this.buttonShow = show + }) + .exec() + } + } +} diff --git a/hive-app/components/uni-swipe-action-item/mpwxs.js b/hive-app/components/uni-swipe-action-item/mpwxs.js new file mode 100644 index 0000000..a0b3313 --- /dev/null +++ b/hive-app/components/uni-swipe-action-item/mpwxs.js @@ -0,0 +1,116 @@ +export default { + data() { + return { + position: [], + button: {}, + btn: "[]" + } + }, + // computed: { + // pos() { + // return JSON.stringify(this.position) + // }, + // btn() { + // return JSON.stringify(this.button) + // } + // }, + watch: { + button: { + handler(newVal) { + this.btn = JSON.stringify(newVal) + }, + deep: true + }, + show(newVal) { + if (this.autoClose) return + if (!this.button) { + this.init() + return + } + this.button.show = newVal + }, + leftOptions() { + this.init() + }, + rightOptions() { + this.init() + } + }, + created() { + if (this.swipeaction.children !== undefined) { + this.swipeaction.children.push(this) + } + }, + mounted() { + this.init() + }, + beforeDestroy() { + this.swipeaction.children.forEach((item, index) => { + if (item === this) { + this.swipeaction.children.splice(index, 1) + } + }) + }, + methods: { + init() { + clearTimeout(this.swipetimer) + this.swipetimer = setTimeout(() => { + this.getButtonSize() + }, 50) + }, + closeSwipe(e) { + if (!this.autoClose) return + this.swipeaction.closeOther(this) + }, + + change(e) { + this.$emit('change', e.open) + let show = this.button.show + if (show !== e.open) { + this.button.show = e.open + } + + }, + + appTouchStart(e) { + const { + clientX + } = e.changedTouches[0] + this.clientX = clientX + this.timestamp = new Date().getTime() + }, + appTouchEnd(e, index, item, position) { + const { + clientX + } = e.changedTouches[0] + // fixed by xxxx 模拟点击事件,解决 ios 13 点击区域错位的问题 + let diff = Math.abs(this.clientX - clientX) + let time = (new Date().getTime()) - this.timestamp + if (diff < 40 && time < 300) { + this.$emit('click', { + content: item, + index, + position + }) + } + }, + getButtonSize() { + const views = uni.createSelectorQuery().in(this) + views + .selectAll('.uni-swipe_button-group') + .boundingClientRect(data => { + let show = 'none' + if (this.autoClose) { + show = 'none' + } else { + show = this.show + } + this.button = { + data, + show + } + }) + .exec() + } + } +} diff --git a/hive-app/components/uni-swipe-action-item/uni-swipe-action-item.vue b/hive-app/components/uni-swipe-action-item/uni-swipe-action-item.vue new file mode 100644 index 0000000..ff2d4db --- /dev/null +++ b/hive-app/components/uni-swipe-action-item/uni-swipe-action-item.vue @@ -0,0 +1,365 @@ +<template> + <!-- 在微信小程序 app vue端 h5 使用wxs 实现--> + <!-- #ifdef APP-VUE || MP-WEIXIN || H5 --> + <view class="uni-swipe"> + <view + class="uni-swipe_box" + :data-threshold="threshold" + :data-disabled="disabled" + :change:prop="swipe.sizeReady" + :prop="btn" + @touchstart="swipe.touchstart" + @touchmove="swipe.touchmove" + @touchend="swipe.touchend" + > + <!-- 在微信小程序 app vue端 h5 使用wxs 实现--> + <view class="uni-swipe_button-group button-group--left"> + <slot name="left"> + <view + v-for="(item,index) in leftOptions" + :data-button="btn" + :key="index" + :style="{ + backgroundColor: item.style && item.style.backgroundColor ? item.style.backgroundColor : '#C7C6CD', + fontSize: item.style && item.style.fontSize ? item.style.fontSize : '16px' + }" + class="uni-swipe_button button-hock" + @touchstart="appTouchStart" + @touchend="appTouchEnd($event,index,item,'left')" + ><text + class="uni-swipe_button-text" + :style="{color: item.style && item.style.color ? item.style.color : '#FFFFFF',}" + >{{ item.text }}</text></view> + </slot> + </view> + <slot></slot> + <view class="uni-swipe_button-group button-group--right"> + <slot name="right"> + <view + v-for="(item,index) in rightOptions" + :data-button="btn" + :key="index" + :style="{ + backgroundColor: item.style && item.style.backgroundColor ? item.style.backgroundColor : '#C7C6CD', + fontSize: item.style && item.style.fontSize ? item.style.fontSize : '16px' + }" + class="uni-swipe_button button-hock" + @touchstart="appTouchStart" + @touchend="appTouchEnd($event,index,item,'right')" + ><text + class="uni-swipe_button-text" + :style="{color: item.style && item.style.color ? item.style.color : '#FFFFFF',}" + >{{ item.text }}</text></view> + </slot> + </view> + </view> + </view> + <!-- #endif --> + <!-- app nvue端 使用 bindingx --> + <!-- #ifdef APP-NVUE --> + <view + ref="selector-box--hock" + class="uni-swipe" + @horizontalpan="touchstart" + @touchend="touchend" + > + <view + ref='selector-left-button--hock' + class="uni-swipe_button-group button-group--left" + > + <slot name="left"> + <view + v-for="(item,index) in leftOptions" + :data-button="btn" + :key="index" + :style="{ + backgroundColor: item.style && item.style.backgroundColor ? item.style.backgroundColor : '#C7C6CD', + fontSize: item.style && item.style.fontSize ? item.style.fontSize : '16px' + }" + class="uni-swipe_button button-hock" + @click.stop="onClick(index,item,'left')" + ><text + class="uni-swipe_button-text" + :style="{color: item.style && item.style.color ? item.style.color : '#FFFFFF',}" + >{{ item.text }}</text></view> + </slot> + </view> + <view + ref='selector-right-button--hock' + class="uni-swipe_button-group button-group--right" + > + <slot name="right"> + <view + v-for="(item,index) in rightOptions" + :data-button="btn" + :key="index" + :style="{ + backgroundColor: item.style && item.style.backgroundColor ? item.style.backgroundColor : '#C7C6CD', + fontSize: item.style && item.style.fontSize ? item.style.fontSize : '16px' + }" + class="uni-swipe_button button-hock" + @click.stop="onClick(index,item,'right')" + ><text + class="uni-swipe_button-text" + :style="{color: item.style && item.style.color ? item.style.color : '#FFFFFF',}" + >{{ item.text }}</text></view> + </slot> + </view> + <view + ref='selector-content--hock' + class="uni-swipe_box" + > + <slot></slot> + </view> + </view> + <!-- #endif --> + <!-- 其他平台使用 js ,长列表性能可能会有影响--> + <!-- #ifdef MP-ALIPAY || MP-BAIDU || MP-TOUTIAO || MP-QQ --> + <view class="uni-swipe"> + <view + class="uni-swipe_box" + @touchstart="touchstart" + @touchmove="touchmove" + @touchend="touchend" + :style="{transform:moveLeft}" + :class="{ani:ani}" + > + <view class="uni-swipe_button-group button-group--left"> + <slot name="left"> + <view + v-for="(item,index) in leftOptions" + :data-button="btn" + :key="index" + :style="{ + backgroundColor: item.style && item.style.backgroundColor ? item.style.backgroundColor : '#C7C6CD', + fontSize: item.style && item.style.fontSize ? item.style.fontSize : '16px' + }" + class="uni-swipe_button button-hock" + @touchstart="appTouchStart" + @touchend="appTouchEnd($event,index,item,'left')" + ><text + class="uni-swipe_button-text" + :style="{color: item.style && item.style.color ? item.style.color : '#FFFFFF',}" + >{{ item.text }}</text></view> + </slot> + </view> + <slot></slot> + <view class="uni-swipe_button-group button-group--right"> + <slot name="right"> + <view + v-for="(item,index) in rightOptions" + :data-button="btn" + :key="index" + :style="{ + backgroundColor: item.style && item.style.backgroundColor ? item.style.backgroundColor : '#C7C6CD', + fontSize: item.style && item.style.fontSize ? item.style.fontSize : '16px' + }" + @touchstart="appTouchStart" + @touchend="appTouchEnd($event,index,item,'right')" + class="uni-swipe_button button-hock" + ><text + class="uni-swipe_button-text" + :style="{color: item.style && item.style.color ? item.style.color : '#FFFFFF',}" + >{{ item.text }}</text></view> + </slot> + </view> + </view> + </view> + <!-- #endif --> + +</template> +<script + src="./index.wxs" + module="swipe" + lang="wxs" +></script> +<script> + // #ifdef APP-VUE|| MP-WEIXIN || H5 + import mpwxs from './mpwxs' + // #endif + + // #ifdef APP-NVUE + import bindingx from './bindingx.js' + // #endif + + // #ifndef APP-PLUS|| MP-WEIXIN || H5 + import mixins from './mpother' + // #endif + + /** + * SwipeActionItem 滑动操作子组件 + * @description 通过滑动触发选项的容器 + * @tutorial https://ext.dcloud.net.cn/plugin?id=181 + * @property {Boolean} show = [left|right|none] 开启关闭组件,auto-close = false 时生效 + * @property {Boolean} disabled = [true|false] 是否禁止滑动 + * @property {Boolean} autoClose = [true|false] 滑动打开当前组件,是否关闭其他组件 + * @property {Number} threshold 滑动缺省值 + * @property {Array} leftOptions 左侧选项内容及样式 + * @property {Array} rgihtOptions 右侧选项内容及样式 + * @event {Function} click 点击选项按钮时触发事件,e = {content,index} ,content(点击内容)、index(下标) + * @event {Function} change 组件打开或关闭时触发,left\right\none + */ + + export default { + // #ifdef APP-VUE|| MP-WEIXIN||H5 + mixins: [mpwxs], + // #endif + + // #ifdef APP-NVUE + mixins: [bindingx], + // #endif + + // #ifndef APP-PLUS|| MP-WEIXIN || H5 + mixins: [mixins], + // #endif + + props: { + // 控制开关 + show: { + type: String, + default: 'none' + }, + + // 禁用 + disabled: { + type: Boolean, + default: false + }, + + // 是否自动关闭 + autoClose: { + type: Boolean, + default: true + }, + + // 滑动缺省距离 + threshold: { + type: Number, + default: 20 + }, + + // 左侧按钮内容 + leftOptions: { + type: Array, + default () { + return [] + } + }, + + // 右侧按钮内容 + rightOptions: { + type: Array, + default () { + return [] + } + } + + }, + inject: ['swipeaction'] + } +</script> +<style + lang="scss" + scoped +> + .uni-swipe { + position: relative; + /* #ifndef APP-NVUE */ + overflow: hidden; + /* #endif */ + } + + .uni-swipe_box { + /* #ifndef APP-NVUE */ + display: flex; + flex-shrink: 0; + /* #endif */ + position: relative; + } + + .uni-swipe_content { + // border: 1px red solid; + } + + .uni-swipe_button-group { + /* #ifndef APP-NVUE */ + box-sizing: border-box; + display: flex; + /* #endif */ + flex-direction: row; + position: absolute; + top: 0; + bottom: 0; + } + + .button-group--left { + left: 0; + transform: translateX(-100%) + } + + .button-group--right { + right: 0; + transform: translateX(100%) + } + + .uni-swipe_button { + /* #ifdef APP-NVUE */ + flex: 1; + /* #endif */ + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: row; + justify-content: center; + align-items: center; + padding: 0 20px; + } + + .uni-swipe_button-text { + /* #ifndef APP-NVUE */ + flex-shrink: 0; + /* #endif */ + font-size: 14px; + } + + .ani { + transition-property: transform; + transition-duration: 0.3s; + transition-timing-function: cubic-bezier(0.165, 0.84, 0.44, 1); + } + + /* #ifdef MP-ALIPAY */ + .movable-area { + /* width: 100%; */ + height: 45px; + } + + .movable-view { + display: flex; + /* justify-content: center; */ + position: relative; + flex: 1; + height: 45px; + z-index: 2; + } + + .movable-view-button { + display: flex; + flex-shrink: 0; + flex-direction: row; + height: 100%; + background: #C0C0C0; + } + + /* .transition { + transition: all 0.3s; + } */ + + .movable-view-box { + flex-shrink: 0; + height: 100%; + background-color: #fff; + } + + /* #endif */ +</style> diff --git a/hive-app/components/uni-swipe-action/uni-swipe-action.vue b/hive-app/components/uni-swipe-action/uni-swipe-action.vue new file mode 100644 index 0000000..45294a8 --- /dev/null +++ b/hive-app/components/uni-swipe-action/uni-swipe-action.vue @@ -0,0 +1,42 @@ +<template> + <view> + <slot></slot> + </view> +</template> + +<script> + /** + * SwipeAction 滑动操作 + * @description 通过滑动触发选项的容器 + * @tutorial https://ext.dcloud.net.cn/plugin?id=181 + */ + export default { + data() { + return {}; + }, + provide() { + return { + swipeaction: this + }; + }, + created() { + this.children = []; + }, + methods: { + closeOther(vm) { + if (this.openItem && this.openItem !== vm) { + // #ifdef APP-VUE || H5 || MP-WEIXIN + this.openItem.button.show = 'none' + // #endif + + // #ifndef APP-VUE || H5 || MP-WEIXIN + this.openItem.close() + // #endif + } + this.openItem = vm + } + } + }; +</script> + +<style></style> diff --git a/hive-app/components/uni-swiper-dot/uni-swiper-dot.vue b/hive-app/components/uni-swiper-dot/uni-swiper-dot.vue new file mode 100644 index 0000000..fc231a7 --- /dev/null +++ b/hive-app/components/uni-swiper-dot/uni-swiper-dot.vue @@ -0,0 +1,201 @@ +<template> + <view class="uni-swiper__warp"> + <slot /> + <view v-if="mode === 'default'" :style="{'bottom':dots.bottom + 'px'}" class="uni-swiper__dots-box" key='default'> + <view v-for="(item,index) in info" :style="{ + 'width': (index === current? dots.width*2:dots.width ) + 'px','height':dots.width/3 +'px' ,'background-color':index !== current?dots.backgroundColor:dots.selectedBackgroundColor,'border-radius':'0px'}" + :key="index" class="uni-swiper__dots-item uni-swiper__dots-bar" /> + </view> + <view v-if="mode === 'dot'" :style="{'bottom':dots.bottom + 'px'}" class="uni-swiper__dots-box" key='dot'> + <view v-for="(item,index) in info" :style="{ + 'width': dots.width + 'px','height':dots.height +'px' ,'background-color':index !== current?dots.backgroundColor:dots.selectedBackgroundColor,'border':index !==current ? dots.border:dots.selectedBorder}" + :key="index" class="uni-swiper__dots-item" /> + </view> + <view v-if="mode === 'round'" :style="{'bottom':dots.bottom + 'px'}" class="uni-swiper__dots-box" key='round'> + <view v-for="(item,index) in info" :class="[index === current&&'uni-swiper__dots-long']" :style="{ + 'width':(index === current? dots.width*3:dots.width ) + 'px','height':dots.height +'px' ,'background-color':index !== current?dots.backgroundColor:dots.selectedBackgroundColor,'border':index !==current ? dots.border:dots.selectedBorder}" + :key="index" class="uni-swiper__dots-item " /> + </view> + <view v-if="mode === 'nav'" key='nav' :style="{'background-color':dotsStyles.backgroundColor,'bottom':'0'}" class="uni-swiper__dots-box uni-swiper__dots-nav"> + <text :style="{'color':dotsStyles.color}" class="uni-swiper__dots-nav-item">{{ (current+1)+"/"+info.length +' ' +info[current][field] }}</text> + </view> + <view v-if="mode === 'indexes'" key='indexes' :style="{'bottom':dots.bottom + 'px'}" class="uni-swiper__dots-box"> + <view v-for="(item,index) in info" :style="{ + 'width':dots.width + 'px','height':dots.height +'px' ,'color':index === current?dots.selectedColor:dots.color,'background-color':index !== current?dots.backgroundColor:dots.selectedBackgroundColor,'border':index !==current ? dots.border:dots.selectedBorder}" + :key="index" class="uni-swiper__dots-item uni-swiper__dots-indexes"><text class="uni-swiper__dots-indexes-text">{{ index+1 }}</text></view> + </view> + </view> +</template> + +<script> + + /** + * SwiperDod 轮播图指示点 + * @description 自定义轮播图指示点 + * @tutorial https://ext.dcloud.net.cn/plugin?id=284 + * @property {Number} current 当前指示点索引,必须是通过 `swiper` 的 `change` 事件获取到的 `e.detail.current` + * @property {String} mode = [default|round|nav|indexes] 指示点的类型 + * @value defualt 默认指示点 + * @value round 圆形指示点 + * @value nav 条形指示点 + * @value indexes 索引指示点 + * @property {String} field mode 为 nav 时,显示的内容字段(mode = nav 时必填) + * @property {String} info 轮播图的数据,通过数组长度决定指示点个数 + * @property {Object} dotsStyles 指示点样式 + * @event {Function} clickItem 组件触发点击事件时触发,e={currentIndex} + */ + + export default { + name: 'UniSwiperDot', + props: { + info: { + type: Array, + default () { + return [] + } + }, + current: { + type: Number, + default: 0 + }, + dotsStyles: { + type: Object, + default () { + return {} + } + }, + // 类型 :default(默认) indexes long nav + mode: { + type: String, + default: 'default' + }, + // 只在 nav 模式下生效,变量名称 + field: { + type: String, + default: '' + } + }, + data() { + return { + dots: { + width: 8, + height: 8, + bottom: 10, + color: '#fff', + backgroundColor: 'rgba(0, 0, 0, .3)', + border: '1px rgba(0, 0, 0, .3) solid', + selectedBackgroundColor: '#333', + selectedBorder: '1px rgba(0, 0, 0, .9) solid' + } + } + }, + watch: { + dotsStyles(newVal) { + this.dots = Object.assign(this.dots, this.dotsStyles) + }, + mode(newVal) { + if (newVal === 'indexes') { + this.dots.width = 20 + this.dots.height = 20 + } else { + this.dots.width = 8 + this.dots.height = 8 + } + } + + }, + created() { + if (this.mode === 'indexes') { + this.dots.width = 20 + this.dots.height = 20 + } + this.dots = Object.assign(this.dots, this.dotsStyles) + } + } +</script> + +<style lang="scss" scoped> + .uni-swiper__warp { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex: 1; + flex-direction: column; + position: relative; + overflow: hidden; + } + + .uni-swiper__dots-box { + position: absolute; + bottom: 10px; + left: 0; + right: 0; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex: 1; + flex-direction: row; + justify-content: center; + align-items: center; + } + + .uni-swiper__dots-item { + width: 8px; + border-radius: 100px; + margin-left: 6px; + background-color: $uni-bg-color-mask; + // transition: width 0.2s linear; 不要取消注释,不然会不能变色 + } + + .uni-swiper__dots-item:first-child { + margin: 0; + } + + .uni-swiper__dots-default { + border-radius: 100px; + } + + .uni-swiper__dots-long { + border-radius: 50px; + } + + .uni-swiper__dots-bar { + border-radius: 50px; + } + + .uni-swiper__dots-nav { + bottom: 0px; + height: 40px; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex: 1; + flex-direction: row; + justify-content: flex-start; + align-items: center; + background-color: rgba(0, 0, 0, 0.2); + } + + .uni-swiper__dots-nav-item { + /* overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; */ + font-size: $uni-font-size-base; + color: #fff; + margin: 0 15px; + } + + .uni-swiper__dots-indexes { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + // flex: 1; + justify-content: center; + align-items: center; + } + + .uni-swiper__dots-indexes-text { + color: #fff; + font-size: $uni-font-size-sm; + } +</style> diff --git a/hive-app/components/uni-table/uni-table.vue b/hive-app/components/uni-table/uni-table.vue new file mode 100644 index 0000000..59b50cd --- /dev/null +++ b/hive-app/components/uni-table/uni-table.vue @@ -0,0 +1,263 @@ +<template> + <view class="uni-table-scroll" :class="{'table--border':border,'border-none':!noData}"> + <view class="uni-table" :style="{'min-width':minWidth+'px'}" :class="{ 'table--stripe':stripe}"> + <slot></slot> + <view v-if="noData" class="uni-table-loading"> + <view class="uni-table-text">{{emptyText}}</view> + </view> + <view v-show="loading" class="uni-table-mask"> + <div class="uni-table--loader"></div> + </view> + </view> + </view> +</template> + +<script> + /** + * Table 表格 + * @description 用于展示多条结构类似的数据 + * @tutorial https://ext.dcloud.net.cn/plugin?id= + * @property {Boolean} border 是否带有纵向边框 + * @property {Boolean} stripe 是否显示斑马线 + * @property {Boolean} type 是否开启多选 + * @property {String} emptyText 空数据时显示的文本内容 + * @property {Boolean} loading 显示加载中 + * @event {Function} selection-change 开启多选时,当选择项发生变化时会触发该事件 + */ + export default { + name: 'uniTable', + options: { + virtualHost: true + }, + props: { + // 是否有竖线 + border: { + type: Boolean, + default: false + }, + // 是否显示斑马线 + stripe: { + type: Boolean, + default: false + }, + // 多选 + type: { + type: String, + default: '' + }, + // 没有更多数据 + emptyText: { + type: String, + default: '没有更多数据' + }, + loading: { + type: Boolean, + default: false + }, + }, + data() { + return { + noData: true, + minWidth:0 + }; + }, + watch: { + loading(val) { + + } + }, + created() { + // 定义tr的实例数组 + this.trChildren = [] + this.backData = [] + }, + methods: { + isNodata() { + if (this.trChildren.length > 1) { + this.noData = false + } else { + this.noData = true + } + }, + /** + * 清除选中 + */ + clearSelection(){ + this.trChildren.forEach((item, index) => { + item.value = false + }) + this.$emit('selection-change', { + detail: { + index: [], + value: [] + } + }) + }, + check(child, check) { + const childDom = this.trChildren.find((item, index) => child === item) + const childDomIndex = this.trChildren.findIndex((item, index) => child === item) + if (childDomIndex === 0) { + if (childDom.value !== check) { + this.backData = [] + this.trChildren.map((item, index) => item.value = check) + } + this.trChildren.forEach((item, index) => { + if (index > 0 && item.value) { + this.backData.push(index - 1) + } + }) + + } else { + if (!check) { + this.trChildren[0].value = false + } + childDom.value = check + if (check) { + this.backData.push(childDomIndex - 1) + } else { + const index = this.backData.findIndex(item => item === (childDomIndex - 1)) + this.backData.splice(index, 1) + } + const domCheckAll = this.trChildren.find((item, index) => index > 0 && !item.value) + if (!domCheckAll) { + this.trChildren[0].value = true + } + } + + this.$emit('selection-change', { + detail: { + index: this.backData.sort(), + value: [] + } + }) + + } + } + } +</script> + +<style lang="scss"> + .uni-table-scroll { + width: 100%; + overflow-x: auto; + } + + .uni-table { + position: relative; + width: 100%; + display: table; + box-sizing: border-box; + border-radius: 5px; + box-shadow: 0px 0px 3px 1px rgba(0, 0, 0, 0.1); + overflow-x: auto; + background-color: #fff; + /deep/ .uni-table-tr:nth-child(n+2) { + &:hover { + background-color: #f5f7fa; + } + } + } + + .table--border { + border: 1px #ddd solid; + } + + .border-none { + border-bottom: none; + } + + .table--stripe { + /deep/ .uni-table-tr:nth-child(2n+3) { + background-color: #fafafa; + } + + } + + /* 表格加载、无数据样式 */ + .uni-table-loading { + position: relative; + display: table-row; + height: 50px; + line-height: 50px; + } + + .uni-table-text { + position: absolute; + right: 0; + left: 0; + text-align: center; + font-size: 14px; + color: #999; + } + + .uni-table-mask { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + margin: auto; + background-color: rgba(255, 255, 255, 0.8); + z-index: 99; + display: flex; + justify-content: center; + align-items: center; + transition: all 0.5s; + } + + .uni-table--loader { + width: 30px; + height: 30px; + border: 2px solid #aaa; + // border-bottom-color: transparent; + border-radius: 50%; + animation: 2s uni-table--loader linear infinite; + position: relative; + } + + @keyframes uni-table--loader { + 0% { + transform: rotate(360deg); + } + + 10% { + border-left-color: transparent; + } + + 20% { + border-bottom-color: transparent; + } + + 30% { + border-right-color: transparent; + } + + 40% { + border-top-color: transparent; + } + + 50% { + transform: rotate(0deg); + } + + 60% { + border-top-color: transparent; + } + + 70% { + border-left-color: transparent; + } + + 80% { + border-bottom-color: transparent; + } + + 90% { + border-right-color: transparent; + } + + 100% { + transform: rotate(-360deg); + } + } +</style> diff --git a/hive-app/components/uni-tag/uni-tag.vue b/hive-app/components/uni-tag/uni-tag.vue new file mode 100644 index 0000000..a9e7087 --- /dev/null +++ b/hive-app/components/uni-tag/uni-tag.vue @@ -0,0 +1,230 @@ +<template> + <view :class="[ + 'uni-tag--' + type, + disabled === true || disabled === 'true' ? 'uni-tag--disabled' : '', + inverted === true || inverted === 'true' ? type + '-uni-tag--inverted' : '', + circle === true || circle === 'true' ? 'uni-tag--circle' : '', + mark === true || mark === 'true' ? 'uni-tag--mark' : '', + 'uni-tag--' + size + ]" + @click="onClick()" class="uni-tag" v-if="text"> + <text :class="[type === 'default' ? 'uni-tag--default':'uni-tag-text',inverted === true || inverted === 'true' ? 'uni-tag-text--'+type : '',size === 'small' ? 'uni-tag-text--small':'' ]">{{ text }}</text> + </view> +</template> + +<script> + /** + * Tag 标签 + * @description 用于展示1个或多个文字标签,可点击切换选中、不选中的状态 + * @tutorial https://ext.dcloud.net.cn/plugin?id=35 + * @property {String} text 标签内容 + * @property {String} size = [normal|small] 大小尺寸 + * @value normal 正常 + * @value small 小尺寸 + * @property {String} type = [default|primary|success|warning|error|royal] 颜色类型 + * @value default 灰色 + * @value primary 蓝色 + * @value success 绿色 + * @value warning 黄色 + * @value error 红色 + * @value royal 紫色 + * @property {Boolean} disabled = [true|false] 是否为禁用状态 + * @property {Boolean} inverted = [true|false] 是否无需背景颜色(空心标签) + * @property {Boolean} circle = [true|false] 是否为圆角 + * @event {Function} click 点击 Tag 触发事件 + */ + + export default { + name: "UniTag", + props: { + type: { + // 标签类型default、primary、success、warning、error、royal + type: String, + default: "default" + }, + size: { + // 标签大小 normal, small + type: String, + default: "normal" + }, + // 标签内容 + text: { + type: String, + default: "" + }, + disabled: { + // 是否为禁用状态 + type: [Boolean, String], + default: false + }, + inverted: { + // 是否为空心 + type: [Boolean, String], + default: false + }, + circle: { + // 是否为圆角样式 + type: [Boolean, String], + default: false + }, + mark: { + // 是否为标记样式 + type: [Boolean, String], + default: false + } + }, + methods: { + onClick() { + if (this.disabled === true || this.disabled === "true") { + return; + } + this.$emit("click"); + } + } + }; +</script> + +<style lang="scss" scoped> + $tag-pd: 0px 16px; + $tag-small-pd: 0px 8px; + + .uni-tag { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + padding: $tag-pd; + height: 30px; + line-height: 30px; + justify-content: center; + color: $uni-text-color; + border-radius: $uni-border-radius-base; + background-color: $uni-bg-color-grey; + border-width: 1rpx; + border-style: solid; + border-color: $uni-bg-color-grey; + } + + .uni-tag--circle { + border-radius: 15px; + } + + .uni-tag--mark { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-top-right-radius: 15px; + border-bottom-right-radius: 15px; + } + + .uni-tag--disabled { + opacity: 0.5; + } + + .uni-tag--small { + height: 20px; + padding: $tag-small-pd; + line-height: 20px; + font-size: $uni-font-size-sm; + } + + .uni-tag--default { + color: $uni-text-color; + font-size: $uni-font-size-base; + } + + .uni-tag-text--small { + font-size: $uni-font-size-sm !important; + } + + .uni-tag-text { + color: $uni-text-color-inverse; + font-size: $uni-font-size-base; + } + + .uni-tag-text--primary { + color: $uni-color-primary !important; + } + + .uni-tag-text--success { + color: $uni-color-success !important; + } + + .uni-tag-text--warning { + color: $uni-color-warning !important; + } + + .uni-tag-text--error { + color: $uni-color-error !important; + } + + .uni-tag--primary { + color: $uni-text-color-inverse; + background-color: $uni-color-primary; + border-width: 1rpx; + border-style: solid; + border-color: $uni-color-primary; + } + + .primary-uni-tag--inverted { + color: $uni-color-primary; + background-color: $uni-bg-color; + border-width: 1rpx; + border-style: solid; + border-color: $uni-color-primary; + } + + .uni-tag--success { + color: $uni-text-color-inverse; + background-color: $uni-color-success; + border-width: 1rpx; + border-style: solid; + border-color: $uni-color-success; + } + + .success-uni-tag--inverted { + color: $uni-color-success; + background-color: $uni-bg-color; + border-width: 1rpx; + border-style: solid; + border-color: $uni-color-success; + } + + .uni-tag--warning { + color: $uni-text-color-inverse; + background-color: $uni-color-warning; + border-width: 1rpx; + border-style: solid; + border-color: $uni-color-warning; + } + + .warning-uni-tag--inverted { + color: $uni-color-warning; + background-color: $uni-bg-color; + border-width: 1rpx; + border-style: solid; + border-color: $uni-color-warning; + } + + .uni-tag--error { + color: $uni-text-color-inverse; + background-color: $uni-color-error; + border-width: 1rpx; + border-style: solid; + border-color: $uni-color-error; + } + + .error-uni-tag--inverted { + color: $uni-color-error; + background-color: $uni-bg-color; + border-width: 1rpx; + border-style: solid; + border-color: $uni-color-error; + } + + .uni-tag--inverted { + color: $uni-text-color; + background-color: $uni-bg-color; + border-width: 1rpx; + border-style: solid; + border-color: $uni-bg-color-grey; + } +</style> diff --git a/hive-app/components/uni-td/uni-td.vue b/hive-app/components/uni-td/uni-td.vue new file mode 100644 index 0000000..4cfcf41 --- /dev/null +++ b/hive-app/components/uni-td/uni-td.vue @@ -0,0 +1,74 @@ +<template> + <!-- :class="{'table--border':border}" --> + <view class="uni-table-td" :class="{'table--border':border}" :style="{width:width + 'px','text-align':align}"> + <slot></slot> + </view> +</template> + +<script> + /** + * Td 单元格 + * @description 表格中的标准单元格组件 + * @tutorial https://ext.dcloud.net.cn/plugin?id= + * @property {Number} align = [left|center|right] 单元格对齐方式 + */ + export default { + name: 'uniTd', + options: { + virtualHost: true + }, + props: { + width: { + type: [String, Number], + default: '' + }, + align: { + type: String, + default: 'left' + } + }, + data() { + return { + border: false + }; + }, + created() { + this.root = this.getTable() + this.border = this.root.border + }, + methods: { + /** + * 获取父元素实例 + */ + getTable() { + let parent = this.$parent; + let parentName = parent.$options.name; + while (parentName !== 'uniTable') { + parent = parent.$parent; + if (!parent) return false; + parentName = parent.$options.name; + } + return parent; + }, + } + } +</script> + +<style lang="scss"> + .uni-table-td { + display: table-cell; + padding: 12px 10px; + // text-align: center; + vertical-align: middle; + border-bottom: 1px #ddd solid; + // min-width: 150px; + color: #666; + font-size: 14px; + box-sizing: border-box; + } + + .table--border { + border-right: 1px #ddd solid; + } + +</style> diff --git a/hive-app/components/uni-test/uni-test.vue b/hive-app/components/uni-test/uni-test.vue new file mode 100644 index 0000000..9587641 --- /dev/null +++ b/hive-app/components/uni-test/uni-test.vue @@ -0,0 +1,19 @@ +<template> + <view> + 测试插件 + </view> +</template> + +<script> + export default { + data() { + return { + + }; + } + } +</script> + +<style> + +</style> diff --git a/hive-app/components/uni-th/uni-th.vue b/hive-app/components/uni-th/uni-th.vue new file mode 100644 index 0000000..03c5af5 --- /dev/null +++ b/hive-app/components/uni-th/uni-th.vue @@ -0,0 +1,78 @@ +<template> + <view class="uni-table-th" :class="{'table--border':border}" :style="{width:width + 'px','text-align':align}"> + <slot></slot> + </view> +</template> + +<script> + /** + * Th 表头 + * @description 表格内的表头单元格组件 + * @tutorial https://ext.dcloud.net.cn/plugin?id= + * @property {Number} width 单元格宽度 + * @property {Number} align = [left|center|right] 单元格对齐方式 + * @value left 单元格文字左侧对齐 + * @value center 单元格文字居中 + * @value right 单元格文字右侧对齐 + */ + + export default { + name: 'uniTh', + options: { + virtualHost: true + }, + props: { + width: { + type: [String, Number], + default: '' + }, + align: { + type: String, + default: 'left' + } + }, + data() { + return { + border:false + }; + }, + created() { + this.root = this.getTable('uniTable') + this.rootTr = this.getTable('uniTr') + this.rootTr.minWidthUpdate(this.width?this.width:140) + this.border = this.root.border + }, + methods:{ + /** + * 获取父元素实例 + */ + getTable(name) { + let parent = this.$parent; + let parentName = parent.$options.name; + while (parentName !== name) { + parent = parent.$parent; + if (!parent) return false; + parentName = parent.$options.name; + } + return parent; + } + } + } +</script> + +<style lang="scss"> + .uni-table-th { + padding: 12px 10px; + display: table-cell; + // text-align: center; + color: #333; + font-weight: 500; + border-bottom: 1px #ddd solid; + font-size: 14px; + // background-color: #efefef; + box-sizing: border-box; + } + .table--border { + border-right: 1px #ddd solid; + } +</style> diff --git a/hive-app/components/uni-title/uni-title.vue b/hive-app/components/uni-title/uni-title.vue new file mode 100644 index 0000000..74e4c8b --- /dev/null +++ b/hive-app/components/uni-title/uni-title.vue @@ -0,0 +1,170 @@ +<template> + <view class="uni-title__box" :style="{'align-items':textAlign}"> + <text class="uni-title__base" :class="['uni-'+type]" :style="{'color':color}">{{title}}</text> + </view> +</template> + +<script> + /** + * Title 章节标题 + * @description 章节标题,通常用于记录页面标题,使用当前组件,uni-app 如果开启统计,将会自动统计页面标题 + * @tutorial https://ext.dcloud.net.cn/plugin?id=1066 + * @property {String} type = [h1|h2|h3|h4|h5] 标题类型 + * @value h1 一级标题 + * @value h2 二级标题 + * @value h3 三级标题 + * @value h4 四级标题 + * @value h5 五级标题 + * @property {String} title 章节标题内容 + * @property {String} align = [left|center|right] 对齐方式 + * @value left 做对齐 + * @value center 居中对齐 + * @value right 右对齐 + * @property {String} color 字体颜色 + * @property {Boolean} stat = [true|false] 是否开启统计功能呢,如不填写type值,默认为开启,填写 type 属性,默认为关闭 + */ + export default { + props: { + type: { + type: String, + default: '' + }, + title: { + type: String, + default: '' + }, + align: { + type: String, + default: 'left' + }, + color: { + type: String, + default: '#333333' + }, + stat: { + type: [Boolean, String], + default: '' + } + }, + data() { + return { + + }; + }, + computed: { + textAlign() { + let align = 'center'; + switch (this.align) { + case 'left': + align = 'flex-start' + break; + case 'center': + align = 'center' + break; + case 'right': + align = 'flex-end' + break; + } + return align + } + }, + watch: { + title(newVal) { + if (this.isOpenStat()) { + // 上报数据 + if (uni.report) { + uni.report('title', this.title) + } + } + } + }, + mounted() { + if (this.isOpenStat()) { + // 上报数据 + if (uni.report) { + uni.report('title', this.title) + } + } + }, + methods: { + isOpenStat() { + if (this.stat === '') { + this.isStat = false + } + let stat_type = (typeof(this.stat) === 'boolean' && this.stat) || (typeof(this.stat) === 'string' && this.stat !== + '') + if (this.type === "") { + this.isStat = true + if (this.stat.toString() === 'false') { + this.isStat = false + } + } + + if (this.type !== '') { + this.isStat = true + if (stat_type) { + this.isStat = true + } else { + this.isStat = false + } + } + return this.isStat + } + } + } +</script> + +<style scoped> + /* .uni-title { + + } */ + .uni-title__box { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; + align-items: flex-start; + justify-content: center; + padding: 8px 0; + flex: 1; + } + + .uni-title__base { + font-size: 15px; + color: #333; + font-weight: 500; + } + + .uni-h1 { + font-size: 20px; + color: #333; + font-weight: bold; + } + + .uni-h2 { + font-size: 18px; + color: #333; + font-weight: bold; + } + + .uni-h3 { + font-size: 16px; + color: #333; + font-weight: bold; + /* font-weight: 400; */ + } + + .uni-h4 { + font-size: 14px; + color: #333; + font-weight: bold; + /* font-weight: 300; */ + } + + .uni-h5 { + font-size: 12px; + color: #333; + font-weight: bold; + /* font-weight: 200; */ + } +</style> diff --git a/hive-app/components/uni-tr/uni-tr.vue b/hive-app/components/uni-tr/uni-tr.vue new file mode 100644 index 0000000..05edb2c --- /dev/null +++ b/hive-app/components/uni-tr/uni-tr.vue @@ -0,0 +1,117 @@ +<template> + <view class="uni-table-tr"> + <checkbox-group v-if="selection === 'selection'" class="checkbox" :class="{'tr-table--border':border}" @change="change"> + <label> + <checkbox value="check" :checked="value"/> + </label> + </checkbox-group> + <slot></slot> + </view> +</template> + +<script> + /** + * Tr 表格行组件 + * @description 表格行组件 仅包含 th,td 组件 + * @tutorial https://ext.dcloud.net.cn/plugin?id= + */ + export default { + name: 'uniTr', + options: { + virtualHost: true + }, + data() { + return { + value: false, + border: false, + selection:false, + widthThArr:[] + }; + }, + created() { + this.root = this.getTable() + this.border = this.root.border + this.selection = this.root.type + this.root.trChildren.push(this) + this.root.isNodata() + }, + mounted() { + if(this.widthThArr.length > 0){ + const selectionWidth = this.selection === 'selection'? 50:0 + this.root.minWidth = this.widthThArr.reduce((a,b)=> Number(a) + Number(b)) + selectionWidth + } + }, + destroyed() { + const index = this.root.trChildren.findIndex(i=>i===this) + this.root.trChildren.splice(index,1) + this.root.isNodata() + }, + methods: { + minWidthUpdate(width){ + this.widthThArr.push(width) + }, + change(e) { + this.root.trChildren.forEach((item) => { + if (item === this) { + this.root.check(this,e.detail.value.length > 0 ? true : false) + } + }) + }, + /** + * 获取父元素实例 + */ + getTable() { + let parent = this.$parent; + let parentName = parent.$options.name; + while (parentName !== 'uniTable') { + parent = parent.$parent; + if (!parent) return false; + parentName = parent.$options.name; + } + return parent; + }, + } + } +</script> + +<style lang="scss"> + .uni-table-tr { + display: table-row; + transition: all .3s; + box-sizing: border-box; + } + + .checkbox { + padding: 12px 8px; + width: 26px; + padding-left: 12px; + display: table-cell; + // text-align: center; + vertical-align: middle; + color: #333; + font-weight: 500; + border-bottom: 1px #ddd solid; + font-size: 14px; + } + + .tr-table--border { + border-right: 1px #ddd solid; + } + + .uni-table-tr { + /deep/ .uni-table-th { + &.table--border:last-child { + border-right: none; + } + } + /deep/ .uni-table-td { + &.table--border:last-child { + border-right: none; + } + } + } + + + + +</style> diff --git a/hive-app/components/uni-transition/uni-transition.vue b/hive-app/components/uni-transition/uni-transition.vue new file mode 100644 index 0000000..908a939 --- /dev/null +++ b/hive-app/components/uni-transition/uni-transition.vue @@ -0,0 +1,279 @@ +<template> + <view v-if="isShow" ref="ani" class="uni-transition" :class="[ani.in]" :style="'transform:' +transform+';'+stylesObject" + @click="change"> + <slot></slot> + </view> +</template> + +<script> + // #ifdef APP-NVUE + const animation = uni.requireNativePlugin('animation'); + // #endif + /** + * Transition 过渡动画 + * @description 简单过渡动画组件 + * @tutorial https://ext.dcloud.net.cn/plugin?id=985 + * @property {Boolean} show = [false|true] 控制组件显示或隐藏 + * @property {Array} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型 + * @value fade 渐隐渐出过渡 + * @value slide-top 由上至下过渡 + * @value slide-right 由右至左过渡 + * @value slide-bottom 由下至上过渡 + * @value slide-left 由左至右过渡 + * @value zoom-in 由小到大过渡 + * @value zoom-out 由大到小过渡 + * @property {Number} duration 过渡动画持续时间 + * @property {Object} styles 组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red` + */ + export default { + name: 'uniTransition', + props: { + show: { + type: Boolean, + default: false + }, + modeClass: { + type: Array, + default () { + return [] + } + }, + duration: { + type: Number, + default: 300 + }, + styles: { + type: Object, + default () { + return {} + } + } + }, + data() { + return { + isShow: false, + transform: '', + ani: { in: '', + active: '' + } + }; + }, + watch: { + show: { + handler(newVal) { + if (newVal) { + this.open() + } else { + this.close() + } + }, + immediate: true + } + }, + computed: { + stylesObject() { + let styles = { + ...this.styles, + 'transition-duration': this.duration / 1000 + 's' + } + let transfrom = '' + for (let i in styles) { + let line = this.toLine(i) + transfrom += line + ':' + styles[i] + ';' + } + return transfrom + } + }, + created() { + // this.timer = null + // this.nextTick = (time = 50) => new Promise(resolve => { + // clearTimeout(this.timer) + // this.timer = setTimeout(resolve, time) + // return this.timer + // }); + }, + methods: { + change() { + this.$emit('click', { + detail: this.isShow + }) + }, + open() { + clearTimeout(this.timer) + this.isShow = true + this.transform = '' + this.ani.in = '' + for (let i in this.getTranfrom(false)) { + if (i === 'opacity') { + this.ani.in = 'fade-in' + } else { + this.transform += `${this.getTranfrom(false)[i]} ` + } + } + this.$nextTick(() => { + setTimeout(() => { + this._animation(true) + }, 50) + }) + + }, + close(type) { + clearTimeout(this.timer) + this._animation(false) + }, + _animation(type) { + let styles = this.getTranfrom(type) + // #ifdef APP-NVUE + if(!this.$refs['ani']) return + animation.transition(this.$refs['ani'].ref, { + styles, + duration: this.duration, //ms + timingFunction: 'ease', + needLayout: false, + delay: 0 //ms + }, () => { + if (!type) { + this.isShow = false + } + this.$emit('change', { + detail: this.isShow + }) + }) + // #endif + // #ifndef APP-NVUE + this.transform = '' + for (let i in styles) { + if (i === 'opacity') { + this.ani.in = `fade-${type?'out':'in'}` + } else { + this.transform += `${styles[i]} ` + } + } + this.timer = setTimeout(() => { + if (!type) { + this.isShow = false + } + this.$emit('change', { + detail: this.isShow + }) + + }, this.duration) + // #endif + + }, + getTranfrom(type) { + let styles = { + transform: '' + } + this.modeClass.forEach((mode) => { + switch (mode) { + case 'fade': + styles.opacity = type ? 1 : 0 + break; + case 'slide-top': + styles.transform += `translateY(${type?'0':'-100%'}) ` + break; + case 'slide-right': + styles.transform += `translateX(${type?'0':'100%'}) ` + break; + case 'slide-bottom': + styles.transform += `translateY(${type?'0':'100%'}) ` + break; + case 'slide-left': + styles.transform += `translateX(${type?'0':'-100%'}) ` + break; + case 'zoom-in': + styles.transform += `scale(${type?1:0.8}) ` + break; + case 'zoom-out': + styles.transform += `scale(${type?1:1.2}) ` + break; + } + }) + return styles + }, + _modeClassArr(type) { + let mode = this.modeClass + if (typeof(mode) !== "string") { + let modestr = '' + mode.forEach((item) => { + modestr += (item + '-' + type + ',') + }) + return modestr.substr(0, modestr.length - 1) + } else { + return mode + '-' + type + } + }, + // getEl(el) { + // console.log(el || el.ref || null); + // return el || el.ref || null + // }, + toLine(name) { + return name.replace(/([A-Z])/g, "-$1").toLowerCase(); + } + } + } +</script> + +<style> + .uni-transition { + transition-timing-function: ease; + transition-duration: 0.3s; + transition-property: transform, opacity; + } + + .fade-in { + opacity: 0; + } + + .fade-active { + opacity: 1; + } + + .slide-top-in { + /* transition-property: transform, opacity; */ + transform: translateY(-100%); + } + + .slide-top-active { + transform: translateY(0); + /* opacity: 1; */ + } + + .slide-right-in { + transform: translateX(100%); + } + + .slide-right-active { + transform: translateX(0); + } + + .slide-bottom-in { + transform: translateY(100%); + } + + .slide-bottom-active { + transform: translateY(0); + } + + .slide-left-in { + transform: translateX(-100%); + } + + .slide-left-active { + transform: translateX(0); + opacity: 1; + } + + .zoom-in-in { + transform: scale(0.8); + } + + .zoom-out-active { + transform: scale(1); + } + + .zoom-out-in { + transform: scale(1.2); + } +</style> diff --git a/hive-app/main.js b/hive-app/main.js new file mode 100644 index 0000000..6abef22 --- /dev/null +++ b/hive-app/main.js @@ -0,0 +1,11 @@ +import Vue from 'vue' +import App from './App' + +Vue.config.productionTip = false + +App.mpType = 'app' + +const app = new Vue({ + ...App +}) +app.$mount() diff --git a/hive-app/manifest.json b/hive-app/manifest.json new file mode 100644 index 0000000..86405ac --- /dev/null +++ b/hive-app/manifest.json @@ -0,0 +1,58 @@ +{ + "name" : "hive-app", + "appid" : "__UNI__EB91E09", + "description": "", + "versionName": "1.0.0", + "versionCode": "100", + "transformPx": false, + "app-plus": { /* 5+App特有相关 */ + "usingComponents": true, + "nvueCompiler": "uni-app", + "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打包配置 */ + + }, + "sdkConfigs": { /* SDK配置 */ + + } + } + }, + "quickapp": { /* 快应用特有相关 */ + + }, + "mp-weixin": { /* 小程序特有相关 */ + "appid": "", + "setting": { + "urlCheck": false + }, + "usingComponents": true + } +} diff --git a/hive-app/pages.json b/hive-app/pages.json new file mode 100644 index 0000000..27973fb --- /dev/null +++ b/hive-app/pages.json @@ -0,0 +1,17 @@ +{ + "pages": [{ + "path": "pages/index/index", + "style": { + "navigationBarTitleText": "uni-app" + } + }], + "globalStyle": { + "navigationBarTextStyle": "black", + "navigationBarTitleText": "uni-app", + "navigationBarBackgroundColor": "#F8F8F8", + "backgroundColor": "#F8F8F8", + "app-plus": { + "background": "#efeff4" + } + } +} diff --git a/hive-app/pages/index/index.vue b/hive-app/pages/index/index.vue new file mode 100644 index 0000000..7697316 --- /dev/null +++ b/hive-app/pages/index/index.vue @@ -0,0 +1,29 @@ +<template> + <view class="container"> + + <view class="intro">本项目已包含uni ui组件,无需import和注册,可直接使用。在代码区键入字母u,即可通过代码助手列出所有可用组件。光标置于组件名称处按F1,即可查看组件文档。</view> + <text class="intro">详见:</text> + <uni-link :href="href" :text="href"></uni-link> + </view> +</template> + +<script> + export default { + data() { + return { + href: 'https://uniapp.dcloud.io/component/README?id=uniui' + } + }, + methods: { + + } + } +</script> + +<style> + .container { + padding: 20px; + font-size: 14px; + line-height: 24px; + } +</style> diff --git a/hive-app/static/uni.ttf b/hive-app/static/uni.ttf new file mode 100644 index 0000000..fcfd021 --- /dev/null +++ b/hive-app/static/uni.ttf Binary files differ diff --git a/hive-app/uni.scss b/hive-app/uni.scss new file mode 100644 index 0000000..79f4e98 --- /dev/null +++ b/hive-app/uni.scss @@ -0,0 +1,76 @@ +/** + * 这里是uni-app内置的常用样式变量 + * + * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 + * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App + * + */ + +/** + * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 + * + * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 + */ + +/* 颜色变量 */ + +/* 行为相关颜色 */ +$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:#e5e5e5; + +/* 尺寸变量 */ + +/* 文字尺寸 */ +$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; -- Gitblit v1.9.1