@ecolovo/uniapp-components 1.0.10 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/README.md +202 -66
  2. package/components/aspect/aspect.vue +24 -15
  3. package/components/badge/badge.scss +20 -0
  4. package/components/badge/badge.vue +49 -40
  5. package/components/button/button.scss +15 -0
  6. package/components/button/button.vue +121 -177
  7. package/components/checkbox/checkbox.scss +10 -0
  8. package/components/checkbox/checkbox.vue +118 -146
  9. package/components/checkbox-group/checkbox-group.vue +79 -85
  10. package/components/checkbox-group2/checkbox-group2.vue +111 -107
  11. package/components/checkbox-group2/checkbox2.vue +104 -0
  12. package/components/data-list/data-list.scss +9 -0
  13. package/components/data-list/data-list.vue +70 -79
  14. package/components/date-picker/date-picker-view.vue +125 -124
  15. package/components/date-picker/date-picker.vue +145 -146
  16. package/components/dialog/dialog-content.vue +39 -47
  17. package/components/dialog/dialog.scss +11 -0
  18. package/components/dialog/dialog.vue +125 -138
  19. package/components/dropdown/dropdown.scss +11 -0
  20. package/components/dropdown/dropdown.vue +55 -97
  21. package/components/empty/empty.scss +14 -0
  22. package/components/empty/empty.vue +42 -41
  23. package/components/flex/flex.vue +45 -54
  24. package/components/form/form.vue +29 -24
  25. package/components/form-item/form-item.scss +11 -0
  26. package/components/form-item/form-item.vue +43 -40
  27. package/components/icon/icon.vue +70 -85
  28. package/components/image/image.vue +35 -38
  29. package/components/input/input.vue +65 -79
  30. package/components/input/password-input.vue +34 -42
  31. package/components/list-page/list-page.vue +73 -99
  32. package/components/load-more/load-more.scss +12 -0
  33. package/components/load-more/load-more.vue +29 -29
  34. package/components/loading/animations/loading-animation1.vue +8 -8
  35. package/components/loading/animations/loading-animation2.vue +8 -9
  36. package/components/loading/animations/loading-animation3.vue +11 -11
  37. package/components/loading/animations/loading-animation4.vue +8 -8
  38. package/components/loading/loading.vue +19 -20
  39. package/components/message-box/message-box.vue +59 -41
  40. package/components/number-dialog/number-dialog.scss +9 -0
  41. package/components/number-dialog/number-dialog.vue +209 -204
  42. package/components/number-input/number-input.scss +9 -0
  43. package/components/number-input/number-input.vue +233 -232
  44. package/components/numpad/numpad.scss +9 -0
  45. package/components/numpad/numpad.vue +133 -150
  46. package/components/page/page-body.scss +15 -0
  47. package/components/page/page-body.vue +137 -119
  48. package/components/page/page-footer.scss +13 -0
  49. package/components/page/page-footer.vue +31 -33
  50. package/components/page/page-header.scss +17 -0
  51. package/components/page/page-header.vue +89 -94
  52. package/components/page/page.scss +9 -0
  53. package/components/page/page.vue +105 -81
  54. package/components/picker/picker-popup.vue +218 -209
  55. package/components/picker/picker.vue +99 -104
  56. package/components/popover/popover-content.vue +54 -63
  57. package/components/popover/popover-mask.vue +63 -63
  58. package/components/popover/popover.scss +28 -0
  59. package/components/popover/popover.vue +49 -47
  60. package/components/popup/popup-content.vue +49 -53
  61. package/components/popup/popup.scss +11 -0
  62. package/components/popup/popup.vue +117 -132
  63. package/components/price/price.scss +20 -0
  64. package/components/price/price.vue +31 -42
  65. package/components/search/search.scss +12 -0
  66. package/components/search/search.vue +93 -117
  67. package/components/searching/searching.vue +33 -40
  68. package/components/selector/selector.vue +43 -48
  69. package/components/switch/switch.scss +12 -0
  70. package/components/switch/switch.vue +99 -96
  71. package/components/tab/tab.scss +19 -0
  72. package/components/tab/tab.vue +151 -145
  73. package/components/textarea/textarea.vue +104 -101
  74. package/composable/component-name.d.ts +2 -0
  75. package/composable/component-name.js +8 -0
  76. package/composable/component-name.ts +10 -0
  77. package/configs/index.d.ts +25 -10
  78. package/configs/index.js +14 -23
  79. package/configs/index.ts +82 -0
  80. package/easyCom.d.ts +9 -0
  81. package/easyCom.js +1 -2
  82. package/easyCom.ts +31 -0
  83. package/index.d.ts +1 -0
  84. package/index.js +1 -0
  85. package/index.ts +1 -0
  86. package/init.d.ts +4 -7
  87. package/init.js +5 -4
  88. package/init.ts +34 -0
  89. package/package.json +14 -4
  90. package/styles/utils/css-var.scss +12 -15
  91. package/styles/utils/ellipsis.scss +0 -7
  92. package/styles/utils/z-index.scss +1 -1
  93. package/styles/var/component.scss +21 -245
  94. package/utils/date.d.ts +47 -0
  95. package/utils/date.js +23 -43
  96. package/utils/date.ts +153 -0
  97. package/utils/freezeDeep.d.ts +1 -0
  98. package/utils/freezeDeep.js +4 -14
  99. package/utils/freezeDeep.ts +9 -0
  100. package/utils/price.d.ts +24 -0
  101. package/utils/price.js +14 -8
  102. package/utils/price.ts +57 -0
  103. package/utils/round.d.ts +2 -0
  104. package/utils/round.js +2 -14
  105. package/utils/round.ts +11 -0
  106. package/utils/windowInfo.d.ts +24 -0
  107. package/utils/windowInfo.js +8 -36
  108. package/utils/windowInfo.ts +50 -0
  109. package/components/checkbox-group2/checkbox.vue +0 -109
  110. package/components/datetime-picker/datetime-picker-view.vue +0 -209
  111. package/components/datetime-picker/datetime-picker.vue +0 -235
  112. package/components/form/mixin.js +0 -13
  113. package/components/list-page/list-page.js +0 -10
  114. package/components/table/table.vue +0 -112
  115. package/components/time-picker/time-picker-view.vue +0 -127
  116. package/components/time-picker/time-picker.vue +0 -140
  117. package/components/uploader/download.js +0 -81
  118. package/components/uploader/file-uploader.vue +0 -52
  119. package/components/uploader/files.js +0 -225
  120. package/components/uploader/media-uploader.vue +0 -189
  121. package/components/uploader/mixin.js +0 -171
  122. package/components/uploader/uni.js +0 -38
  123. package/components/uploader/upload-button.vue +0 -29
  124. package/components/uploader/uploader copy.vue +0 -547
  125. package/components/uploader/uploader.vue +0 -117
  126. package/mixins/componentName.js +0 -32
  127. package/mixins/listPage.js +0 -1
  128. package/mixins/windowInfo.js +0 -1
  129. package/utils/fileSize.js +0 -39
  130. package/utils/floor.js +0 -20
