728x90

vuex 를 사용해서 store 에 저장하고 여러페이지에서 동일하게 사용하기 예시

aa 라는 변수를 예로 든다면 state 에 초기값을 설정해 두고 mutations 과 actions 으로 값을 변경 할수도 있고 값을 참조 할때는 store.state.aa 로  가져와서 사용 할 수 있다. 

 

vue 에는 세션이 없기에  의외로 많이 사용하는 방법이라 참고해 두면 좋겠다. 

// store.js
import { createStore } from 'vuex';

const store = createStore({
    state: {
        aa: null, // 초기값
    },
    mutations: {
        setAa(state, value) {
            state.aa = value; // 상태 변경
        },
    },
    actions: {
        updateAa({ commit }, value) {
            commit('setAa', value); // 액션을 통해 상태 업데이트
        },
    },
});

export default store;
<template>
    <div>
        <h1>현재 aa 값: {{ aa }}</h1>
        <button @click="changeValue">값 변경</button>
    </div>
</template>

<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';

// Vuex 스토어 사용
const store = useStore();

// aa 값을 가져옵니다.
const aa = computed(() => store.state.aa);

// aa 값을 변경하는 메서드
const changeValue = () => {
    const newValue = Math.random(); // 새로운 랜덤 값
    store.dispatch('updateAa', newValue); // Vuex 액션 호출
};
</script>

 

<template>
    <div>
        <h1>bb.vue에서 aa 값: {{ aa }}</h1>
    </div>
</template>

<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';

// Vuex 스토어 사용
const store = useStore();

// aa 값을 가져옵니다.
const aa = computed(() => store.state.aa);
</script>
728x90
728x90

약관동의 체크박스는 보기에는 간단한데 의외로 체크해야할 로직이 많다. 전체 동의를 하면 하위 체크박스가 모두 선택 되어야 하고 하나가 풀리면 전체동의 체크박스도 풀려야 하고 필수 동의 체크값에 따라 하위 버튼도 disabled 가 해제 되어야 해서 체크박스가 선택 될때마다 하단 등록 버튼 상태를 변경하는 로직은 한번 정리해두는게 좋다. 

 

아래 예시는 하단에 확인 버튼의 disabled 속성을 isConfirmDisabled 변수에 담고 체크박스의 값에 따라 watch 에서 체크 하는 로직으로 구현 하였다. 체크박스 상태가 변경 될때마다 확인이 되어야 하는 부분이어서 watch 로 걸어 주는것이 조금 더 편리하다. vue 에서 watch 는 너무 많이 걸면 조건이 꼬이기도 하니까 주의할 필요가 있다.  

<template>
  <CheckField
    v-for="(term, index) in terms"
    :key="index"
    v-model="term.agreed"
    :class="'terms-item'"
    :label="term.text"
    :required-indicator="term.required"
    :optional-indicator="!term.required"
    :show-view-button="index !== 0"
    :toggle-checkbox="index === 3 ? toggleMarketingConsent : () => {}"
    @view="() => clickConsent(term.content, index)"
  />
  <button :disabled="isConfirmDisabled">확인</button>
</template>

<script setup>
import { ref, watch } from 'vue';

const terms = ref([
  { agreed: false, text: '항목 1', required: true, content: '내용 1' },
  { agreed: false, text: '항목 2', required: true, content: '내용 2' },
  { agreed: false, text: '항목 3', required: false, content: '내용 3' },
  { agreed: false, text: '마케팅 동의', required: false, content: '내용 4' },
]);

const isConfirmDisabled = ref(true);

// 모든 항목의 동의 상태를 체크
watch(
  () => terms.value.map(term => term.agreed),
  (newAgreements) => {
    const allAgreed = terms.value[0]?.agreed && 
                      terms.value[1]?.agreed && 
                      terms.value[2]?.agreed;

    isConfirmDisabled.value = !allAgreed; // 모두 동의하지 않으면 버튼 비활성화
  },
  { immediate: true } // 컴포넌트 마운트 시 즉시 체크
);
</script>
728x90
728x90

vue3 에서 컴포넌트간에 메서드 호출 예시를 예제로 알아 본다. 작업을 하다 보면 자식 컴포넌트에서 부모 컴포넌트의 메소드를 호출하기도 하고 반대로 부모 컴포넌트에서 자식 컴포넌트 함수를 호출 하기도 한다. 

 

