@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,18 +1,18 @@
1
1
  <template>
2
- <view :class="[componentClassNames, { [$style['Tab--evenly']]: evenly }]" :style="styleStr">
2
+ <view :class="[...classNames, $style['Tab'], { [$style['Tab--evenly']]: evenly }]" :style="inlineStyle">
3
3
  <scroll-view scroll-x :class="$style['Tab--tab-scroll']">
4
4
  <view :class="$style['Tab--tab-wrapper']">
5
5
  <view
6
6
  v-for="(tab, index) in tabLabels"
7
7
  :key="index"
8
- :class="[[$style['Tab--tab']], { [$style['Tab--tab-active']]: index === tabIndex }]"
8
+ :class="[[$style['Tab--tab']], { [$style['Tab--tab-active']]: index === selectedIndex }]"
9
9
  @click="onChangeTab(index)"
10
10
  >
11
11
  <view :class="$style['Tab--tab-text']">{{ tab }}</view>
12
12
  </view>
13
13
  <view
14
14
  v-if="indicator"
15
- :class="[$style['Tab--indicator'], { [$style['Tab--initialing']]: !initialized }]"
15
+ :class="[$style['Tab--indicator'], { [$style['Tab--indicator-animation']]: hasAnimation }]"
16
16
  >
17
17
  </view>
18
18
  </view>
@@ -20,160 +20,166 @@
20
20
  </view>
21
21
  </template>
22
22
 
23
- <script>
24
- import componentName from '../../mixins/componentName';
23
+ <script lang="ts" setup>
24
+ import { computed, getCurrentInstance, nextTick, onMounted, ref, useCssModule, watch } from 'vue';
25
+ import useClassNames from '../../composable/component-name';
25
26
 
