728x90

소개

💡 i18n은 국제화 프레임워크로서 다양한 프레임워크나 플랫폼에서 활용 가능하다.

공식 웹사이트 : https://www.i18next.com/

 

Introduction | i18next documentation

 

www.i18next.com

여기서는 해당 프레임워크를 Nuxt3와 결합해 프론트엔드 텍스트들을 구글 시트와 통합하여 빌드 시 마다 현지화 텍스트를 자동으로 동기화하고 적용하는 방법을 다룬다. 이 방식을 통해 번역된 텍스트를 더욱 효율적으로 관리 및 유지보수가능하다.


목차


필요 라이브러리 설치

Front-end

  • 다국어 처리 프레임워크 i18n
    npx nuxi@latest module add i18n
  • 구글 인증
    yarn add google-auth-library
  • 구글 스프레드시트
    yarn add google-spreadsheet

구글 시트 API 사용 설정

  1. 구글 클라우드 콘솔 에서 프로젝트 생성
  2. Google Sheets API 검색 후 사용 설정
  3. API/서비스 세부정보에서 사용자 인증정보 만들기 → 서비스 계정 생성
  4. 계정 클릭 → "키" 탭 → 새 키 생성 → JSON 다운로드

예시 JSON:

{
  "type": "service_account",
  "project_id": "liahnson",
  "private_key": "-----BEGIN PRIVATE KEY-----\ntesttesttest\n——END PRIVATE KEY-----\n",
  "client_email": "service-account@test.iam.gserviceaccount.com",
  "client_id": "1234",
  "token_uri": "https://oauth2.googleapis.com/token"
}

프로젝트 기본 구조화

  • locales 폴더 생성
  • 다운받은 JSON 파일을 credentials.json 으로 이름 변경 → locales 폴더에 위치
  • sync.js 파일 작성 (구글시트 ↔ JSON 동기화)

sync.js 예제

// 모듈 및 전역 변수 선언
const { GoogleSpreadsheet } = require('google-spreadsheet');
const { JWT } = require('google-auth-library');
const credentials = require('./credentials.json'); // 서비스 계정 인증 파일 경로
const fs = require('fs');
const path = require('path'); // 경로 조작을 위한 모듈 추가
const _ = require('lodash');

// 전역 설정
const localesPath = 'locales';
const lngs = ['ko', 'en']; // 언어 목록
const spreadsheetDocId = '1URVacsXhql_XHCj9DQRbvOeb_3Kg1uChB1spkmv3zAQ';
const fileNm = 'translation';

// Google API 인증
const serviceAccountAuth = new JWT({
    email: credentials.client_email,
    key: credentials.private_key,
    scopes: ['<https://www.googleapis.com/auth/spreadsheets>'],
});

// 스프레드시트 로드 함수
async function loadSpreadsheet() {
    console.info(
        '\\u001B[32m',
        '=====================================================================================================================\\n',
        `(\\u001B[34mhttps://docs.google.com/spreadsheets/d/${spreadsheetDocId}/#gid=0\\u001B[0m)\\n`,
        '=====================================================================================================================',
        '\\u001B[0m'
    );

    const doc = new GoogleSpreadsheet(spreadsheetDocId, serviceAccountAuth);
    await doc.loadInfo(); // 문서 정보 로드
    return doc;
}

// 시트 데이터 매핑 함수
async function makeTranslationsMapFromSheet(sheet, lngsMap) {
    if (!sheet) {
        console.error('Sheet not found');
        return lngsMap;
    }

    const rows = await sheet.getRows();

    rows.forEach((row) => {
        const combinedKey = row._rawData[5]; // Combined key는 해당 위치에 있다고 가정

        if (combinedKey) {
            lngs.forEach((lang, langIndex) => {
                let translation = row._rawData[langIndex + 3]; // ko, en 필드에 접근

                if (translation) {
                    translation = translation.replace(/\\\\n/g, '\\n'); // 이스케이프된 \\n 변환
                    _.set(lngsMap, `${lang}.${combinedKey}`, translation);
                } else {
                    console.warn(`No translation found for ${lang} in row with combined key: ${combinedKey}`);
                }
            });
        }
    });

    return lngsMap;
}