아래 예시는 <script setup> 코드로 vue3 이다. 자식 컴포넌트에서 부모를 호출하는 부분은 defineExpose 에 정의해 주어야 하고 부모컴포넌트에서 받아 오는 값은 props 로 정의해주어야 한다. 

<template>
  <div>
    <p>받은 값: {{ receivedValue }}</p>
    <button @click="callParentFunction">부모 함수 호출</button>
  </div>
</template>

<script setup>
import { defineProps, defineExpose } from 'vue';

// 부모로부터 값을 받기
const props = defineProps({
  receivedValue: String,
});

// 자식에서 부모가 호출할 수 있는 함수를 정의
const childFunction = () => {
  console.log('자식 함수가 호출되었습니다!');
};

// 부모가 호출할 수 있도록 expose
defineExpose({
  childFunction,
});
</script>

 

부모컴포넌트에서 자식함수를 호출 하려면 ref 를 정의하고 호출 할수 있다. 

<template>
  <div>
    <h1>부모 컴포넌트</h1>
    <ChildComponent ref="childRef" :receivedValue="parentValue" @childEvent="handleChildEvent" />
    <button @click="callChildFunction">자식 함수 호출</button>
  </div>
</template>

<script setup>
import ChildComponent from './ChildComponent.vue';
import { ref } from 'vue';

// 부모 컴포넌트의 상태
const parentValue = ref('부모에서 전달한 값');

// 자식의 이벤트를 처리하는 메서드
const handleChildEvent = (message) => {
  console.log(message); // "자식에서 호출된 이벤트" 출력
};

// 자식 컴포넌트에 대한 참조 생성
const childRef = ref(null);

// 자식의 함수를 호출하는 메서드
const callChildFunction = () => {
  if (childRef.value) {
    childRef.value.childFunction(); // 자식의 함수를 호출
  }
};
</script>
728x90
728x90

vue3 <script setup> 코드로 새창으로 링크 거는 예시이다. a 태그에서 바로 쓸수도 있지만 버튼에서 링크가 걸려야 하는 경우 예시도 같이 메모해 본다. 

 

sns 로그인 같은 버튼을 연동할때 유용하게 쓸수 있는거 같다. 

<template>
  <div>
    <a :href="link" target="_blank" rel="noopener noreferrer">새 창으로 열기 (링크)</a>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const link = ref('https://www.example.com');
</script>

 

버튼 예시

<template>
  <div>
    <button @click="openLink">새 창으로 열기 (버튼)</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const link = ref('https://www.example.com');

const openLink = () => {
  window.open(link.value, '_blank', 'noopener,noreferrer');
};
</script>
728x90
728x90

vue 에서 api 호출시 axios 로 호출 되는 모든 api 의 오류를 체크 할수 있는 파일은 interceptor.js 에서 한번에 처리 할수 있다. 그래서 headers 부분도 매번 api 호출 할때마다 적어주는것이 아니라 congif.headers 로 한번에 지정이 가능하다. 

 

아래 예시는 localStorage 에 토큰값을 저장해 두고 api 호출시 토큰값을 헤더에 실어 보내는 예시다. 보통 권한 오류는 401 로 떨어 지기에 401 로 코드를 받았을때 와 403, 404 정도로 분기를 하고 필유시 코드별로 메세지를 지정해 둘수도 있다. 

 

원래는 이렇게 router 가 사용이 가능해야 하는데 안되는 경우도 있으니 참고하는게 좋겠다. 

import axios from 'axios';
import store from '@/store'; // Vuex store
import router from '@/router'; // Vue Router

// Axios 인스턴스 생성
const axiosInstance = axios.create();

// 요청 인터셉터
axiosInstance.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('token'); // 토큰 가져오기
    if (token) {
      config.headers.Authorization = `Bearer ${token}`; // Authorization 헤더 추가
    }
    return config;
  },
  (error) => {
    return Promise.reject(error); // 요청 오류 처리
  }
);

