hy-app 0.4.10 → 0.4.12

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.
@@ -12,37 +12,47 @@
12
12
  <!-- @slot 头部插槽 -->
13
13
  <slot v-if="$slots.header" name="header" />
14
14
  <view v-else class="hy-card--head__flex">
15
- <view class="hy-card--head__left" v-if="title">
16
- <image
17
- :src="thumb"
18
- class="hy-card--head__left__thumb"
19
- mode="aspectFill"
15
+ <view class="hy-card--head__left">
16
+ <hy-icon
20
17
  v-if="thumb"
21
- :style="{
22
- height: addUnit(thumbWidth),
23
- width: addUnit(thumbWidth),
24
- borderRadius: thumbCircle ? '50px' : '4px',
25
- }"
26
- ></image>
27
- <text
28
- class="hy-card--head__left__title"
29
- :style="{
30
- fontSize: addUnit(titleSize),
31
- color: titleColor,
32
- }"
33
- >
34
- {{ title }}
35
- </text>
18
+ :name="thumb"
19
+ custom-class="hy-card--head__left__thumb"
20
+ :height="thumbWidth"
21
+ :width="thumbWidth"
22
+ :round="thumbCircle ? '50%' : '4px'"
23
+ ></hy-icon>
24
+ <view>
25
+ <view
26
+ v-if="title"
27
+ class="hy-card--head__left__title"
28
+ :style="{
29
+ fontSize: addUnit(titleSize),
30
+ color: titleColor,
31
+ }"
32
+ >
33
+ {{ title }}
34
+ </view>
35
+ <text
36
+ v-if="subTitle"
37
+ class="hy-card--head__left__sub"
38
+ :style="{
39
+ fontSize: addUnit(subTitleSize),
40
+ color: subTitleColor,
41
+ }"
42
+ >
43
+ {{ subTitle }}
44
+ </text>
45
+ </view>
36
46
  </view>
37
- <view class="hy-card--head__right hy-line-1" v-if="subTitle">
47
+ <view class="hy-card--head__right" v-if="subTitle">
38
48
  <text
39
- class="hy-card--head__title__text"
49
+ class="hy-card--head__right__text"
40
50
  :style="{
41
- fontSize: addUnit(subTitleSize),
42
- color: subTitleColor,
51
+ fontSize: addUnit(rightTextSize),
52
+ color: rightTextColor,
43
53
  }"
44
54
  >
45
- {{ subTitle }}
55
+ {{ rightText }}
46
56
  </text>
47
57
  </view>
48
58
  </view>
@@ -90,6 +100,9 @@ import type { CSSProperties, PropType } from "vue";
90
100
  import type { ICardEmits } from "./typing";
91
101
  import { addUnit, getPx } from "../../libs";
92
102
 
