@didaoktv/didaoui-uniapp 1.3.1 → 1.3.3

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
@@ -1,4 +1,4 @@
1
- # @didaoktv/didaoui-uniapp
1
+ # @didaoktv/didaoui-uniapp
2
2
 
3
3
  专为 KTV 场景打造的 UniApp 组件库。
4
4
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  - **帝王级配色** — 帝王金 `#F5A623` + 皇家蓝 `#2D4BA0` 双主色,暗色主题默认
8
8
  - **玻璃拟态设计** — 多层级玻璃拟态效果,打造高端夜店沉浸感
9
- - **67 个精心打磨的组件** — 覆盖表单、导航、布局卡片、数据展示、浮层反馈、交互六大类,含 KTV 业务组件(房态卡、账单明细、工单卡、日历、二维码)
9
+ - **89 个精心打磨的组件** — 覆盖表单、导航、布局卡片、数据展示、浮层反馈、交互、画布七大类,含 KTV 业务组件(房态卡、账单明细、工单卡、日历、二维码)
10
10
  - **Vue3 + TypeScript** — 完整类型定义,Composition API
11
11
  - **多端兼容** — H5 / 微信小程序 / 抖音小程序 / Android / iOS
12
12
 
@@ -50,13 +50,14 @@ import { DdButton, DdInput } from '@didaoktv/didaoui-uniapp'
50
50
 
51
51
  | 分类 | 数量 | 组件 |
52
52
  | ----- | -- | --------------------------------------------------------------------------------------------------------------------------------------------- |
53
- | 表单输入 | 17 | Button, Input, Form, Field, Switch, Checkbox, CheckboxGroup, Radio, RadioGroup, Stepper, SearchBar, Slider, DatePicker, Picker, Rate, Upload, Icon |
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
54
  | 导航 | 12 | Navigation, TopNavbar, SwipeableTab, SegmentedTab, Drawer, Tabbar, TabbarItem, Backtop, Collapse, CollapseItem, DropdownMenu, DropdownItem |
55
55
  | 布局/卡片 | 8 | Card, RoomCard, FeatureGrid, Sticky, BillDetail, WorkorderCard, Calendar, Qrcode |
56
- | 数据展示 | 16 | Tag, StatCard, ChampionCard, Avatar, Badge, ListCell, Cell, CellGroup, CountDown, Divider, Image, Progress, Skeleton, EmptyState, Step, Steps |
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 |
57
57
  | 浮层反馈 | 10 | Modal, ActionSheet, Toast, Alert, Loading, Dialog, Overlay, Popup, Popover, PopoverItem |
58
58
  | 交互 | 5 | SwipeAction, Swipe, SwipeItem, PullRefresh, Loadmore |
59
- | 小程序专属 | 2 | CapsuleButton, MiniProgramNavbar |
59
+ | 小程序专属 | 2 | CapsuleButton, MiniProgramNavbar |
60
+ | 画布 | 5 | Canvas, Barcode, Cropper, Signature, Poster |
60
61
 
61
62
  ## 设计规范
62
63
 