// 응답 인터셉터
axiosInstance.interceptors.response.use(
  (response) => {
    return response; // 응답이 성공적일 경우 응답 반환
  },
  async (error) => {
    if (error.response) {
      const status = error.response.status;

      // 401 Unauthorized 처리
      if (status === 401) {
        alert('로그인 세션이 종료되었습니다. 다시 로그인해주세요.');
        store.dispatch('auth/logout'); // 로그아웃 액션
        router.push('/auth/login'); // 로그인 페이지로 리다이렉트
        return Promise.reject(error);
      }

      // 403 Forbidden 처리
      if (status === 403) {
        alert('이 작업을 수행할 권한이 없습니다.');
        return Promise.reject(error);
      }

      // 404 Not Found 처리
      if (status === 404) {
        router.push('/error/404'); // 404 페이지로 리다이렉트
        return Promise.reject(error);
      }

      // 기타 오류 처리
      alert(`오류 발생: ${error.response.data.message || '알 수 없는 오류'}`);
      router.push('/error/general'); // 일반 오류 페이지로 리다이렉트
    } else {
      // 네트워크 오류 처리
      alert('네트워크 오류가 발생했습니다. 인터넷 연결을 확인해주세요.');
    }

    return Promise.reject(error); // 오류를 다음으로 전파
  }
);

export default axiosInstance;

 

코드상에서 api 호출 예시는 아래와 같다. intercepter.js 를 먼저 타기 때문에 일괄로 관리하기에 효율적이다. 

import axios from '@/path/to/interceptor'; // interceptor.js 경로

// API 요청 예시
axios.get('/api/example')
  .then(response => {
    // 성공적으로 데이터를 받아온 경우 처리
  })
  .catch(error => {
    // 에러 처리 (여기서는 interceptor가 처리하므로 추가 필요 없음)
  });
728x90
728x90

예시는 vue3 에서 vuetify 를 사용중일때 년/월/일 을 차례로 입력할때 enter key 로 자료를 빠르게 등록 하고자 할때 사용하면 유용한 스크립트 이다.  

<template>
  <VTextField
    ref="input1"
    maxlength="4"
    variant="underlined"
    class="h-small inputNext"
    @keyup.enter="focusNextInput($event)"
  />
  <VTextField
    ref="input2"
    maxlength="4"
    variant="underlined"
    class="h-small inputNext"
    @keyup.enter="focusNextInput($event)"
  />
  <VTextField
    ref="input3"
    maxlength="4"
    variant="underlined"
    class="h-small inputNext"
    @keyup.enter="focusNextInput($event)"
  />
</template>

<script setup>
import { ref } from 'vue';

function focusNextInput(event) {
  // 현재 입력 필드의 다음 형제 요소를 찾음
  const currentInput = event.target;
  const nextInput = currentInput.nextElementSibling;

  if (nextInput && nextInput.classList.contains('inputNext')) {
    nextInput.focus(); // 다음 입력 필드로 포커스 이동
  }
}
</script>

 

여러 필드가 있어도 dom 에서 그려지는 순서대로 이동하기 때문에 인덱스값 없이 함수 하나만 호출 하면 된다. 

 

아래 예제는 maxlength 값에 따라 글자수 만큼 채워지면 다음 칸으로 이동하는 스크립트다. 

<template>
  <VTextField
    ref="input1"
    :maxlength="4"
    variant="underlined"
    class="h-small inputNext"
    @keyup.enter="focusNextInput($event)"
    @input="checkInput($event)"
  />
  <VTextField
    ref="input2"
    :maxlength="2"
    variant="underlined"
    class="h-small inputNext"
    @keyup.enter="focusNextInput($event)"
    @input="checkInput($event)"
  />
  <VTextField
    ref="input3"
    :maxlength="4"
    variant="underlined"
    class="h-small inputNext"
    @keyup.enter="focusNextInput($event)"
    @input="checkInput($event)"
  />
  <VTextField
    ref="input4"
    :maxlength="3"
    variant="underlined"
    class="h-small inputNext"
    @keyup.enter="focusNextInput($event)"
    @input="checkInput($event)"
  />
</template>

<script setup>
import { ref } from 'vue';

function focusNextInput(event) {
  const currentInput = event.target;
  const nextInput = currentInput.nextElementSibling;

  if (nextInput && nextInput.classList.contains('inputNext')) {
    nextInput.focus();
  }
}

