Skip to content

컨펌 만들기

@jood/v-modal 을 사용해서 아래와 같은 컨펌창을 만들 수 있습니다.

vue
<template>
  <div>
    <el-button type="primary" @click="onSave">컨펌</el-button>
  </div>
</template>

<script lang="ts" setup>
import { useConfirm } from './useConfirm';

const { open: openConfirm } = useConfirm();

const onSave = () => {
  save();
};

const save = async () => {
  const confirm = await openConfirm<number>({
    message: '안녕하세요. 컨펌창 예제 입니다.',
    actions: [
      { result: 0, label: '취소' },
      { result: 1, label: '확인', type: 'primary' },
    ],
  });
  if (confirm !== 1) {
    return;
  }
  alert('확인 클릭!');
};
</script>

<style lang="scss" scoped></style>
ts
import { onUnmounted } from 'vue';
import { type ModalData, JdModalRef, useJdModalService } from '@jood/v-modal';
import type { Subscription } from 'rxjs';
import type { ConfirmData } from './types';
import ConfirmBox from './ConfirmBox.vue';

/**
 * 컨펌
 */
export const useConfirm = () => {
  const modalService = useJdModalService();
  let modalRef: JdModalRef | null = null;
  let closeListener: Subscription | null = null;
  const beforeUsedHistoryStrategy = {
    disabledHistory: true,
    beforeValue: true,
  };

  const open = <Result = any>(data: ConfirmData, modalData?: Partial<ModalData>) => {
    dispose();
    const { disabledHistory = true } = data;
    if (disabledHistory) {
      beforeUsedHistoryStrategy.disabledHistory = true;
      beforeUsedHistoryStrategy.beforeValue = modalService.usedHistoryStrategy;
      modalService.setUseHistoryStrategy(false);
    }

    let promiseResolver: (result: PromiseLike<Result>) => void;
    modalRef = modalService.open({
      data,
      component: ConfirmBox,
      disableShadow: true,
      floatingMode: false,
      overlayClose: false,
      ...modalData,
    });

    closeListener = modalRef.observeClosed().subscribe((result) => {
      if (promiseResolver) promiseResolver(result);
      if (closeListener) {
        closeListener.unsubscribe();
        closeListener = null;
      }
    });

    return new Promise<Result>((resolve) => {
      promiseResolver = resolve;
    });
  };

  const dispose = () => {
    if (beforeUsedHistoryStrategy.disabledHistory) {
      modalService.setUseHistoryStrategy(beforeUsedHistoryStrategy.beforeValue);
    }
    if (closeListener) {
      closeListener.unsubscribe();
      closeListener = null;
    }
    if (modalRef) {
      modalRef.close();
      modalRef = null;
    }
  };

  onUnmounted(() => {
    dispose();
  });

  return {
    open,
  };
};
vue
<template>
  <div class="confirm-box" :class="classes">
    <template v-if="viewState.hasHead">
      <div class="cb-head">
        <div class="tit">{{ modalData.title }}</div>
      </div>
    </template>
    <div class="cb-body">
      <template v-if="modalData.messageComponent">
        <component :is="modalData.messageComponent"></component>
      </template>
      <template v-else>
        <div class="message" v-html="modalData.message"></div>
      </template>
    </div>
    <div class="cb-actions">
      <button
        v-for="(action, index) in modalData.actions"
        :key="index"
        :class="action.type"
        class="action-button"
        @click="onAction(action)"
      >
        <span class="label">{{ action.label }}</span>
      </button>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { computed } from 'vue';
import { useJdModalRef } from '@jood/v-modal';
import type { ConfirmAction, ConfirmData } from './types';

const modalRef = useJdModalRef<unknown, ConfirmData>();
const modalData = modalRef.data || ({} as ConfirmData);

const viewState = computed(() => {
  const { title } = modalData;
  const hasHead = !!title;
  return {
    hasHead,
  };
});

const classes = computed(() => {
  const { hasHead } = viewState.value;
  return {
    'has-head': hasHead,
  };
});

/**
 * 액션 버튼 클릭
 */
const onAction = (action: ConfirmAction) => {
  modalRef.close(action.result);
};
</script>

<style lang="scss" scoped>
.confirm-box {
  padding: 20px;
  max-width: 94vw;
  width: 360px;
  border-radius: 10px;
  box-sizing: border-box;
  background: #fff;
  &.has-head {
    .cb-body {
      padding-top: 20px;
      font-size: 15px;
    }
  }
  .cb-head {
    font-size: 16px;
    font-weight: bold;
    text-align: center;
    word-break: break-all;
    color: #111;
  }
  .cb-body {
    padding: 20px 0 30px 0;
    font-size: 16px;
    color: #333;
    text-align: left;
    word-break: break-all;
    white-space: pre-line;
  }
  .cb-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    .action-button {
      position: relative;
      display: block;
      margin: 0 2px;
      font-size: 13px;
      font-weight: bold;
      letter-spacing: -0.2px;
      text-align: center;
      color: #444;
      border: none;
      border-radius: 4px;
      border: 1px solid #ccc;
      appearance: none;
      background-color: #fff;
      cursor: pointer;
      &:focus-visible {
        outline: 1px solid #222;
      }
      .label {
        display: flex;
        align-items: center;
        justify-content: center;
        padding: 0 20px;
        min-width: 72px;
        height: 36px;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        box-sizing: border-box;
      }
      &:hover {
        opacity: 0.9;
      }
      &.negative {
        color: #666;
        border-color: #ccc;
        background: #fff;
      }
      &.primary {
        color: #fff;
        border-color: #0095ff;
        background: #0095ff;
      }
      &.danger {
        color: #fff;
        border-color: #d33838;
        background: #d33838;
      }
    }
  }
}
</style>
ts
import { type Component } from 'vue';

export interface ConfirmData<Result = unknown> {
  title?: string; // 컨펌 타이틀
  message?: string; // 컨펌 메세지
  actions: ConfirmAction<Result>[]; // 컨펌 버튼
  messageComponent?: Component; // 메세지 영역에 컴포넌트를 넣어서 사용하려는 경우
  disabledHistory?: boolean; // 모달 히스토리 처리 disable 여부
}

export interface ConfirmAction<Result = unknown> {
  type?: 'primary' | 'danger' | 'warning' | '' | undefined;
  label: string;
  result?: Result;
}