103
+ // 组件
104
+ import HyIcon from "../hy-icon/hy-icon.vue";
105
+
93
106
  /**
94
107
  * 卡片组件一般用于多个列表条目,且风格统一的场景。
95
108
  * @displayName hy-card
@@ -98,7 +111,7 @@ defineOptions({});
98
111
 
99
112
  // const props = withDefaults(defineProps<IProps>(), defaultProps);
100
113
  const props = defineProps({
101
- /** 卡片与屏幕两侧是否留空隙 */
114
+ /** 卡片与屏幕两侧是否留空隙,false-不留缝隙,true-有margin */
102
115
  full: {
103
116
  type: Boolean,
104
117
  default: false,
@@ -117,13 +130,28 @@ const props = defineProps({
117
130
  /** 副标题颜色 */
118
131
  subTitleColor: {
119
132
  type: String,
120
- default: "#909399",
133
+ default: "",
121
134
  },
122
135
  /** 副标题字体大小 */
123
136
  subTitleSize: {
124
137
  type: [String, Number],
125
138
  default: 13,
126
139
  },
140
+ /** 右边内容 */
141
+ rightText: {
142
+ type: String,
143
+ default: "",
144
+ },
145
+ /** 右边内容颜色 */
146
+ rightTextColor: {
147
+ type: String,
148
+ default: "",
149
+ },
150
+ /** 右边内容字体大小 */
151
+ rightTextSize: {
152
+ type: [String, Number],
153
+ default: "",
154
+ },
127
155
  /** 是否显示边框 */
128
156
  border: {
129
157
  type: Boolean,
@@ -11,8 +11,8 @@
11
11
 
12
12
  @include m(full) {
13
13
  // 如果是与屏幕之间不留空隙,应该设置左右边距为0
14
- margin-left: 0 !important;
15
- margin-right: 0 !important;
14
+ margin-left: $hy-border-margin-padding-base !important;
15
+ margin-right: $hy-border-margin-padding-base !important;
16
16
  width: 100%;
17
17
  }
18
18
 
@@ -31,20 +31,28 @@
31
31
 
32
32
  @include e(left) {
33
33
  display: flex;
34
+ align-items: center;
35
+ flex: 1;
34
36
 
35
37
  @include e(thumb) {
36
- margin-right: 16rpx;
38
+ margin-right: $hy-border-margin-padding-base;
39
+ flex-shrink: 0
37
40
  }
38
41
 
39
42
  @include e(title) {
40
- max-width: 400rpx;
43
+ max-width: 450rpx;
41
44
  @include lineEllipsis;
42
45
  }
46
+
47
+ @include e(sub) {
48
+ color: $hy-text-color--grey;
49
+ font-size: $hy-font-size-sm;
50
+ }
43
51
  }
44
52
 
45
53
  @include e(right) {
46
54
  color: $hy-text-color--grey;
47
- margin-left: 6rpx;
55
+ margin-left: $hy-border-margin-padding-sm;
48
56
  }
49
57
  }
50
58
  }
@@ -27,11 +27,6 @@ const props = defineProps({
27
27
  type: Boolean,
28
28
  default: false,
29
29
  },
30
- /**
31
- * 快捷设置 flex-direction
32
- * @values row,row-reverse,column,column-reverse
33
- * */
34
- direction: String as PropType<FlexDirection>,
35
30
  /**
36
31
  * 设置元素在主轴方向上的对齐方式
37
32
  * @values flex-start,flex-end,space-between,space-around,space-evenly,center
@@ -108,4 +103,6 @@ const flexStyle = computed(() => {
108
103
  });
109
104
  </script>
110
105
 
111
- <style scoped></style>
106
+ <style lang="scss" scoped>
107
+ @import "./index.scss";
108
+ </style>
@@ -1,163 +1,397 @@
1
1
  <template>
2
2
  <view class="hy-folding-panel">
3
- <HyCell
4
- :title="title"
5
- :titleBorder="titleBorder"
6
- :border="border"
7
- :showVertical="showVertical"
8
- :verticalColor="verticalColor"
9
- :size="size"
10
- :disabled="disabled"
11
- :list="lists"
12
- @click="clickHandler"
13
- >
14
- <template #icon="{ icon }">
15
- <slot name="icon" :icon="icon"></slot>
16
- </template>
17
- <template #title="{ title }">
18
- <slot name="title" :title="title"></slot>
19
- </template>
20
- <template #value="{ record }">
21
- <slot name="value" :record="record"></slot>
22
- </template>
23
- <template #bottom="{ record }">
24
- <view
25
- class="hy-folding-panel__main"
26
- :style="[
27
- customStyle,
28
- {
29
- height: record?.spread ? addUnit(contentHeight) : '0px',
30
- },
31
- ]"
32
- >
33
- <slot :record="record?.content" />
3
+ <!-- 容器模式 - 通过 slot 支持自定义内容 -->
4
+ <view class="hy-folding-panel__wrapper">
5
+ <!-- 头部标题 -->
6
+ <view v-if="title" class="hy-folding-panel__title">
7
+ <text class="hy-folding-panel__title-text">{{ title }}</text>
8
+ </view>
9
+
10
+ <!-- 面板内容区域 -->
11
+ <view class="hy-folding-panel__body">
12
+ <!-- 列表模式 -->
13
+ <view v-if="list && list.length" class="hy-folding-panel__list">
14
+ <view
15
+ v-for="(item, index) in panelItems"
16
+ :key="index"
17
+ class="hy-folding-panel__item"
18
+ :class="{
19
+ 'hy-folding-panel__item--disabled': disabled || item.disabled,
20
+ 'hy-folding-panel__item--active': item.expanded,
21
+ 'hy-folding-panel__item--border': border,
22
+ [`hy-folding-panel__item--${size}`]: true
23
+ }"
24
+ >
25
+ <!-- 面板头部 -->
26
+ <view class="hy-folding-panel__item-header" @click="toggleItem(index)">
27
+ <view class="hy-folding-panel__item-left">
28
+ <!-- 图标 -->
29
+ <view v-if="item.icon" class="hy-folding-panel__item-icon">
30
+ <image :src="item.icon" mode="aspectFit" />
31
+ </view>
32
+ <!-- 标题 -->
33
+ <text class="hy-folding-panel__item-title">{{ item.title }}</text>
34
+ </view>
35
+ <view class="hy-folding-panel__item-right">
36
+ <!-- 右侧值 -->
37
+ <text v-if="item.value" class="hy-folding-panel__item-value">{{ item.value }}</text>
38
+ <!-- 箭头 -->
39
+ <view class="hy-folding-panel__item-arrow" :class="{ 'hy-folding-panel__item-arrow--up': item.expanded }">
40
+ <text>{{ item.expanded ? '↑' : '↓' }}</text>
41
+ </view>
42
+ </view>
43
+ </view>
44
+
45
+ <!-- 面板内容 -->
46
+ <view
47
+ class="hy-folding-panel__item-content"
48
+ :style="[
49
+ customStyle,
50
+ {
51
+ height: item.expanded ? (contentHeight ? addUnit(contentHeight) : 'auto') : '0px',
52
+ overflow: 'hidden'
53
+ }
54
+ ]"
55
+ >
56
+ <slot name="item-content" :item="item" :index="index">
57
+ <text>{{ item.content || '' }}</text>
58
+ </slot>
59
+ </view>
60
+ </view>
34
61
  </view>
35
- <HyLine v-if="record?.spread"></HyLine>
36
- </template>
37
- </HyCell>
62
+
63
+ <!-- 自定义内容 -->
64
+ <slot v-else></slot>
65
+ </view>
66
+ </view>
38
67
  </view>
39
68
  </template>
40
69
 
41
70
  <script lang="ts">
42
71
  export default {
43
- name: "hy-folding-panel",
44
- options: {
45
- addGlobalClass: true,
46
- virtualHost: true,
47
- styleIsolation: "shared",
48
- },
72
+ name: "hy-folding-panel"
49
73
  };
50
74
  </script>
51
75
 
52
76
  <script setup lang="ts">
53
- import { ref, watch } from "vue";
77
+ import { ref, computed, watch } from "vue";
54
78
  import type { CSSProperties, PropType } from "vue";
55
79
  import type { IFoldingPanel, PanelVo } from "./typing";
56
- import { ColorConfig, addUnit } from "../../libs";
57
- // 组件
58
- import HyCell from "../hy-cell/hy-cell.vue";
59
- import HyLine from "../hy-line/hy-line.vue";
80
+ import { addUnit } from "../../libs";
60
81
 
61
82
  /**
62
- * 通过折叠面板收纳内容区域。
83
+ * 折叠面板组件
84
+ * 用于展示可展开/折叠的内容区域
63
85
  * @displayName hy-folding-panel
64
86
  */
65
- defineOptions({});
66
87
 
67
- // const props = withDefaults(defineProps<IProps>(), defaultProps);
68
88
  const props = defineProps({
69
- /** 数据集 */
89
+ /**
90
+ * 数据集
91
+ */
70
92
  list: {
71
93
  type: Array as PropType<PanelVo[]>,
72
- default: [],
94
+ default: () => []
73
95
  },
74
- /** 是否手风琴模式 */
96
+ /**
97
+ * 是否手风琴模式
98
+ */
75
99
  accordion: {
76
100
  type: Boolean,
77
- default: false,
78
- },
79
- /** 头部标题 */
80
- title: String,
81
- /** 是否显示头部底部边框 */
82
- titleBorder: {
83
- type: Boolean,
84
- default: false,
85
- },
86
- /** 是否显示cell下边框 */
87
- border: {
88
- type: Boolean,
89
- default: true,
101
+ default: false
90
102
  },
91
- /** 标题前缀竖线颜色 */
92
- verticalColor: {
103
+ /**
104
+ * 面板标题
105
+ */
106
+ title: {
93
107
  type: String,
94
- default: ColorConfig.primary,
108
+ default: ''
95
109
  },
96
- /** 是否显示标题前缀竖线 */
97
- showVertical: {
110
+ /**
111
+ * 是否显示边框
112
+ */
113
+ border: {
98
114
  type: Boolean,
99
- default: true,
115
+ default: true
100
116
  },
101
- /** 是否禁用 */
117
+ /**
118
+ * 是否禁用
119
+ */
102
120
  disabled: {
103
121
  type: Boolean,
104
- default: false,
122
+ default: false
105
123
  },
106
124
  /**
107
- * 单元的大小
108
- * @values large,medium,small
109
- * */
125
+ * 面板大小 large, medium, small
126
+ */
110
127
  size: {
111
- type: String,
112
- default: "medium",
128
+ type: String as PropType<'large' | 'medium' | 'small'>,
129
+ default: 'medium'
113
130
  },
114
- /** 内容面板高度 */
131
+ /**
132
+ * 内容区域高度
133
+ */
115
134
  contentHeight: {
116
135
  type: [Number, String],
117
- default: 120,
136
+ default: 150
118
137
  },
119
- /** 定义需要用到的外部样式 */
138
+ /**
139
+ * 自定义样式
140
+ */
120
141
  customStyle: {
121
142
  type: Object as PropType<CSSProperties>,
122
- },
143
+ default: () => ({})
144
+ }
123
145
  });
146
+
147
+ // 事件定义
124
148
  const emit = defineEmits<IFoldingPanel>();
125
149
 
126
- const lists = ref<PanelVo[]>([]);
150
+ // 内部面板状态
151
+ interface PanelItem extends PanelVo {
152
+ expanded: boolean;
153
+ }
127
154
 
128
- watch(
129
- () => props.list,
130
- (newValue: PanelVo[]) => {
131
- lists.value = newValue.map((item) => ({
132
- ...item,
133
- arrowDirection: "down",
134
- spread: false,
135
- }));
136
- },
137
- { immediate: true },
138
- );
155
+ // 计算的面板项,包含展开状态
156
+ const panelItems = ref<PanelItem[]>([]);
139
157
 
140
- const clickHandler = (temp: PanelVo, index: number) => {
141
- // if (temp?.disabled && temp?.animating) return;
142
- lists.value = props.list.map((item, i) => {
143
- if (props.accordion) {
144
- // 判断是否是收起来
145
- item.spread = i === index ? !item.spread : false;
146
- } else {
147
- if (i === index) {
148
- item.spread = !item.spread;
149
- }
150
- }
158
+ // 初始化面板数据
159
+ const initializePanels = () => {
160
+ panelItems.value = props.list.map(item => ({
161
+ ...item,
162
+ expanded: !!item.spread
163
+ }));
164
+ };
151
165
 
152
- item.arrowDirection = item.spread ? "up" : "down";
153
- return item;
154
- });
155
- const event: "open" | "close" = temp.spread ? "open" : "close";
156
- emit("change", temp, index);
157
- emit(event, temp, index);
166
+ // 监听列表数据变化
167
+ watch(() => props.list, () => {
168
+ initializePanels();
169
+ }, { deep: true, immediate: true });
170
+
171
+ // 切换面板展开状态
172
+ const toggleItem = (index: number) => {
173
+ if (props.disabled || panelItems.value[index].disabled) {
174
+ return;
175
+ }
176
+
177
+ const currentItem = panelItems.value[index];
178
+ const isCurrentlyExpanded = currentItem.expanded;
179
+
180
+ // 手风琴模式下,关闭其他面板
181
+ if (props.accordion) {
182
+ panelItems.value.forEach((item, i) => {
183
+ if (i !== index) {
184
+ item.expanded = false;
185
+ }
186
+ });
187
+ }
188
+
189
+ // 切换当前面板状态
190
+ currentItem.expanded = !isCurrentlyExpanded;
191
+
192
+ // 触发事件
193
+ emit('change', currentItem, index);
194
+ emit(currentItem.expanded ? 'open' : 'close', currentItem, index);
158
195
  };
196
+
197
+ // 对外暴露的方法
198
+ defineExpose({
199
+ /**
200
+ * 打开指定索引的面板
201
+ */
202
+ open: (index: number) => {
203
+ if (index >= 0 && index < panelItems.value.length) {
204
+ if (props.accordion) {
205
+ panelItems.value.forEach((item, i) => {
206
+ item.expanded = i === index;
207
+ });
208
+ } else {
209
+ panelItems.value[index].expanded = true;
210
+ }
211
+
212
+ const item = panelItems.value[index];
213
+ emit('change', item, index);
214
+ emit('open', item, index);
215
+ }
216
+ },
217
+
218
+ /**
219
+ * 关闭指定索引的面板
220
+ */
221
+ close: (index: number) => {
222
+ if (index >= 0 && index < panelItems.value.length) {
223
+ panelItems.value[index].expanded = false;
224
+
225
+ const item = panelItems.value[index];
226
+ emit('change', item, index);
227
+ emit('close', item, index);
228
+ }
229
+ },
230
+
231
+ /**
232
+ * 切换指定索引的面板
233
+ */
234
+ toggle: (index: number) => {
235
+ toggleItem(index);
236
+ },
237
+
238
+ /**
239
+ * 关闭所有面板
240
+ */
241
+ closeAll: () => {
242
+ panelItems.value.forEach((item, index) => {
243
+ item.expanded = false;
244
+ emit('change', item, index);
245
+ emit('close', item, index);
246
+ });
247
+ }
248
+ });
159
249
  </script>
160
250
 
161
251
  <style lang="scss" scoped>
162
- @import "./index.scss";
252
+ .hy-folding-panel {
253
+ width: 100%;
254
+
255
+ &__wrapper {
256
+ background-color: #ffffff;
257
+ border-radius: 8px;
258
+ overflow: hidden;
259
+ }
260
+
261
+ &__title {
262
+ padding: 16px;
263
+ font-size: 16px;
264
+ font-weight: 600;
265
+ color: #333333;
266
+ border-bottom: 1px solid #f0f0f0;
267
+ }
268
+
269
+ &__body {
270
+ // 主体容器样式
271
+ }
272
+
273
+ &__list {
274
+ // 列表容器样式
275
+ }
276
+
277
+ &__item {
278
+ position: relative;
279
+
280
+ &--disabled {
281
+ opacity: 0.6;
282
+ }
283
+
284
+ &--border {
285
+ border-bottom: 1px solid #f0f0f0;
286
+
287
+ &:last-child {
288
+ border-bottom: none;
289
+ }
290
+ }
291
+
292
+ &--large {
293
+ .hy-folding-panel__item-header {
294
+ padding: 20px 16px;
295
+
296
+ .hy-folding-panel__item-title {
297
+ font-size: 16px;
298
+ }
299
+
300
+ .hy-folding-panel__item-value {
301
+ font-size: 14px;
302
+ }
303
+ }
304
+ }
305
+
306
+ &--medium {
307
+ .hy-folding-panel__item-header {
308
+ padding: 16px;
309
+
310
+ .hy-folding-panel__item-title {
311
+ font-size: 15px;
312
+ }
313
+
314
+ .hy-folding-panel__item-value {
315
+ font-size: 13px;
316
+ }
317
+ }
318
+ }
319
+
320
+ &--small {
321
+ .hy-folding-panel__item-header {
322
+ padding: 12px 16px;
323
+
324
+ .hy-folding-panel__item-title {
325
+ font-size: 14px;
326
+ }
327
+
328
+ .hy-folding-panel__item-value {
329
+ font-size: 12px;
330
+ }
331
+ }
332
+ }
333
+ }
334
+
335
+ &__item-header {
336
+ display: flex;
337
+ justify-content: space-between;
338
+ align-items: center;
339
+ background-color: #ffffff;
340
+ transition: background-color 0.3s;
341
+
342
+ &:active {
343
+ background-color: #f5f5f5;
344
+ }
345
+ }
346
+
347
+ &__item-left {
348
+ display: flex;
349
+ align-items: center;
350
+ flex: 1;
351
+ }
352
+
353
+ &__item-icon {
354
+ width: 24px;
355
+ height: 24px;
356
+ margin-right: 8px;
357
+
358
+ image {
359
+ width: 100%;
360
+ height: 100%;
361
+ }
362
+ }
363
+
364
+ &__item-title {
365
+ font-size: 15px;
366
+ color: #333333;
367
+ flex: 1;
368
+ }
369
+
370
+ &__item-right {
371
+ display: flex;
372
+ align-items: center;
373
+ }
374
+
375
+ &__item-value {
376
+ font-size: 13px;
377
+ color: #999999;
378
+ margin-right: 8px;
379
+ }
380
+
381
+ &__item-arrow {
382
+ font-size: 12px;
383
+ color: #cccccc;
384
+ transition: transform 0.3s;
385
+
386
+ &--up {
387
+ transform: rotate(0deg);
388
+ }
389
+ }
390
+
391
+ &__item-content {
392
+ background-color: #fafafa;
393
+ transition: height 0.3s ease;
394
+ box-sizing: border-box;
395
+ }
396
+ }
163
397
  </style>