// JSON 파일로 저장 함수
function saveTranslationsToFile(lngsMap) {
    lngs.forEach((lng) => {
        const localeJsonFilePath = path.join(__dirname, `${lng}.json`);
        const jsonString = JSON.stringify(lngsMap[lng] || {}, null, 2); // JSON 포맷으로 저장

        fs.writeFile(localeJsonFilePath, jsonString, 'utf8', (err) => {
            if (err) {
                throw err;
            }
            console.log(`File written successfully: ${localeJsonFilePath}`);
        });
    });
}

// 메인 업데이트 함수
async function updateJsonFromSheet() {
    const doc = await loadSpreadsheet();

    let lngsMap = lngs.reduce((acc, lng) => {
        acc[lng] = {}; // 언어별 빈 객체 초기화
        return acc;
    }, {});

    for (const sheet of doc.sheetsByIndex) {
        console.log(`Processing sheet: ${sheet.title}`);
        lngsMap = await makeTranslationsMapFromSheet(sheet, lngsMap);
    }

    saveTranslationsToFile(lngsMap);
}

// 함수 실행
updateJsonFromSheet();

// 모듈 내보내기
module.exports = {
    localesPath,
    lngs,
    loadSpreadsheet,
    updateJsonFromSheet,
    fileNm,
};

 

위 스크립트에 맞춰 아래와 같은 구조로 구글 시트를 작성한다.

 

페이지명 대상 원문 Key(N) ko
(한글번역)
en
(영문번역)
combinedKey
(key1 + 2 + 3)
Home 버튼 Login - 로그인 Login home.button.login
Home 메뉴 My Page - 마이페이지 My Page home.menu.mypage
Manager 타이틀 Main Manager - 메인관리자 Main Manager manager.title.main

페이지명, 대상, 원문, key1,2,3 : 사용자가 참고하기 위한 데이터

실질적으론 아래의 값이 번역에 사용된다.

ko : 한글번역 데이터

en : 영문번역 데이터

key : i18n 에서 참고하는 키 값 (위 스크립트의 combinedKey에 해당)

 

그럼 작성한 sync.js 스크립트를 실행하면 아래와 같이 현지화 파일이 생성 혹은 갱신될 것이다.

빌드 시에 현지화 데이터 업데이트 및 빌드를 같이 진행하려면 아래와 같이 스크립트를 작성하면 될 것이다.

package.json script 예시

"scripts": {
  "build": "npm run lang:sync && nuxt build",
  "lang:sync": "node ./locales/sync.js"
}

프론트엔드 현지화 예시

템플릿에서는 $t('key'), 스크립트에서는 t('key')로 사용한다.

언어 변경 시에는 반드시 setLocale 함수를 호출하여 변경하도록 한다.

 

템플릿 :

{{ $t(mobileTitle) }}

스크립트 : 

    $alert(t('client.common.passwordHasBeenModified'));

layout/manager.vue

<template>
    <div id="manager">
	    <span>{{ $t(mobileTitle) }}</span>
	  </div>
</template>
const { setLocale, t } = useI18n();
const mainStore = useMainStore();

const selectedLanguage = computed({
  get: () => mainStore.lang,
  set: (value) => {
    mainStore.setLang(value);
    setLocale(value);
    document.documentElement.lang = value;
  }
});

const mobileTitle = computed(() => {
  switch (route.path) {
    case '/manager/home':
      return 'manager.home.Home';
  }
});

const updatePassword = async () => {
  try {
    await useCustomFetch.put("/common/password", { body: { newPassword, target, email } });
    $alert(t('client.common.passwordHasBeenModified'));
  } catch (e) {
    $toastError(e.message || t('common.toastError.An error occurred while processing the request'));
  }
};
728x90
반응형 데이터를 만들기 위한 함수

ref

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

let id = 0

