|
@@ -1,548 +0,0 @@
|
|
|
-<script lang="ts" setup>
|
|
|
-import {useZIndex, ElIcon, ElInput, ElSelect, ElOption} from "element-plus";
|
|
|
-import {nextTick, onMounted, ref, h, computed} from "vue";
|
|
|
-import sleep from "@/utils/sleep";
|
|
|
-// @ts-ignore
|
|
|
-import {
|
|
|
- Close,
|
|
|
- Warning,
|
|
|
- SuccessFilled,
|
|
|
- InfoFilled,
|
|
|
- CircleCloseFilled,
|
|
|
- DeleteFilled
|
|
|
-} from '@element-plus/icons-vue'
|
|
|
-import {useCompRef} from "@/utils/useCompRef";
|
|
|
-import {useCyDraggable} from "@/utils/use-cy-draggable";
|
|
|
-import {uuid} from "@/utils/getUuid";
|
|
|
-import ElFocusTrap from "@/components/xiao-chan/focus-trap/src/focus-trap.vue";
|
|
|
-
|
|
|
-const props = withDefaults(defineProps<{
|
|
|
- message?: string;
|
|
|
- showCancel?: boolean,
|
|
|
- confirmClick?: (value: string) => void;
|
|
|
- cancel?: (val: string) => void;
|
|
|
- type?: 'success' | 'error' | 'warning' | 'info',
|
|
|
- confirmButtonText?: string,
|
|
|
- cancelButtonText?: string,
|
|
|
- showInput?: boolean,
|
|
|
- inputMinLength?: number,
|
|
|
- inputMaxLength?: number,
|
|
|
- inputValidator?: (val: string) => boolean | string,
|
|
|
- inputErrorMessage?: string,
|
|
|
- inputDefaultValue?: string,
|
|
|
- setClose?: (val: any) => void,
|
|
|
- inputRows?: number,
|
|
|
- boxId?: string,
|
|
|
- allowToBeEmpty?: boolean,
|
|
|
- dangerouslyUseHTMLString?: boolean,
|
|
|
- title?: string,
|
|
|
- draggable?: boolean,
|
|
|
- selectOption?: {
|
|
|
- value: string,
|
|
|
- label: string
|
|
|
- }[],
|
|
|
- closeOnPressEscape?: boolean;
|
|
|
- closeOnClickModal?: boolean
|
|
|
-}>(), {
|
|
|
- type: 'info',
|
|
|
- showCancel: true,
|
|
|
- confirmButtonText: '确认',
|
|
|
- cancelButtonText: '取消',
|
|
|
- inputErrorMessage: '校验未通过',
|
|
|
- allowToBeEmpty: false,
|
|
|
- dangerouslyUseHTMLString: false,
|
|
|
- draggable: true,
|
|
|
- inputDefaultValue: '',
|
|
|
- selectOption: null,
|
|
|
- inputRows: 0,
|
|
|
- inputMaxLength: 0,
|
|
|
- closeOnPressEscape: true,
|
|
|
- closeOnClickModal: true
|
|
|
-})
|
|
|
-
|
|
|
-const visible = ref(false)
|
|
|
-const headerRef = ref<HTMLHeadElement>(null)
|
|
|
-const inputValue = ref(props.inputDefaultValue)
|
|
|
-const errorMsg = ref('')
|
|
|
-const inputRef = useCompRef(ElInput)
|
|
|
-const confirmRef = ref<HTMLElement>()
|
|
|
-const bodyRef = ref<HTMLElement>()
|
|
|
-const mainRef = ref<HTMLElement>()
|
|
|
-const focusStartRef = ref<HTMLElement>()
|
|
|
-const isSelectInput = ref<boolean>(props.selectOption !== null)
|
|
|
-const inputId = ref('cy-' + uuid(5, 62))
|
|
|
-
|
|
|
-let closeMode = ''
|
|
|
-
|
|
|
-const mainStyle = ref({
|
|
|
- zIndex: 0,
|
|
|
- display: 'flex'
|
|
|
-})
|
|
|
-
|
|
|
-const contentStyle = ref({
|
|
|
- zIndex: 0,
|
|
|
-})
|
|
|
-
|
|
|
-function nextZIndex() {
|
|
|
- return useZIndex().nextZIndex()
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * 关闭动画
|
|
|
- */
|
|
|
-function onAfterLeave() {
|
|
|
- mainStyle.value.display = 'none'
|
|
|
- if (!visible.value) {
|
|
|
- if (closeMode === 'close' || closeMode === 'cancel') {
|
|
|
- props.cancel(closeMode)
|
|
|
- } else {
|
|
|
- props.confirmClick(inputValue.value)
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * 开启动画
|
|
|
- */
|
|
|
-function onAfterEnter() {
|
|
|
- mainStyle.value.display = 'flex';
|
|
|
-}
|
|
|
-
|
|
|
-function handleConfirm() {
|
|
|
- if (props.showInput) {
|
|
|
- handlePrompt()
|
|
|
- } else {
|
|
|
- visible.value = false;
|
|
|
- closeMode = ''
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-function verificationFailed(value: string) {
|
|
|
- errorMsg.value = value
|
|
|
- popupJitter()
|
|
|
-}
|
|
|
-
|
|
|
-function handlePrompt() {
|
|
|
- function next() {
|
|
|
- visible.value = false;
|
|
|
- closeMode = ''
|
|
|
- }
|
|
|
-
|
|
|
- if (!props.allowToBeEmpty) {
|
|
|
- if (inputValue.value === '') {
|
|
|
- verificationFailed('不允许为空。')
|
|
|
- return
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (props.inputMaxLength !== 0) {
|
|
|
- if (inputValue.value.length > props.inputMaxLength) {
|
|
|
- verificationFailed(`最大长度不得超过${props.inputMaxLength}个字。`)
|
|
|
- return
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- if (props.inputValidator) {
|
|
|
- const inputValidator = props.inputValidator(inputValue.value)
|
|
|
- if (typeof inputValidator === 'boolean') {
|
|
|
- if (inputValidator) {
|
|
|
- next()
|
|
|
- } else {
|
|
|
- verificationFailed(props.inputErrorMessage)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (typeof inputValidator === 'string') {
|
|
|
- if (inputValidator) {
|
|
|
- verificationFailed(inputValidator)
|
|
|
- } else {
|
|
|
- next()
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- } else {
|
|
|
- next()
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-function handleClose(val: 'close' | 'cancel') {
|
|
|
- bodyRef.value.style.transition = ''
|
|
|
- visible.value = false
|
|
|
- closeMode = val
|
|
|
-}
|
|
|
-
|
|
|
-const headerData = {
|
|
|
- success: {
|
|
|
- icon: h(SuccessFilled),
|
|
|
- title: '成功',
|
|
|
- color: '#67c23a'
|
|
|
- },
|
|
|
- warning: {
|
|
|
- icon: h(Warning),
|
|
|
- title: '警告',
|
|
|
- color: '#e6a23c'
|
|
|
- },
|
|
|
- info: {
|
|
|
- icon: h(InfoFilled),
|
|
|
- title: '提示',
|
|
|
- color: '#909399'
|
|
|
- },
|
|
|
- error: {
|
|
|
- icon: h(CircleCloseFilled),
|
|
|
- title: '错误',
|
|
|
- color: '#f56c6c'
|
|
|
- },
|
|
|
- delete: {
|
|
|
- icon: h(DeleteFilled),
|
|
|
- title: '删除',
|
|
|
- color: '#f56c6c'
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-function headerColor() {
|
|
|
- return headerData[props.type].color
|
|
|
-}
|
|
|
-
|
|
|
-function headerIcon() {
|
|
|
- return headerData[props.type].icon
|
|
|
-}
|
|
|
-
|
|
|
-function titleRender() {
|
|
|
- return h('span', {style: {marginLeft: '8px'}}, props.title || headerData[props.type].title)
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * 弹窗抖动
|
|
|
- */
|
|
|
-async function popupJitter() {
|
|
|
- const oldTransform = bodyRef.value.style.transform
|
|
|
- bodyRef.value.style.transform = oldTransform + ' scale(1.1)';
|
|
|
- await sleep(100)
|
|
|
- bodyRef.value.style.transform = oldTransform;
|
|
|
-}
|
|
|
-
|
|
|
-const draggable = computed(() => props.draggable)
|
|
|
-
|
|
|
-useCyDraggable(bodyRef, headerRef, draggable,
|
|
|
- () => {
|
|
|
- bodyRef.value.style.transition = 'none'
|
|
|
- inputRef.value.blur();
|
|
|
- }, () => {
|
|
|
- mainRef.value.parentElement.style.setProperty('--cy-body-transform', bodyRef.value.style.transform)
|
|
|
- bodyRef.value.style.transition = ''
|
|
|
- }
|
|
|
-)
|
|
|
-
|
|
|
-function onCloseRequested() {
|
|
|
- if (props.closeOnPressEscape) {
|
|
|
- handleClose('close')
|
|
|
- } else {
|
|
|
- popupJitter()
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-function maskClick() {
|
|
|
- if (props.closeOnClickModal) {
|
|
|
- handleClose('close')
|
|
|
- } else {
|
|
|
- popupJitter()
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-onMounted(async () => {
|
|
|
- mainStyle.value.zIndex = nextZIndex()
|
|
|
- contentStyle.value.zIndex = nextZIndex()
|
|
|
- document.body.className = 'el-popup-parent--hidden'
|
|
|
- await nextTick()
|
|
|
- if (props.showInput) {
|
|
|
- focusStartRef.value = mainRef.value
|
|
|
- } else {
|
|
|
- focusStartRef.value = confirmRef?.value ?? mainRef.value
|
|
|
- }
|
|
|
- await sleep(100)
|
|
|
- visible.value = true
|
|
|
- props.setClose(close)
|
|
|
-})
|
|
|
-
|
|
|
-function close() {
|
|
|
- handleClose('close')
|
|
|
-}
|
|
|
-</script>
|
|
|
-
|
|
|
-<template>
|
|
|
- <div class="cy-message-box_main"
|
|
|
- :style="mainStyle"
|
|
|
- ref="mainRef"
|
|
|
- role="dialog"
|
|
|
- aria-modal="true"
|
|
|
- @click.stop="maskClick">
|
|
|
- <el-focus-trap
|
|
|
- :trapped="visible"
|
|
|
- :focus-trap-el="mainRef"
|
|
|
- :focus-start-el="focusStartRef"
|
|
|
- @release-requested="onCloseRequested">
|
|
|
- <transition name="cy-message_show" @after-enter="onAfterEnter" @after-leave="onAfterLeave">
|
|
|
- <div class="cy-message-box_body"
|
|
|
- tabindex="-1"
|
|
|
- v-show="visible"
|
|
|
- :style="contentStyle"
|
|
|
- @click.stop
|
|
|
- ref="bodyRef">
|
|
|
- <div class="cy-message-box_close" @click.stop="handleClose('close')">
|
|
|
- <ElIcon>
|
|
|
- <Close/>
|
|
|
- </ElIcon>
|
|
|
- </div>
|
|
|
- <header ref="headerRef">
|
|
|
- <el-icon :style="{color: headerColor() }"
|
|
|
- class="cy-message_header-icon">
|
|
|
- <component :is="headerIcon"/>
|
|
|
- </el-icon>
|
|
|
- <Component :is="titleRender"/>
|
|
|
- </header>
|
|
|
- <div class="cy_message-box_content">
|
|
|
- <div class="message" v-if="props.message">
|
|
|
- <component
|
|
|
- v-if="props.dangerouslyUseHTMLString"
|
|
|
- :for="inputId"
|
|
|
- :is="props.showInput ? 'label' : 'span'"
|
|
|
- v-html="props.message">
|
|
|
- </component>
|
|
|
- <component v-else
|
|
|
- :is="props.showInput ? 'label' : 'span'"
|
|
|
- :for="inputId">
|
|
|
- {{ props.message }}
|
|
|
- </component>
|
|
|
- </div>
|
|
|
- <div v-if="props.showInput" style="margin-top: 9px">
|
|
|
- <ElSelect v-model="inputValue"
|
|
|
- v-if="isSelectInput"
|
|
|
- :id="inputId"
|
|
|
- style="width: 100%"
|
|
|
- :multiple="false"
|
|
|
- :filterable="true"
|
|
|
- :allow-create="true"
|
|
|
- :default-first-option="true"
|
|
|
- :reserve-keyword="false"
|
|
|
- ref="inputRef">
|
|
|
- <ElOption v-for="item in props.selectOption" :key="item.value" :label="item.label" :value="item.value"/>
|
|
|
- </ElSelect>
|
|
|
- <ElInput
|
|
|
- v-else
|
|
|
- ref="inputRef"
|
|
|
- :autofocus="true"
|
|
|
- :rows="props.inputRows"
|
|
|
- :type="props.inputRows === 0 ? '' : 'textarea'"
|
|
|
- :minlength="props.inputMinLength"
|
|
|
- :maxlength="props.inputMaxLength"
|
|
|
- :show-word-limit="true"
|
|
|
- :id="inputId"
|
|
|
- v-model.trim="inputValue"/>
|
|
|
- </div>
|
|
|
- <div v-if="props.showInput" class="error_msg">
|
|
|
- {{ errorMsg }}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <footer>
|
|
|
- <button class="cancel"
|
|
|
- style="margin-right: 12px"
|
|
|
- v-if="props.showCancel"
|
|
|
- @click="handleClose('cancel')"
|
|
|
- :style="{'--cy--bg-color': '245,108,108','--cy-color': '#F56C6C'}">
|
|
|
- {{ props.cancelButtonText }}
|
|
|
- </button>
|
|
|
- <button class="confirm"
|
|
|
- ref="confirmRef"
|
|
|
- :style="{'--cy--bg-color': '26, 92, 255','--cy-color': '#2C69FF'}"
|
|
|
- @click="handleConfirm">
|
|
|
- {{ props.confirmButtonText }}
|
|
|
- </button>
|
|
|
- </footer>
|
|
|
- </div>
|
|
|
- </transition>
|
|
|
- </el-focus-trap>
|
|
|
- </div>
|
|
|
-</template>
|
|
|
-
|
|
|
-<style lang="scss">
|
|
|
-.cy-message_show-enter-active {
|
|
|
- transform: scale(0.9);
|
|
|
-}
|
|
|
-
|
|
|
-.cy-message_show-enter-to {
|
|
|
- transform: scale(1);
|
|
|
-}
|
|
|
-
|
|
|
-.cy-message_show-leave-active {
|
|
|
- transform: var(--cy-body-transform) scale(1) !important;
|
|
|
-}
|
|
|
-
|
|
|
-.cy-message_show-leave-to {
|
|
|
- transform: var(--cy-body-transform) scale(0) !important;
|
|
|
-}
|
|
|
-
|
|
|
-.cy-message-box_main {
|
|
|
- background: rgba(0, 0, 0, .2);
|
|
|
- position: fixed;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
-
|
|
|
- .cy-message-box_body {
|
|
|
- display: inline-block;
|
|
|
- height: max-content;
|
|
|
- width: 418px;
|
|
|
- background-color: white;
|
|
|
- border-radius: 20px;
|
|
|
- padding: 5px;
|
|
|
- position: relative;
|
|
|
- transition: transform .2s ease-out;
|
|
|
-
|
|
|
- .cy-message-box_close {
|
|
|
- position: absolute;
|
|
|
- top: -6px;
|
|
|
- right: -6px;
|
|
|
- border: 0;
|
|
|
- height: 34px;
|
|
|
- width: 34px;
|
|
|
- box-sizing: border-box;
|
|
|
- background: inherit;
|
|
|
- border-radius: 12px;
|
|
|
- box-shadow: 0 5px 20px 0 rgba(0, 0, 0, .05);
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- font-size: 20px;
|
|
|
- color: black;
|
|
|
- cursor: pointer;
|
|
|
-
|
|
|
- &:hover {
|
|
|
- -webkit-box-shadow: 0 0 4px 0 rgba(0, 0, 0, .05);
|
|
|
- box-shadow: 0 0 4px 0 rgba(0, 0, 0, .05);
|
|
|
- transform: translate(-2px, 2px);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- header {
|
|
|
- cursor: move;
|
|
|
- user-select: none;
|
|
|
- display: -webkit-box;
|
|
|
- display: -ms-flexbox;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: start;
|
|
|
- padding: 10px 16px;
|
|
|
- box-sizing: border-box !important;
|
|
|
-
|
|
|
- span {
|
|
|
- margin-left: 8px;
|
|
|
- font-size: 20px;
|
|
|
- font-weight: bold;
|
|
|
- }
|
|
|
-
|
|
|
- .cy-message_header-icon {
|
|
|
- font-size: 24px;
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- .cy_message-box_content {
|
|
|
- padding: 10px 16px;
|
|
|
- width: 100%;
|
|
|
- position: relative;
|
|
|
- border-radius: inherit;
|
|
|
- box-sizing: border-box;
|
|
|
-
|
|
|
- .error_msg {
|
|
|
- color: #f56c6c;
|
|
|
- font-size: 12px;
|
|
|
- min-height: 18px;
|
|
|
- margin-top: 2px;
|
|
|
- }
|
|
|
-
|
|
|
- .message {
|
|
|
- box-sizing: border-box;
|
|
|
- min-height: 20px;
|
|
|
- max-height: 200px;
|
|
|
- overflow: auto;
|
|
|
-
|
|
|
- &::-webkit-scrollbar {
|
|
|
- width: 5px;
|
|
|
- height: 5px;
|
|
|
- display: block;
|
|
|
- background: white;
|
|
|
- }
|
|
|
-
|
|
|
- &::-webkit-scrollbar-thumb {
|
|
|
- background: #2C3E50;
|
|
|
- border-radius: 5px;
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- footer {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: flex-end;
|
|
|
- padding: 0 16px 10px 16px;
|
|
|
-
|
|
|
- button {
|
|
|
- width: 60px;
|
|
|
- line-height: 28px;
|
|
|
- text-align: center;
|
|
|
- border-radius: 12px;
|
|
|
- cursor: pointer;
|
|
|
- position: relative;
|
|
|
- color: var(--cy-color);
|
|
|
- outline: none;
|
|
|
- list-style: none;
|
|
|
- user-select: none;
|
|
|
- background: transparent;
|
|
|
- box-sizing: border-box;
|
|
|
- border: 1px solid transparent;
|
|
|
- transition: border .3s ease-out;
|
|
|
-
|
|
|
- &:focus-visible {
|
|
|
- border: 1px solid var(--cy-color);
|
|
|
- }
|
|
|
-
|
|
|
- &:hover {
|
|
|
- border: 1px solid transparent !important;
|
|
|
- }
|
|
|
-
|
|
|
- &:hover:before {
|
|
|
- transform: scale(1.3);
|
|
|
- }
|
|
|
-
|
|
|
- &:before {
|
|
|
- opacity: 1;
|
|
|
- transform: scale(1);
|
|
|
- content: "";
|
|
|
- background: rgba(var(--cy--bg-color), .1);
|
|
|
- position: absolute;
|
|
|
- bottom: 0;
|
|
|
- left: 0;
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- border-radius: 8px;
|
|
|
- pointer-events: none;
|
|
|
- -webkit-transition: all .25s ease;
|
|
|
- transition: all .25s ease;
|
|
|
- z-index: -1;
|
|
|
- box-sizing: border-box;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-}
|
|
|
-</style>
|