uview-plus 3.4.26 → 3.4.28

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.
package/changelog.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 3.4.28(2025-05-12)
2
+ feat: 新增table表格组件
3
+
4
+ feat: 新增element-plus风格的table2组件
5
+
6
+ ## 3.4.27(2025-05-06)
7
+ fix: 修复card组件props
8
+
1
9
  ## 3.4.26(2025-05-06)
2
10
  fix: 修复test工具引入
3
11
 
@@ -8,8 +8,8 @@
8
8
  * @FilePath : /uview-plus/libs/config/props/card.js
9
9
  */
10
10
  export default {
11
- // cell组件的props
12
- cell: {
11
+ // card组件的props
12
+ card: {
13
13
  full: false,
14
14
  title: '',
15
15
  titleColor: '#303133',
@@ -1,6 +1,8 @@
1
1
  <template>
2
- <view class="u-table">
3
-
2
+ <view class="u-table" :style="[tableStyle]">
3
+ <template v-if="show">
4
+ <slot />
5
+ </template>
4
6
  </view>
5
7
  </template>
6
8
 
@@ -12,14 +14,86 @@
12
14
  * Table 表格
13
15
  * @description 表格组件一般用于展示大量结构化数据的场景 本组件标签类似HTML的table表格,由table、tr、th、td四个组件组成
14
16
  * @tutorial https://ijry.github.io/uview-plus/components/table.html
17
+ * @property {String} border-color 表格边框的颜色(默认#e4e7ed)
18
+ * @property {String} bg-color 表格的背景颜色(默认#ffffff)
19
+ * @property {String} align 单元格的内容对齐方式,作用类似css的text-align(默认center)
20
+ * @property {String} padding 单元格的内边距,同css的padding写法(默认10rpx 0)
21
+ * @property {String Number} font-size 单元格字体大小,单位rpx(默认28)
22
+ * @property {String} color 单元格字体颜色(默认#606266)
23
+ * @property {Object} th-style th单元格的样式,对象形式(将th所需参数放在table组件,是为了避免每一个th组件要写一遍)
24
+ * @event {Function} click 点击组件时触发
25
+ * @event {Function} close 点击关闭按钮时触发
15
26
  * @example <u-table><u-tr><u-th>学校</u-th </u-tr> <u-tr><u-td>浙江大学</u-td> </u-tr> <u-tr><u-td>清华大学</u-td> </u-tr></u-table>
16
27
  */
17
28
  export default {
18
29
  name: 'u-table',
19
30
  mixins: [mpMixin, mixin, props],
31
+ props: {
32
+ borderColor: {
33
+ type: String,
34
+ default: '#e4e7ed'
35
+ },
36
+ align: {
37
+ type: String,
38
+ default: 'center'
39
+ },
40
+ // td的内边距
41
+ padding: {
42
+ type: String,
43
+ default: '5px 3px'
44
+ },
45
+ // 字体大小
46
+ fontSize: {
47
+ type: [String],
48
+ default: '14px'
49
+ },
50
+ // 字体颜色
51
+ color: {
52
+ type: String,
53
+ default: '#606266'
54
+ },
55
+ // th的自定义样式
56
+ thStyle: {
57
+ type: Object,
58
+ default () {
59
+ return {}
60
+ }
61
+ },
62
+ // table的背景颜色
63
+ bgColor: {
64
+ type: String,
65
+ default: '#ffffff'
66
+ }
67
+ },
20
68
  data() {
21
69
  return {
22
-
70
+ show: true
71
+ }
72
+ },
73
+ watch: {
74
+ align() {
75
+ this.change();
76
+ },
77
+ borderColor() {
78
+ this.change();
79
+ }
80
+ },
81
+ computed: {
82
+ tableStyle() {
83
+ let style = {};
84
+ style.borderLeft = `solid 1px ${this.borderColor}`;
85
+ style.borderTop = `solid 1px ${this.borderColor}`;
86
+ style.backgroundColor = this.bgColor;;
87
+ return style;
88
+ }
89
+ },
90
+ methods: {
91
+ change() {
92
+ this.show = false;
93
+ this.$nextTick(() => {
94
+ this.show = true;
95
+ });
96
+ // this.$forceUpdate();
23
97
  }
24
98
  }
25
99
  }
@@ -0,0 +1,451 @@
1
+ <template>
2
+ <view class="u-table" :style="{ height: height ? height + 'px' : 'auto' }">
3
+ <!-- 表头 -->
4
+ <view v-if="showHeader" class="u-table-header" :class="{ 'u-table-sticky': fixedHeader }">
5
+ <view class="u-table-row">
6
+ <view
7
+ v-for="(col, colIndex) in columns"
8
+ :key="col.key"
9
+ class="u-table-cell"
10
+ :style="{ width: col.width ? addUnit(col.width) : 'auto', flex: col.width ? 'none' : 1 }"
11
+ :class="[
12
+ col.align ? 'u-text-' + col.align : '',
13
+ headerCellClassName ? headerCellClassName(col) : '',
14
+ col.fixed === 'left' ? 'u-table-fixed-left' : '',
15
+ col.fixed === 'right' ? 'u-table-fixed-right' : ''
16
+ ]"
17
+ @click="handleHeaderClick(col)"
18
+ >
19
+ {{ col.title }}
20
+ <view v-if="col.sortable">
21
+ {{ getSortIcon(col.key) }}
22
+ </view>
23
+ </view>
24
+ </view>
25
+ </view>
26
+
27
+ <!-- 表体 -->
28
+ <view class="u-table-body" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : 'none' }">
29
+ <block v-if="data.length > 0">
30
+ <template v-for="(row, index) in sortedData" :key="row[rowKey] || index">
31
+ <view
32
+ class="u-table-row"
33
+ :class="[
34
+ highlightCurrentRow && currentRow === row ? 'u-table-row-highlight' : '',
35
+ rowClassName ? rowClassName(row, index) : '',
36
+ stripe && index % 2 === 1 ? 'u-table-row-zebra' : ''
37
+ ]"
38
+ @click="handleRowClick(row)"
39
+ >
40
+ <view
41
+ v-for="(col, colIndex) in columns"
42
+ :key="col.key"
43
+ class="u-table-cell"
44
+ :class="[
45
+ col.align ? 'u-text-' + col.align : '',
46
+ cellClassName ? cellClassName(row, col) : '',
47
+ col.fixed === 'left' ? 'u-table-fixed-left' : '',
48
+ col.fixed === 'right' ? 'u-table-fixed-right' : ''
49
+ ]"
50
+ :style="{ width: col.width ? addUnit(col.width) : 'auto', flex: col.width ? 'none' : 1 }"
51
+ >
52
+ <!-- 复选框列 -->
53
+ <view v-if="col.type === 'selection'">
54
+ <checkbox
55
+ :checked="isSelected(row)"
56
+ @click.stop="toggleSelect(row)"
57
+ />
58
+ </view>
59
+
60
+ <!-- 树形结构展开图标 -->
61
+ <view v-else-if="col.type === 'expand'" @click.stop="toggleExpand(row)">
62
+ {{ isExpanded(row) ? '▼' : '▶' }}
63
+ </view>
64
+
65
+ <!-- 默认插槽或文本 -->
66
+ <slot :name="col.key" :row="row">
67
+ {{ row[col.key] }}
68
+ </slot>
69
+ </view>
70
+ </view>
71
+
72
+ <!-- 子级渲染 -->
73
+ <template v-if="isExpanded(row) && row[treeProps.children] && row[treeProps.children].length">
74
+ <view
75
+ v-for="child in row[treeProps.children]"
76
+ :key="child[rowKey]"
77
+ class="u-table-row u-table-row-child"
78
+ >
79
+ <view
80
+ v-for="col in columns"
81
+ :key="col.key"
82
+ class="u-table-cell"
83
+ :style="{ width: col.width ? addUnit(col.width) : 'auto', paddingLeft: '24px', flex: col.width ? 'none' : 1 }"
84
+ >
85
+ <slot :name="col.key" :row="child">
86
+ {{ child[col.key] }}
87
+ </slot>
88
+ </view>
89
+ </view>
90
+ </template>
91
+ </template>
92
+ </block>
93
+ <block v-else>
94
+ <slot name="empty">
95
+ <view class="u-table-empty">暂无数据</view>
96
+ </slot>
97
+ </block>
98
+ </view>
99
+ </view>
100
+ </template>
101
+
102
+ <script>
103
+ import { ref, watch, computed } from 'vue'
104
+ import { addUnit } from '../../libs/function/index';
105
+
106
+ export default {
107
+ name: 'u-table2',
108
+ props: {
109
+ data: {
110
+ type: Array,
111
+ required: true,
112
+ default: () => []
113
+ },
114
+ columns: {
115
+ type: Array,
116
+ required: true,
117
+ default: () => [],
118
+ validator: cols =>
119
+ cols.every(col =>
120
+ ['default', 'selection', 'expand'].includes(col.type || 'default')
121
+ )
122
+ },
123
+ stripe: {
124
+ type: Boolean,
125
+ default: false
126
+ },
127
+ border: {
128
+ type: Boolean,
129
+ default: false
130
+ },
131
+ height: {
132
+ type: [String, Number],
133
+ default: null
134
+ },
135
+ maxHeight: {
136
+ type: [String, Number],
137
+ default: null
138
+ },
139
+ showHeader: {
140
+ type: Boolean,
141
+ default: true
142
+ },
143
+ highlightCurrentRow: {
144
+ type: Boolean,
145
+ default: false
146
+ },
147
+ rowKey: {
148
+ type: String,
149
+ default: 'id'
150
+ },
151
+ currentRowKey: {
152
+ type: [String, Number],
153
+ default: null
154
+ },
155
+ rowStyle: {
156
+ type: Object,
157
+ default: () => ({})
158
+ },
159
+ cellClassName: {
160
+ type: Function,
161
+ default: null
162
+ },
163
+ headerCellClassName: {
164
+ type: Function,
165
+ default: null
166
+ },
167
+ rowClassName: {
168
+ type: Function,
169
+ default: null
170
+ },
171
+ context: {
172
+ type: Object,
173
+ default: null
174
+ },
175
+ showOverflowTooltip: {
176
+ type: Boolean,
177
+ default: false
178
+ },
179
+ lazy: {
180
+ type: Boolean,
181
+ default: false
182
+ },
183
+ load: {
184
+ type: Function,
185
+ default: null
186
+ },
187
+ treeProps: {
188
+ type: Object,
189
+ default: () => ({
190
+ children: 'children',
191
+ hasChildren: 'hasChildren'
192
+ })
193
+ },
194
+ defaultExpandAll: {
195
+ type: Boolean,
196
+ default: false
197
+ },
198
+ expandRowKeys: {
199
+ type: Array,
200
+ default: () => []
201
+ },
202
+ sortOrders: {
203
+ type: Array,
204
+ default: () => ['ascending', 'descending']
205
+ },
206
+ sortable: {
207
+ type: [Boolean, String],
208
+ default: false
209
+ },
210
+ multiSort: {
211
+ type: Boolean,
212
+ default: false
213
+ },
214
+ sortBy: {
215
+ type: String,
216
+ default: null
217
+ },
218
+ sortMethod: {
219
+ type: Function,
220
+ default: null
221
+ },
222
+ filters: {
223
+ type: Object,
224
+ default: () => ({})
225
+ },
226
+ fixedHeader: {
227
+ type: Boolean,
228
+ default: true
229
+ }
230
+ },
231
+ emits: [
232
+ 'select', 'select-all', 'selection-change',
233
+ 'cell-click', 'row-click', 'row-dblclick',
234
+ 'header-click', 'sort-change', 'filter-change',
235
+ 'current-change', 'expand-change'
236
+ ],
237
+ methods: {
238
+ addUnit
239
+ },
240
+ setup(props, { emit }) {
241
+ const expandedKeys = ref([...props.expandRowKeys]);
242
+ const selectedRows = ref([]);
243
+ const sortConditions = ref([]);
244
+
245
+ // 当前高亮行
246
+ const currentRow = ref(null);
247
+
248
+ watch(
249
+ () => props.expandRowKeys,
250
+ newVal => {
251
+ expandedKeys.value = [...newVal];
252
+ }
253
+ );
254
+
255
+ watch(
256
+ () => props.currentRowKey,
257
+ newVal => {
258
+ const found = props.data.find(item => item[props.rowKey] === newVal);
259
+ if (found) {
260
+ currentRow.value = found;
261
+ }
262
+ }
263
+ );
264
+
265
+ // 过滤后的数据
266
+ const filteredData = computed(() => {
267
+ return props.data.filter(row => {
268
+ return Object.keys(props.filters).every(key => {
269
+ const filter = props.filters[key];
270
+ if (!filter) return true;
271
+ return row[key]?.toString().includes(filter.toString());
272
+ });
273
+ });
274
+ });
275
+
276
+ // 排序后的数据
277
+ const sortedData = computed(() => {
278
+ if (!sortConditions.value.length) return filteredData.value;
279
+
280
+ const data = [...filteredData.value];
281
+
282
+ return data.sort((a, b) => {
283
+ for (const condition of sortConditions.value) {
284
+ const { field, order } = condition;
285
+ let valA = a[field];
286
+ let valB = b[field];
287
+
288
+ if (props.sortMethod) {
289
+ const result = props.sortMethod(a, b, field);
290
+ if (result !== 0) return result * (order === 'ascending' ? 1 : -1);
291
+ }
292
+
293
+ if (valA < valB) return order === 'ascending' ? -1 : 1;
294
+ if (valA > valB) return order === 'ascending' ? 1 : -1;
295
+ }
296
+ return 0;
297
+ });
298
+ });
299
+
300
+ function handleRowClick(row) {
301
+ if (props.highlightCurrentRow) {
302
+ const oldRow = currentRow.value;
303
+ currentRow.value = row;
304
+ emit('current-change', row, oldRow);
305
+ }
306
+ emit('row-click', row);
307
+ }
308
+
309
+ function handleHeaderClick(column) {
310
+ if (!column.sortable) return;
311
+
312
+ const index = sortConditions.value.findIndex(c => c.field === column.key);
313
+ let newOrder = 'ascending';
314
+
315
+ if (index >= 0) {
316
+ if (sortConditions.value[index].order === 'ascending') {
317
+ newOrder = 'descending';
318
+ } else {
319
+ sortConditions.value.splice(index, 1);
320
+ emit('sort-change', sortConditions.value);
321
+ return;
322
+ }
323
+ }
324
+
325
+ if (!props.multiSort) {
326
+ sortConditions.value = [{ field: column.key, order: newOrder }];
327
+ } else {
328
+ if (index >= 0) {
329
+ sortConditions.value[index].order = newOrder;
330
+ } else {
331
+ sortConditions.value.push({ field: column.key, order: newOrder });
332
+ }
333
+ }
334
+
335
+ emit('sort-change', sortConditions.value);
336
+ }
337
+
338
+ function getSortIcon(field) {
339
+ const cond = sortConditions.value.find(c => c.field === field);
340
+ if (!cond) return '';
341
+ return cond.order === 'ascending' ? '↑' : '↓';
342
+ }
343
+
344
+ function toggleSelect(row) {
345
+ const index = selectedRows.value.findIndex(r => r[props.rowKey] === row[props.rowKey]);
346
+ if (index >= 0) {
347
+ selectedRows.value.splice(index, 1);
348
+ } else {
349
+ selectedRows.value.push(row);
350
+ }
351
+ emit('selection-change', selectedRows.value);
352
+ emit('select', row);
353
+ }
354
+
355
+ function isSelected(row) {
356
+ return selectedRows.value.some(r => r[props.rowKey] === row[props.rowKey]);
357
+ }
358
+
359
+ function toggleExpand(row) {
360
+ const key = row[props.rowKey];
361
+ const index = expandedKeys.value.indexOf(key);
362
+ if (index === -1) {
363
+ expandedKeys.value.push(key);
364
+ } else {
365
+ expandedKeys.value.splice(index, 1);
366
+ }
367
+ emit('expand-change', expandedKeys.value);
368
+ }
369
+
370
+ function isExpanded(row) {
371
+ return expandedKeys.value.includes(row[props.rowKey]);
372
+ }
373
+
374
+ return {
375
+ currentRow,
376
+ sortedData,
377
+ expandedKeys,
378
+ selectedRows,
379
+ sortConditions,
380
+ handleRowClick,
381
+ handleHeaderClick,
382
+ getSortIcon,
383
+ toggleSelect,
384
+ isSelected,
385
+ toggleExpand,
386
+ isExpanded
387
+ };
388
+ }
389
+ };
390
+ </script>
391
+
392
+ <style lang="scss" scoped>
393
+ .u-table {
394
+ width: 100%;
395
+ overflow: auto;
396
+ }
397
+ .u-table-header {
398
+ background-color: #f5f7fa;
399
+ }
400
+ .u-table-sticky {
401
+ position: sticky;
402
+ top: 0;
403
+ z-index: 10;
404
+ }
405
+ .u-table-row {
406
+ display: flex;
407
+ flex-direction: row;
408
+ align-items: center;
409
+ border-bottom: 1rpx solid #ebeef5;
410
+ }
411
+ .u-table-cell {
412
+ flex: 1;
413
+ display: flex;
414
+ flex-direction: row;
415
+ padding: 5px 4px;
416
+ font-size: 14px;
417
+ white-space: nowrap;
418
+ overflow: hidden;
419
+ text-overflow: ellipsis;
420
+ }
421
+ .u-table-fixed-left {
422
+ position: sticky;
423
+ left: 0;
424
+ z-index: 9;
425
+ }
426
+ .u-table-fixed-right {
427
+ position: sticky;
428
+ right: 0;
429
+ z-index: 9;
430
+ }
431
+ .u-table-row-zebra {
432
+ background-color: #fafafa;
433
+ }
434
+ .u-table-row-highlight {
435
+ background-color: #f5f7fa;
436
+ }
437
+ .u-table-empty {
438
+ text-align: center;
439
+ padding: 20px;
440
+ color: #999;
441
+ }
442
+ .u-text-left {
443
+ text-align: left;
444
+ }
445
+ .u-text-center {
446
+ text-align: center;
447
+ }
448
+ .u-text-right {
449
+ text-align: right;
450
+ }
451
+ </style>
@@ -1,6 +1,6 @@
1
1
  <template>
2
- <view class="u-td">
3
-
2
+ <view class="u-td" :style="[tdStyle]">
3
+ <slot></slot>
4
4
  </view>
5
5
  </template>
6
6
 
@@ -8,6 +8,7 @@
8
8
  import { props } from './props';
9
9
  import { mpMixin } from '../../libs/mixin/mpMixin';
10
10
  import { mixin } from '../../libs/mixin/mixin';
11
+ import { addUnit, $parent } from '../../libs/function/index';
11
12
  /**
12
13
  * Td 表格中的单元格
13
14
  * @description
@@ -19,9 +20,36 @@
19
20
  export default {
20
21
  name: 'u-td',
21
22
  mixins: [mpMixin, mixin, props],
23
+ props: {
24
+ // 宽度,百分比或者具体带单位的值,如30%, 200rpx等,一般使用百分比
25
+ width: {
26
+ type: [String],
27
+ default: 'auto'
28
+ }
29
+ },
22
30
  data() {
23
31
  return {
24
-
32
+ tdStyle: {
33
+
34
+ }
35
+ }
36
+ },
37
+ created() {
38
+ this.parent = false;
39
+ },
40
+ mounted() {
41
+ this.parent = $parent.call(this, 'u-table');
42
+ if (this.parent) {
43
+ // 将父组件的相关参数,合并到本组件
44
+ let style = {};
45
+ if (this.width != "auto") style.flex = `0 0 ${this.width}`;
46
+ style.textAlign = this.parent.align;
47
+ style.fontSize = addUnit(this.parent.fontSize);
48
+ style.padding = this.parent.padding;
49
+ style.borderBottom = `solid 1px ${this.parent.borderColor}`;
50
+ style.borderRight = `solid 1px ${this.parent.borderColor}`;
51
+ style.color = this.parent.color;
52
+ this.tdStyle = style;
25
53
  }
26
54
  }
27
55
  }
@@ -29,5 +57,15 @@
29
57
 
30
58
  <style lang="scss" scoped>
31
59
  @import "../../libs/css/components.scss";
32
-
60
+ .u-td {
61
+ @include flex;
62
+ flex-direction: column;
63
+ flex: 1;
64
+ justify-content: center;
65
+ font-size: 14px;
66
+ color: $u-content-color;
67
+ align-self: stretch;
68
+ box-sizing: border-box;
69
+ height: 100%;
70
+ }
33
71
  </style>
@@ -0,0 +1,7 @@
1
+ import { defineMixin } from '../../libs/vue'
2
+ import defProps from '../../libs/config/props.js'
3
+ export const props = defineMixin({
4
+ props: {
5
+
6
+ }
7
+ })
@@ -0,0 +1,67 @@
1
+ <template>
2
+ <view class="u-th" :style="[thStyle]">
3
+ <slot></slot>
4
+ </view>
5
+ </template>
6
+
7
+ <script>
8
+ import { props } from './props';
9
+ import { mpMixin } from '../../libs/mixin/mpMixin';
10
+ import { mixin } from '../../libs/mixin/mixin';
11
+ import { addUnit, $parent } from '../../libs/function/index';
12
+ /**
13
+ * Td 表格中的单元格
14
+ * @description
15
+ * @tutorial url
16
+ * @property {String | Number}
17
+ * @event {Function}
18
+ * @example
19
+ */
20
+ export default {
21
+ name: 'u-th',
22
+ mixins: [mpMixin, mixin, props],
23
+ props: {
24
+ // 宽度,百分比或者具体带单位的值,如30%, 200rpx等,一般使用百分比
25
+ width: {
26
+ type: [String],
27
+ default: ''
28
+ }
29
+ },
30
+ data() {
31
+ return {
32
+ thStyle: {}
33
+ }
34
+ },
35
+ created() {
36
+ this.parent = false;
37
+ },
38
+ mounted() {
39
+ this.parent = $parent.call(this, 'u-table');
40
+ if (this.parent) {
41
+ // 将父组件的相关参数,合并到本组件
42
+ let style = {};
43
+ if (this.width) style.flex = `0 0 ${this.width}`;
44
+ style.textAlign = this.parent.align;
45
+ style.padding = this.parent.padding;
46
+ style.borderBottom = `solid 1px ${this.parent.borderColor}`;
47
+ style.borderRight = `solid 1px ${this.parent.borderColor}`;
48
+ Object.assign(style, this.parent.thStyle);
49
+ this.thStyle = style;
50
+ }
51
+ }
52
+ }
53
+ </script>
54
+
55
+ <style lang="scss" scoped>
56
+ @import "../../libs/css/components.scss";
57
+ .u-th {
58
+ @include flex;
59
+ flex-direction: column;
60
+ flex: 1;
61
+ justify-content: center;
62
+ font-size: 28rpx;
63
+ color: $u-main-color;
64
+ font-weight: bold;
65
+ background-color: rgb(245, 246, 248);
66
+ }
67
+ </style>
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <view class="u-tr">
3
-
3
+ <slot></slot>
4
4
  </view>
5
5
  </template>
6
6
 
@@ -29,5 +29,7 @@
29
29
 
30
30
  <style lang="scss" scoped>
31
31
  @import "../../libs/css/components.scss";
32
-
32
+ .u-tr {
33
+ @include flex;
34
+ }
33
35
  </style>
@@ -22,6 +22,7 @@ import Badge from '../../components/u-badge/badge'
22
22
  import Button from '../../components/u-button/button'
23
23
  import Calendar from '../../components/u-calendar/calendar'
24
24
  import CarKeyboard from '../../components/u-car-keyboard/carKeyboard'
25
+ import Card from '../../components/u-card/card'
25
26
  import Cell from '../../components/u-cell/cell'
26
27
  import CellGroup from '../../components/u-cell-group/cellGroup'
27
28
  import Checkbox from '../../components/u-checkbox/checkbox'
@@ -112,6 +113,7 @@ const props = {
112
113
  ...Button,
113
114
  ...Calendar,
114
115
  ...CarKeyboard,
116
+ ...Card,
115
117
  ...Cell,
116
118
  ...CellGroup,
117
119
  ...Checkbox,
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "id": "uview-plus",
3
3
  "name": "uview-plus",
4
4
  "displayName": "零云®uview-plus3.0重磅发布,全面的Vue3鸿蒙移动组件库。",
5
- "version": "3.4.26",
5
+ "version": "3.4.28",
6
6
  "description": "零云®uview-plus已兼容vue3,全面的组件和便捷的工具会让您信手拈来,如鱼得水。",
7
7
  "keywords": [
8
8
  "uview",