const newTodo = ref('')
const hideCompleted = ref(false)
const todos = ref([
  { id: id++, text: 'HTML 배우기', done: true },
  { id: id++, text: 'JavaScript 배우기', done: true },
  { id: id++, text: 'Vue 배우기', done: false }
])
  • 사용 대상: 모든 종류의 값 (기본 데이터 타입 또는 객체/배열).
  • 접근 방법: .value 속성을 통해 값에 접근하고 변경합니다.
  • 주요 특징: 기본 데이터 타입(예: 문자열, 숫자, 불리언)에 반응성을 부여할 수 있으며, 객체나 배열에도 사용 가능하지만, 단일 값에 대한 반응성을 관리하는 데 주로 사용됩니다.

reactive

<script setup>
import { reactive } from 'vue'

let id = 0

const state = reactive({
  newTodo: '',
  hideCompleted: false,
  todos: [
    { id: id++, text: 'HTML 배우기', done: true },
    { id: id++, text: 'JavaScript 배우기', done: true },
    { id: id++, text: 'Vue 배우기', done: false }
  ]
})
</script>
  • 사용 대상: 객체나 배열.
  • 접근 방법: .value 속성 없이 객체나 배열 내부의 속성에 직접 접근합니다.
  • 주요 특징: 객체나 배열 전체에 대한 반응성을 부여합니다. 중첩된 객체나 배열을 포함하여 구조 내의 모든 데이터에 반응성이 적용됩니다.

 

reactive 사용의 이점

  1. 직관적인 접근과 수정: reactive 객체의 속성에 접근하거나 수정할 때, .value를 사용할 필요가 없습니다. 이는 코드를 좀 더 직관적으로 만들고, 일반 JavaScript 객체와 동일한 방식으로 작업할 수 있게 합니다.
  2. 중첩된 반응성: reactive를 사용하면, 객체 내의 중첩된 속성도 자동으로 반응성이 적용됩니다. 따라서 객체 내부 구조가 복잡해지거나 중첩된 경우에도, 별도의 처리 없이 모든 레벨에서 반응성을 유지할 수 있습니다.
  3. 코드 조직: 여러 반응형 데이터를 하나의 reactive 객체에 그룹화하여 관리할 수 있습니다. 이는 관련 데이터와 로직을 함께 묶어서 관리하기 편리하게 해줍니다, 코드의 가독성과 유지보수성을 향상시킬 수 있습니다.

선택 기준

  • 기본 타입(문자열, 숫자, 불리언 등)에 대한 반응성이 필요한 경우, 또는 Vue 템플릿에서 직접 참조되는 단일 변수에 대해서는 ref를 사용하는 것이 좋습니다.
  • 객체나 배열과 같은 복합적인 데이터 구조에 대해 반응성을 제공하고, 중첩된 데이터 구조를 다루거나 여러 관련 데이터를 함께 관리해야 하는 경우 reactive를 사용하는 것이 더 적합할 수 있습니다.
728x90

전통적인 Options API

 

Vue에서의 스크립트 구현은 크게 두가지 방식이 있다. 전통적인 Options API와 Composition API이다.

전통적인 Options API

<template>
  <div>
    <input v-model="newTodo" @keyup.enter="addTodo">
    <ul>
      <li v-for="todo in todos" :key="todo.id">
        {{ todo.text }}
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      newTodo: '',
      todos: [],
      nextId: 0
    }
  },
  methods: {
    addTodo() {
      this.todos.push({ id: this.nextId++, text: this.newTodo });
      this.newTodo = ''; // 입력 필드 초기화
    }
  }
}
</script>

Composition API

Vue 3의 Composition API는 컴포넌트 로직을 구성하는 새로운 방법을 제공합니다. 이 API의 도입은 Vue 애플리케이션을 설계하고 개발하는 방식에 근본적인 변화를 가져왔습니다. Composition API의 특징과 장점은 다음과 같습니다:

특징

  1. 반응형 상태 정의: refreactive 함수를 사용하여 반응형 상태를 선언적으로 정의할 수 있습니다. 이 상태들은 컴포넌트의 템플릿에서 직접 사용될 수 있으며, 상태 변경 시 자동으로 화면을 업데이트합니다.
  2. setup 함수: 컴포넌트의 반응형 상태, 계산된 속성, 메서드 등 모든 로직을 setup 함수 내에 정의합니다. setup 함수는 컴포넌트의 옵션/라이프사이클 훅보다 먼저 실행되며, 여기에서 정의된 모든 값과 함수는 템플릿에서 직접 사용할 수 있습니다.
  3. 함수 기반의 구성: 로직을 재사용 가능한 함수로 캡슐화하고 조합하여 컴포넌트를 구성할 수 있습니다. 이러한 접근 방식은 컴포넌트 간에 코드를 재사용하고 조합하기 쉽게 만듭니다.