@@ -1,10 +1,10 @@
1
1
  <template>
2
- <view :class="componentClassNames">
2
+ <view :class="[...classNames, $style['Numpad']]">
3
3
  <button-cpn
4
4
  v-for="btn in buttons"
5
5
  :key="btn.key"
6
- :cool-down="0"
7
- :class="[$style['Numpad--key'], $style[btn.className]]"
6
+ :cold-down="0"
7
+ :class="[$style['Numpad--key'], $style[btn.className || '']]"
8
8
  @click="onClick(btn)"
9
9
  >
10
10
  <icon-cpn v-if="btn.icon" :icon="btn.icon" />
@@ -13,18 +13,36 @@
13
13
  </view>
14
14
  </template>
15
15
 
16
- <script>
17
- import componentName from '../../mixins/componentName';
16
+ <script lang="ts" setup>
17
+ import { computed } from 'vue';
18
18
  import { freezeDeep } from '../../utils/freezeDeep';
19
+ import useClassNames from '../../composable/component-name';
19
20
  import ButtonCpn from '../button/button.vue';
20
21
  import IconCpn from '../icon/icon.vue';
21
22
 
23
+ defineOptions({
24
+ options: {
25
+ virtualHost: true,
26
+ styleIsolation: 'shared',
27
+ },
28
+ });
29
+
30
+ const classNames = useClassNames('numpad');
31
+
22
32
  /** 默认最小值 */
