@zfqh/uview-x 0.0.0-stage → 0.0.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.
Files changed (93) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE +21 -0
  3. package/README.md +127 -3
  4. package/components/uv-badge/props.ts +83 -0
  5. package/components/uv-badge/uv-badge.vue +186 -0
  6. package/components/uv-button/props.ts +148 -0
  7. package/components/uv-button/uv-button.vue +565 -0
  8. package/components/uv-checkbox/props.ts +83 -0
  9. package/components/uv-checkbox/uv-checkbox.vue +403 -0
  10. package/components/uv-checkbox-group/props.ts +97 -0
  11. package/components/uv-checkbox-group/uv-checkbox-group.vue +163 -0
  12. package/components/uv-code/props.ts +48 -0
  13. package/components/uv-code/uv-code.vue +156 -0
  14. package/components/uv-dialog/props.ts +56 -0
  15. package/components/uv-dialog/uv-dialog.vue +492 -0
  16. package/components/uv-form/props.ts +59 -0
  17. package/components/uv-form/uv-form.vue +256 -0
  18. package/components/uv-form/valid.ts +1344 -0
  19. package/components/uv-form-item/props.ts +62 -0
  20. package/components/uv-form-item/uv-form-item.vue +335 -0
  21. package/components/uv-icon/icons.ts +161 -0
  22. package/components/uv-icon/props.ts +91 -0
  23. package/components/uv-icon/uv-icon.vue +204 -0
  24. package/components/uv-icon/uvicons.ttf +0 -0
  25. package/components/uv-input/props.ts +185 -0
  26. package/components/uv-input/uv-input.vue +431 -0
  27. package/components/uv-line/props.ts +42 -0
  28. package/components/uv-line/uv-line.vue +75 -0
  29. package/components/uv-line-progress/props.ts +43 -0
  30. package/components/uv-line-progress/uv-line-progress.vue +166 -0
  31. package/components/uv-loading-icon/props.ts +78 -0
  32. package/components/uv-loading-icon/uv-loading-icon.vue +226 -0
  33. package/components/uv-overlay/props.ts +38 -0
  34. package/components/uv-overlay/uv-overlay.vue +120 -0
  35. package/components/uv-popup/props.ts +101 -0
  36. package/components/uv-popup/uv-popup.vue +371 -0
  37. package/components/uv-safe-bottom/props.ts +18 -0
  38. package/components/uv-safe-bottom/uv-safe-bottom.vue +120 -0
  39. package/components/uv-status-bar/props.ts +22 -0
  40. package/components/uv-status-bar/uv-status-bar.vue +121 -0
  41. package/components/uv-tabs/props.ts +84 -0
  42. package/components/uv-tabs/uv-tabs.vue +432 -0
  43. package/components/uv-transition/props.ts +38 -0
  44. package/components/uv-transition/uv-transition.vue +170 -0
  45. package/hooks/index.ts +5 -0
  46. package/hooks/useDebounce.ts +35 -0
  47. package/hooks/useI18n.ts +95 -0
  48. package/hooks/useThrottle.ts +38 -0
  49. package/index.ts +23 -0
  50. package/libs/dayjs/index.ts +217 -0
  51. package/libs/index.ts +4 -0
  52. package/libs/luch-request/adapters/index.ts +133 -0
  53. package/libs/luch-request/core/InterceptorManager.ts +52 -0
  54. package/libs/luch-request/core/Request.ts +202 -0
  55. package/libs/luch-request/core/buildFullPath.ts +21 -0
  56. package/libs/luch-request/core/defaults.ts +34 -0
  57. package/libs/luch-request/core/dispatchRequest.ts +7 -0
  58. package/libs/luch-request/core/mergeConfig.ts +127 -0
  59. package/libs/luch-request/core/settle.ts +17 -0
  60. package/libs/luch-request/helpers/buildURL.ts +65 -0
  61. package/libs/luch-request/helpers/combineURLs.ts +15 -0
  62. package/libs/luch-request/helpers/isAbsoluteURL.ts +15 -0
  63. package/libs/luch-request/index.d.ts +197 -0
  64. package/libs/luch-request/index.ts +3 -0
  65. package/libs/luch-request/utils/clone.ts +265 -0
  66. package/libs/luch-request/utils.ts +136 -0
  67. package/locales/en.ts +78 -0
  68. package/locales/index.ts +169 -0
  69. package/locales/zh-Hans.ts +78 -0
  70. package/package.json +42 -4
  71. package/styles/color.scss +33 -0
  72. package/styles/common.scss +134 -0
  73. package/styles/components.scss +19 -0
  74. package/styles/index.scss +7 -0
  75. package/styles/theme.scss +44 -0
  76. package/styles/var.module.scss +32 -0
  77. package/styles/vue.scss +39 -0
  78. package/theme.scss +2 -0
  79. package/types/index.d.ts +66 -0
  80. package/utils/colorGradient.ts +139 -0
  81. package/utils/compat.ts +165 -0
  82. package/utils/compress.ts +34 -0
  83. package/utils/config.ts +95 -0
  84. package/utils/debounce.ts +7 -0
  85. package/utils/digit.ts +157 -0
  86. package/utils/function.ts +771 -0
  87. package/utils/http.ts +5 -0
  88. package/utils/index.ts +57 -0
  89. package/utils/locale.ts +296 -0
  90. package/utils/platform.ts +71 -0
  91. package/utils/route.ts +142 -0
  92. package/utils/test.ts +256 -0
  93. package/utils/throttle.ts +7 -0
