@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
|
-
- **
|
|
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
|
-
| 表单输入 |
|
|
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
|
-
| 数据展示 |
|
|
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
|
|
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
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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 (
|
|
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 (
|
|
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,
|