@didaoktv/didaoui-uniapp 1.3.9 → 1.4.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.
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  - **帝王级配色** — 帝王金 `#F5A623` + 皇家蓝 `#2D4BA0` 双主色,暗色主题默认
8
8
  - **玻璃拟态设计** — 多层级玻璃拟态效果,打造高端夜店沉浸感
9
- - **90 个精心打磨的组件** — 覆盖表单、导航、布局卡片、数据展示、浮层反馈、交互、画布七大类,含 KTV 业务组件(房态卡、账单明细、工单卡、日历、二维码)
9
+ - **95 个精心打磨的组件** — 覆盖表单、导航、布局卡片、数据展示、浮层反馈、交互、画布七大类,含 KTV 业务组件(房态卡、账单明细、工单卡、日历、二维码)
10
10
  - **Vue3 + TypeScript** — 完整类型定义,Composition API
11
11
  - **多端兼容** — H5 / 微信小程序 / 抖音小程序 / Android / iOS
12
12
 
@@ -50,10 +50,10 @@ import { DdButton, DdInput } from '@didaoktv/didaoui-uniapp'
50
50
 
51
51
  | 分类 | 数量 | 组件 |
52
52
  | ----- | -- | --------------------------------------------------------------------------------------------------------------------------------------------- |
53
- | 表单输入 | 23 | Button, CodeButton, Input, Form, Field, Switch, Checkbox, CheckboxGroup, Radio, RadioGroup, Stepper, SearchBar, Slider, DatePicker, Picker, Rate, Upload, Icon, Keyboard, NumberKeyboard, CarKeyboard, Cascader, GoodsSku |
54
- | 导航 | 12 | Navigation, TopNavbar, SwipeableTab, SegmentedTab, Drawer, Tabbar, TabbarItem, Backtop, Collapse, CollapseItem, DropdownMenu, DropdownItem |
55
- | 布局/卡片 | 8 | Card, RoomCard, FeatureGrid, Sticky, BillDetail, WorkorderCard, Calendar, Qrcode |
56
- | 数据展示 | 24 | Tag, StatCard, ChampionCard, Avatar, AvatarGroup, Badge, ListCell, Cell, CellGroup, CountDown, Divider, Image, Progress, Skeleton, EmptyState, Step, Steps, Text, Link, Copy, Album, Coupon, Parse, Markdown |
53
+ | 表单输入 | 24 | Button, CodeButton, Input, Form, Field, Switch, Checkbox, CheckboxGroup, Radio, RadioGroup, Stepper, SearchBar, Slider, DatePicker, Picker, Rate, Upload, Icon, Keyboard, NumberKeyboard, CarKeyboard, Cascader, GoodsSku, TreeSelect |
54
+ | 导航 | 14 | Navigation, TopNavbar, SegmentedTab, SwipeableTab, Drawer, Tabbar, TabbarItem, Backtop, Collapse, CollapseItem, DropdownMenu, DropdownItem, Sidebar, SidebarItem | |
55
+ | 布局/卡片 | 9 | Card, RoomCard, FeatureGrid, Sticky, BillDetail, WorkorderCard, Calendar, Qrcode, SubmitBar |
56
+ | 数据展示 | 25 | Tag, StatCard, ChampionCard, Avatar, AvatarGroup, Badge, ListCell, Cell, CellGroup, CountDown, Divider, Image, Progress, Skeleton, EmptyState, Step, Steps, Text, Link, Copy, Album, Coupon, Parse, Markdown, Price |
57
57
  | 浮层反馈 | 11 | Modal, ActionSheet, Toast, Alert, Loading, Dialog, Overlay, Popup, Popover, PopoverItem, Portal |
58
58
  | 交互 | 5 | SwipeAction, Swipe, SwipeItem, PullRefresh, Loadmore |
59
59
  | 小程序专属 | 2 | CapsuleButton, MiniProgramNavbar |
@@ -77,7 +77,7 @@ import { DdButton, DdInput } from '@didaoktv/didaoui-uniapp'
77
77
 
78
78
  ### 圆角
79
79
 
80
- 2 / 4 / 8 / 12 / 16 / 9999 px (Vant 对齐小圆角)
80
+ 2 / 4 / 8 / 12 / 16 / 9999 px
81
81
 
82
82
  ### 间距
83
83
 
@@ -144,13 +144,13 @@ $dd-primary: #d32f2f;
144
144
 
145
145
  ## 新增业务组件(2026-08-22 纳入设计系统)
146
146
 
147
- > 新组件统一以主流组件库(Vant 等)同名/同类组件为 API 设计基准,符合 uni-app 多端规范,只消费上层 `scss/_variables.scss` 既有 token。
147
+ > 新组件统一以主流组件库同名/同类组件为 API 设计基准,符合 uni-app 多端规范,只消费上层 `scss/_variables.scss` 既有 token。
148
148
 
149
149
  | 组件 | 用途 | 参考基准 |
150
150
  |------|------|---------|
151
- | `dd-bill-detail` | 账单明细(商品项/小计/合计/支付状态/操作按钮 slot) | Vant `Card` + GoodsAction 聚合 |
152
- | `dd-workorder-card` | 工单条目卡片(类型/状态/进度/布置 checklist/完成确认) | Vant `Card` + `Tag` + Checklist |
153
- | `dd-calendar` | 月历 + 时段(日期多选区间/禁选/时段格) | Vant `Calendar` + 主流日历组件 |
151
+ | `dd-bill-detail` | 账单明细(商品项/小计/合计/支付状态/操作按钮 slot) | 卡片 + 底部操作条聚合 |
152
+ | `dd-workorder-card` | 工单条目卡片(类型/状态/进度/布置 checklist/完成确认) | 卡片 + 标签 + 清单聚合 |
153
+ | `dd-calendar` | 月历 + 时段(日期多选区间/禁选/时段格) | 主流日历组件 |
154
154
  | `dd-qrcode` | 二维码渲染(canvas,内容/尺寸/前景色/背景色/logo) | 主流二维码组件 API |