function checkInput(event) {
  const currentInput = event.target;
  const maxLength = currentInput.getAttribute('maxlength'); // maxlength 값 가져오기

  // 입력된 글자의 길이를 확인
  if (currentInput.value.length >= maxLength) {
    focusNextInput(event); // 다음 입력 필드로 포커스 이동
  }
}
</script>
728x90
728x90

nvm-windows 다운로드

사이트를 검색해서 nvm-setup.exe 다운 받고 파일을 실행한다.

 

cmd 창에서 아래와 같이 사용하고자 하는 node 버전을 설치하고 nvm use 로 해당 버전으로 실행해 주면 node -v 로 버전을 확인 했을때 봐뀐 정보가 확인이 된다.

 

nvm install 16.0.0

 

nvm list available

nvm use 16.0.0

 

node -v

728x90
728x90

vue 컴포넌 중 아주 유용하게 쓸만한 것이 달력 컴포넌트다. vuetify 에는 수많은 기능이 있지만 그중 실제 실무에서 많이 사용하기도 했고 사용하면서 우와 이것도돼? 하며 달력을 아주 간단하게 만들어 볼수 있는 장점이 있다. 이벤트 핸들링도 상당히 간소한 편이다. 

처음 템플릿 작업이 조금 복잡하긴 하지만 한번 만들어 두면 유용하게 쓸수 있고 달력 내부에 div 조작도 html 코드 그대로 사용 할수 있어서 상당한 이점이 있다. 

 

아래는 간략하게 달력 노출 부분만 메모를 하고 오늘 찾은 기능은 상단에 년도를 클릭하면 년도/월을 선택 해서 바로 이동할 수 있는 기능을 추가 하며 코드 메모를 남겨 본다. 예전에는 셀렉트박스로 배열에 담아 불러 왔던 부분이 이제는 컴포넌트 호출 만으로도 가능하다니 참말로 신기한 부분이다. 

<v-calendar
  ref="calendar"
  v-model="focus"
  color="primary"
  :type="type"
  :locale="'ko-KR'"
  :events="events"
  :format="customFormat"
  @click:date="viewDay"
  @change="updateRange"
>
</v-calendar>

 

이때 아무 설정도 하지 않으면 모두 영어로 나오기에 한국에서 개발중이라면 locale 옵션을 사용해 보면 년도와 월 뒤에 한글도 넣을 수 있다. 아주 간단하게 한글로 샤라락 변환이 된다. 따지고 보면 변환이라기 보다는 날짜 뒤에 월 / 년 같은 글자를 붙이는 효과 이지만 말이다. 

 

v-date-picker 은 여러가지 타입이 있어서 년도/월만 선택 하는 month 도 있고 일수 까지 모두 선택 하게 하려면 type 정보를 빼면 기본은 우리가 알고 있는 현재 월의 모든 일수가 노출 되는 달력이 뜬다. 

<v-date-picker
  v-model="selectedPickerDate"
  type="month"
  @input="setFirstDayOfMonth"
  :locale="localeOptions"
/>

<script>
export default {
  data() {
    return {
      selectedPickerDate: null,
      localeOptions: {
        firstDayOfWeek: 1, // 월요일부터 시작하는 경우
        masks: {
          L: 'YYYY년 MM월', // 한글로 월을 표시하는 포맷
          l: 'YYYY-MM' // 기본 포맷
        },
        // 다른 locale 옵션들...
      }
    };
  },
  methods: {
    setFirstDayOfMonth(selectedDate) {
      // 선택된 날짜를 처리하는 로직
    }
  }
};
</script>

 

아래 코드는 년도월을 선택 후 해당 월로 이동하는 스크립트 예시다. v-model="focus" 로 캘린더에 명시 했기 때문에 해당 일자를 this.focus 에 넣어 주기만 하면 달력을 자동으로 선택된 달로 변경 된다. 참말로 편리한 세상이다.

async setFirstDayOfMonth(selectedPickerDate) {
  if (selectedPickerDate) {
    const dateParts = selectedPickerDate.split('-');
    const year = parseInt(dateParts[0], 10);
    const month = parseInt(dateParts[1], 10);
    const firstDayOfMonth = new Date(year, month - 1, 1);
    this.focus = firstDayOfMonth; 
  }
},

 