장점

  1. 로직의 재사용 및 조직화: Composition API를 사용하면 로직을 기능별로 구성하고 필요한 곳에서 재사용할 수 있습니다. 이는 코드의 중복을 줄이고, 프로젝트의 유지보수성을 향상시킵니다.
  2. 유연성: 컴포넌트의 로직을 더 유연하게 조직할 수 있습니다. 예를 들어, 관련된 모든 반응형 상태와 함수를 함께 그룹화하여 관리할 수 있으며, 이는 컴포넌트의 가독성을 크게 향상시킵니다.
  3. 타입스크립트와의 호환성: Composition API는 타입스크립트와 더욱 잘 호환됩니다. setup 함수 내에서 정의된 상태와 함수에 타입을 적용하기 쉬워, 타입스크립트를 사용하는 프로젝트에서 타입 안정성과 개발 경험을 향상시킬 수 있습니다.
  4. 응집력 있는 코드 베이스: 기능별로 로직을 그룹화하고 재사용할 수 있기 때문에, 관련 로직이 물리적으로 가까운 위치에 있게 됩니다. 이는 응집력 있는 코드 베이스를 유지하는 데 도움이 됩니다.
  5. 더 나은 성능: Composition API를 사용하면 필요한 로직만을 정확히 구성하여 사용할 수 있기 때문에, 불필요한 로직의 실행을 최소화할 수 있습니다. 이는 애플리케이션의 성능을 최적화하는 데 기여할 수 있습니다.

Composition API는 Vue 애플리케이션을 개발할 때 더 큰 유연성과 향상된 코드 재사용성을 제공합니다. 이는 특히 대규모 애플리케이션 또는 복잡한 컴포넌트 구조를 가진 프로젝

트에서 그 장점이 두드러집니다.

Vue 3 초기 버전에서의 Composition API 사용 예시

<template>
  <div>
    <input v-model="newTodo" @keyup.enter="addTodo" />
    <ul>
      <li v-for="todo in todos" :key="todo.id">{{ todo.text }}</li>
    </ul>
  </div>
</template>

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

export default {
  setup() {
    const newTodo = ref('');
    const todos = ref([]);

    const addTodo = () => {
      todos.value.push({ id: todos.value.length + 1, text: newTodo.value });
      newTodo.value = '';
    };

    return { newTodo, todos, addTodo };
  }
};
</script>

Vue 3 초기 버전에서 Composition API를 사용할 때는 setup 함수 내에서 컴포넌트의 반응형 상태, 계산된 속성, 메서드 등을 정의하고, 필요한 값들을 객체로 반환하여 템플릿에서 사용할 수 있도록 했습니다.

 

Vue 3.2 이후에서의 <script setup> 구문 사용 예시

Vue 3.2부터 도입된 <script setup> 구문을 사용하면, setup 함수 내에서 반환 과정 없이 직접 변수와 함수를 정의할 수 있으며, 이들은 자동으로 템플릿에서 사용 가능해집니다. 이로 인해 코드가 더욱 간결하고 선언적으로 변화했습니다.

 

<template>
  <div>
    <input v-model="newTodo" @keyup.enter="addTodo" />
    <ul>
      <li v-for="todo in todos" :key="todo.id">{{ todo.text }}</li>
    </ul>
  </div>
</template>

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

const newTodo = ref('');
const todos = ref([]);

const addTodo = () => {
  todos.value.push({ id: todos.value.length + 1, text: newTodo.value });
  newTodo.value = '';
};
</script>