155
155
 
156
156
  ## License
@@ -198,7 +198,7 @@ function onClick(e: Event) {
198
198
  color: var(--dd-error-contrast, #{$dd-error-contrast});
199
199
  }
200
200
 
201
- // plain:线框按钮,透明底 + 保留各 type 的主题色文字/描边(对齐 Vant 语义)
201
+ // plain:线框按钮,透明底 + 保留各 type 的主题色文字/描边
202
202
  .dd-btn--plain {
203
203
  background: transparent;
204
204
  border-color: currentColor;
@@ -124,12 +124,12 @@ function handleClick(e: Event) {
124
124
  padding: 0 24rpx 0 $dd-space-4;
125
125
  background: var(--dd-bg-elevated, #{$dd-bg-elevated});
126
126
  font-size: $dd-font-size-body;
127
- // vant 式恒定行高(24px),title/value/label/icon 全部基线对齐
127
+ // 恒定行高(24px),title/value/label/icon 全部基线对齐
128
128
  line-height: $dd-line-height-lead;
129
129
  color: var(--dd-text-primary, #{$dd-text-primary});
130
130
  @include dd-hairline-bottom(var(--dd-border-subtle, #{$dd-border-subtle}));
131
131
 
132
- // vant 式:高度 = 垂直 padding + 行高(12/16px × 2 + 23.8px ≈ 96/112rpx),内容天然居中
132
+ // 高度 = 垂直 padding + 行高(12/16px × 2 + 23.8px ≈ 96/112rpx),内容天然居中
133
133
  &--normal {
134
134
  padding-top: $dd-space-3;
135
135
  padding-bottom: $dd-space-3;
@@ -159,7 +159,7 @@ function handleClick(e: Event) {
159
159
  margin-right: $dd-space-2;
160
160
  display: flex;
161
161
  align-items: center;
162
- // ponytail: stretch 拉伸到兄弟 content 高度使图标居中于首行;title+label 多行时会居中于整块而非首行(vant 是贴首行),如需精确可改为固定行高盒
162
+ // ponytail: stretch 拉伸到兄弟 content 高度使图标居中于首行;title+label 多行时会居中于整块而非首行,如需精确可改为固定行高盒
163
163
  align-self: stretch;
164
164
  }
165
165
 
@@ -43,7 +43,7 @@ const props = withDefaults(defineProps<Props>(), {
43
43
  indeterminate: false,
44
44
  })
45
45
 
46
- // 必须配合 dd-checkbox-group 使用(vant/uview 同款约定)
46
+ // 必须配合 dd-checkbox-group 使用
47
47
  const group = inject<{
48
48
  props: { modelValue: any[]; disabled: boolean }
49
49
  isMaxed: { value: boolean }
@@ -388,7 +388,7 @@ function scrollIntoView() {
388
388
  uni.pageScrollTo({ selector: `.${rootClass}`, duration: 300 })
389
389
  }
390
390
 
391
- /** 手动聚焦/失焦(vant focus()/blur() 对齐;blur 经 uni.hideKeyboard 收起键盘) */
391
+ /** 手动聚焦/失焦(blur 经 uni.hideKeyboard 收起键盘) */
392
392
  const focusState = ref(false)
393
393
 
394
394
  function focusField() {
@@ -426,7 +426,7 @@ defineExpose({ validate, validateWithTrigger, resetValidation, scrollIntoView, f
426
426
  @import '../../scss/variables';
427
427
  @import '../../scss/mixins';
428
428
 
429
- // 样式对齐 vant 4 Field(cell:padding 10px 16px / line-height 24px / 16px 字号 / label 6.2em 主文本色)
429
+ // 样式基准(cell:padding 10px 16px / line-height 24px / 16px 字号 / label 6.2em 主文本色)
430
430
  // 颜色全部走 $dd-* 主题 token;1px 发丝线为刻意的例外
431
431
  .dd-field {
432
432
  position: relative;
@@ -442,7 +442,7 @@ defineExpose({ validate, validateWithTrigger, resetValidation, scrollIntoView, f
442
442
  color: var(--dd-text-primary, #{$dd-text-primary});
443
443
  background: var(--dd-bg-elevated, #{$dd-bg-elevated});
444
444
 
445
- // vant 式发丝线:两侧缩进 32rpx
445
+ // 发丝线:两侧缩进 32rpx
446
446
  &::after {
447
447
  position: absolute;
448
448
  box-sizing: border-box;
@@ -626,7 +626,7 @@ defineExpose({ validate, validateWithTrigger, resetValidation, scrollIntoView, f
626
626
  text-align: right;
627
627
  }
628
628
 
629
- // error:输入文字与 placeholder 变红,label 不变(vant 行为)
629
+ // error:输入文字与 placeholder 变红,label 不变
630
630
  &--error {
631
631
  .dd-field__control,
632
632
  .dd-field__control::placeholder {
@@ -634,7 +634,7 @@ defineExpose({ validate, validateWithTrigger, resetValidation, scrollIntoView, f
634
634
  }
635
635
  }
636
636
 
637
- // disabled:仅文字变灰,不整体降透明度(vant 行为)
637
+ // disabled:仅文字变灰,不整体降透明度
638
638
  &--disabled {
639
639
  .dd-field__label,
640
640
  .dd-field__left-icon {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * dd-form 轻量校验引擎(vant runRules 思想,自研无依赖)
2
+ * dd-form 轻量校验引擎(自研无依赖)
3
3
  * 规则分散在各 dd-field 上,dd-form 只做调度。
4
4
  */
5
5
 
@@ -36,7 +36,7 @@ export interface DdFieldRule {
36
36
  pattern?: RegExp
37
37
  /**
38
38
  * 自定义校验:返回 true=通过,false=用 message 报错,
39
- * 返回 string=直接以该字符串为错误消息(vant 语义);支持 Promise
39
+ * 返回 string=直接以该字符串为错误消息;支持 Promise
40
40
  */
41
41
  validator?: (value: any) => boolean | string | Promise<boolean | string>
42
42
  /** 触发时机;缺省 = 任意触发都执行 */
@@ -0,0 +1,84 @@
1
+ <template>
2
+ <view v-if="pricePair" class="dd-price">
3
+ <text v-if="prefix" class="dd-price__prefix">{{ prefix }}</text>
4
+ <text class="dd-price__currency">{{ currency }}</text>
5
+ <text class="dd-price__integer">{{ pricePair[0] }}</text>
6
+ <text v-if="pricePair[1]" class="dd-price__decimal">{{ mark }}{{ pricePair[1] }}</text>
7
+ <text v-if="suffix" class="dd-price__suffix">{{ suffix }}</text>
8
+ </view>
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ import { computed } from 'vue'
13
+
14
+ interface Props {
15
+ /** 金额(元,直接展示不除以 100);不传不渲染 */
16
+ rate?: number | string
17
+ /** 小数位数(rate 为 number 时生效,0 隐藏小数) */
18
+ decimalLength?: string | number
19
+ /** 货币符号 */
20
+ currency?: string
21
+ /** 整数/小数分隔符 */
22
+ mark?: string
23
+ /** 额外前缀文案(货币符之前) */
24
+ prefix?: string
25
+ /** 额外后缀文案(如「起」) */
26
+ suffix?: string
27
+ }
28
+
29
+ const props = withDefaults(defineProps<Props>(), {
30
+ rate: undefined,
31
+ decimalLength: 2,
32
+ currency: '¥',
33
+ mark: '.',
34
+ prefix: '',
35
+ suffix: '',
36
+ })
37
+
38
+ const pricePair = computed<string[] | null>(() => {
39
+ if (props.rate === undefined || props.rate === null || props.rate === '') return null
40
+ if (typeof props.rate === 'number') {
41
+ const len = Math.max(0, Number(props.decimalLength) || 0)
42
+ return props.rate.toFixed(len).split('.')
43
+ }
44
+ return props.rate.split('.')
45
+ })
46
+ </script>
47
+
48
+ <style lang="scss" scoped>
49
+ @import '../../scss/variables';
50
+ @import '../../scss/mixins';
51
+
52
+ .dd-price {
53
+ display: inline-flex;
54
+ align-items: baseline;
55
+ // ponytail: 颜色跟随使用方(inherit),如 submit-bar 的红价由使用方控制
56
+ color: inherit;
57
+ font-weight: 600;
58
+ line-height: 1;
59
+
60
+ &__prefix,
61
+ &__suffix {
62
+ color: var(--dd-text-secondary, #{$dd-text-secondary});
63
+ font-size: 24rpx;
64
+ font-weight: 400;
65
+ }
66
+
67
+ &__prefix {
68
+ margin-right: $dd-space-1;
69
+ }
70
+
71
+ &__suffix {
72
+ margin-left: $dd-space-1;
73
+ }
74
+
75
+ &__currency,
76
+ &__decimal {
77
+ font-size: 24rpx;
78
+ }
79
+
80
+ &__integer {
81
+ font-size: 36rpx;
82
+ }
83
+ }
84
+ </style>
@@ -37,7 +37,7 @@ const props = withDefaults(defineProps<Props>(), {
37
37
  label: '',
38
38
  })
39
39
 
40
- // 必须配合 dd-radio-group 使用(vant/uview 同款约定)
40
+ // 必须配合 dd-radio-group 使用
41
41
  const group = inject<{
42
42
  props: { modelValue: any; disabled: boolean }
43
43
  toggle: (value: any) => void
@@ -0,0 +1,54 @@
1
+ <template>
2
+ <view class="dd-sidebar">
3
+ <slot></slot>
4
+ </view>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { computed, provide, ref } from 'vue'
9
+
10
+ interface Props {
11
+ /** 当前选中项的索引(v-model) */
12
+ modelValue?: number
13
+ }
14
+
15
+ const props = withDefaults(defineProps<Props>(), {
16
+ modelValue: 0,
17
+ })
18
+
19
+ const emit = defineEmits<{
20
+ (e: 'update:modelValue', val: number): void
21
+ (e: 'change', val: number): void
22
+ }>()
23
+
24
+ const counter = ref(0)
25
+
26
+ function register(): number {
27
+ const idx = counter.value
28
+ counter.value += 1
29
+ return idx
30
+ }
31
+
32
+ function onItemClick(idx: number) {
33
+ if (idx === props.modelValue) return
34
+ emit('update:modelValue', idx)
35
+ emit('change', idx)
36
+ }
37
+
38
+ provide('ddSidebar', {
39
+ active: computed(() => props.modelValue),
40
+ register,
41
+ onItemClick,
42
+ })
43
+ </script>
44
+
45
+ <style lang="scss" scoped>
46
+ @import '../../scss/variables';
47
+ @import '../../scss/mixins';
48
+
49
+ .dd-sidebar {
50
+ display: flex;
51
+ flex-direction: column;
52
+ background: var(--dd-bg-section, #{$dd-bg-section});
53
+ }
54
+ </style>
@@ -0,0 +1,129 @@
1
+ <template>
2
+ <view
3
+ class="dd-sidebar-item"
4
+ :class="[
5
+ customClass,
6
+ {
7
+ 'dd-sidebar-item--active': isActive,
8
+ 'dd-sidebar-item--disabled': disabled,
9
+ },
10
+ ]"
11
+ @click="onClick"
12
+ >
13
+ <view class="dd-sidebar-item__text">
14
+ <slot name="title">{{ title }}</slot>
15
+ </view>
16
+ <view v-if="badge" class="dd-sidebar-item__badge">
17
+ <text class="dd-sidebar-item__badge-text">{{ badge }}</text>
18
+ </view>
19
+ <view v-else-if="dot" class="dd-sidebar-item__dot"></view>
20
+ </view>
21
+ </template>
22
+
23
+ <script setup lang="ts">
24
+ import { computed, inject, ref } from 'vue'
25
+
26
+ interface Props {
27
+ /** 导航项文案 */
28
+ title?: string
29
+ /** 右上角红点 */
30
+ dot?: boolean
31
+ /** 右上角徽标数 */
32
+ badge?: string | number
33
+ /** 禁用(点击不切换、不触发 click) */
34
+ disabled?: boolean
35
+ /** 额外类名 */
36
+ customClass?: string
37
+ }
38
+
39
+ const props = withDefaults(defineProps<Props>(), {
40
+ title: '',
41
+ dot: false,
42
+ badge: '',
43
+ disabled: false,
44
+ customClass: '',
45
+ })
46
+
47
+ const emit = defineEmits<{
48
+ (e: 'click', index: number): void
49
+ }>()
50
+
51
+ const ctx = inject<any>('ddSidebar', null)
52
+ const fallbackIdx = ref(ctx ? ctx.register() : 0)
53
+
54
+ const isActive = computed(() => (ctx ? ctx.active.value === fallbackIdx.value : false))
55
+
56
+ function onClick() {
57
+ if (props.disabled) return
58
+ ctx?.onItemClick(fallbackIdx.value)
59
+ emit('click', fallbackIdx.value)
60
+ }
61
+ </script>
62
+
63
+ <style lang="scss" scoped>
64
+ @import '../../scss/variables';
65
+ @import '../../scss/mixins';
66
+
67
+ .dd-sidebar-item {
68
+ position: relative;
69
+ padding: $dd-space-4 $dd-space-3;
70
+ color: var(--dd-text-secondary, #{$dd-text-secondary});
71
+ font-size: $dd-font-size-body;
72
+ line-height: $dd-line-height-body;
73
+
74
+ &--active {
75
+ background: var(--dd-bg-card, #{$dd-bg-card});
76
+ color: var(--dd-text-primary, #{$dd-text-primary});
77
+ font-weight: 600;
78
+
79
+ // 左侧金色选中条
80
+ &::before {
81
+ content: '';
82
+ position: absolute;
83
+ left: 0;
84
+ top: 50%;
85
+ transform: translateY(-50%);
86
+ width: 6rpx;
87
+ height: 32rpx;
88
+ border-radius: 0 $dd-radius-full $dd-radius-full 0;
89
+ background: var(--dd-primary, #{$dd-primary});
90
+ }
91
+ }
92
+
93
+ &--disabled {
94
+ color: var(--dd-text-tertiary, #{$dd-text-tertiary});
95
+ }
96
+
97
+ &__text {
98
+ @include dd-ellipsis(1);
99
+ }
100
+
101
+ &__badge {
102
+ position: absolute;
103
+ top: $dd-space-2;
104
+ right: $dd-space-2;
105
+ min-width: $dd-space-4;
106
+ height: $dd-space-4;
107
+ padding: 0 $dd-space-1;
108
+ border-radius: $dd-radius-full;
109
+ background: var(--dd-error, #{$dd-error});
110
+ @include dd-flex-center;
111
+ }
112
+
113
+ &__badge-text {
114
+ color: $dd-color-white;
115
+ font-size: 20rpx;
116
+ line-height: 1;
117
+ }
118
+
119
+ &__dot {
120
+ position: absolute;
121
+ top: $dd-space-2;
122
+ right: $dd-space-2;
123
+ width: $dd-space-2;
124
+ height: $dd-space-2;
125
+ border-radius: 50%;
126
+ background: var(--dd-error, #{$dd-error});
127
+ }
128
+ }
129
+ </style>
@@ -0,0 +1,194 @@
1
+ <template>
2
+ <view
3
+ v-if="placeholder"
4
+ class="dd-submit-bar__placeholder"
5
+ :class="{ 'dd-submit-bar__placeholder--safe': safeAreaInsetBottom }"
6
+ ></view>
7
+ <view class="dd-submit-bar" :class="{ 'dd-submit-bar--safe': safeAreaInsetBottom }">
8
+ <slot name="top"></slot>
9
+ <view v-if="hasTip" class="dd-submit-bar__tip">
10
+ <dd-icon v-if="tipIcon" :name="tipIcon" class="dd-submit-bar__tip-icon" />
11
+ <text v-if="tip" class="dd-submit-bar__tip-text">{{ tip }}</text>
12
+ <slot name="tip"></slot>
13
+ </view>
14
+ <view class="dd-submit-bar__bar">
15
+ <slot></slot>
16
+ <view v-if="hasPrice" class="dd-submit-bar__text" :style="textStyle">
17
+ <text class="dd-submit-bar__label">{{ label }}</text>
18
+ <dd-price
19
+ class="dd-submit-bar__price"
20
+ :rate="priceYuan"
21
+ :currency="currency"
22
+ :decimal-length="decimalLength"
23
+ />
24
+ <text v-if="suffixLabel" class="dd-submit-bar__suffix-label">{{ suffixLabel }}</text>
25
+ </view>
26
+ <slot name="button">
27
+ <dd-button
28
+ class="dd-submit-bar__button"
29
+ round
30
+ :type="buttonType"
31
+ :style="buttonStyle"
32
+ :loading="loading"
33
+ :disabled="disabled"
34
+ @click="onSubmit"
35
+ >{{ buttonText }}</dd-button>
36
+ </slot>
37
+ </view>
38
+ </view>
39
+ </template>
40
+
41
+ <script setup lang="ts">
42
+ import { computed, useSlots } from 'vue'
43
+ import type { CSSProperties } from 'vue'
44
+ import DdButton from '../dd-button/dd-button.vue'
45
+ import DdIcon from '../dd-icon/dd-icon.vue'
46
+ import DdPrice from '../dd-price/dd-price.vue'
47
+
48
+ interface Props {
49
+ /** 提示文案(tip 区域,如「小计不含服务费」) */
50
+ tip?: string
51
+ /** 价格左侧文案 */
52
+ label?: string
53
+ /** 价格(单位:分),传 number 才渲染价格区 */
54
+ price?: number
55
+ /** 提示区图标名(dd-icon name) */
56
+ tipIcon?: string
57
+ /** 按钮加载态 */
58
+ loading?: boolean
59
+ /** 货币符号 */
60
+ currency?: string
61
+ /** 按钮禁用态 */
62
+ disabled?: boolean
63
+ /** 价格区对齐方式(left/center/right) */
64
+ textAlign?: string
65
+ /** 按钮文案 */
66
+ buttonText?: string
67
+ /** 按钮类型(dd-button type) */
68
+ buttonType?: 'default' | 'primary' | 'secondary' | 'ghost' | 'text' | 'success' | 'warning' | 'danger'
69
+ /** 按钮自定义背景色(透传覆盖 dd-button 背景) */
70
+ buttonColor?: string
71
+ /** 价格右侧附加文案 */
72
+ suffixLabel?: string
73
+ /** 固定在底部时是否生成占位视图 */
74
+ placeholder?: boolean
75
+ /** 价格小数位数 */
76
+ decimalLength?: string | number
77
+ /** 是否预留底部安全区 */
78
+ safeAreaInsetBottom?: boolean
79
+ }
80
+
81
+ const props = withDefaults(defineProps<Props>(), {
82
+ tip: '',
83
+ label: '合计:',
84
+ tipIcon: '',
85
+ loading: false,
86
+ currency: '¥',
87
+ disabled: false,
88
+ textAlign: '',
89
+ buttonText: '',
90
+ buttonType: 'danger',
91
+ buttonColor: '',
92
+ suffixLabel: '',
93
+ placeholder: false,
94
+ decimalLength: 2,
95
+ safeAreaInsetBottom: true,
96
+ })
97
+
98
+ const emit = defineEmits<{
99
+ (e: 'submit'): void
100
+ }>()
101
+
102
+ const slots = useSlots()
103
+
104
+ const hasTip = computed(() => !!props.tip || !!slots.tip)
105
+
106
+ // price 单位为分,除以 100 转元交给 dd-price 渲染
107
+ const priceYuan = computed(() => (typeof props.price === 'number' ? props.price / 100 : undefined))
108
+ const hasPrice = computed(() => priceYuan.value !== undefined)
109
+
110
+ const textStyle = computed<CSSProperties | undefined>(() =>
111
+ props.textAlign ? { textAlign: props.textAlign as CSSProperties['textAlign'] } : undefined,
112
+ )
113
+
114
+ const buttonStyle = computed(() => (props.buttonColor ? { background: props.buttonColor } : undefined))
115
+
116
+ function onSubmit() {
117
+ if (props.disabled || props.loading) return
118
+ emit('submit')
119
+ }
120
+ </script>
121
+
122
+ <style lang="scss" scoped>
123
+ @import '../../scss/variables';
124
+ @import '../../scss/mixins';
125
+
126
+ .dd-submit-bar {
127
+ position: fixed;
128
+ left: 0;
129
+ right: 0;
130
+ bottom: 0;
131
+ z-index: $dd-z-index-sticky;
132
+ background: var(--dd-bg-card, #{$dd-bg-card});
133
+
134
+ &--safe {
135
+ @include dd-safe-area-bottom;
136
+ }
137
+ }
138
+
139
+ .dd-submit-bar__placeholder {
140
+ width: 100%;
141
+ height: 100rpx;
142
+ box-sizing: content-box;
143
+
144
+ &--safe {
145
+ @include dd-safe-area-bottom;
146
+ }
147
+ }
148
+
149
+ .dd-submit-bar__tip {
150
+ display: flex;
151
+ align-items: center;
152
+ padding: $dd-space-2 $dd-space-3;
153
+ background: var(--dd-warning, #{$dd-warning});
154
+ color: $dd-color-white;
155
+ font-size: $dd-font-size-caption;
156
+ line-height: $dd-line-height-caption;
157
+ }
158
+
159
+ .dd-submit-bar__tip-icon {
160
+ margin-right: $dd-space-1;
161
+ font-size: 28rpx;
162
+ }
163
+
164
+ .dd-submit-bar__bar {
165
+ display: flex;
166
+ align-items: center;
167
+ min-height: 100rpx;
168
+ padding: $dd-space-2 $dd-space-3;
169
+ }
170
+
171
+ .dd-submit-bar__text {
172
+ flex: 1;
173
+ padding-right: $dd-space-3;
174
+ color: var(--dd-text-primary, #{$dd-text-primary});
175
+ font-size: $dd-font-size-body;
176
+ }
177
+
178
+ .dd-submit-bar__price {
179
+ margin-left: $dd-space-1;
180
+ color: var(--dd-error, #{$dd-error});
181
+ }
182
+
183
+ .dd-submit-bar__suffix-label {
184
+ margin-left: $dd-space-1;
185
+ color: var(--dd-text-secondary, #{$dd-text-secondary});
186
+ font-size: $dd-font-size-caption;
187
+ }
188
+
189
+ .dd-submit-bar__button {
190
+ flex-shrink: 0;
191
+ min-width: 200rpx;
192
+ margin-left: $dd-space-2;
193
+ }
194
+ </style>
@@ -0,0 +1,204 @@
1
+ <template>
2
+ <view class="dd-tree-select" :style="rootStyle">
3
+ <!-- 左侧分类导航(dd-sidebar) -->
4
+ <scroll-view class="dd-tree-select__nav" :scroll-y="true">
5
+ <dd-sidebar :model-value="mainActiveIndex" @update:model-value="onNavChange">
6
+ <dd-sidebar-item
7
+ v-for="(item, index) in items"
8
+ :key="index"
9
+ :title="item.text"
10
+ :dot="item.dot"
11
+ :badge="item.badge"
12
+ :disabled="item.disabled"
13
+ :custom-class="item.className"
14
+ >
15
+ <template #title>
16
+ <slot name="nav-text" :item="item">{{ item.text }}</slot>
17
+ </template>
18
+ </dd-sidebar-item>
19
+ </dd-sidebar>
20
+ </scroll-view>
21
+ <!-- 右侧子项区(content 插槽可整体自定义) -->
22
+ <scroll-view class="dd-tree-select__content" :scroll-y="true">
23
+ <slot name="content">
24
+ <view
25
+ v-for="child in activeChildren"
26
+ :key="child.id"
27
+ class="dd-tree-select__item"
28
+ :class="{
29
+ 'dd-tree-select__item--active': isActive(child.id),
30
+ 'dd-tree-select__item--disabled': child.disabled,
31
+ }"
32
+ @click="onItemClick(child)"
33
+ >
34
+ <text class="dd-tree-select__item-text">{{ child.text }}</text>
35
+ <dd-icon
36
+ v-if="isActive(child.id)"
37
+ :name="selectedIcon"
38
+ class="dd-tree-select__selected"
39
+ />
40
+ </view>
41
+ </slot>
42
+ </scroll-view>
43
+ </view>
44
+ </template>
45
+
46
+ <script setup lang="ts">
47
+ import { computed } from 'vue'
48
+ import DdIcon from '../dd-icon/dd-icon.vue'
49
+ import DdSidebar from '../dd-sidebar/dd-sidebar.vue'
50
+ import DdSidebarItem from '../dd-sidebar-item/dd-sidebar-item.vue'
51
+
52
+ /** 右侧子选项 */
53
+ interface TreeSelectChild {
54
+ /** 选项唯一 id */
55
+ id: string | number
56
+ /** 选项文案 */
57
+ text: string
58
+ disabled?: boolean
59
+ }
60
+
61
+ /** 左侧导航分类项 */
62
+ interface TreeSelectItem {
63
+ /** 导航文案 */
64
+ text?: string
65
+ disabled?: boolean
66
+ /** 导航项右上角红点 */
67
+ dot?: boolean
68
+ /** 导航项右上角徽标数 */
69
+ badge?: string | number
70
+ /** 额外类名 */
71
+ className?: string
72
+ /** 该分类下的子选项 */
73
+ children?: TreeSelectChild[]
74
+ }
75
+
76
+ interface Props {
77
+ /** 分类数据(左侧导航 + 右侧子项) */
78
+ items?: TreeSelectItem[]
79
+ /** 左侧导航选中索引(v-model:main-active-index) */
80
+ mainActiveIndex?: number
81
+ /** 右侧选中项 id,传数组开启多选(v-model:active-id) */
82
+ activeId?: string | number | Array<string | number>
83
+ /** 多选时最多可选数量(activeId 为数组时生效) */
84
+ max?: number
85
+ /** 组件高度,数字按 px,字符串原样输出(支持 rpx) */
86
+ height?: string | number
87
+ /** 选中项右侧图标名(dd-icon name) */
88
+ selectedIcon?: string
89
+ }
90
+
91
+ const props = withDefaults(defineProps<Props>(), {
92
+ items: () => [],
93
+ mainActiveIndex: 0,
94
+ activeId: 0,
95
+ max: Number.POSITIVE_INFINITY,
96
+ height: 300,
97
+ selectedIcon: 'success',
98
+ })
99
+
100
+ const emit = defineEmits<{
101
+ (e: 'update:mainActiveIndex', index: number): void
102
+ (e: 'update:activeId', id: string | number | Array<string | number>): void
103
+ (e: 'clickNav', index: number): void
104
+ (e: 'clickItem', item: TreeSelectChild): void
105
+ }>()
106
+
107
+ const rootStyle = computed(() => ({
108
+ height: typeof props.height === 'number' ? `${props.height}px` : props.height,
109
+ }))
110
+
111
+ /** 当前选中分类的子选项 */
112
+ const activeChildren = computed<TreeSelectChild[]>(
113
+ () => props.items[props.mainActiveIndex]?.children ?? [],
114
+ )
115
+
116
+ function isActive(id: string | number) {
117
+ return Array.isArray(props.activeId) ? props.activeId.includes(id) : props.activeId === id
118
+ }
119
+
120
+ function onNavChange(index: number) {
121
+ emit('update:mainActiveIndex', index)
122
+ emit('clickNav', index)
123
+ }
124
+
125
+ function onItemClick(child: TreeSelectChild) {
126
+ if (child.disabled) return
127
+ let activeId: string | number | Array<string | number>
128
+ if (Array.isArray(props.activeId)) {
129
+ activeId = props.activeId.slice()
130
+ const index = activeId.indexOf(child.id)
131
+ if (index !== -1) {
132
+ activeId.splice(index, 1)
133
+ } else if (activeId.length < props.max) {
134
+ activeId.push(child.id)
135
+ } else {
136
+ return
137
+ }
138
+ } else {
139
+ activeId = child.id
140
+ }
141
+ emit('update:activeId', activeId)
142
+ emit('clickItem', child)
143
+ }
144
+ </script>
145
+
146
+ <style lang="scss" scoped>
147
+ @import '../../scss/variables';
148
+ @import '../../scss/mixins';
149
+
150
+ .dd-tree-select {
151
+ display: flex;
152
+ font-size: $dd-font-size-body;
153
+
154
+ &__nav {
155
+ flex: 1;
156
+ height: 100%;
157
+ background: var(--dd-bg-section, #{$dd-bg-section});
158
+ }
159
+
160
+ &__content {
161
+ flex: 2;
162
+ height: 100%;
163
+ background: var(--dd-bg-card, #{$dd-bg-card});
164
+ }
165
+
166
+ &__item {
167
+ display: flex;
168
+ align-items: center;
169
+ min-height: 96rpx;
170
+ padding: 0 $dd-space-4;
171
+ font-weight: 600;
172
+ color: var(--dd-text-primary, #{$dd-text-primary});
173
+
174
+ &:active {
175
+ background: var(--dd-interactive-hover, #{$dd-interactive-hover});
176
+ }
177
+
178
+ &--active {
179
+ background: var(--dd-primary, #{$dd-primary});
180
+ color: $dd-neutral-50;
181
+ }
182
+
183
+ &--disabled {
184
+ color: var(--dd-text-tertiary, #{$dd-text-tertiary});
185
+
186
+ &:active {
187
+ background: transparent;
188
+ }
189
+ }
190
+ }
191
+
192
+ &__item-text {
193
+ overflow: hidden;
194
+ white-space: nowrap;
195
+ text-overflow: ellipsis;
196
+ }
197
+
198
+ &__selected {
199
+ margin-left: $dd-space-2;
200
+ font-size: 32rpx;
201
+ color: $dd-neutral-50;
202
+ }
203
+ }
204
+ </style>
package/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { App } from 'vue'
2
2
 
3
- // === Input (23) ===
3
+ // === Input (24) ===
4
4
  import DdButton from './components/dd-button/dd-button.vue'
5
5
  import DdCodeButton from './components/dd-code-button/dd-code-button.vue'
6
6
  import DdInput from './components/dd-input/dd-input.vue'
@@ -24,8 +24,9 @@ import DdNumberKeyboard from './components/dd-number-keyboard/dd-number-keyboard
24
24
  import DdCarKeyboard from './components/dd-car-keyboard/dd-car-keyboard.vue'
25
25
  import DdCascader from './components/dd-cascader/dd-cascader.vue'
26
26
  import DdGoodsSku from './components/dd-goods-sku/dd-goods-sku.vue'
27
+ import DdTreeSelect from './components/dd-tree-select/dd-tree-select.vue'
27
28
 
28
- // === Navigation (12) ===
29
+ // === Navigation (14) ===
29
30
  import DdNavigation from './components/dd-navigation/dd-navigation.vue'
30
31
  import DdTopNavbar from './components/dd-top-navbar/dd-top-navbar.vue'
31
32
  import DdSegmentedTab from './components/dd-segmented-tab/dd-segmented-tab.vue'
@@ -38,8 +39,10 @@ import DdCollapse from './components/dd-collapse/dd-collapse.vue'
38
39
  import DdCollapseItem from './components/dd-collapse-item/dd-collapse-item.vue'
39
40
  import DdDropdownMenu from './components/dd-dropdown-menu/dd-dropdown-menu.vue'
40
41
  import DdDropdownItem from './components/dd-dropdown-item/dd-dropdown-item.vue'
42
+ import DdSidebar from './components/dd-sidebar/dd-sidebar.vue'
43
+ import DdSidebarItem from './components/dd-sidebar-item/dd-sidebar-item.vue'
41
44
 
42
- // === Layout (8) ===
45
+ // === Layout (9) ===
43
46
  import DdCard from './components/dd-card/dd-card.vue'
44
47
  import DdRoomCard from './components/dd-room-card/dd-room-card.vue'
45
48
  import DdFeatureGrid from './components/dd-feature-grid/dd-feature-grid.vue'
@@ -48,8 +51,9 @@ import DdBillDetail from './components/dd-bill-detail/dd-bill-detail.vue'
48
51
  import DdWorkorderCard from './components/dd-workorder-card/dd-workorder-card.vue'
49
52
  import DdCalendar from './components/dd-calendar/dd-calendar.vue'
50
53
  import DdQrcode from './components/dd-qrcode/dd-qrcode.vue'
54
+ import DdSubmitBar from './components/dd-submit-bar/dd-submit-bar.vue'
51
55
 
52
- // === Display (24) ===
56
+ // === Display (25) ===
53
57
  import DdTag from './components/dd-tag/dd-tag.vue'
54
58
  import DdStatCard from './components/dd-stat-card/dd-stat-card.vue'
55
59
  import DdChampionCard from './components/dd-champion-card/dd-champion-card.vue'
@@ -74,6 +78,7 @@ import DdAlbum from './components/dd-album/dd-album.vue'
74
78
  import DdCoupon from './components/dd-coupon/dd-coupon.vue'
75
79
  import DdParse from './components/dd-parse/dd-parse.vue'
76
80
  import DdMarkdown from './components/dd-markdown/dd-markdown.vue'
81
+ import DdPrice from './components/dd-price/dd-price.vue'
77
82
 
78
83
  // === Overlay (11) ===
79
84
  import DdPortal from './components/dd-portal/dd-portal.vue'
@@ -110,15 +115,17 @@ const components = {
110
115
  DdButton, DdCodeButton, DdInput, DdSwitch, DdCheckbox, DdCheckboxGroup, DdRadio, DdRadioGroup, DdForm,
111
116
  DdSearchBar, DdSlider, DdStepper, DdDatePicker, DdField,
112
117
  DdPicker, DdRate, DdUpload, DdIcon,
113
- DdKeyboard, DdNumberKeyboard, DdCarKeyboard, DdCascader, DdGoodsSku,
118
+ DdKeyboard, DdNumberKeyboard, DdCarKeyboard, DdCascader, DdGoodsSku, DdTreeSelect,
114
119
  DdNavigation, DdTopNavbar, DdSegmentedTab, DdSwipeableTab,
115
120
  DdDrawer, DdTabbar, DdTabbarItem, DdBacktop,
116
121
  DdCollapse, DdCollapseItem, DdDropdownMenu, DdDropdownItem,
122
+ DdSidebar, DdSidebarItem,
117
123
  DdCard, DdRoomCard, DdFeatureGrid, DdSticky, DdBillDetail, DdWorkorderCard, DdCalendar, DdQrcode,
124
+ DdSubmitBar,
118
125
  DdTag, DdStatCard, DdChampionCard, DdAvatar, DdAvatarGroup, DdBadge,
119
126
  DdListCell, DdCell, DdCellGroup, DdCountDown, DdDivider,
120
127
  DdImage, DdProgress, DdSkeleton, DdEmptyState, DdStep, DdSteps,
121
- DdText, DdLink, DdCopy, DdAlbum, DdCoupon, DdParse, DdMarkdown,
128
+ DdText, DdLink, DdCopy, DdAlbum, DdCoupon, DdParse, DdMarkdown, DdPrice,
122
129
  DdModal, DdActionSheet, DdToast, DdAlert, DdLoading,
123
130
  DdPortal, DdDialog, DdOverlay, DdPopup, DdPopover, DdPopoverItem,
124
131
  DdSwipeAction, DdSwipe, DdSwipeItem, DdPullRefresh, DdLoadmore,
@@ -140,15 +147,17 @@ export {
140
147
  DdButton, DdCodeButton, DdInput, DdSwitch, DdCheckbox, DdCheckboxGroup, DdRadio, DdRadioGroup, DdForm,
141
148
  DdSearchBar, DdSlider, DdStepper, DdDatePicker, DdField,
142
149
  DdPicker, DdRate, DdUpload, DdIcon,
143
- DdKeyboard, DdNumberKeyboard, DdCarKeyboard, DdCascader, DdGoodsSku,
150
+ DdKeyboard, DdNumberKeyboard, DdCarKeyboard, DdCascader, DdGoodsSku, DdTreeSelect,
144
151
  DdNavigation, DdTopNavbar, DdSegmentedTab, DdSwipeableTab,
145
152
  DdDrawer, DdTabbar, DdTabbarItem, DdBacktop,
146
153
  DdCollapse, DdCollapseItem, DdDropdownMenu, DdDropdownItem,
154
+ DdSidebar, DdSidebarItem,
147
155
  DdCard, DdRoomCard, DdFeatureGrid, DdSticky, DdBillDetail, DdWorkorderCard, DdCalendar, DdQrcode,
156
+ DdSubmitBar,
148
157
  DdTag, DdStatCard, DdChampionCard, DdAvatar, DdAvatarGroup, DdBadge,
149
158
  DdListCell, DdCell, DdCellGroup, DdCountDown, DdDivider,
150
159
  DdImage, DdProgress, DdSkeleton, DdEmptyState, DdStep, DdSteps,
151
- DdText, DdLink, DdCopy, DdAlbum, DdCoupon, DdParse, DdMarkdown,
160
+ DdText, DdLink, DdCopy, DdAlbum, DdCoupon, DdParse, DdMarkdown, DdPrice,
152
161
  DdModal, DdActionSheet, DdToast, DdAlert, DdLoading,
153
162
  DdPortal, DdDialog, DdOverlay, DdPopup, DdPopover, DdPopoverItem,
154
163
  DdSwipeAction, DdSwipe, DdSwipeItem, DdPullRefresh, DdLoadmore,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@didaoktv/didaoui-uniapp",
3
- "version": "1.3.9",
3
+ "version": "1.4.1",
4
4
  "description": "帝到KTV UniApp 组件库",
5
5
  "main": "index.ts",
6
6
  "types": "index.ts",