@@ -0,0 +1,156 @@
1
+ <template>
2
+ <view class="uv-code" :class="[p.customClass]" :style="[$uv.addStyle(p.customStyle)]">
3
+ <!-- 此组件功能由js完成,无需写html逻辑 -->
4
+ </view>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
9
+ import { $uv } from '../../utils'
10
+ import props from './props'
11
+ import { useI18n } from '../../hooks'
12
+
13
+ defineOptions({
14
+ name: 'uv-code',
15
+ // #ifdef UNI-APP-X
16
+ styleIsolation: 'app-and-page',
17
+ // #endif
18
+ virtualHost: true,
19
+ })
20
+
21
+ /**
22
+ * Code 验证码倒计时
23
+ * @description 考虑到用户实际发送验证码的场景,可能是一个按钮,也可能是一段文字,提示语各有不同,所以本组件不提供界面显示,只提供倒计时文本,由用户将文本嵌入到具体的场景
24
+ * @tutorial https://www.uvui.cn/components/code.html
25
+ * @property {String | Number} seconds 倒计时所需的秒数(默认 60 )
26
+ * @property {String} startText 开始前的提示语,默认 ''(使用内置文案「获取验证码」)
27
+ * @property {String} changeText 倒计时期间的提示语,必须带有字母"x",默认 ''(使用内置文案「X秒重新获取」)
28
+ * @property {String} endText 倒计结束的提示语,默认 ''(使用内置文案「重新获取」)
29
+ * @property {Boolean} keepRunning 是否在H5刷新或各端返回再进入时继续倒计时( 默认false )
30
+ * @property {String} uniqueKey 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了
31
+ * @property {Object} customStyle 定义需要用到的外部样式
32
+ * @event {Function} change 倒计时期间,每秒触发一次
33
+ * @event {Function} start 开始倒计时触发
34
+ * @event {Function} end 结束倒计时触发
35
+ * @example <uv-code ref="uCode" @change="codeChange" seconds="20"></uv-code>
36
+ */
37
+ const p = defineProps(props)
38
+
39
+ const emit = defineEmits(['change', 'start', 'end'])
40
+
41
+ // 内置文案:props 优先,否则使用内置 i18n(locales/ 的 uv.code.*)
42
+ const { t } = useI18n({ useScope: 'global' })
43
+ const startTextValue = computed<string>(() => p.startText || t('uv.code.start'))
44
+ const changeTextValue = computed<string>(() => p.changeText || t('uv.code.change'))
45
+ const endTextValue = computed<string>(() => p.endText || t('uv.code.end'))
46
+
47
+ // 当前倒计时的秒数
48
+ const secNum = ref<any>(p.seconds)
49
+ // 定时器
50
+ let timer: any = null
51
+ // 是否可以执行验证码操作
52
+ const canGetCode = ref(true)
53
+
54
+ const changeEvent = (text: string) => {
55
+ emit('change', text)
56
+ }
57
+
58
+ // 保存时间戳,为了防止倒计时尚未结束,H5刷新或者各端的右上角返回上一页再进来
59
+ const setTimeToStorage = () => {
60
+ if (!p.keepRunning || !timer) return
61
+ // 倒计时尚未结束,结果大于0;倒计时已经开始,就会小于初始值,如果等于初始值,说明没有开始倒计时,无需处理
62
+ if (secNum.value > 0 && secNum.value <= Number(p.seconds)) {
63
+ // 获取当前时间戳,除以1000变成秒,再去除小数部分
64
+ const nowTimestamp = Math.floor(+new Date() / 1000)
65
+ // 将本该结束时候的时间戳保存起来 => 当前时间戳 + 剩余的秒数
66
+ uni.setStorageSync(p.uniqueKey + '_$uCountDownTimestamp', nowTimestamp + Number(secNum.value))
67
+ }
68
+ }
69
+
70
+ // 开始倒计时
71
+ const start = () => {
72
+ // 防止快速点击获取验证码的按钮而导致内部产生多个定时器导致混乱
73
+ if (timer) {
74
+ clearInterval(timer)
75
+ timer = null
76
+ }
77
+ emit('start')
78
+ canGetCode.value = false
79
+ // 这里放这句,是为了一开始时就提示,否则要等setInterval的1秒后才会有提示
80
+ changeEvent(changeTextValue.value.replace(/x|X/, String(secNum.value)))
81
+ timer = setInterval(() => {
82
+ secNum.value -= 1
83
+ if (secNum.value > 0) {
84
+ // 用当前倒计时的秒数替换提示字符串中的"x"字母
85
+ changeEvent(changeTextValue.value.replace(/x|X/, String(secNum.value)))
86
+ } else {
87
+ clearInterval(timer)
88
+ timer = null
89
+ changeEvent(endTextValue.value)
90
+ secNum.value = p.seconds
91
+ emit('end')
92
+ canGetCode.value = true
93
+ }
94
+ }, 1000)
95
+ setTimeToStorage()
96
+ }
97
+
98
+ // 重置,可以让用户再次获取验证码
99
+ const reset = () => {
100
+ canGetCode.value = true
101
+ if (timer) {
102
+ clearInterval(timer)
103
+ timer = null
104
+ }
105
+ secNum.value = p.seconds
106
+ changeEvent(endTextValue.value)
107
+ }
108
+
109
+ const checkKeepRunning = () => {
110
+ // 获取上一次退出页面(H5还包括刷新)时的时间戳,如果没有上次的保存,此值可能为空
111
+ const lastTimestamp = Number(uni.getStorageSync(p.uniqueKey + '_$uCountDownTimestamp'))
112
+ if (!lastTimestamp) {
113
+ changeEvent(startTextValue.value)
114
+ return
115
+ }
116
+ // 当前秒的时间戳
117
+ const nowTimestamp = Math.floor(+new Date() / 1000)
118
+ // 判断当前的时间戳,是否小于上一次的本该按设定结束,却提前结束的时间戳
119
+ if (p.keepRunning && lastTimestamp && lastTimestamp > nowTimestamp) {
120
+ // 剩余尚未执行完的倒计秒数
121
+ secNum.value = lastTimestamp - nowTimestamp
122
+ // 清除本地保存的变量
123
+ uni.removeStorageSync(p.uniqueKey + '_$uCountDownTimestamp')
124
+ // 开始倒计时
125
+ start()
126
+ } else {
127
+ // 如果不存在需要继续上一次的倒计时,执行正常的逻辑
128
+ changeEvent(startTextValue.value)
129
+ }
130
+ }
131
+
132
+ watch(
133
+ () => p.seconds,
134
+ (n: any) => {
135
+ secNum.value = n
136
+ },
137
+ { immediate: true }
138
+ )
139
+
140
+ onMounted(() => {
141
+ checkKeepRunning()
142
+ })
143
+
144
+ onUnmounted(() => {
145
+ setTimeToStorage()
146
+ clearInterval(timer)
147
+ timer = null
148
+ })
149
+
150
+ defineExpose({
151
+ start,
152
+ reset,
153
+ // 父级(merchant login/forgot/change-password)通过 ref 读取 canGetCode 控制按钮 disabled/文案
154
+ canGetCode
155
+ })
156
+ </script>
@@ -0,0 +1,56 @@
1
+ // @ts-nocheck
2
+
3
+ import config from '../../utils/config';
4
+
5
+ const props = {
6
+ modelValue: { type: Boolean, default: false },
7
+ mode: { type: String, default: 'center' },
8
+ size: { type: String, default: 'normal' },
9
+ title: { type: String, default: '' },
10
+ content: { type: String, default: '' },
11
+ showHeader: { type: Boolean, default: true },
12
+ showTitle: { type: Boolean, default: true },
13
+ showContent: { type: Boolean, default: true },
14
+ showFooter: { type: Boolean, default: true },
15
+ showCancelButton: { type: Boolean, default: true },
16
+ showConfirmButton: { type: Boolean, default: true },
17
+ titleAlign: { type: String, default: 'center' },
18
+ titleBold: { type: Boolean, default: true },
19
+ align: { type: String, default: 'left' },
20
+ width: { type: [String, Number], default: 325 },
21
+ cancelText: { type: String, default: '' },
22
+ confirmText: { type: String, default: '' },
23
+ headerBorder: { type: Boolean, default: false },
24
+ footerBorder: { type: Boolean, default: false },
25
+ closeOnConfirm: { type: Boolean, default: true },
26
+ closeOnCancel: { type: Boolean, default: true },
27
+ cancelProps: { type: Object, default: () => ({}) },
28
+ confirmProps: { type: Object, default: () => ({}) },
29
+ buttonProps: { type: Object, default: () => ({}) },
30
+ headerStyle: { type: Object, default: () => ({}) },
31
+ titleStyle: { type: Object, default: () => ({}) },
32
+ footerStyle: { type: Object, default: () => ({}) },
33
+ contentStyle: { type: Object, default: () => ({}) },
34
+ scrollStyle: { type: Object, default: () => ({}) },
35
+ customStyle: { type: [Object, String], default: () => ({}) },
36
+ customClass: { type: String, default: '' },
37
+ // 透传 uv-popup
38
+ duration: { type: [String, Number], default: 300 },
39
+ overlay: { type: Boolean, default: true },
40
+ overlayOpacity: { type: [Number, String], default: 0.4 },
41
+ overlayStyle: { type: [Object, String], default: '' },
42
+ closeOnClickOverlay: { type: Boolean, default: true },
43
+ zIndex: { type: [Number, String], default: 10075 },
44
+ closeable: { type: Boolean, default: false },
45
+ closeIconPos: { type: String, default: 'top-right' },
46
+ closeIconColor: { type: String, default: '#909399' },
47
+ closeIconSize: { type: [String, Number], default: 18 },
48
+ bgColor: { type: String, default: '#ffffff' },
49
+ round: { type: [Number, String], default: 8 },
50
+ zoom: { type: Boolean, default: true },
51
+ safeAreaInsetBottom: { type: Boolean, default: true },
52
+
53
+ ...config.props.dialog,
54
+ };
55
+
56
+ export default props;
@@ -0,0 +1,492 @@
1
+ <template>
2
+ <uv-popup
3
+ ref="popupRef"
4
+ :mode="cfg.mode"
5
+ :duration="Number(cfg.duration)"
6
+ :z-index="cfg.zIndex"
7
+ :bg-color="cfg.bgColor"
8
+ :overlay="cfg.overlay"
9
+ :overlay-opacity="cfg.overlayOpacity"
10
+ :overlay-style="cfg.overlayStyle"
11
+ :close-on-click-overlay="cfg.closeOnClickOverlay"
12
+ :closeable="cfg.closeable"
13
+ :close-icon-pos="cfg.closeIconPos"
14
+ :close-icon-color="cfg.closeIconColor"
15
+ :close-icon-size="cfg.closeIconSize"
16
+ :zoom="cfg.zoom"
17
+ :round="cfg.round"
18
+ :safe-area-inset-bottom="cfg.safeAreaInsetBottom && cfg.mode === 'bottom'"
19
+ @change="onPopupChange"
20
+ @close="onPopupClose"
21
+ >
22
+ <view class="uv-dialog" :class="[p.customClass]" :style="[dialogStyle]">
23
+ <slot v-if="cfg.showHeader" name="header">
24
+ <view class="uv-dialog__header" :class="[headerClass]" :style="[headerStyleCom]">
25
+ <text
26
+ v-if="cfg.showTitle"
27
+ class="uv-dialog__title"
28
+ :class="[cfg.titleBold ? 'uv-dialog__title--bold' : '']"
29
+ :style="[titleStyleCom]"
30
+ >{{ titleText }}</text>
31
+ </view>
32
+ </slot>
33
+ <slot v-if="cfg.showContent" name="content">
34
+ <scroll-view
35
+ <!-- #ifdef UNI-APP-X -->
36
+ direction="vertical"
37
+ <!-- #endif -->
38
+ <!-- #ifndef UNI-APP-X -->
39
+ scroll-y="true"
40
+ <!-- #endif -->
41
+ class="uv-dialog__scroll"
42
+ :style="[scrollStyleCom]"
43
+ >
44
+ <view class="uv-dialog__content" :class="[contentClass]" :style="[contentStyleCom]">
45
+ <slot>
46
+ <text class="uv-dialog__content-text" :style="[contentTextStyle]">{{ cfg.content }}</text>
47
+ </slot>
48
+ </view>
49
+ </scroll-view>
50
+ </slot>
51
+ <slot v-if="cfg.showFooter" name="footer">
52
+ <view
53
+ v-if="showCancelBtn || showConfirmBtn"
54
+ class="uv-dialog__footer"
55
+ :class="[cfg.footerBorder ? 'uv-dialog__footer--border' : '']"
56
+ :style="[footerStyleCom]"
57
+ >
58
+ <view v-if="showCancelBtn" class="uv-dialog__footer-btn">
59
+ <uv-button
60
+ :text="cancelTextValue"
61
+ :type="cancelButton.type"
62
+ :size="cancelButton.size"
63
+ :shape="cancelButton.shape"
64
+ :plain="cancelButton.plain"
65
+ :hairline="cancelButton.hairline"
66
+ :disabled="cancelButton.disabled"
67
+ :loading="cancelButton.loading"
68
+ :loading-mode="cancelButton.loadingMode"
69
+ :custom-style="cancelButton.customStyle"
70
+ @click="onCancel"
71
+ />
72
+ </view>
73
+ <view v-if="showCancelBtn && showConfirmBtn" class="uv-dialog__footer-gap" :style="[gapStyle]"></view>
74
+ <view v-if="showConfirmBtn" class="uv-dialog__footer-btn">
75
+ <uv-button
76
+ :text="confirmTextValue"
77
+ :type="confirmButton.type"
78
+ :size="confirmButton.size"
79
+ :shape="confirmButton.shape"
80
+ :plain="confirmButton.plain"
81
+ :hairline="confirmButton.hairline"
82
+ :disabled="confirmButton.disabled"
83
+ :loading="confirmButton.loading"
84
+ :loading-mode="confirmButton.loadingMode"
85
+ :custom-style="confirmButton.customStyle"
86
+ @click="onConfirm"
87
+ />
88
+ </view>
89
+ </view>
90
+ </slot>
91
+ </view>
92
+ </uv-popup>
93
+ </template>
94
+
95
+ <script setup lang="ts">
96
+ import { computed, nextTick, onMounted, ref, watch } from 'vue'
97
+ import { $uv } from '../../utils'
98
+ import props from './props'
99
+ import { useI18n } from '../../hooks'
100
+
101
+ defineOptions({
102
+ name: 'uv-dialog',
103
+ // #ifdef UNI-APP-X
104
+ styleIsolation: 'app-and-page',
105
+ // #endif
106
+ virtualHost: true,
107
+ })
108
+
109
+ /**
110
+ * Dialog 业务对话框
111
+ * @description 基于 uv-popup 的业务对话框:标题/内容/底部取消确认/关闭/确认回调,支持 ref.open() 返回 Promise;
112
+ * 标题与按钮内置文案支持 i18n(内置 zh-Hans / en,可通过 createI18n messages 覆盖 uv.dialog.*)
113
+ * @tutorial https://www.uvui.cn/components/popup.html
114
+ * @property {Boolean} modelValue 是否显示(v-model)
115
+ * @property {String} mode 弹出方式,默认 center
116
+ * @property {String} size 尺寸,mini/normal/large
117
+ * @property {String} title 标题,默认 ''(使用内置文案「提示」)
118
+ * @property {String} content 内容文本
119
+ * @property {Boolean} showHeader 是否显示头部
120
+ * @property {Boolean} showTitle 是否显示标题
121
+ * @property {Boolean} showContent 是否显示内容
122
+ * @property {Boolean} showFooter 是否显示底部
123
+ * @property {Boolean} showCancelButton 是否显示取消按钮,默认 true(隐藏按钮请用它,不要传 false 文案)
124
+ * @property {Boolean} showConfirmButton 是否显示确认按钮,默认 true(隐藏按钮请用它,不要传 false 文案)
125
+ * @property {String} titleAlign 标题对齐,left/center/right/between/around
126
+ * @property {Boolean} titleBold 标题加粗
127
+ * @property {String} align 内容对齐,left/center/right
128
+ * @property {String | Number} width center 模式宽度,默认 325
129
+ * @property {String} cancelText 取消文案,默认 ''(使用内置「取消」)
130
+ * @property {String} confirmText 确认文案,默认 ''(使用内置「确定」)
131
+ * @property {Boolean} headerBorder 头部下边框
132
+ * @property {Boolean} footerBorder 底部上边框
133
+ * @property {Boolean} closeOnConfirm 确认后是否自动关闭
134
+ * @property {Boolean} closeOnCancel 取消后是否自动关闭
135
+ * @property {Object} cancelProps 取消按钮 props 覆盖
136
+ * @property {Object} confirmProps 确认按钮 props 覆盖
137
+ * @property {Object} buttonProps 底部按钮公共 props
138
+ * @property {Object} headerStyle 头部样式
139
+ * @property {Object} titleStyle 标题样式
140
+ * @property {Object} footerStyle 底部样式
141
+ * @property {Object} contentStyle 内容区样式
142
+ * @property {Object} scrollStyle 滚动区样式
143
+ * @property {Object} customStyle 最外层样式
144
+ * @property {String} customClass 最外层 class
145
+ * @event {Function} open 对话框打开
146
+ * @event {Function} close 对话框关闭
147
+ * @event {Function} confirm 点击确认
148
+ * @event {Function} cancel 点击取消
149
+ * @event {Function} change 弹层状态变化
150
+ */
151
+ const p = defineProps(props)
152
+
153
+ const emit = defineEmits(['update:modelValue', 'open', 'close', 'confirm', 'cancel', 'change'])
154
+
155
+ interface DialogMetrics {
156
+ titleSize: number
157
+ contentSize: number
158
+ lineHeight: number
159
+ padding: number
160
+ gap: number
161
+ }
162
+
163
+ const sizeMap: Record<string, DialogMetrics> = {
164
+ mini: { titleSize: 14, contentSize: 12, lineHeight: 18, padding: 12, gap: 8 },
165
+ normal: { titleSize: 17, contentSize: 14, lineHeight: 20, padding: 16, gap: 12 },
166
+ large: { titleSize: 19, contentSize: 16, lineHeight: 24, padding: 20, gap: 16 }
167
+ }
168
+
169
+ const popupRef = ref<any>(null)
170
+ const visible = ref(false)
171
+ const overrides = ref<Record<string, any>>({})
172
+ let resolveFn: ((value: any) => void) | null = null
173
+ let pendingAction: 'confirm' | 'cancel' | 'close' = 'close'
174
+
175
+ const cfg = computed<Record<string, any>>(() => Object.assign({}, p, overrides.value))
176
+ const metrics = computed<DialogMetrics>(() => sizeMap[String(cfg.value.size)] || sizeMap.normal)
177
+
178
+ // 内置文案:props / open(opts) 覆盖优先,否则使用内置 i18n(locales/ 的 uv.dialog.*)
179
+ const { t } = useI18n({ useScope: 'global' })
180
+ const titleText = computed<string>(() => String(cfg.value.title || '') || t('uv.dialog.title'))
181
+ const cancelTextValue = computed<string>(() => String(cfg.value.cancelText || '') || t('uv.dialog.cancel'))
182
+ const confirmTextValue = computed<string>(() => String(cfg.value.confirmText || '') || t('uv.dialog.confirm'))
183
+ // 按钮显隐只由 show*Button 控制(文案只能是 String,不能传 false 隐藏按钮)
184
+ const showCancelBtn = computed(() => Boolean(cfg.value.showCancelButton))
185
+ const showConfirmBtn = computed(() => Boolean(cfg.value.showConfirmButton))
186
+
187
+ const addUnit = (value: string | number) => $uv.addUnit(value)
188
+
189
+ const titleAlignValue = computed(() => {
190
+ const map: Record<string, string> = {
191
+ left: 'flex-start',
192
+ center: 'center',
193
+ right: 'flex-end',
194
+ between: 'space-between',
195
+ around: 'space-around'
196
+ }
197
+ return map[String(cfg.value.titleAlign)] || 'center'
198
+ })
199
+
200
+ const headerClass = computed(() => ['uv-dialog__header--' + (cfg.value.titleAlign || 'center')])
201
+ const contentClass = computed(() => ['uv-dialog__content--' + (cfg.value.align || 'left')])
202
+
203
+ const dialogStyle = computed(() => {
204
+ const style: any = {}
205
+ if (cfg.value.mode === 'center') {
206
+ style.width = addUnit(cfg.value.width)
207
+ }
208
+ return $uv.deepMerge(style, $uv.addStyle(cfg.value.customStyle))
209
+ })
210
+
211
+ const headerStyleCom = computed(() => {
212
+ const m = metrics.value
213
+ const style: any = {
214
+ paddingTop: m.padding + 'px',
215
+ paddingLeft: m.padding + 'px',
216
+ paddingRight: m.padding + 'px',
217
+ paddingBottom: Math.round(m.padding / 2) + 'px',
218
+ justifyContent: titleAlignValue.value
219
+ }
220
+ if (cfg.value.headerBorder) {
221
+ style.borderBottomWidth = '1px'
222
+ style.borderBottomStyle = 'solid'
223
+ style.borderBottomColor = '#f2f3f5'
224
+ }
225
+ return $uv.deepMerge(style, $uv.addStyle(cfg.value.headerStyle))
226
+ })
227
+
228
+ const titleStyleCom = computed(() => {
229
+ const m = metrics.value
230
+ const style: any = {
231
+ fontSize: m.titleSize + 'px',
232
+ lineHeight: m.titleSize + 8 + 'px',
233
+ color: '#303133'
234
+ }
235
+ return $uv.deepMerge(style, $uv.addStyle(cfg.value.titleStyle))
236
+ })
237
+
238
+ const scrollStyleCom = computed(() => {
239
+ const style: any = {}
240
+ // #ifdef WEB || H5
241
+ style.maxHeight = '60vh'
242
+ // #endif
243
+ // #ifndef WEB || H5
244
+ try {
245
+ const info = $uv.sys()
246
+ style.maxHeight = Math.floor(Number(info.windowHeight || 800) * 0.6) + 'px'
247
+ } catch (e) {
248
+ style.maxHeight = '500px'
249
+ }
250
+ // #endif
251
+ return $uv.deepMerge(style, $uv.addStyle(cfg.value.scrollStyle))
252
+ })
253
+
254
+ const contentStyleCom = computed(() => {
255
+ const m = metrics.value
256
+ const style: any = {
257
+ paddingLeft: m.padding + 'px',
258
+ paddingRight: m.padding + 'px',
259
+ paddingTop: Math.round(m.padding / 2) + 'px',
260
+ paddingBottom: m.padding + 'px'
261
+ }
262
+ const align = String(cfg.value.align || 'left')
263
+ const textAlign = align === 'center' ? 'center' : align === 'right' ? 'right' : 'left'
264
+ style.textAlign = textAlign
265
+ style.alignItems = textAlign === 'center' ? 'center' : textAlign === 'right' ? 'flex-end' : 'flex-start'
266
+ return $uv.deepMerge(style, $uv.addStyle(cfg.value.contentStyle))
267
+ })
268
+
269
+ const contentTextStyle = computed(() => {
270
+ const m = metrics.value
271
+ const style: any = {
272
+ fontSize: m.contentSize + 'px',
273
+ lineHeight: m.lineHeight + 'px',
274
+ color: '#606266'
275
+ }
276
+ const align = String(cfg.value.align || 'left')
277
+ style.textAlign = align === 'center' ? 'center' : align === 'right' ? 'right' : 'left'
278
+ return $uv.deepMerge(style, $uv.addStyle(cfg.value.contentStyle))
279
+ })
280
+
281
+ const footerStyleCom = computed(() => {
282
+ const m = metrics.value
283
+ const style: any = {
284
+ paddingLeft: m.padding + 'px',
285
+ paddingRight: m.padding + 'px',
286
+ paddingBottom: m.padding + 'px',
287
+ paddingTop: m.padding + 'px'
288
+ }
289
+ if (cfg.value.footerBorder) {
290
+ style.borderTopWidth = '1px'
291
+ style.borderTopStyle = 'solid'
292
+ style.borderTopColor = '#f2f3f5'
293
+ }
294
+ return $uv.deepMerge(style, $uv.addStyle(cfg.value.footerStyle))
295
+ })
296
+
297
+ const gapStyle = computed(() => ({ width: metrics.value.gap + 'px' }))
298
+
299
+ const resolveButtonProps = (specific: Record<string, any>, isCancel: boolean) => {
300
+ const base: any = cfg.value.buttonProps || {}
301
+ const extra: any = specific || {}
302
+ return {
303
+ type: extra.type !== undefined ? extra.type : base.type !== undefined ? base.type : 'primary',
304
+ size: extra.size !== undefined ? extra.size : base.size !== undefined ? base.size : cfg.value.size,
305
+ shape: extra.shape !== undefined ? extra.shape : base.shape !== undefined ? base.shape : 'square',
306
+ plain: extra.plain !== undefined ? extra.plain : base.plain !== undefined ? base.plain : isCancel,
307
+ hairline: extra.hairline !== undefined ? extra.hairline : base.hairline !== undefined ? base.hairline : true,
308
+ disabled: extra.disabled !== undefined ? extra.disabled : base.disabled !== undefined ? base.disabled : false,
309
+ loading: extra.loading !== undefined ? extra.loading : base.loading !== undefined ? base.loading : false,
310
+ loadingMode:
311
+ extra.loadingMode !== undefined
312
+ ? extra.loadingMode
313
+ : base.loadingMode !== undefined
314
+ ? base.loadingMode
315
+ : 'spinner',
316
+ customStyle: Object.assign({}, base.customStyle || {}, extra.customStyle || {})
317
+ }
318
+ }
319
+
320
+ const cancelButton = computed(() => resolveButtonProps(cfg.value.cancelProps, true))
321
+ const confirmButton = computed(() => resolveButtonProps(cfg.value.confirmProps, false))
322
+
323
+ const finish = (payload: Record<string, any>) => {
324
+ if (resolveFn) {
325
+ resolveFn(payload)
326
+ resolveFn = null
327
+ }
328
+ }
329
+
330
+ const doOpen = (opts: Record<string, any> = {}) => {
331
+ overrides.value = Object.assign({}, opts)
332
+ if (visible.value) return
333
+ visible.value = true
334
+ emit('open')
335
+ emit('update:modelValue', true)
336
+ nextTick(() => {
337
+ popupRef.value && popupRef.value.open()
338
+ })
339
+ }
340
+
341
+ const doClose = () => {
342
+ pendingAction = 'close'
343
+ if (popupRef.value) {
344
+ popupRef.value.close()
345
+ } else {
346
+ visible.value = false
347
+ emit('update:modelValue', false)
348
+ finish({ close: true })
349
+ }
350
+ }
351
+
352
+ const open = (opts: Record<string, any> = {}) => {
353
+ return new Promise<Record<string, any>>((resolve) => {
354
+ resolveFn = resolve
355
+ doOpen(opts)
356
+ })
357
+ }
358
+
359
+ const close = () => {
360
+ doClose()
361
+ }
362
+
363
+ const confirm = () => {
364
+ emit('confirm')
365
+ pendingAction = 'confirm'
366
+ if (cfg.value.closeOnConfirm) {
367
+ doCloseByAction()
368
+ } else {
369
+ finish({ confirm: true })
370
+ }
371
+ }
372
+
373
+ const cancel = () => {
374
+ emit('cancel')
375
+ pendingAction = 'cancel'
376
+ if (cfg.value.closeOnCancel) {
377
+ doCloseByAction()
378
+ } else {
379
+ finish({ cancel: true })
380
+ }
381
+ }
382
+
383
+ // confirm/cancel 触发关闭时保留 pendingAction,避免被 doClose 重置为 close
384
+ const doCloseByAction = () => {
385
+ if (popupRef.value) {
386
+ popupRef.value.close()
387
+ } else {
388
+ onPopupClose()
389
+ }
390
+ }
391
+
392
+ const onConfirm = () => {
393
+ confirm()
394
+ }
395
+
396
+ const onCancel = () => {
397
+ cancel()
398
+ }
399
+
400
+ const onPopupChange = (e: any) => {
401
+ emit('change', e)
402
+ }
403
+
404
+ const onPopupClose = () => {
405
+ const wasVisible = visible.value
406
+ visible.value = false
407
+ emit('update:modelValue', false)
408
+ overrides.value = {}
409
+ if (wasVisible) emit('close')
410
+ const action = pendingAction
411
+ pendingAction = 'close'
412
+ if (action === 'confirm') finish({ confirm: true })
413
+ else if (action === 'cancel') finish({ cancel: true })
414
+ else finish({ close: true })
415
+ }
416
+
417
+ watch(
418
+ () => p.modelValue,
419
+ (value) => {
420
+ if (value) {
421
+ if (!visible.value) doOpen()
422
+ } else {
423
+ if (visible.value) doClose()
424
+ }
425
+ }
426
+ )
427
+
428
+ onMounted(() => {
429
+ if (p.modelValue) doOpen()
430
+ })
431
+
432
+ defineExpose({
433
+ open,
434
+ close,
435
+ confirm,
436
+ cancel
437
+ })
438
+ </script>
439
+
440
+ <style lang="scss">
441
+ @import '../../styles/color.scss';
442
+
443
+ .uv-dialog {
444
+ /* #ifndef APP-NVUE */
445
+ box-sizing: border-box;
446
+ display: flex;
447
+ /* #endif */
448
+ flex-direction: column;
449
+ }
450
+
451
+ .uv-dialog__header {
452
+ /* #ifndef APP-NVUE */
453
+ display: flex;
454
+ /* #endif */
455
+ flex-direction: row;
456
+ align-items: center;
457
+ }
458
+
459
+ .uv-dialog__title--bold {
460
+ font-weight: bold;
461
+ }
462
+
463
+ .uv-dialog__scroll {
464
+ /* #ifndef APP-NVUE */
465
+ display: flex;
466
+ /* #endif */
467
+ flex-direction: column;
468
+ }
469
+
470
+ .uv-dialog__content {
471
+ /* #ifndef APP-NVUE */
472
+ display: flex;
473
+ /* #endif */
474
+ flex-direction: column;
475
+ }
476
+
477
+ .uv-dialog__footer {
478
+ /* #ifndef APP-NVUE */
479
+ display: flex;
480
+ /* #endif */
481
+ flex-direction: row;
482
+ align-items: center;
483
+ }
484
+
485
+ .uv-dialog__footer-btn {
486
+ flex: 1;
487
+ /* #ifndef APP-NVUE */
488
+ display: flex;
489
+ /* #endif */
490
+ flex-direction: column;
491
+ }
492
+ </style>