너무 예스러운 애기지만 예전에는 한국에 맞게 날짜 타입을 바꿀려면 맨붕이 오기도 하는데 요즘은 옵션 설정이 상당히 좋은거 같다. 달력도 참말로 계산법으로 한땀한땀 스크립트를 만들고 달력스크립트 있어요? 물어 보기도 하고 말이다. 달력 스크립트를 보유한 사람은 꽤 실력자 였으니 말이다.

 

vue 로 달력으로 삽질하고 있다면 도움이 되길 바란다. 

 

728x90
728x90

vue.js numeric and string conversion 

Numeric to String Conversion:

// Numeric to String Conversion
let numericValue = 123;
let stringValue = `${numericValue}`; // String interpolation
// Or
let stringValue = numericValue.toString(); // Using toString() method

 

String to Numeric Conversion:

// String to Numeric Conversion
let stringValue = "123";
let numericValue = parseInt(stringValue); // For integers
// Or
let floatValue = parseFloat(stringValue); // For floats

 

Rounding:

let number = 10.6;

let rounded = Math.round(number); // Rounds to the nearest integer
// Output: 11

let roundedDown = Math.floor(number); // Rounds down to the nearest integer
// Output: 10

let roundedUp = Math.ceil(number); // Rounds up to the nearest integer
// Output: 11
let number = 10.6789;
let decimalPlaces = 2;
let rounded = Math.round(number * Math.pow(10, decimalPlaces)) / Math.pow(10, decimalPlaces);
// Output: 10.68

Template Example:

<template>
  <div>
    <p>Numeric to String Conversion: {{ numericValue.toString() }}</p>
    <p>String to Numeric Conversion: {{ parseInt(stringValue) }}</p>
    <p>Rounded Number: {{ Math.round(number) }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      numericValue: 123,
      stringValue: "123",
      number: 10.6
    };
  }
};
</script>

 

Result:

<p>Numeric to String Conversion: 123</p>
<p>String to Numeric Conversion: 456</p>
<p>Rounded Number: 11</p>
728x90
728x90

object 타입 선언은 문법이 조금 다르기에 메모해 본다 

vue3 에서는 ref 로 선언을 해야 하는 경우 아래와 같이 선언한다.

const selectedLangSrcOption = ref({
  // 자식 컴포넌트가 예상하는 프로퍼티와 값을 제공
  key: 'value',
});

초기화 해줄때 역시 key value 의 형태로 초기화를 해주어야 waring 메세지가 에러처럼 나지 않으니 참고 해야 한다. 

const selectedLangSrcOption = ref({ key : "" });

 

아래는 vue2 에서 object 타입의 객체를 선언 하는 예제 이다. 

셀렉트 컴포넌트에서 보통 이런 key value 의 형태를 많이 사용하기에 알아 두면 유용하게 사용 할수 있다. 

 

부모 컴포넌트 

<template>
  <div>
    <SelectComponent
      class="selectbox-gray"
      :options="langsTgtOpt"
      id="tgtlan"
      :selectValue="selectedLangTgtOption"
    />
    <button @click="resetSelectBox">Reset Select Box</button>
  </div>
</template>

<script>
import SelectComponent from "./SelectComponent"; // 자식 컴포넌트의 경로에 맞게 수정하세요

export default {
  data() {
    return {
      langsTgtOpt: [
        { key: "Option 1", value: "Value 1" },
        { key: "Option 2", value: "Value 2" },
        { key: "Option 3", value: "Value 3" },
      ],
      selectedLangTgtOption: {}, // 초기값은 빈 객체로 설정
    };
  },
  methods: {
    resetSelectBox() {
      this.selectedLangTgtOption = {}; // 버튼을 클릭하면 선택된 값 초기화
    },
  },
  components: {
    SelectComponent,
  },
};
</script>

자식 컴포넌트

<template>
  <select v-model="selectedValue">
    <option v-for="option in options" :key="option.key" :value="option">{{ option.value }}</option>
  </select>
</template>

<script>
export default {
  props: {
    options: {
      type: Array,
      required: true,
    },
    selectValue: {
      type: Object, // 셀렉트 박스에서 선택된 값을 객체로 받습니다.
      required: true,
    },
  },
  computed: {
    selectedValue: {
      get() {
        return this.selectValue;
      },
      set(newValue) {
        this.$emit("update:selectValue", newValue);
      },
    },
  },
};
</script>
728x90

+ Recent posts