23
33
  const MIN = 0;
24
34
  /** 默认最大值 */
25
35
  const MAX = 9999;
26
36
 
27
- const buttons = freezeDeep([
37
+ type BtnItem = {
38
+ key: string;
39
+ type: 'number' | '-' | 'dot' | 'delete' | 'clear';
40
+ icon?: string;
41
+ title?: string;
42
+ className?: string;
43
+ };
44
+
45
+ const buttons = freezeDeep<BtnItem[]>([
28
46
  { key: '1', type: 'number' },
29
47
  { key: '2', type: 'number' },
30
48
  { key: '3', type: 'number' },
@@ -41,157 +59,122 @@ const buttons = freezeDeep([
41
59
  { key: '0', type: 'number', className: 'Numpad--zero-key' },
42
60
  ]);
43
61
 
44
- export default {
45
- name: 'Numpad',
46
- options: {
47
- virtualHost: true,
48
- styleIsolation: 'shared',
49
- },
50
- components: { ButtonCpn, IconCpn },
51
- mixins: [componentName],
52
- props: {
53
- /** 绑定值 */
54
- value: {
55
- type: [String, Number],
56
- default: '',
57
- },
58
- /** 小数的精度 */
59
- precision: {
60
- type: Number,
61
- default: 0,
62
- },
63
- /** 允许输入的最大值 */
64
- max: {
65
- type: Number,
66
- default: MAX,
67
- },
68
- /** 允许输入的最小值 */
69
- min: {
70
- type: Number,
71
- default: MIN,
72
- },
73
- },
74
- data() {
75
- return {
76
- buttons,
77
- };
78
- },
79
- computed: {
80
- editValue: {
81
- get() {
82
- return this.value || this.value === 0 ? this.value.toString() : '';
83
- },
84
- set(val) {
85
- this.$emit('input', val);
86
- },
87
- },
88
- },
89
- created() {},
90
- methods: {
91
- /** 点击按钮 */
92
- onClick(btn) {
93
- switch (btn.type) {
94
- case 'number':
95
- this.onInputNumber(btn.key);
96
- break;
97
- case 'dot':
98
- this.onInputDot();
99
- break;
100
- case '-':
101
- this.onNegative();
102
- break;
103
- case 'delete':
104
- this.onDelete();
105
- break;
106
- case 'clear':
107
- this.onClear();
108
- break;
109
- }
110
- },
111
- /** 输入数字 */
112
- onInputNumber(key) {
113
- const nextValue = this.editValue + key;
114
- if (Number(nextValue) <= this.min) {
115
- this.editValue = this.min + '';
116
- return;
117
- }
118
- if (Number(nextValue) >= this.max) {
119
- this.editValue = this.max + '';
120
- return;
121
- }
122
-
123
- if (this.editValue === '0') {
124
- if (key === '0') return;
125
- this.editValue = key;
126
- return;
127
- }
128
-
129
- if (this.precision > 0 && this.editValue.includes('.')) {
130
- const decimalPart = this.editValue.split('.')[1];
131
- if (decimalPart.length >= this.precision) {
132
- return;
133
- }
134
- }
135
-
136
- this.editValue += key;
137
- },
138
- /** 输入小数点 */
139
- onInputDot() {
140
- if (this.precision <= 0) return;
141
- if (this.editValue.includes('.')) return;
142
-
143
- if (this.editValue) {
144
- this.editValue += '.';
145
- } else {
146
- this.editValue = '0.';
147
- }
148
- },
149
- /** 负号 */
150
- onNegative() {
151
- if (this.min >= 0) return;
152
-
153
- const nextValue = -this.editValue;
154
- if (nextValue < this.min) {
155
- this.editValue = this.min + '';
156
- return;
157
- }
158
- if (nextValue > this.max) {
159
- this.editValue = this.max + '';
160
- return;
161
- }
162
-
163
- if (this.editValue.startsWith('-')) {
164
- this.editValue = this.editValue.slice(1);
165
- } else {
166
- this.editValue = '-' + this.editValue;
167
- }
168
- },
169
- /** 删除 */
170
- onDelete() {
171
- if (!this.editValue) return;
172
-
173
- this.editValue = this.editValue.slice(0, -1);
174
- },
175
- /** 清空 */
176
- onClear() {
177
- this.editValue = '';
178
- },
179
- // onConfirm() {
180
- // this.$emit('input', false);
181
- // this.$emit('confirm', { value: this.editValue, number: Number(this.editValue) });
182
- // },
183
- },
62
+ type _Props = {
63
+ /** 绑定值 */
64
+ modelValue?: string | number;
65
+ /** 小数的精度 */
66
+ precision?: number;
67
+ /** 允许输入的最大值 */
68
+ max?: number;
69
+ /** 允许输入的最小值 */
70
+ min?: number;
71
+ };
72
+
73
+ const props = withDefaults(defineProps<_Props>(), {
74
+ modelValue: '',
75
+ precision: 0,
76
+ max: MAX,
77
+ min: MIN,
78
+ });
79
+
80
+ const emit = defineEmits<{
81
+ (e: 'update:modelValue', val: string): void;
82
+ }>();
83
+
84
+ const editValue = computed({
85
+ get: () => (props.modelValue || props.modelValue === 0 ? props.modelValue.toString() : ''),
86
+ set: (val: string) => emit('update:modelValue', val),
87
+ });
88
+
89
+ /** 点击按钮 */
90
+ const onClick = (btn: BtnItem) => {
91
+ switch (btn.type) {
92
+ case 'number':
93
+ onInputNumber(btn.key);
94
+ break;
95
+ case 'dot':
96
+ onInputDot();
97
+ break;
98
+ case '-':
99
+ onNegative();
100
+ break;
101
+ case 'delete':
102
+ onDelete();
103
+ break;
104
+ case 'clear':
105
+ onClear();
106
+ break;
107
+ }
108
+ };
109
+
110
+ /** 输入数字 */
111
+ const onInputNumber = (key: string) => {
112
+ const nextValue = editValue.value + key;
113
+ if (Number(nextValue) <= props.min) {
114
+ editValue.value = props.min + '';
115
+ return;
116
+ }
117
+ if (Number(nextValue) >= props.max) {
118
+ editValue.value = props.max + '';
119
+ return;
120
+ }
121
+ if (editValue.value === '0') {
122
+ if (key === '0') return;
123
+ editValue.value = key;
124
+ return;
125
+ }
126
+ if (props.precision > 0 && editValue.value.includes('.')) {
127
+ const decimalPart = editValue.value.split('.')[1]!;
128
+ if (decimalPart.length >= props.precision) return;
129
+ }
130
+ editValue.value += key;
131
+ };
132
+
133
+ /** 输入小数点 */
134
+ const onInputDot = () => {
135
+ if (props.precision <= 0) return;
136
+ if (editValue.value.includes('.')) return;
137
+ editValue.value = editValue.value ? editValue.value + '.' : '0.';
138
+ };
139
+
140
+ /** 负号 */
141
+ const onNegative = () => {
142
+ if (props.min >= 0) return;
143
+ const nextValue = -Number(editValue.value);
144
+ if (nextValue < props.min) {
145
+ editValue.value = props.min + '';
146
+ return;
147
+ }
148
+ if (nextValue > props.max) {
149
+ editValue.value = props.max + '';
150
+ return;
151
+ }
152
+ if (editValue.value.startsWith('-')) {
153
+ editValue.value = editValue.value.slice(1);
154
+ } else {
155
+ editValue.value = '-' + editValue.value;
156
+ }
157
+ };
158
+
159
+ /** 删除 */
160
+ const onDelete = () => {
161
+ if (!editValue.value) return;
162
+ editValue.value = editValue.value.slice(0, -1);
163
+ };
164
+
165
+ /** 清空 */
166
+ const onClear = () => {
167
+ editValue.value = '';
184
168
  };
185
169
  </script>
186
170
 
187
171
  <style lang="scss" module>
188
- @use '../../styles/index' as *;
172
+ @use '../../styles/utils' as *;
173
+ @use './numpad' as *;
189
174
 
190
175
  // 定义外部变量
191
- $component-name: 'numpad';
192
- $component-vars: $numpad;
193
176
  %external-vars {
194
- @include UTILS__DEFINE_COMPONENT_VARS();
177
+ @include UTILS__DEFINE_COMPONENT_VARS($numpad, 'numpad');
195
178
  }
196
179
 
197
180
  .Numpad {
@@ -0,0 +1,15 @@
1
+ @use 'sass:map';
2
+ @use '../../styles/utils' as *;
3
+
4
+ // 可配置的变量
5
+ $page-body: () !default;
6
+ $page-body-default: (
7
+ // 可视区域背景
8
+ background: UTILS__GLOBAL_VAR('color', 'background'),
9
+ padding: 20rpx,
10
+ padding-top: null,
11
+ padding-right: null,
12
+ padding-bottom: null,
13
+ padding-left: null
14
+ );
15
+ $page-body: map.merge($page-body-default, $page-body);
@@ -1,7 +1,11 @@
1
1
  <template>
2
2
  <view
3
- :class="[componentClassNames, useScroll ? $style['PageBody--with-scroll'] : $style['PageBody--no-scroll']]"
4
- :style="styleStr"
3
+ :class="[
4
+ ...classNames,
5
+ $style['PageBody'],
6
+ useScroll ? $style['PageBody--with-scroll'] : $style['PageBody--no-scroll'],
7
+ ]"
8
+ :style="inlineStyle"
5
9
  >
6
10
  <!-- 不使用 scroll-view ,即直接使用 Page 的滚动功能 -->
7
11
  <view v-if="!useScroll" :class="$style['PageBody--body']">
@@ -41,144 +45,158 @@
41
45
  </view>
42
46
  </template>
43
47
 
44
- <script>
48
+ <script lang="ts" setup>
45
49
  /**
46
50
  * 页面内容部分容器组件,不单独使用,通过 page 组件间接引入
47
51
  */
52
+ import { debounce } from 'lodash-es';
53
+ import { computed, getCurrentInstance, nextTick, ref, useCssModule } from 'vue';
54
+ import useClassNames from '../../composable/component-name';
55
+ import { navBarBottomOffset, safeAreaInsetBottom } from '../../utils/windowInfo';
48
56
 
49
- import debounce from 'lodash-es/debounce';
50
- import componentName from '../../mixins/componentName';
51
- import windowInfoMixin from '../../mixins/windowInfo';
57
+ defineOptions({
58
+ options: {
59
+ styleIsolation: 'shared',
60
+ virtualHost: true,
61
+ },
62
+ });
52
63
 
53
64
  /** 滚动动画持续时长 */
54
65
  const SCROLL_ANIMATION_DURATION = 300;
55
66
 
56
- export default {
57
- name: 'PageBody',
58
- options: {
59
- virtualHost: true,
60
- styleIsolation: 'shared',
61
- },
62
- mixins: [componentName, windowInfoMixin],
63
- props: {
64
- /** 是否有自定义导航头 */
65
- hasCustomNav: {
66
- type: Boolean,
67
- default: true,
68
- },
69
- /** 是否有底部区域 */
70
- hasFooter: {
71
- type: Boolean,
72
- default: false,
73
- },
74
- /** 是否使用scroll-view */
75
- useScroll: {
76
- type: Boolean,
77
- default: false,
78
- },
79
- /** 是否是 tab 页 */
80
- isTabPage: {
81
- type: Boolean,
82
- default: false,
83
- },
84
- /** 是否启用下拉刷新,仅 useScroll 为 true 时有效 */
85
- enableRefresh: {
86
- type: Boolean,
87
- default: false,
88
- },
89
- /** 是否触发下拉刷新,仅 useScroll 为 true 时有效 */
90
- refreshing: {
91
- type: Boolean,
92
- default: false,
93
- },
94
- },
95
- data() {
96
- return {
97
- /** scroll-view 的当前滚动距离 */
98
- scrollOffset: 0,
99
- /** 传递给 scroll-view ,用于控制其滚动距离 */
100
- scrollTop: 0,
101
- /** 传递给 scroll-view ,用于控制其是否有滚动动画 */
102
- scrollWithAnimation: true,
103
- };
104
- },
105
- computed: {
106
- styleStr() {
107
- return [
108
- `--header-bar-bottom: ${this.navBarBottomOffset}px;`,
109
- `--safe-area-bottom: ${this.isTabPage ? 0 : this.safeAreaInsetBottom}px;`,
110
- ...(this.hasCustomNav ? [] : ['--header-placeholder-height: 0px;']),
111
- ...(this.hasFooter
112
- ? ['--bottom-safe-height: 0px;']
113
- : ['--footer-placeholder-height: 0px;', '--bottom-safe-height: var(--safe-area-bottom);']),
114
- ].join(' ');
115
- },
116
- },
117
- methods: {
118
- $_debounceScroll: debounce(function () {
119
- uni.createSelectorQuery()
120
- /* #ifndef MP-ALIPAY */
121
- .in(this)
122
- /* #endif */
123
- .select('.' + this.$style['PageBody--scroll-body'])
124
- .fields({ size: true, scrollOffset: true }, (data) => {
125
- if (data.scrollHeight - data.height - data.scrollTop <= 50) {
126
- this.$emit('reach-bottom');
127
- }
128
- })
129
- .exec();
130
- }, 100),
131
- /** 处理 scroll-view 滚动 */
132
- $_handleScroll(e) {
133
- this.scrollOffset = e.detail.scrollTop;
134
- this.$emit('scroll', e.detail);
135
- if (this.useScroll) {
136
- this.$_debounceScroll(this.loadedPages);
137
- }
138
- },
139
- /** 滚动到顶部
140
- * @param {number} position 滚动到与顶部的距离
141
- * @param {boolean} animation 是否有滚动动画
142
- */
143
- scrollToTop(position = 0, animation = true) {
144
- if (this.useScroll) {
145
- this.scrollWithAnimation = animation;
146
- this.$nextTick(() => {
147
- this.scrollTop = typeof this.scrollTop === 'number' ? position + 'px' : position;
148
- this.scrollOffset = position;
149
- setTimeout(() => {
150
- this.scrollWithAnimation = true;
151
- }, SCROLL_ANIMATION_DURATION);
152
- });
153
- } else {
154
- uni.pageScrollTo({
155
- scrollTop: position,
156
- duration: animation ? SCROLL_ANIMATION_DURATION : 0,
157
- });
67
+ const classNames = useClassNames('page-body');
68
+ const $style = useCssModule();
69
+
70
+ type _Props = {
71
+ /** 是否有自定义导航头 */
72
+ hasHeader?: boolean;
73
+ /** 是否有底部区域 */
74
+ hasFooter?: boolean;
75
+ /** 是否使用scroll-view */
76
+ useScroll?: boolean;
77
+ /** 是否是 tab 页 */
78
+ isTabPage?: boolean;
79
+ /** 是否启用下拉刷新,仅 useScroll 为 true 时有效 */
80
+ enableRefresh?: boolean;
81
+ /** 是否触发下拉刷新,仅 useScroll 为 true 时有效 */
82
+ refreshing?: boolean;
83
+ };
84
+
85
+ const props = withDefaults(defineProps<_Props>(), {
86
+ hasHeader: true,
87
+ hasFooter: false,
88
+ useScroll: false,
89
+ isTabPage: false,
90
+ enableRefresh: false,
91
+ refreshing: undefined,
92
+ });
93
+
94
+ type ScrollEvent = {
95
+ detail: {
96
+ scrollLeft: number;
97
+ scrollTop: number;
98
+ scrollHeight: number;
99
+ scrollWidth: number;
100
+ deltaX: number;
101
+ deltaY: number;
102
+ };
103
+ };
104
+
105
+ const emit = defineEmits<{
106
+ (event: 'scroll', value: ScrollEvent): void;
107
+ (event: 'refresh'): void;
108
+ (event: 'reach-bottom'): void;
109
+ }>();
110
+
111
+ const scrollOffset = ref<number | string>(0);
112
+ const scrollTop = ref<number | string>(0);
113
+ const scrollWithAnimation = ref(true);
114
+
115
+ const inlineStyle = computed(() => {
116
+ return [
117
+ `--page-header-bottom: ${navBarBottomOffset}px;`,
118
+ `--safe-area-bottom: ${props.isTabPage ? 0 : safeAreaInsetBottom}px;`,
119
+ props.hasHeader ? '' : `--header-placeholder-height: 0px;`,
120
+ props.hasFooter
121
+ ? '--bottom-safe-height: 0px;'
122
+ : '--footer-placeholder-height: 0px; --bottom-safe-height: var(--safe-area-bottom);',
123
+ ].join('');
124
+ });
125
+
126
+ const vm = getCurrentInstance();
127
+
128
+ const $_debounceScroll = debounce(function () {
129
+ uni.createSelectorQuery()
130
+ /* #ifndef MP-ALIPAY */
131
+ .in(vm)
132
+ /* #endif */
133
+ .select('.' + $style['PageBody--scroll-body'])
134
+ .fields({ size: true, scrollOffset: true }, (data) => {
135
+ if (!Array.isArray(data) && data.scrollHeight! - data.height! - data.scrollTop! <= 50) {
136
+ emit('reach-bottom');
158
137
  }
159
- },
160
- },
138
+ })
139
+ .exec();
140
+ }, 100);
141
+
142
+ /** 处理 scroll-view 滚动 */
143
+ const $_handleScroll = (e: {
144
+ detail: {
145
+ scrollLeft: number;
146
+ scrollTop: number;
147
+ scrollHeight: number;
148
+ scrollWidth: number;
149
+ deltaX: number;
150
+ deltaY: number;
151
+ };
152
+ }) => {
153
+ scrollOffset.value = e.detail.scrollTop;
154
+ emit('scroll', e);
155
+ if (props.useScroll) {
156
+ $_debounceScroll();
157
+ }
158
+ };
159
+
160
+ /** 滚动到顶部
161
+ * @param {number} position 滚动到与顶部的距离
162
+ * @param {boolean} animation 是否有滚动动画
163
+ */
164
+ const scrollToTop = (position = 0, animation = true) => {
165
+ if (props.useScroll) {
166
+ scrollWithAnimation.value = animation;
167
+ nextTick(() => {
168
+ scrollTop.value = typeof scrollTop.value === 'number' ? position + 'px' : position;
169
+ scrollOffset.value = position;
170
+ setTimeout(() => {
171
+ scrollWithAnimation.value = true;
172
+ }, SCROLL_ANIMATION_DURATION);
173
+ });
174
+ } else {
175
+ uni.pageScrollTo({
176
+ scrollTop: position,
177
+ duration: animation ? SCROLL_ANIMATION_DURATION : 0,
178
+ });
179
+ }
161
180
  };
181
+
182
+ defineExpose({
183
+ scrollToTop,
184
+ });
162
185
  </script>
163
186
 
164
187
  <style lang="scss" module>
165
- @use '../../styles/index' as *;
188
+ @use '../../styles/utils' as *;
189
+ @use './page-body' as *;
166
190
 
167
191
  // 定义外部变量
168
- $component-name: 'page-body';
169
- $component-vars: $page-body;
170
192
  %external-vars {
171
- @include UTILS__DEFINE_COMPONENT_VARS();
172
- }
173
-
174
- :host {
175
- display: block;
193
+ @include UTILS__DEFINE_COMPONENT_VARS($page-body, 'page-body');
176
194
  }
177
195
 
178
196
  .PageBody {
179
197
  @extend %external-vars;
180
198
 
181
- --header-placeholder-height: calc(var(--header-bar-bottom) + #{UTILS__VAR(extra-height, page-header, 0px)});
199
+ --header-placeholder-height: calc(var(--page-header-bottom) + #{UTILS__VAR(extra-height, page-header, 0px)});
182
200
  --footer-placeholder-height: calc(var(--safe-area-bottom) + #{UTILS__VAR(height, page-footer, 100rpx)});
183
201
 
184
202
  box-sizing: border-box;
@@ -0,0 +1,13 @@
1
+ @use 'sass:map';
2
+
3
+ // 可配置的变量
4
+ $page-footer: () !default;
5
+ $page-footer-default: (
6
+ // 背景色
7
+ background: #fff,
8
+ // 高度
9
+ height: 100rpx,
10
+ // 横向两端间距
11
+ spacing-horizontal: 30rpx
12
+ );
13
+ $page-footer: map.merge($page-footer-default, $page-footer);