@@ -0,0 +1,150 @@
1
+ <template>
2
+ <!-- class/style 经单根节点 fallthrough 合并到 dd-button 根元素 -->
3
+ <dd-button
4
+ :type="type"
5
+ :size="size"
6
+ :disabled="!canGetCode"
7
+ :class="customClass"
8
+ :style="customStyle"
9
+ @click="handleClick"
10
+ >
11
+ {{ showText }}
12
+ </dd-button>
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ import { computed, onMounted, onUnmounted, ref } from 'vue'
17
+ import DdButton from '../dd-button/dd-button.vue'
18
+
19
+ interface Props {
20
+ /** 倒计时秒数 */
21
+ time?: number | string
22
+ /** 初始文本 */
23
+ startText?: string
24
+ /** 倒计时文本,X/x 占位剩余秒数 */
25
+ changeText?: string
26
+ /** 倒计时结束文本 */
27
+ endText?: string
28
+ /** H5刷新/各端离开再进入时是否继续倒计时 */
29
+ keepRunning?: boolean
30
+ /** 多个组件间的区分 key */
31
+ uniqueKey?: string
32
+ type?: 'primary' | 'secondary' | 'ghost' | 'text' | 'success' | 'warning' | 'danger'
33
+ size?: 'sm' | 'md' | 'lg'
34
+ customClass?: string
35
+ customStyle?: Record<string, string>
36
+ }
37
+
38
+ const props = withDefaults(defineProps<Props>(), {
39
+ time: 60,
40
+ startText: '获取验证码',
41
+ changeText: 'X秒重新获取',
42
+ endText: '重新获取',
43
+ keepRunning: false,
44
+ uniqueKey: 'dd-code-button',
45
+ type: 'primary',
46
+ size: 'sm',
47
+ customClass: '',
48
+ customStyle: () => ({}),
49
+ })
50
+
51
+ const emit = defineEmits<{
52
+ (e: 'click'): void
53
+ (e: 'start'): void
54
+ (e: 'change', seconds: number): void
55
+ (e: 'finish'): void
56
+ }>()
57
+
58
+ const totalSeconds = computed(() => Math.max(1, Math.floor(Number(props.time) || 60)))
59
+
60
+ const storageKey = computed(() => `dd-code-btn:${props.uniqueKey}`)
61
+
62
+ const remainMs = ref(totalSeconds.value * 1000)
63
+ // 是否点过(用于区分初始文本与结束文本)
64
+ const isClick = ref(false)
65
+ const isRunning = ref(false)
66
+ const canGetCode = ref(true)
67
+
68
+ // ponytail: endTime + Date.now() 差值计时而非递减计数,避免 setInterval 漂移;小程序退后台定时器冻结,回前台下一个 tick 自动对齐
69
+ let endTime = 0
70
+ let timer: ReturnType<typeof setInterval> | null = null
71
+
72
+ const showText = computed(() => {
73
+ if (!isClick.value) return props.startText
74
+ if (!isRunning.value) return props.endText
75
+ const seconds = Math.ceil(remainMs.value / 1000)
76
+ return props.changeText.replace(/X|x/g, String(seconds))
77
+ })
78
+
79
+ function tick() {
80
+ remainMs.value = Math.max(0, endTime - Date.now())
81
+ if (remainMs.value <= 0) {
82
+ finish()
83
+ return
84
+ }
85
+ emit('change', Math.ceil(remainMs.value / 1000))
86
+ }
87
+
88
+ function finish() {
89
+ stopTimer()
90
+ isRunning.value = false
91
+ canGetCode.value = true
92
+ remainMs.value = totalSeconds.value * 1000
93
+ if (props.keepRunning) uni.removeStorageSync(storageKey.value)
94
+ emit('finish')
95
+ }
96
+
97
+ function stopTimer() {
98
+ if (timer) {
99
+ clearInterval(timer)
100
+ timer = null
101
+ }
102
+ }
103
+
104
+ /** 开始倒计时(外部发验证码成功后调用),可传入剩余毫秒用于恢复 */
105
+ function start(remainOverride?: number) {
106
+ stopTimer()
107
+ if (!canGetCode.value && isClick.value) return
108
+ emit('start')
109
+ canGetCode.value = false
110
+ isClick.value = true
111
+ isRunning.value = true
112
+ remainMs.value = remainOverride ?? totalSeconds.value * 1000
113
+ endTime = Date.now() + remainMs.value
114
+ // 开始即落盘结束时间戳:H5 刷新/进程被杀时 onUnmounted 不可靠,写一次比每秒写便宜
115
+ if (props.keepRunning) {
116
+ uni.setStorageSync(storageKey.value, Math.floor(endTime / 1000))
117
+ }
118
+ tick()
119
+ timer = setInterval(tick, 1000)
120
+ }
121
+
122
+ function reset() {
123
+ stopTimer()
124
+ canGetCode.value = true
125
+ isRunning.value = false
126
+ remainMs.value = totalSeconds.value * 1000
127
+ if (props.keepRunning) uni.removeStorageSync(storageKey.value)
128
+ }
129
+
130
+ function handleClick() {
131
+ if (!canGetCode.value) return
132
+ emit('click')
133
+ }
134
+
135
+ /** keepRunning: 挂载时检查本地记录的结束时间戳,未到期则续跑 */
136
+ function resume() {
137
+ if (!props.keepRunning) return
138
+ const endTs = Number(uni.getStorageSync(storageKey.value))
139
+ if (!endTs) return
140
+ uni.removeStorageSync(storageKey.value)
141
+ const remain = (endTs - Math.floor(Date.now() / 1000)) * 1000
142
+ if (remain > 0) start(remain)
143
+ }
144
+
145
+ onMounted(resume)
146
+
147
+ onUnmounted(stopTimer)
148
+
149
+ defineExpose({ canGetCode, start, reset })
150
+ </script>
@@ -69,11 +69,8 @@ const emit = defineEmits<{
69
69
  const isFocused = ref(false)
70
70
  const visible = ref(false)
71
71
 
72
- // ponytail: search/number 在小程序降级处理;password 用 uni 原生 password 布尔属性跨端
73
- const inputType = computed<'text' | 'number'>(() => {
74
- if (props.type === 'password' || props.type === 'search') return 'text'
75
- return 'number'
76
- })
72
+ // ponytail: search 在小程序降级为 text;password 用 uni 原生 password 布尔属性跨端
73
+ const inputType = computed<'text' | 'number'>(() => (props.type === 'number' ? 'number' : 'text'))
77
74
  const isPassword = computed(() => props.type === 'password' && !visible.value)
78
75
  const confirmType = computed(() => (props.type === 'search' ? 'search' : 'done'))
79
76
  const showClear = computed(() => props.clearable && !!props.modelValue && !props.disabled)
@@ -77,18 +77,25 @@ const config = {
77
77
  }
78
78
  const tagSelector = {}
79
79
  let windowWidth, system
80
- // #ifdef MP-WEIXIN
81
- if (uni.canIUse('getWindowInfo')) {
82
- windowWidth = uni.getWindowInfo().windowWidth
83
- system = uni.getDeviceInfo().system
84
- } else {
85
- // #endif
80
+ // ponytail: 不能在模块顶层调用 uni.*——H5 下组件库 ESM 求值早于 uni-h5 运行时挂载 window.uni,
81
+ // 会抛 "uni is not defined";改为首次使用时惰性初始化
82
+ function initSystemInfo () {
83
+ if (windowWidth !== undefined) {
84
+ return
85
+ }
86
+ // #ifdef MP-WEIXIN
87
+ if (uni.canIUse('getWindowInfo')) {
88
+ windowWidth = uni.getWindowInfo().windowWidth
89
+ system = uni.getDeviceInfo().system
90
+ return
91
+ }
92
+ // #endif
86
93
  const systemInfo = uni.getSystemInfoSync()
87
94
  windowWidth = systemInfo.windowWidth
88
95
  // #ifdef MP-WEIXIN
89
96
  system = systemInfo.system
97
+ // #endif
90
98
  }
91
- // #endif
92
99
  const blankChar = makeMap(' ,\r,\n,\t,\f')
93
100
  let idIndex = 0
94
101
 
@@ -310,6 +317,7 @@ Parser.prototype.parseStyle = function (node) {
310
317
  }
311
318
  } else if (value.includes('rpx')) {
312
319
  // 转换 rpx(rich-text 内部不支持 rpx)
320
+ initSystemInfo()
313
321
  value = value.replace(/[0-9.]+\s*rpx/g, $ => parseFloat($) * windowWidth / 750 + 'px')
314
322
  }
315
323
  styleObj[key] = value
@@ -556,6 +564,7 @@ Parser.prototype.onOpenTag = function (selfClose) {
556
564
  }
557
565
  // #endif
558
566
  // 设置的宽度超出屏幕,为避免变形,高度转为自动
567
+ initSystemInfo()
559
568
  if (parseInt(styleObj.width) > windowWidth) {
560
569
  styleObj.height = undefined
561
570
  }
@@ -790,6 +799,7 @@ Parser.prototype.popNode = function () {
790
799
 
791
800
  Object.assign(styleObj, this.parseStyle(node))
792
801
 
802
+ initSystemInfo()
793
803
  if (node.name !== 'table' && parseInt(styleObj.width) > windowWidth) {
794
804
  styleObj['max-width'] = '100%'
795
805
  styleObj['box-sizing'] = 'border-box'
@@ -1186,6 +1196,7 @@ Parser.prototype.onText = function (text) {
1186
1196
  node.text = decodeEntity(text)
1187
1197
  if (this.hook(node)) {
1188
1198
  // #ifdef MP-WEIXIN
1199
+ initSystemInfo()
1189
1200
  if (this.options.selectable === 'force' && system.includes('iOS') && !uni.canIUse('rich-text.user-select')) {
1190
1201
  this.expose()
1191
1202
  }
package/index.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import type { App } from 'vue'
2
2
 
3
- // === Input (21) ===
3
+ // === Input (23) ===
4
4
  import DdButton from './components/dd-button/dd-button.vue'
5
+ import DdCodeButton from './components/dd-code-button/dd-code-button.vue'
5
6
  import DdInput from './components/dd-input/dd-input.vue'
6
7
  import DdSwitch from './components/dd-switch/dd-switch.vue'
7
8
  import DdCheckbox from './components/dd-checkbox/dd-checkbox.vue'
@@ -38,7 +39,7 @@ import DdCollapseItem from './components/dd-collapse-item/dd-collapse-item.vue'
38
39
  import DdDropdownMenu from './components/dd-dropdown-menu/dd-dropdown-menu.vue'
39
40
  import DdDropdownItem from './components/dd-dropdown-item/dd-dropdown-item.vue'
40
41
 
41
- // === Layout (4) ===
42
+ // === Layout (8) ===
42
43
  import DdCard from './components/dd-card/dd-card.vue'
43
44
  import DdRoomCard from './components/dd-room-card/dd-room-card.vue'
44
45
  import DdFeatureGrid from './components/dd-feature-grid/dd-feature-grid.vue'
@@ -105,7 +106,7 @@ import DdSignature from './components/dd-signature/dd-signature.vue'
105
106
  import DdPoster from './components/dd-poster/dd-poster.vue'
106
107
 
107
108
  const components = {
108
- DdButton, DdInput, DdSwitch, DdCheckbox, DdCheckboxGroup, DdRadio, DdRadioGroup, DdForm,
109
+ DdButton, DdCodeButton, DdInput, DdSwitch, DdCheckbox, DdCheckboxGroup, DdRadio, DdRadioGroup, DdForm,
109
110
  DdSearchBar, DdSlider, DdStepper, DdDatePicker, DdField,
110
111
  DdPicker, DdRate, DdUpload, DdIcon,
111
112
  DdKeyboard, DdNumberKeyboard, DdCarKeyboard, DdCascader, DdGoodsSku,
@@ -135,7 +136,7 @@ export default { install }
135
136
  // 全量具名导出
136
137
  export {
137
138
  components,
138
- DdButton, DdInput, DdSwitch, DdCheckbox, DdCheckboxGroup, DdRadio, DdRadioGroup, DdForm,
139
+ DdButton, DdCodeButton, DdInput, DdSwitch, DdCheckbox, DdCheckboxGroup, DdRadio, DdRadioGroup, DdForm,
139
140
  DdSearchBar, DdSlider, DdStepper, DdDatePicker, DdField,
140
141
  DdPicker, DdRate, DdUpload, DdIcon,
141
142
  DdKeyboard, DdNumberKeyboard, DdCarKeyboard, DdCascader, DdGoodsSku,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@didaoktv/didaoui-uniapp",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
4
4
  "description": "帝到KTV UniApp 组件库",
5
5
  "main": "index.ts",
6
6
  "types": "index.ts",