주요 차이점

  • 코드 간결성: <script setup> 구문을 사용하면, 컴포넌트의 로직을 더욱 간결하게 작성할 수 있습니다. setup 함수 내에서 반환 과정 없이 모든 로직을 정의할 수 있으며, 이는 코드의 가독성과 유지보수성을 크게 향상시킵니다.
  • 자동 import 추론: <script setup>은 IDE와 도구에서 더 나은 타입 추론과 자동 완성을 제공합니다. 또한, Vue 컴파일러는 <script setup> 내에서 사용된 Vue API를 자동으로 감지하여 필요한 코드를 자동으로 import 합니다.
  • 컴포넌트 정의의 선언적 표현: <script setup> 구문은 컴포넌트의 정의를 더 선언적으로 만들어 줍니다. 이는 Vue 컴포넌트의 구조와 로직을 더 명확하게 표현할 수 있게 해줍니다.

<script setup> 구문은 Vue 3.2 이상에서 Composition API를 사용하는 새로운 표준이 되었으며, 많은 개발자들이 이 새로운 패턴을 선호하고 있습니다.

728x90

백엔드에서 이미지 파일을 static 폴더에 올렸다면 일반적으로는 프론트에서 참조할 수가 없다.

하지만 express는 이미지 파일과 같은 정적 파일들을 쉽게 참조하여 반환할 수 있는 메소드를 제공한다.

 

| Back-end

app.use('/static', express.static(path.join(__dirname, 'static')));

express.static 메소드는 node프로세스가 실행되는 path에 따라 상대적이므로 위와 같이 환경변수인 __dirname을 써서 디렉토리명을 지정해줄 수 있다. node 프로세스가 실행되는 같은 경로의 static 폴더를 참조한다.

 

그러면 아래와 같이 참조했을 때 백엔드의 static 디렉토리내의 모든 파일을 참조할 수 있게 된다.

http://localhost:3000/api/static/images/mt/resize-1697954077172.jpg

 

| Front-end

다음은 nuxt.config.js를 수정하여

api라는 경로로 들어왔을 때 경로를 다시 쓰기해주도록 한다.

 axios: {
        proxy: true,
        prefix: '/api'
    },

    proxy: {
        '/api': { target: process.env.AXIOS_BASE_URL, pathRewrite: { '^/api/': '/' } },
    },

[프론트 .env 폴더에 정의된 api 주소]

AXIOS_BASE_URL=http://127.0.0.1:10072

 

만약 DB에 저장된 path과 다음과 같다면 프론트의 메소드나 computed 속성으로 경로를 반환하면 될 것이다.

static/images/mt/resize-1697954077178.jpg

 

  getImageUrl(img) {
            if (img.path && img.path.includes('static/images')) {
                return '/api/' + img.path;
            }
            return URL.createObjectURL(img);
        },

그러면 백엔드에서 굳이 파일객체를 리턴한다는지 리소스가 많이들고 비효율적인 방법을 쓰지않고도 쉽게 리소스를 참조할 수 있게 된다.

728x90

Nuxt.js / Node.js로 구성된 프로젝트에서 다음과 같은 에러가 뜨고 백엔드 API 요청이 되지 않는 현상이 있었다.

Error occurred while trying to proxy request

 

결론적으로 아래 환경 변수 (프론트의 .env)에서 localhost를 127.0.0.1 로 바꾸니 해결되었다.

AXIOS_BASE_URL=http://localhost:8071

m1 mac과 node18 버전에서 생기는 호환성 문제라고 하는데 이것도 환경마다 차이가 있어서 정확한 원인을 알 수가 없다.

아시는 분은 댓글 달아주세요~!

 

| 참고

https://github.com/chimurai/http-proxy-middleware/issues/171

728x90

Template

 <tr
    v-for="(diary, idx) in f_rec_diary_list"
    :key="idx"
    draggable="true"
    @dragstart="drag_area(idx, 'M')"
    @dragend="drag_end_area"
    @dragover.prevent
    @dragenter="drag_enter_area(idx)"
    @drop="drop_area(idx, f_rec_diary_list)"
>

 

Data

data() {
        return {            
            dragged_area_index: null,
            hover_area_index: null,
            dragged_item_index: null,
            hover_item_index: null,
            dragged_type: null,
        };
},

 

Methods

