DialogInner.vue 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. <template>
  2. <!--TODO: 在uniapp插槽问题修复后,此处可修改为插槽默认值-->
  3. <FlexCol :innerStyle="{
  4. ...themeStyles.dialog.value,
  5. width: themeContext.resolveThemeSize(width)
  6. }">
  7. <slot v-if="topSlots?.default" />
  8. <FlexCol v-else :padding="contentPadding" align="center">
  9. <!-- 图标 -->
  10. <FlexCol v-if="icon" :innerStyle="themeStyles.icon.value">
  11. <slot name="icon" :icon="icon" />
  12. <Icon :icon="icon" :color="iconColor" :size="iconSize || 40" />
  13. </FlexCol>
  14. <!-- 标题 -->
  15. <slot v-if="topSlots?.title" name="title" :title="title" />
  16. <text v-else-if="title" :style="themeStyles.title.value">{{ title }}</text>
  17. <!-- 内容 -->
  18. <scroll-view
  19. v-if="contentScroll"
  20. scroll-y
  21. scroll-x
  22. :style="{
  23. position: 'relative',
  24. maxHeight: contentScrollMaxHeight
  25. }"
  26. >
  27. <slot v-if="topSlots?.content" name="content" />
  28. <text :style="themeStyles.contentText.value">{{ content }}</text>
  29. </scroll-view>
  30. <template v-else>
  31. <slot v-if="topSlots?.content" name="content" />
  32. <text v-else :style="themeStyles.contentText.value">{{ content }}</text>
  33. </template>
  34. </FlexCol>
  35. <!-- 底部按钮 -->
  36. <slot
  37. v-if="topSlots?.bottomContent"
  38. name="bottomContent"
  39. :onConfirmClick="(name?: string) => onConfirmClick(name || 'confirm')"
  40. :onCancelClick="onCancelClick"
  41. />
  42. <FlexView v-else :direction="bottomVertical ? 'column' : 'row'" :innerStyle="themeStyles.bottomView.value">
  43. <DialogButton
  44. v-if="showCancel"
  45. key="cancel"
  46. :vertical="bottomVertical"
  47. :text="cancelText"
  48. :loading="buttomLoadingState.cancel"
  49. :buttonColor="cancelColor"
  50. @click="onCancelClick"
  51. />
  52. <DialogButton
  53. v-for="(button, key) in customButtons"
  54. :vertical="bottomVertical"
  55. :key="key"
  56. :text="button.text"
  57. :loading="buttomLoadingState[button.name]"
  58. :buttonColor="button.color || 'text.content'"
  59. @click="onConfirmClick(button.name)"
  60. />
  61. <DialogButton
  62. v-if="showConfirm"
  63. key="confirm"
  64. :vertical="bottomVertical"
  65. :text="confirmCountDownValue > 0 ? `${confirmText} (${confirmCountDownValue})` : confirmText"
  66. :loading="buttomLoadingState.confirm"
  67. :touchable="confirmCountDownValue <= 0"
  68. :buttonColor="confirmColor"
  69. @click="onConfirmClick('confirm')"
  70. />
  71. </FlexView>
  72. </FlexCol>
  73. </template>
  74. <script setup lang="ts">
  75. import { onMounted, ref } from 'vue';
  76. import { propGetThemeVar, useTheme } from '../theme/ThemeDefine';
  77. import { DynamicColor, DynamicSize, DynamicVar } from '../theme/ThemeTools';
  78. import FlexView from '../layout/FlexView.vue';
  79. import FlexCol from '../layout/FlexCol.vue';
  80. import Icon from '../basic/Icon.vue';
  81. import DialogButton from './DialogButton.vue';
  82. import type { DialogProps } from './Dialog.vue';
  83. const themeContext = useTheme();
  84. const themeStyles = themeContext.useThemeStyles({
  85. dialog: {
  86. minWidth: DynamicSize('DialogMinWidth', 400),
  87. maxWidth: DynamicSize('DialogMaxWidth', 700),
  88. },
  89. bottomView: {
  90. position: 'relative',
  91. },
  92. icon: {
  93. marginTop: DynamicSize('DialogIconMarginTop', 16),
  94. marginBottom: DynamicSize('DialogIconMarginBottom', 12),
  95. justifyContent: 'center',
  96. alignItems: 'center',
  97. },
  98. title: {
  99. fontSize: DynamicSize('DialogTitleFontSize', 36),
  100. color: DynamicColor('DialogTitleColor', 'text.content'),
  101. fontWeight: DynamicSize('DialogTitleFontWeight', 'bold'),
  102. textAlign: 'center',
  103. marginBottom: DynamicSize('DialogTitleMarginBottom', 20),
  104. },
  105. contentText: {
  106. width: '100%',
  107. display: 'block',
  108. fontSize: DynamicSize('DialogContentTextFontSize', 28),
  109. color: DynamicColor('DialogContentTextColor', 'text.second'),
  110. textAlign: DynamicVar('DialogContentTextAlign', 'center'),
  111. },
  112. });
  113. export interface DialogInnerProps extends Omit<DialogProps, 'show'> {
  114. topSlots?: Record<string, boolean>,
  115. confirmCountDownTime?: number,
  116. }
  117. const emit = defineEmits([ 'close' ]);
  118. const props = withDefaults(defineProps<DialogInnerProps>(), {
  119. showConfirm: true,
  120. cancelText: '取消',
  121. cancelColor: () => propGetThemeVar('DialogCancelColor', 'text.content'),
  122. confirmText: '确定',
  123. confirmColor: () => propGetThemeVar('DialogConfirmColor', 'primary'),
  124. iconSize: () => propGetThemeVar('DialogIconSize', 70),
  125. iconColor: () => propGetThemeVar('DialogIconColor', 'primary'),
  126. contentScroll: true,
  127. contentScrollMaxHeight: () => propGetThemeVar('DialogContentScrollMaxHeight', '1000rpx'),
  128. contentPadding: () => propGetThemeVar('DialogContentPadding', [ 30, 40 ]),
  129. });
  130. const buttomLoadingState = ref<Record<string, boolean>>({});
  131. const confirmCountDownValue = ref<number>(0);
  132. function setButtonLoadingStateByName(name: string, state: boolean) {
  133. buttomLoadingState.value[name] = state;
  134. }
  135. function checkAnyButtonLoading() {
  136. for (const key in buttomLoadingState.value) {
  137. if (buttomLoadingState.value[key] === true)
  138. return true;
  139. }
  140. return false;
  141. }
  142. function startConfirmCountDown() {
  143. if (!props.confirmCountDownTime)
  144. return;
  145. confirmCountDownValue.value = props.confirmCountDownTime;
  146. const interval = setInterval(() => {
  147. confirmCountDownValue.value--;
  148. if (confirmCountDownValue.value <= 0) {
  149. clearInterval(interval);
  150. }
  151. }, 1000);
  152. }
  153. function onPopupClose() {
  154. emit('close');
  155. }
  156. function onCancelClick() {
  157. if (checkAnyButtonLoading())
  158. return;
  159. if (!props.onCancel) {
  160. onPopupClose();
  161. return;
  162. }
  163. const ret = props.onCancel();
  164. if (typeof ret === 'object') {
  165. setButtonLoadingStateByName('cancel', true);
  166. ret.then(() => {
  167. setButtonLoadingStateByName('cancel', false);
  168. onPopupClose();
  169. }).catch(() => {
  170. setButtonLoadingStateByName('cancel', false);
  171. });
  172. } else onPopupClose();
  173. }
  174. function onConfirmClick(name: string) {
  175. if (checkAnyButtonLoading())
  176. return;
  177. if (!props.onConfirm) {
  178. onPopupClose();
  179. return;
  180. }
  181. const ret = props.onConfirm(name);
  182. if (typeof ret === 'object') {
  183. setButtonLoadingStateByName(name, true);
  184. ret.then(() => {
  185. setButtonLoadingStateByName(name, false);
  186. onPopupClose();
  187. }).catch(() => {
  188. setButtonLoadingStateByName(name, false);
  189. });
  190. } else onPopupClose();
  191. }
  192. onMounted(() => {
  193. setTimeout(() => {
  194. startConfirmCountDown();
  195. }, 200);
  196. });
  197. defineExpose({
  198. startConfirmCountDown,
  199. })
  200. </script>