26
- export default {
27
- name: 'Tab',
27
+ defineOptions({
28
28
  options: {
29
29
  virtualHost: true,
30
30
  styleIsolation: 'shared',
31
31
  },
32
- mixins: [componentName],
33
- props: {
34
- value: {
35
- type: [String, Number, null],
36
- default: null,
37
- },
38
- tabs: {
39
- type: Array,
40
- default: () => [],
41
- },
42
- /** 是否均分大小 */
43
- evenly: {
44
- type: Boolean,
45
- default: true,
46
- },
47
- /** 是否显示指示器 */
48
- indicator: {
49
- type: Boolean,
50
- default: true,
51
- },
52
- },
53
- data() {
54
- return {
55
- /** 组件内部记录的索引值 */
56
- innerIndex: 0,
57
- /** 左边距 */
58
- boundingLeft: 0,
59
- /** 标签的绘制信息 */
60
- tabRects: [],
61
- /** 是否已初始化 */
62
- initialized: false,
63
- };
64
- },
65
- computed: {
66
- /** 当前选中的索引 */
67
- selectedIndex: {
68
- get: function () {
69
- return this.value === null ? this.innerIndex : this.value;
70
- },
71
- set: function (value) {
72
- this.$emit('input', value);
73
- this.innerIndex = value;
74
- this.$emit('change', this.innerIndex);
75
- },
76
- },
77
- isTabsNotEmpty() {
78
- return this.tabs && this.tabs.length > 0;
79
- },
80
- isStringTabs() {
81
- return this.tabs.every((item) => typeof item === 'string');
82
- },
83
- tabLabels() {
84
- return this.tabs.map((item) => (typeof item === 'string' ? item : item.label));
85
- },
86
- /** 当前值对应的索引 */
87
- tabIndex() {
88
- if (!this.isTabsNotEmpty) return 0;
89
- return this.isStringTabs
90
- ? Number(this.selectedIndex) || 0
91
- : this.tabs.findIndex((item) => item.value === this.selectedIndex);
92
- },
93
- /** 指示器宽度 */
94
- indicatorWidth() {
95
- const rect = this.tabRects[this.tabIndex];
96
- if (!rect) return 0;
97
- return rect.width;
98
- },
99
- /** 指示器中心点 */
100
- indicatorCenter() {
101
- const rect = this.tabRects[this.tabIndex];
102
- if (!rect) return 0;
103
- return rect.left + rect.width / 2 - this.boundingLeft;
104
- },
105
- styleStr() {
106
- return [
107
- `--tab-count: ${this.tabs.length};`,
108
- `--tab-text-width: ${this.indicatorWidth}px;`,
109
- `--indicator-center: ${this.indicatorCenter}px;`,
110
- ].join(' ');
111
- },
112
- },
113
- watch: {
114
- tabs: {
115
- handler() {
116
- if (this.isStringTabs) {
117
- this.selectedIndex = Math.min(this.selectedIndex, this.tabs.length - 1);
118
- return;
119
- }
120
- if (this.isTabsNotEmpty && this.tabIndex < 0) {
121
- this.selectedIndex = this.tabs[0].value;
122
- }
123
- },
124
- immediate: true,
125
- },
32
+ });
33
+
34
+ const classNames = useClassNames('tab');
35
+ const $style = useCssModule();
36
+
37
+ type _TabItem = {
38
+ /** 标题 */
39
+ label: string;
40
+ /** 值 */
41
+ value: string | number;
42
+ };
43
+
44
+ type _Props = {
45
+ /** 绑定值,当前所选 tab 的索引 */
46
+ modelValue?: number;
47
+ /** 值,当前所选 tab 的值,当 tabs 为字符串列表时,不使用该值 */
48
+ value?: string | number;
49
+ /** 页签配置 */
50
+ tabs?: string[] | _TabItem[];
51
+ /** 是否均分大小 */
52
+ evenly?: boolean;
53
+ /** 是否显示指示器 */
54
+ indicator?: boolean;
55
+ };
56
+
57
+ const props = withDefaults(defineProps<_Props>(), {
58
+ modelValue: undefined,
59
+ value: undefined,
60
+ tabs: () => [],
61
+ evenly: true,
62
+ indicator: true,
63
+ });
64
+
65
+ const emit = defineEmits<{
66
+ (e: 'update:modelValue', payload: number): void;
67
+ (e: 'update:value', payload: string | number | undefined): void;
68
+ (e: 'change', payload: number): void;
69
+ }>();
70
+
71
+ /** 组件内部记录索引用的值 */
72
+ const innerIndex = ref<number>(0);
73
+ /** 组件左边距 */
74
+ const boundingLeft = ref(0);
75
+ /** 标签文本的尺寸信息 */
76
+ const tabTextRects = ref<UniApp.NodeInfo[]>([]);
77
+ /** 标签移动是否应用动画 */
78
+ const hasAnimation = ref(false);
79
+
80
+ /** 标签标题列表 */
81
+ const tabLabels = computed(() => props.tabs.map((item) => (typeof item === 'string' ? item : item.label)));
82
+ /** 当前选中的索引 */
83
+ const selectedIndex = computed<number>({
84
+ get: () => (props.modelValue === undefined ? innerIndex.value : props.modelValue),
85
+ set: (value) => {
86
+ emit('update:modelValue', value);
87
+ innerIndex.value = value;
126
88
  },
127
- mounted() {
128
- uni.createSelectorQuery()
129
- .in(this)
130
- .select('.' + this.$style['Tab--tab-wrapper'])
131
- .boundingClientRect((rect) => {
132
- this.boundingLeft = rect.left;
133
- })
134
- .exec();
135
- uni.createSelectorQuery()
136
- .in(this)
137
- .selectAll('.' + this.$style['Tab--tab-text'])
138
- .boundingClientRect((rects) => {
139
- if (!rects[this.tabIndex]) return;
140
- this.tabRects = rects;
141
- this.$nextTick(() => {
142
- this.initialized = true;
143
- });
144
- })
145
- .exec();
89
+ });
90
+
91
+ // 监听选项、索引、值的变化,以保证 tab 选择始终是对应的
92
+ watch(
93
+ [() => props.tabs, selectedIndex, () => props.value],
94
+ ([nTabs, nIdx, nVal], [_t, _i, oVal]) => {
95
+ // 如果值改变了,查找对应的索引
96
+ if (nVal !== oVal && nVal !== undefined) {
97
+ nIdx = nTabs.findIndex((i) => (typeof i === 'string' ? nVal === i : nVal === i.value));
98
+ }
99
+ if (nIdx < 0) nIdx = 0;
100
+ const tab = nTabs[nIdx];
101
+ // 再度获取对应索引位置的值,如果是字符串类型标签,则为 undefined
102
+ nVal = typeof tab === 'string' ? undefined : tab?.value;
103
+ const idx = nIdx < 0 || nIdx >= nTabs.length ? 0 : nIdx;
104
+ if (selectedIndex.value !== idx) {
105
+ // 更新索引和值
106
+ selectedIndex.value = nIdx < 0 || nIdx >= nTabs.length ? 0 : nIdx;
107
+ emit('change', idx);
108
+ }
109
+ emit('update:value', nVal);
146
110
  },
147
- methods: {
148
- /** 改变标签响应 */
149
- onChangeTab(index) {
150
- if (this.isStringTabs) {
151
- if (this.selectedIndex === index) return;
152
-
153
- this.selectedIndex = index;
154
- } else {
155
- if (this.selectedIndex === this.tabs[index].value) return;
156
-
157
- this.selectedIndex = this.tabs[index].value;
158
- }
159
- this.$emit('change', this.selectedIndex);
111
+ { immediate: true, deep: true }
112
+ );
113
+
114
+ /** 指示器宽度 */
115
+ const indicatorWidth = computed(() => {
116
+ const rect = tabTextRects.value[selectedIndex.value];
117
+ if (!rect) return 0;
118
+ return rect.width;
119
+ });
120
+
121
+ /** 指示器中心点 */
122
+ const indicatorCenter = computed(() => {
123
+ const rect = tabTextRects.value[selectedIndex.value];
124
+ if (!rect || !rect.width) return 0;
125
+ return (rect.left || 0) + rect.width / 2 - boundingLeft.value;
126
+ });
127
+
128
+ const inlineStyle = computed(() => {
129
+ return [
130
+ `--tab-count: ${props.tabs.length};`,
131
+ `--tab-text-width: ${indicatorWidth.value}px;`,
132
+ `--indicator-center: ${indicatorCenter.value}px;`,
133
+ ].join(' ');
134
+ });
135
+
136
+ const vm = getCurrentInstance();
137
+
138
+ onMounted(() => {
139
+ uni.createSelectorQuery()
140
+ .in(vm)
141
+ .select('.' + $style['Tab--tab-wrapper'])
142
+ .boundingClientRect((rect) => {
143
+ boundingLeft.value = (rect as UniApp.NodeInfo).left || 0;
144
+ })
145
+ .exec();
146
+
147
+ // 选项发生变化,重新计算各个选项的尺寸信息
148
+ watch(
149
+ () => props.tabs,
150
+ () => {
151
+ hasAnimation.value = false;
152
+ nextTick(() => {
153
+ uni.createSelectorQuery()
154
+ .in(vm)
155
+ .selectAll('.' + $style['Tab--tab-text'])
156
+ .boundingClientRect((rects) => {
157
+ tabTextRects.value = rects as UniApp.NodeInfo[];
158
+ nextTick(() => {
159
+ hasAnimation.value = true;
160
+ });
161
+ })
162
+ .exec();
163
+ });
160
164
  },
161
- },
165
+ { immediate: true, deep: true }
166
+ );
167
+ });
168
+
169
+ /** 改变标签响应 */
170
+ const onChangeTab = (index: number) => {
171
+ selectedIndex.value = index;
172
+ // emit('change', selectedIndex.value);
162
173
  };
163
174
  </script>
164
175
 
165
176
  <style lang="scss" module>
166
- @use '../../styles/index' as *;
177
+ @use '../../styles/utils' as *;
178
+ @use './tab' as *;
167
179
 
168
180
  // 定义外部变量
169
- $component-name: 'tab';
170
- $component-vars: $tab;
171
181
  %external-vars {
172
- @include UTILS__DEFINE_COMPONENT_VARS();
173
- }
174
-
175
- :host {
176
- display: block;
182
+ @include UTILS__DEFINE_COMPONENT_VARS($tab, 'tab');
177
183
  }
178
184
 
179
185
  .Tab {
@@ -226,9 +232,9 @@ $component-vars: $tab;
226
232
  border-radius: 10rpx;
227
233
  background: UTILS__LOCAL_VAR(indicator-color);
228
234
  transform: translateX(-50%);
229
- &:not(.Tab--initialing) {
230
- left: var(--indicator-center);
231
- width: UTILS__LOCAL_VAR(indicator-width, var(--tab-text-width));
235
+ left: var(--indicator-center, 0);
236
+ width: UTILS__LOCAL_VAR(indicator-width, var(--tab-text-width, 0));
237
+ &.Tab--indicator-animation {
232
238
  transition: left 0.3s, width 0.3s;
233
239
  }
234
240
  }
@@ -1,16 +1,16 @@
1
1
  <template>
2
- <view :class="componentClassNames">
2
+ <view :class="[...classNames, $style['Textarea']]">
3
3
  <template v-if="!isReadonly">
4
4
  <textarea
5
- :value="value"
5
+ :value="modelValue"
6
6
  :maxlength="maxlength"
7
7
  :auto-height="autoHeight"
8
8
  :class="$style['Textarea--input']"
9
- @input="$emit('input', $event.detail.value)"
10
- @blur="$_trim"
9
+ @input="onInput"
10
+ @blur="trimValue"
11
11
  >
12
12
  </textarea>
13
- <view v-if="!value" :class="$style['Textarea--placeholder']">
13
+ <view v-if="!modelValue" :class="$style['Textarea--placeholder']">
14
14
  <slot name="placeholder">
15
15
  <icon-cpn icon="textarea-edit" :class="$style['Textarea--icon']" />
16
16
  <text> {{ placeholder }} </text>
@@ -19,125 +19,123 @@
19
19
  <view :class="$style['Textarea--counter']"> 已输入 {{ count }}/{{ maxlength }} </view>
20
20
  </template>
21
21
  <!-- 只读文本展示 -->
22
- <view v-else-if="value" :class="$style['Textarea--readonly']">
23
- <view :class="{ [$style['Textarea--fold']]: fold && isFolded }"> {{ value }} </view>
22
+ <view v-else-if="modelValue" :class="$style['Textarea--readonly']">
23
+ <view :class="{ [$style['Textarea--fold']]: fold && isFolded }"> {{ modelValue }} </view>
24
24
  <view v-if="fold && hasFoldButton" :class="$style['Textarea--fold-button']" @click="isFolded = !isFolded">
25
25
  <text v-if="isFolded"> <icon-cpn icon="arrow-down" /> 展开 </text>
26
26
  <text v-else> <icon-cpn icon="arrow-up" /> 收起 </text>
27
27
  </view>
28
28
  <!-- 隐藏文字,用于计算是否显示收起展开按钮 -->
29
- <view v-if="fold" :class="[$style['Textarea--hidden-text'], $style['Textarea--fold']]"> {{ value }} </view>
30
- <view v-if="fold" :class="$style['Textarea--hidden-text']"> {{ value }} </view>
29
+ <view v-if="fold" :class="[$style['Textarea--hidden-text'], $style['Textarea--fold']]">
30
+ {{ modelValue }}
31
+ </view>
32
+ <view v-if="fold" :class="$style['Textarea--hidden-text']"> {{ modelValue }} </view>
31
33
  </view>
32
34
  <!-- 空文本展示 -->
33
35
  <view v-else :class="[$style['Textarea--readonly'], $style['Textarea--empty']]"> 未填写 </view>
34
36
  </view>
35
37
  </template>
36
38
 
37
- <script>
38
- import componentName from '../../mixins/componentName';
39
- import { isFormReadonly } from '../form/mixin';
39
+ <script lang="ts" setup>
40
+ import { computed, getCurrentInstance, inject, nextTick, ref, useCssModule, watch } from 'vue';
41
+ import useClassNames from '../../composable/component-name';
40
42
  import IconCpn from '../icon/icon.vue';
41
43
 
42
- export default {
43
- name: 'Textarea',
44
+ defineOptions({
44
45
  options: {
45
46
  virtualHost: true,
46
47
  styleIsolation: 'shared',
47
48
  },
48
- components: { IconCpn },
49
- mixins: [componentName, isFormReadonly],
50
- props: {
51
- /** 绑定值 */
52
- value: {
53
- type: String,
54
- default: '',
55
- },
56
- /** 最大输入文本长度 */
57
- maxlength: {
58
- type: Number,
59
- default: 300,
60
- },
61
- /** 占位文本 */
62
- placeholder: {
63
- type: String,
64
- default: '请输入',
65
- },
66
- /** 是否自动调整高度 */
67
- autoHeight: {
68
- type: Boolean,
69
- default: true,
70
- },
71
- /** 是否只读 */
72
- readonly: {
73
- type: Boolean,
74
- default: false,
75
- },
76
- /** 当只读时,如果文字过多,是否折叠 */
77
- fold: {
78
- type: Boolean,
79
- default: true,
80
- },
81
- },
82
- data() {
83
- return {
84
- /** 是否有收起展开按钮 */
85
- hasFoldButton: false,
86
- /** 当前是否处于收起状态 */
87
- isFolded: true,
88
- };
89
- },
90
- computed: {
91
- /** 是否只读 */
92
- isReadonly() {
93
- return this.readonly || this.isFormReadonly;
94
- },
95
- /** 已输入字符长度 */
96
- count() {
97
- return this.value.length;
98
- },
99
- },
100
- watch: {
101
- isReadonly: {
102
- handler(newVal) {
103
- if (newVal && this.fold) {
104
- this.$nextTick(() => {
105
- this.shouldShowFold();
106
- });
107
- }
108
- },
109
- immediate: true,
110
- },
111
- },
112
- methods: {
113
- /** 去除头尾空白字符 */
114
- $_trim(event) {
115
- this.$emit('input', event.detail.value.trim());
116
- },
117
- /** 计算是否需要显示收起展开按钮 */
118
- shouldShowFold() {
119
- uni.createSelectorQuery()
120
- .in(this)
121
- .selectAll('.' + this.$style['Textarea--hidden-text'])
122
- .boundingClientRect((rects) => {
123
- this.hasFoldButton = rects.length === 2 && rects[1].height > rects[0].height;
124
- })
125
- .exec();
126
- },
49
+ });
50
+
51
+ const classNames = useClassNames('textarea');
52
+ const $style = useCssModule();
53
+
54
+ type _Props = {
55
+ /** 绑定值 */
56
+ modelValue?: string;
57
+ /** 最大输入文本长度 */
58
+ maxlength?: number;
59
+ /** 占位文本 */
60
+ placeholder?: string;
61
+ /** 是否自动调整高度 */
62
+ autoHeight?: boolean;
63
+ /** 是否只读 */
64
+ readonly?: boolean;
65
+ /** 当只读时,如果文字过多,是否折叠 */
66
+ fold?: boolean;
67
+ };
68
+
69
+ const props = withDefaults(defineProps<_Props>(), {
70
+ modelValue: '',
71
+ maxlength: 300,
72
+ placeholder: '请输入',
73
+ autoHeight: true,
74
+ readonly: false,
75
+ fold: true,
76
+ });
77
+
78
+ const emit = defineEmits<{
79
+ (e: 'update:modelValue', val: string): void;
80
+ }>();
81
+
82
+ /** 表单是否只读 */
83
+ const isFormReadonly = inject(
84
+ 'ecolovo/form/readonly',
85
+ computed(() => false)
86
+ );
87
+
88
+ /** 是否有收起展开按钮 */
89
+ const hasFoldButton = ref(false);
90
+ /** 当前是否处于收起状态 */
91
+ const isFolded = ref(true);
92
+
93
+ /** 是否只读 */
94
+ const isReadonly = computed(() => props.readonly || isFormReadonly.value);
95
+
96
+ /** 已输入字符长度 */
97
+ const count = computed(() => props.modelValue.length);
98
+
99
+ const vm = getCurrentInstance();
100
+
101
+ /** 计算是否需要显示收起展开按钮 */
102
+ const shouldShowFold = () => {
103
+ uni.createSelectorQuery()
104
+ .in(vm)
105
+ .selectAll('.' + $style['Textarea--hidden-text'])
106
+ .boundingClientRect((rects) => {
107
+ if (!Array.isArray(rects)) return;
108
+ hasFoldButton.value = rects.length === 2 && (rects[1]?.height || 0) > (rects[0]?.height || 0);
109
+ })
110
+ .exec();
111
+ };
112
+
113
+ watch(
114
+ () => isReadonly.value,
115
+ (newVal) => {
116
+ if (newVal && props.fold) {
117
+ nextTick(() => {
118
+ shouldShowFold();
119
+ });
120
+ }
127
121
  },
122
+ { immediate: true }
123
+ );
124
+
125
+ /** 输入响应 */
126
+ const onInput = (e: Event) => {
127
+ emit('update:modelValue', (e as CustomEvent).detail.value);
128
+ };
129
+
130
+ /** 去除头尾空白字符 */
131
+ const trimValue = (e: Event) => {
132
+ emit('update:modelValue', (e as CustomEvent).detail.value.trim());
128
133
  };
129
134
  </script>
130
135
 
131
136
  <style lang="scss" module>
132
137
  @use '../../styles/index' as *;
133
138
 
134
- :host {
135
- display: block;
136
- }
137
- :host(.display-inline) {
138
- display: inline;
139
- }
140
-
141
139
  .Textarea {
142
140
  flex: 1;
143
141
  position: relative;
@@ -173,7 +171,12 @@ export default {
173
171
  overflow: hidden;
174
172
  }
175
173
  .Textarea--fold {
176
- @include UTILS__ELLIPSIS(5);
174
+ overflow: hidden;
175
+ text-overflow: ellipsis;
176
+ display: -webkit-box;
177
+ -webkit-line-clamp: 5;
178
+ line-clamp: 5;
179
+ -webkit-box-orient: vertical;
177
180
  }
178
181
  .Textarea--fold-button {
179
182
  text-align: right;
@@ -0,0 +1,2 @@
1
+ declare const useClassNames: (componentName: string) => [string, string];
2
+ export default useClassNames;
@@ -0,0 +1,8 @@
1
+ import { kebabCase, pascalCase } from 'case-anything';
2
+ import { componentPrefix } from '../configs';
3
+ const useClassNames = (componentName) => {
4
+ const pascalCaseClass = pascalCase(componentPrefix) + pascalCase(componentName);
5
+ const kebabCaseClass = kebabCase(pascalCaseClass);
6
+ return [pascalCaseClass, kebabCaseClass];
7
+ };
8
+ export default useClassNames;
@@ -0,0 +1,10 @@
1
+ import { kebabCase, pascalCase } from 'case-anything';
2
+ import { componentPrefix } from '../configs';
3
+
4
+ const useClassNames = (componentName: string): [string, string] => {
5
+ const pascalCaseClass = pascalCase(componentPrefix) + pascalCase(componentName);
6
+ const kebabCaseClass = kebabCase(pascalCaseClass);
7
+ return [pascalCaseClass, kebabCaseClass];
8
+ };
9
+
10
+ export default useClassNames;
@@ -2,21 +2,36 @@
2
2
  declare let componentPrefix: string;
3
3
  /** 首页路径 */
4
4
  declare let homePath: string;
5
- /** 设置组件前缀 */
6
- declare const setComponentPrefix: (prefix: string) => void;
7
- /** 设置首页路径 */
8
- declare const setHomePath: (path: string) => void;
5
+ /**
6
+ * 设置组件前缀
7
+ * @param prefix 组件前缀
8
+ */
9
+ declare function setComponentPrefix(prefix?: string): void;
10
+ /**
11
+ * 设置首页路径
12
+ * @param path 首页路径
13
+ */
14
+ declare function setHomePath(path?: string): void;
15
+ interface IconInfo {
16
+ /** 字体文件名 */
17
+ fontFamily: string;
18
+ /** 图标前缀 */
19
+ prefix: string;
20
+ /** 图标名称 */
21
+ name: string;
22
+ }
9
23
  /** iconfont 图标 map */
10
- declare let iconMap: Map<string, { fontFamily: string; prefix: string; name: string }>;
11
- interface IconFontJson {
24
+ declare const iconMap: Map<string, IconInfo>;
25
+ export interface IconFontJson {
12
26
  font_family: string;
13
27
  css_prefix_text: string;
14
- glyphs: { font_class: string }[];
28
+ glyphs: {
29
+ font_class: string;
30
+ }[];
15
31
  }
16
32
  /**
17
33
  * 添加一组图标,传入 iconfont.json 文件内容
18
34
  * @param iconJson iconfont.json 文件内容
19
35
  */
20
- declare const addIcons: (iconJson: IconFontJson) => void;
21
-
22
- export { componentPrefix, homePath, IconFontJson, setComponentPrefix, setHomePath, iconMap, addIcons };
36
+ declare function addIcons(iconJson: IconFontJson): void;
37
+ export { componentPrefix, homePath, setComponentPrefix, setHomePath, iconMap, addIcons };