@didaoktv/didaoui-uniapp 1.0.0

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 (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +89 -0
  3. package/components/dd-action-sheet/dd-action-sheet.vue +207 -0
  4. package/components/dd-alert/dd-alert.vue +240 -0
  5. package/components/dd-avatar/dd-avatar.vue +168 -0
  6. package/components/dd-backtop/dd-backtop.vue +75 -0
  7. package/components/dd-badge/dd-badge.vue +150 -0
  8. package/components/dd-button/dd-button.vue +215 -0
  9. package/components/dd-capsule-button/dd-capsule-button.vue +109 -0
  10. package/components/dd-card/dd-card.vue +155 -0
  11. package/components/dd-cell/dd-cell.vue +201 -0
  12. package/components/dd-cell-group/dd-cell-group.vue +56 -0
  13. package/components/dd-champion-card/dd-champion-card.vue +258 -0
  14. package/components/dd-checkbox/dd-checkbox.vue +145 -0
  15. package/components/dd-collapse/dd-collapse.vue +63 -0
  16. package/components/dd-collapse-item/dd-collapse-item.vue +154 -0
  17. package/components/dd-count-down/dd-count-down.vue +163 -0
  18. package/components/dd-date-picker/dd-date-picker.vue +253 -0
  19. package/components/dd-dialog/dd-dialog.vue +264 -0
  20. package/components/dd-divider/dd-divider.vue +104 -0
  21. package/components/dd-drawer/dd-drawer.vue +214 -0
  22. package/components/dd-dropdown-item/dd-dropdown-item.vue +203 -0
  23. package/components/dd-dropdown-menu/dd-dropdown-menu.vue +177 -0
  24. package/components/dd-empty-state/dd-empty-state.vue +140 -0
  25. package/components/dd-feature-grid/dd-feature-grid.vue +139 -0
  26. package/components/dd-field/dd-field.vue +240 -0
  27. package/components/dd-icon/dd-icon.vue +464 -0
  28. package/components/dd-image/dd-image.vue +158 -0
  29. package/components/dd-input/dd-input.vue +172 -0
  30. package/components/dd-list-cell/dd-list-cell.vue +152 -0
  31. package/components/dd-loading/dd-loading.vue +187 -0
  32. package/components/dd-loadmore/dd-loadmore.vue +152 -0
  33. package/components/dd-mini-program-navbar/dd-mini-program-navbar.vue +245 -0
  34. package/components/dd-modal/dd-modal.vue +286 -0
  35. package/components/dd-navigation/dd-navigation.vue +134 -0
  36. package/components/dd-overlay/dd-overlay.vue +114 -0
  37. package/components/dd-picker/dd-picker.vue +203 -0
  38. package/components/dd-popover/dd-popover.vue +190 -0
  39. package/components/dd-popover-item/dd-popover-item.vue +88 -0
  40. package/components/dd-popup/dd-popup.vue +243 -0
  41. package/components/dd-progress/dd-progress.vue +255 -0
  42. package/components/dd-pull-refresh/dd-pull-refresh.vue +232 -0
  43. package/components/dd-radio/dd-radio.vue +129 -0
  44. package/components/dd-rate/dd-rate.vue +133 -0
  45. package/components/dd-room-card/dd-room-card.vue +309 -0
  46. package/components/dd-search-bar/dd-search-bar.vue +176 -0
  47. package/components/dd-segmented-tab/dd-segmented-tab.vue +187 -0
  48. package/components/dd-skeleton/dd-skeleton.vue +184 -0
  49. package/components/dd-slider/dd-slider.vue +227 -0
  50. package/components/dd-stat-card/dd-stat-card.vue +174 -0
  51. package/components/dd-step/dd-step.vue +188 -0
  52. package/components/dd-stepper/dd-stepper.vue +171 -0
  53. package/components/dd-steps/dd-steps.vue +61 -0
  54. package/components/dd-sticky/dd-sticky.vue +132 -0
  55. package/components/dd-swipe/dd-swipe.vue +146 -0
  56. package/components/dd-swipe-action/dd-swipe-action.vue +248 -0
  57. package/components/dd-swipe-item/dd-swipe-item.vue +27 -0
  58. package/components/dd-swipeable-tab/dd-swipeable-tab.vue +202 -0
  59. package/components/dd-switch/dd-switch.vue +106 -0
  60. package/components/dd-tabbar/dd-tabbar.vue +112 -0
  61. package/components/dd-tabbar-item/dd-tabbar-item.vue +122 -0
  62. package/components/dd-tag/dd-tag.vue +178 -0
  63. package/components/dd-toast/dd-toast.vue +195 -0
  64. package/components/dd-top-navbar/dd-top-navbar.vue +184 -0
  65. package/components/dd-upload/dd-upload.vue +187 -0
  66. package/index.ts +127 -0
  67. package/libs/utils.ts +106 -0
  68. package/package.json +58 -0
  69. package/scss/_functions.scss +68 -0
  70. package/scss/_mixins.scss +154 -0
  71. package/scss/_reset.scss +30 -0
  72. package/scss/_variables.scss +317 -0
  73. package/uni.scss +4 -0
@@ -0,0 +1,122 @@
1
+ <template>
2
+ <view
3
+ class="dd-tabbar-item"
4
+ :style="{ color: isActive ? activeColor : inactiveColor }"
5
+ @click="onClick"
6
+ >
7
+ <view class="dd-tabbar-item__icon-wrap">
8
+ <slot name="icon" :active="isActive">
9
+ <dd-icon :name="icon" class="dd-tabbar-item__icon" />
10
+ </slot>
11
+ <view v-if="badge" class="dd-tabbar-item__badge">
12
+ <text class="dd-tabbar-item__badge-text">{{ badge }}</text>
13
+ </view>
14
+ <view v-else-if="dot" class="dd-tabbar-item__dot"></view>
15
+ </view>
16
+ <text class="dd-tabbar-item__label">
17
+ <slot>{{ label }}</slot>
18
+ </text>
19
+ </view>
20
+ </template>
21
+
22
+ <script setup lang="ts">
23
+ import { computed, inject, ref } from 'vue'
24
+ import DdIcon from '../dd-icon/dd-icon.vue'
25
+
26
+ interface Props {
27
+ name?: string | number
28
+ icon?: string
29
+ label?: string
30
+ dot?: boolean
31
+ badge?: string | number
32
+ }
33
+
34
+ const props = withDefaults(defineProps<Props>(), {
35
+ name: '',
36
+ icon: '',
37
+ label: '',
38
+ dot: false,
39
+ badge: '',
40
+ })
41
+
42
+ const ctx = inject<any>('ddTabbar', null)
43
+ const fallbackIdx = ref(ctx ? ctx.register() : 0)
44
+
45
+ const identity = computed(() =>
46
+ props.name !== '' && props.name !== undefined ? props.name : fallbackIdx.value
47
+ )
48
+
49
+ const isActive = computed(() =>
50
+ ctx ? ctx.active.value === identity.value : false
51
+ )
52
+ const activeColor = computed(() => (ctx ? ctx.activeColor.value : '#F5A623'))
53
+ const inactiveColor = computed(() =>
54
+ ctx ? ctx.inactiveColor.value : '#9E9E9E'
55
+ )
56
+
57
+ function onClick() {
58
+ ctx?.onItemClick(identity.value)
59
+ }
60
+ </script>
61
+
62
+ <style lang="scss" scoped>
63
+ @import '../../scss/variables';
64
+ @import '../../scss/mixins';
65
+
66
+ .dd-tabbar-item {
67
+ flex: 1;
68
+ height: 100%;
69
+ @include dd-flex-center;
70
+ flex-direction: column;
71
+ gap: 4rpx;
72
+ box-sizing: border-box;
73
+ @include dd-transition(color 0.3s);
74
+
75
+ &__icon-wrap {
76
+ position: relative;
77
+ width: 40rpx;
78
+ height: 40rpx;
79
+ @include dd-flex-center;
80
+ }
81
+
82
+ &__icon {
83
+ font-size: 40rpx;
84
+ line-height: 1;
85
+ }
86
+
87
+ &__label {
88
+ font-size: 20rpx;
89
+ line-height: 1.2;
90
+ @include dd-ellipsis(1);
91
+ }
92
+
93
+ &__badge {
94
+ position: absolute;
95
+ top: 0;
96
+ right: 0;
97
+ transform: translate(40%, -50%);
98
+ min-width: 32rpx;
99
+ height: 32rpx;
100
+ padding: 0 8rpx;
101
+ border-radius: $dd-radius-full;
102
+ background: $dd-error;
103
+ @include dd-flex-center;
104
+ }
105
+
106
+ &__badge-text {
107
+ color: $dd-color-white;
108
+ font-size: 20rpx;
109
+ line-height: 1;
110
+ }
111
+
112
+ &__dot {
113
+ position: absolute;
114
+ top: -4rpx;
115
+ right: -4rpx;
116
+ width: 16rpx;
117
+ height: 16rpx;
118
+ border-radius: 50%;
119
+ background: $dd-error;
120
+ }
121
+ }
122
+ </style>
@@ -0,0 +1,178 @@
1
+ <template>
2
+ <view
3
+ class="dd-tag"
4
+ :class="[
5
+ `dd-tag--${type}`,
6
+ `dd-tag--${variant}`,
7
+ `dd-tag--${size}`,
8
+ { 'dd-tag--round': round, 'dd-tag--closable': closable },
9
+ ]"
10
+ @click="onClick"
11
+ >
12
+ <view v-if="type === 'dot'" class="dd-tag__dot"></view>
13
+ <text v-if="$slots.default || text" class="dd-tag__label"><slot>{{ text }}</slot></text>
14
+ <view v-if="closable" class="dd-tag__close" @click.stop="onClose">
15
+ <dd-icon name="cross" />
16
+ </view>
17
+ </view>
18
+ </template>
19
+
20
+ <script setup lang="ts">
21
+ import { useSlots } from 'vue'
22
+ import DdIcon from '../dd-icon/dd-icon.vue'
23
+
24
+ interface Props {
25
+ type?: 'filled' | 'outlined' | 'dot'
26
+ variant?: 'primary' | 'accent' | 'success' | 'warning' | 'error' | 'info' | 'default'
27
+ size?: 'sm' | 'md'
28
+ round?: boolean
29
+ closable?: boolean
30
+ text?: string
31
+ }
32
+
33
+ const props = withDefaults(defineProps<Props>(), {
34
+ type: 'filled',
35
+ variant: 'primary',
36
+ size: 'md',
37
+ round: false,
38
+ closable: false,
39
+ text: '',
40
+ })
41
+
42
+ const emit = defineEmits<{ (e: 'click', val: Event): void; (e: 'close', val: Event): void }>()
43
+
44
+ const slots = useSlots()
45
+ void slots
46
+
47
+ function onClick(e: Event) {
48
+ emit('click', e)
49
+ }
50
+
51
+ function onClose(e: Event) {
52
+ emit('close', e)
53
+ }
54
+ </script>
55
+
56
+ <style lang="scss" scoped>
57
+ @import '../../scss/variables';
58
+ @import '../../scss/mixins';
59
+
60
+ .dd-tag {
61
+ display: inline-flex;
62
+ align-items: center;
63
+ justify-content: center;
64
+ gap: 8rpx;
65
+ box-sizing: border-box;
66
+ white-space: nowrap;
67
+ vertical-align: middle;
68
+ user-select: none;
69
+
70
+ &__label {
71
+ line-height: 1;
72
+ }
73
+
74
+ &__dot {
75
+ width: 12rpx;
76
+ height: 12rpx;
77
+ border-radius: 50%;
78
+ flex-shrink: 0;
79
+ }
80
+
81
+ &__close {
82
+ display: flex;
83
+ align-items: center;
84
+ justify-content: center;
85
+ width: 28rpx;
86
+ height: 28rpx;
87
+ border-radius: 50%;
88
+ color: inherit;
89
+ font-size: $dd-font-size-caption;
90
+ opacity: 0.7;
91
+ }
92
+ }
93
+
94
+ // === 尺寸: md(48rpx) / sm(40rpx) ===
95
+ .dd-tag--md {
96
+ height: 48rpx;
97
+ padding: 0 $dd-space-3;
98
+ border-radius: $dd-radius-sm;
99
+ font-size: $dd-font-size-body;
100
+ font-weight: 500;
101
+ }
102
+
103
+ .dd-tag--sm {
104
+ height: 40rpx;
105
+ padding: 0 $dd-space-2;
106
+ border-radius: $dd-radius-md;
107
+ font-size: $dd-font-size-caption;
108
+ }
109
+
110
+ .dd-tag--round {
111
+ border-radius: $dd-radius-full !important;
112
+ }
113
+
114
+ // === Filled: 渐变背景 + 反色文字 ===
115
+ .dd-tag--filled {
116
+ &.dd-tag--primary {
117
+ background: $dd-gradient-primary;
118
+ color: $dd-color-white;
119
+ }
120
+ &.dd-tag--accent {
121
+ background: $dd-gradient-accent;
122
+ color: $dd-accent-contrast;
123
+ }
124
+ &.dd-tag--success {
125
+ background: $dd-success-500;
126
+ color: $dd-success-contrast;
127
+ }
128
+ &.dd-tag--warning {
129
+ background: $dd-warning-500;
130
+ color: $dd-warning-contrast;
131
+ }
132
+ &.dd-tag--error {
133
+ background: $dd-error-500;
134
+ color: $dd-error-contrast;
135
+ }
136
+ &.dd-tag--info {
137
+ background: $dd-info-500;
138
+ color: $dd-info-contrast;
139
+ }
140
+ &.dd-tag--default {
141
+ background: $dd-neutral-700;
142
+ color: $dd-text-secondary;
143
+ }
144
+ }
145
+
146
+ // === Outlined: 透明背景 + 1px 边框 + 语义色 ===
147
+ .dd-tag--outlined {
148
+ background: transparent;
149
+ border: 1px solid $dd-border-strong;
150
+
151
+ &.dd-tag--primary { color: $dd-primary-400; border-color: $dd-primary-500; }
152
+ &.dd-tag--accent { color: $dd-accent-300; border-color: $dd-accent-500; }
153
+ &.dd-tag--success { color: $dd-success-400; border-color: $dd-success-500; }
154
+ &.dd-tag--warning { color: $dd-warning-400; border-color: $dd-warning-500; }
155
+ &.dd-tag--error { color: $dd-error-400; border-color: $dd-error-500; }
156
+ &.dd-tag--info { color: $dd-info-400; border-color: $dd-info-500; }
157
+ &.dd-tag--default { color: $dd-text-secondary; border-color: $dd-border-strong; }
158
+ }
159
+
160
+ // === Dot: elevated 背景 + 彩色点 + 次级色文字 ===
161
+ .dd-tag--dot {
162
+ background: $dd-bg-elevated;
163
+ border: 1px solid $dd-border-default;
164
+ color: $dd-text-secondary;
165
+
166
+ &.dd-tag--primary .dd-tag__dot { background: $dd-primary-500; }
167
+ &.dd-tag--accent .dd-tag__dot { background: $dd-accent-500; }
168
+ &.dd-tag--success .dd-tag__dot { background: $dd-success-500; }
169
+ &.dd-tag--warning .dd-tag__dot { background: $dd-warning-500; }
170
+ &.dd-tag--error .dd-tag__dot { background: $dd-error-500; }
171
+ &.dd-tag--info .dd-tag__dot { background: $dd-info-500; }
172
+ &.dd-tag--default .dd-tag__dot { background: $dd-neutral-400; }
173
+ }
174
+
175
+ .dd-tag--closable {
176
+ padding-right: $dd-space-1;
177
+ }
178
+ </style>
@@ -0,0 +1,195 @@
1
+ <template>
2
+ <view class="dd-toast" :class="[`dd-toast--${toastState.position}`, { 'dd-toast--show': toastState.visible }]">
3
+ <view class="dd-toast__capsule">
4
+ <view v-if="showIcon" class="dd-toast__icon">
5
+ <view v-if="toastState.type === 'loading'" class="dd-toast__spinner"></view>
6
+ <dd-icon v-else :name="iconName" class="dd-toast__icon-text" :class="`dd-toast__icon-text--${toastState.type}`" />
7
+ </view>
8
+ <text v-if="toastState.message" class="dd-toast__msg">{{ toastState.message }}</text>
9
+ </view>
10
+ </view>
11
+ </template>
12
+
13
+ <script lang="ts">
14
+ import { reactive } from 'vue'
15
+
16
+ export type ToastType = 'text' | 'success' | 'error' | 'warning' | 'loading'
17
+ export type ToastPosition = 'top' | 'center' | 'bottom'
18
+
19
+ export interface ToastOptions {
20
+ message?: string
21
+ type?: ToastType
22
+ position?: ToastPosition
23
+ duration?: number
24
+ icon?: boolean
25
+ }
26
+
27
+ // 模块级单例状态 (createApp 在 MP 不支持,用响应式 state + 组件挂载到页面)
28
+ export const toastState = reactive({
29
+ visible: false,
30
+ message: '',
31
+ type: 'text' as ToastType,
32
+ position: 'center' as ToastPosition,
33
+ duration: 2000,
34
+ icon: true,
35
+ })
36
+
37
+ let timer: ReturnType<typeof setTimeout> | null = null
38
+
39
+ export function hideToast() {
40
+ toastState.visible = false
41
+ if (timer) {
42
+ clearTimeout(timer)
43
+ timer = null
44
+ }
45
+ }
46
+
47
+ export function showToast(options: string | ToastOptions) {
48
+ const opts = typeof options === 'string' ? { message: options } : options
49
+ if (timer) {
50
+ clearTimeout(timer)
51
+ timer = null
52
+ }
53
+ toastState.visible = true
54
+ toastState.message = opts.message || ''
55
+ toastState.type = opts.type || 'text'
56
+ toastState.position = opts.position || 'center'
57
+ toastState.duration = opts.duration ?? 2000
58
+ toastState.icon = opts.icon ?? true
59
+ // loading 类型不自动关闭,需手动 hideToast()
60
+ if (toastState.type !== 'loading') {
61
+ timer = setTimeout(hideToast, toastState.duration)
62
+ }
63
+ }
64
+
65
+ export function showLoading(message = '加载中') {
66
+ showToast({ message, type: 'loading', position: 'center' })
67
+ }
68
+
69
+ export function showSuccess(message: string) {
70
+ showToast({ message, type: 'success' })
71
+ }
72
+
73
+ export function showError(message: string) {
74
+ showToast({ message, type: 'error' })
75
+ }
76
+ </script>
77
+
78
+ <script setup lang="ts">
79
+ import { computed } from 'vue'
80
+ import DdIcon from '../dd-icon/dd-icon.vue'
81
+
82
+ // text 类型强制隐藏图标
83
+ const showIcon = computed(() => toastState.icon && toastState.type !== 'text')
84
+
85
+ const iconName = computed(() => {
86
+ const map: Record<string, string> = {
87
+ success: 'success',
88
+ error: 'fail',
89
+ warning: 'warning',
90
+ }
91
+ return map[toastState.type] || ''
92
+ })
93
+ </script>
94
+
95
+ <style lang="scss" scoped>
96
+ @import '../../scss/variables';
97
+ @import '../../scss/mixins';
98
+
99
+ .dd-toast {
100
+ position: fixed;
101
+ left: 50%;
102
+ transform: translateX(-50%) scale(0.9);
103
+ opacity: 0;
104
+ visibility: hidden;
105
+ z-index: $dd-z-index-toast;
106
+ @include dd-no-touch; // pointer-events none
107
+ @include dd-transition(opacity 0.25s ease, transform 0.25s ease, visibility 0.25s);
108
+
109
+ &--top {
110
+ top: 20%;
111
+ }
112
+
113
+ &--center {
114
+ top: 50%;
115
+ transform: translate(-50%, -50%) scale(0.9);
116
+ }
117
+
118
+ &--bottom {
119
+ bottom: 20%;
120
+ }
121
+
122
+ &--show {
123
+ opacity: 1;
124
+ visibility: visible;
125
+
126
+ &.dd-toast--top {
127
+ transform: translateX(-50%) scale(1);
128
+ }
129
+
130
+ &.dd-toast--center {
131
+ transform: translate(-50%, -50%) scale(1);
132
+ }
133
+
134
+ &.dd-toast--bottom {
135
+ transform: translateX(-50%) scale(1);
136
+ }
137
+ }
138
+
139
+ &__capsule {
140
+ max-width: 70vw;
141
+ min-width: 200rpx;
142
+ @include dd-glass;
143
+ border-radius: $dd-radius-full;
144
+ padding: $dd-space-3 $dd-space-5;
145
+ display: flex;
146
+ flex-direction: column;
147
+ align-items: center;
148
+ gap: $dd-space-2;
149
+ }
150
+
151
+ &__icon {
152
+ @include dd-flex-center;
153
+ }
154
+
155
+ &__icon-text {
156
+ font-size: 64rpx;
157
+ line-height: 1;
158
+ font-weight: 700;
159
+
160
+ &--success {
161
+ color: $dd-success-400;
162
+ }
163
+
164
+ &--error {
165
+ color: $dd-error-400;
166
+ }
167
+
168
+ &--warning {
169
+ color: $dd-warning-400;
170
+ }
171
+ }
172
+
173
+ &__spinner {
174
+ width: 64rpx;
175
+ height: 64rpx;
176
+ border: 6rpx solid $dd-border-default;
177
+ border-top-color: $dd-primary-400;
178
+ border-radius: 50%;
179
+ animation: dd-toast-spin 0.8s linear infinite;
180
+ }
181
+
182
+ &__msg {
183
+ color: $dd-color-white;
184
+ font-size: $dd-font-size-body;
185
+ line-height: 1.4;
186
+ text-align: center;
187
+ }
188
+ }
189
+
190
+ @keyframes dd-toast-spin {
191
+ to {
192
+ transform: rotate(360deg);
193
+ }
194
+ }
195
+ </style>
@@ -0,0 +1,184 @@
1
+ <template>
2
+ <view
3
+ class="dd-topbar"
4
+ :class="{
5
+ 'dd-topbar--fixed': fixed,
6
+ 'dd-topbar--transparent': transparent,
7
+ 'dd-topbar--border': border && !transparent,
8
+ }"
9
+ >
10
+ <view
11
+ v-if="fixed && statusBarHeight > 0"
12
+ class="dd-topbar__status"
13
+ :style="{ height: statusBarHeight + 'px' }"
14
+ ></view>
15
+ <view class="dd-topbar__content">
16
+ <view class="dd-topbar__left" @click="onBack">
17
+ <view v-if="showBack" class="dd-topbar__back-icon"><dd-icon name="arrow-left" size="18" /></view>
18
+ <text v-if="showBack && backText" class="dd-topbar__back-text">{{ backText }}</text>
19
+ <view class="dd-topbar__left-slot" @click.stop>
20
+ <slot name="left"></slot>
21
+ </view>
22
+ </view>
23
+ <text class="dd-topbar__title">
24
+ <slot>{{ title }}</slot>
25
+ </text>
26
+ <view class="dd-topbar__right">
27
+ <slot name="right"></slot>
28
+ </view>
29
+ </view>
30
+ </view>
31
+ <view
32
+ v-if="fixed && placeholder"
33
+ class="dd-topbar__placeholder"
34
+ :style="{ height: totalHeight + 'px' }"
35
+ ></view>
36
+ </template>
37
+
38
+ <script setup lang="ts">
39
+ import { computed } from 'vue'
40
+ import DdIcon from '../dd-icon/dd-icon.vue'
41
+ import { getStatusBarHeight, isH5 } from '../../libs/utils'
42
+
43
+ interface Props {
44
+ title?: string
45
+ fixed?: boolean
46
+ transparent?: boolean
47
+ border?: boolean
48
+ showBack?: boolean
49
+ backText?: string
50
+ placeholder?: boolean
51
+ fallbackPath?: string
52
+ }
53
+
54
+ const props = withDefaults(defineProps<Props>(), {
55
+ title: '',
56
+ fixed: true,
57
+ transparent: false,
58
+ border: true,
59
+ showBack: true,
60
+ backText: '',
61
+ placeholder: true,
62
+ fallbackPath: '',
63
+ })
64
+
65
+ const emit = defineEmits<{ (e: 'back'): void }>()
66
+
67
+ // ponytail: 运行时兜底 —— 条件编译宏在 .ts 文件中可能未生效,
68
+ // 用已导出的 isH5 常量做二次校验,确保 H5 端状态栏高度为 0
69
+ const rawStatusBarHeight = getStatusBarHeight()
70
+ const statusBarHeight = isH5 ? 0 : rawStatusBarHeight
71
+
72
+ // uni.upx2px 与 CSS rpx 使用相同的屏幕宽度基准,结果一致
73
+ const CONTENT_PX = uni.upx2px(88)
74
+ const totalHeight = computed(() => statusBarHeight + CONTENT_PX)
75
+
76
+ function onBack() {
77
+ if (!props.showBack) return
78
+ emit('back')
79
+ const pages = getCurrentPages()
80
+ const goFallback = () => {
81
+ if (props.fallbackPath) uni.reLaunch({ url: props.fallbackPath, fail: () => {} })
82
+ }
83
+ if (pages.length > 1) {
84
+ // navigateBack 可能因历史栈为空而静默失败(H5 新标签页打开等),失败时走 fallbackPath
85
+ uni.navigateBack({ delta: 1, fail: goFallback })
86
+ } else {
87
+ goFallback()
88
+ }
89
+ }
90
+ </script>
91
+
92
+ <style lang="scss" scoped>
93
+ @import '../../scss/variables';
94
+ @import '../../scss/mixins';
95
+
96
+ .dd-topbar {
97
+ width: 100%;
98
+ box-sizing: border-box;
99
+
100
+ &--fixed {
101
+ position: fixed;
102
+ top: 0;
103
+ left: 0;
104
+ right: 0;
105
+ z-index: $dd-z-index-sticky;
106
+ }
107
+
108
+ &--transparent {
109
+ background: transparent;
110
+ .dd-topbar__content {
111
+ border-bottom: none;
112
+ }
113
+ }
114
+
115
+ &:not(.dd-topbar--transparent) {
116
+ @include dd-glass;
117
+ border: none;
118
+ }
119
+
120
+ &__status {
121
+ width: 100%;
122
+ }
123
+
124
+ &__content {
125
+ position: relative;
126
+ height: 88rpx;
127
+ @include dd-flex-between;
128
+ padding: 0 $dd-space-4;
129
+ box-sizing: border-box;
130
+ }
131
+
132
+ &--border &__content {
133
+ @include dd-hairline-bottom($dd-border-subtle);
134
+ }
135
+
136
+ &__left {
137
+ display: flex;
138
+ align-items: center;
139
+ gap: $dd-space-2;
140
+ min-width: 120rpx;
141
+ }
142
+
143
+ &__left-slot {
144
+ flex: none;
145
+ }
146
+
147
+ &__back-icon {
148
+ display: flex;
149
+ align-items: center;
150
+ font-size: 56rpx;
151
+ color: $dd-primary-400;
152
+ text-shadow: 0 0 8px rgba(245, 166, 35, 0.4);
153
+ }
154
+
155
+ &__back-text {
156
+ font-size: $dd-font-size-body;
157
+ color: $dd-primary-400;
158
+ }
159
+
160
+ &__title {
161
+ position: absolute;
162
+ left: 50%;
163
+ top: 50%;
164
+ transform: translate(-50%, -50%);
165
+ max-width: 50%;
166
+ font-size: $dd-font-size-lead;
167
+ font-weight: 700;
168
+ color: $dd-text-primary;
169
+ @include dd-ellipsis(1);
170
+ }
171
+
172
+ &__right {
173
+ display: flex;
174
+ align-items: center;
175
+ gap: $dd-space-3;
176
+ min-width: 120rpx;
177
+ justify-content: flex-end;
178
+ }
179
+
180
+ &__placeholder {
181
+ width: 100%;
182
+ }
183
+ }
184
+ </style>