drag_area(index, type) {
    console.log("drag_area -> index", index, "type", type);
    this.dragged_area_index = index;
    this.dragged_type = type;
},
drag_enter_area(index) {
    this.hover_area_index = index;
},
drag_end_area() {
    this.hover_area_index = null;
},
async drop_area(drop_index, parent) {
    if (this.dragged_type !== "M") {
        return;
    }

    const dragged_item = parent.splice(this.dragged_area_index, 1)[0];
    parent.splice(drop_index, 0, dragged_item);

    console.log('parent', parent);

    // Update seq_no values after reordering
    for (let i = 0; i < parent.length; i++) {
        parent[i].seq_no = i + 1;
    }

    console.log("dragged_item", dragged_item);

    this.dragged_area_index = null;
    this.hover_area_index = null;
    this.dragged_type = null;
},
drag_item(index, type) {
    this.dragged_item_index = index;
    this.dragged_type = type;
},
drag_enter_item(index) {
    this.hover_item_index = index;
},
drag_end_item() {
    this.hover_item_index = null;
},
728x90

MathJax는 웹 브라우저에 수학 표기법을 표시하기 위한 JS Library이며 아래와 같은 형식을 따른다.

\(-{1 \over {500}}x + 212\)

 

프론트엔드에서 위 MathJax 수식을 올바르게 보여주기 위해 아래 과정이 필요하다.

 

1. nuxt.config.js의 head에 스크립트 코드 추가

 // Global page headers: https://go.nuxtjs.dev/config-head
    head: {
         ( ... ) 
        script: [
            {
              src: 'https://polyfill.io/v3/polyfill.min.js?features=es6',
              type: 'text/javascript',
              body: true
            },
            {
              src: 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js',
              type: 'text/javascript',
              body: true
            }
        ]
    },

 

2. 프론트 페이지의 mounted에 아래 코드 추가

async mounted() {
    // mathjax 수식 렌더링 적용
    if (window.MathJax) {
        window.MathJax.typeset();
    }
}

 

그러면 아래와 같은 html 요소가 있을 때 수식이 제대로 보여지게 된다.

<p v-html="math_exp"></p>
 
data() {
	return {
		math_exp : "<div class="math">&nbsp;<p>The graph of \(y = 3x - 5\) in the \(xy\)-plane is a line. What is the slope of the line?</p></div>"
}

728x90

개요

node에서 oracle client를 연동하기 위해서는 node-oracledb 라는 라이브러리가 필요하다. 이 글에서는 라이브러리 설치 및 환경변수 설정, 노드 백엔드에서의 간단한 연동법을 소개한다.

설치 및 환경변수 설정

💡 실행 환경 : linux arm64, m1 mac

 

  1. 다운로드
  • 아래 링크에서 Basic Light Package (ZIP)을 내려받는다

https://www.oracle.com/database/technologies/instant-client/linux-arm-aarch64-downloads.html

instantclient-basiclite-linux.arm64-19.10.0.0.0dbru (1).zip

  1. opt/oracle 폴더로 이동하여 압축해제
  • 경로이동

cd ~/../../opt/

/opt/oracle/instantclient_19_8 과 같이 라이브러리가 위치하도록한다.

  1. bash_profile 수정
  • vi ~/.bash_profile

DYLD_LIBRARY_PATH=/opt/oracle

(리눅스는 LD_LIBRARY_PATH)

  • source ~/.bash_profile
  1. 백엔드 실행 시 아래와 같은 오류가 없다면 정상적으로 연동된 것이다. 오류가 난다면 환경변수의 경로가 옳게 설정되었는지 다시 확인해본다.

Error: DPI-1047: Cannot locate a 64-bit Oracle Client library

사용법

  • 초기화 (라이브러리 경로 : libDir 을 확인)
const oracledb = require('oracledb');
const os = require('os');

switch (os.type().toLowerCase()) {
    case 'windows_nt':
        oracledb.initOracleClient({libDir: 'C:/EXTERNAL_LIBRARY/instantclient_21_8'});
        break;
    case 'darwin':
        oracledb.initOracleClient({libDir: '/opt/oracle/instantclient_19_8'});
        break;
    case 'linux':
        oracledb.initOracleClient({libDir: '/opt/oracle/instantclient_21_9'});
        break;
}

// 쿼리 아웃풋을 객체로 받을지 배열로 받을지 옵션 설정 (배열의 경우엔 .OUT_FORMAT_ARRAY)
oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT;
  • 오라클 class 및 메소드 정의
class Oracle {
    constructor() {
    }
		// 접속 초기화
    async init() {
        const _this = this;
        return new Promise((resolve, reject) => {
            oracledb.getConnection({
                user: process.env.ORACLE_USER,
                password: process.env.ORACLE_PASS,
                connectString: process.env.ORACLE_HOST
            }, function (error, connection) {
                if (error) {
                    return reject(error);
                }
                _this.connection = connection;
                return resolve();
            });
        });
    }
		// 쿼리 실행
    execute(sql, params = []) {
        const _this = this;
        return new Promise((resolve, reject) => {
            _this.connection.execute(sql, params, function (error, result) {
                if (error) {
                    return reject(error);
                }
                return resolve(result.rows);
            });
        });
    }
		// 쿼리 커밋
    commit() {
        const _this = this;
        return new Promise((resolve, reject) => {
            if (_this.connection) {
                _this.connection.commit(error => {
                    _this.release();
                    if (error) {
                        return reject(error);
                    }
                    return resolve();
                });
            } else {
                return resolve();
            }
        });
    }
		// 롤백
    rollback() {
        const _this = this;
        return new Promise((resolve, reject) => {
            if (_this.connection) {
                _this.connection.rollback(error => {
                    _this.release();
                    if (error) {
                        return reject(error);
                    }
                    return resolve();
                });
            } else {
                return resolve();
            }
        });
    }
		// 접속 해제 (commit, rollback시 반드시 수행)
    release() {
        this.connection.release();
    }
}
  • 노드 백엔드의 객체 초기화 및 쿼리 실행 예제
const oracledb = new Oracle();
await oracledb.init();

( ... )
try {
	const [max_no] = await oracledb.execute(`SELECT MAX("NO") AS value FROM INTF_JJ.INTF_ES_ORDER WHERE REQ_DT = TO_CHAR(SYSDATE, 'YYYYMMDD')`);
	await oracledb.commit();
} catch (e) {
  await oracledb.rollback();
  return next(e);
}

참고

http://oracle.github.io/node-oracledb/

728x90

라디오박스라면 하나만 체크되니 css 를 수정해서 체크박스처럼 보일 수도 있겠지만

체크박스로 이미 퍼블이 된 상태에서 수정할 필요가 있어 직접 코드를 짜보았다.

 

템플릿내 코드 

 <input type="checkbox" v-model="pay_method.card" name="chkbox" id="card"
 @change="check_one( {type:'card' })" >
                            
<input type="checkbox" v-model="pay_method.bank" name="chkbox"  @change="check_one( {type:'bank' })"
 id="noBankBook">

 

data 정의

data(){
        return{            
            pay_method: { card: true, bank: false },
        }
    },

method 정의

methods: {
	check_one(element) {
      console.log('checkbox check : ', element.type)
      let obj = this.pay_method

      if (element.type) {
        for(const [key, value] of Object.entries(obj)) {          
          console.log(`${key}: ${value}`);

          if (key == element.type) {
            obj[key] = true
          } else {
            obj[key] = false
          }
        }

        const check = document.getElementById(`${element.type}`);
        
        if (check && check.checked != undefined) {
          check.checked = true;
        }
        console.log('checked', check.checked)
      } 

      console.log('type', JSON.stringify(this.search_type))

    },
}
728x90

https://github.com/microsoft/vscode-eslint/issues/696#issuecomment-512252381

 

ESLint fails to load plugins when using ESLint 6.x · Issue #696 · microsoft/vscode-eslint

I have the following packages installed as dev deps: { "babel-eslint": "^10.0.2", "eslint": "^6.0.1", "eslint-config-prettier": "^5.1.0",...

github.com

eslint 플러긴 호환성 문제인듯하다

 

1. cmd + shift + p 를 눌러

>user setting 를 검색

 

2. settings.json에서 아래 항목 추가

{
    "eslint.workingDirectories": [
        { "mode": "auto" }
    ],
 }

+ Recent posts