Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

Vue, Vuex 및 Rails를 사용하여 전체 스택 애플리케이션 구축

확장성을 염두에 두고 전체 스택 응용 프로그램을 빌드하는 것은 특히 전체 유형 스크립트를 지원하는 최신 버전의 Vue 및 Vuex로 빌드할 때 두려울 수 있습니다. 이 기사는 건강에 해로운 가축에 대한 치료 처방을 관리하는 CRUD 애플리케이션을 탐색하여 API 요청 및 데이터베이스 상호 작용을 처리하기 위해 Vuex 4.0을 사용한 상태 관리에서 확장 가능한 풀 스택 애플리케이션을 구축하는 데 필요한 모든 것을 독자들에게 가르칠 것입니다. 백엔드는 Rails로 구축되어 프론트엔드 통합을 위한 기본 CRUD API를 제공합니다.

대부분의 회사는 프론트엔드 개발 팀이 개발에 적합한 프론트엔드 도구를 선택할 수 있는 유연성을 제공하기 때문에 API 기반 개발 아키텍처를 채택했습니다. 이것이 최선의 선택은 아닐 수도 있지만 신뢰할 수 있는 선택이었으며 많은 회사에서 팀에 유연성을 더해주기 때문에 이 개발 아키텍처를 채택했습니다.

전제조건

시작하기 전에 이 튜토리얼을 따라갈 수 있도록 다음 사항을 확인하세요.

  • Rails V6.x
  • Node.js V10.x
  • Ruby on Rails에 대한 사전 작업 지식
  • TypeScript에 대한 사전 작업 지식
  • Vue.js에 대한 사전 작업 지식

우리가 구축할 것

Vue, Vuex 및 Rails를 사용하여 전체 스택 애플리케이션 구축

이 튜토리얼에서는 건강에 해로운 가축에 대한 치료 처방을 관리하는 풀 스택 CRUD 애플리케이션을 빌드합니다. 사용자는 가축에 대한 처방전을 생성, 업데이트 및 삭제할 수 있습니다. CRUD API는 Rails를 사용하여 빌드되어 Vue 3으로 빌드될 프런트엔드의 통합을 위해 CRUD API를 노출합니다. 이러한 각 스택은 서로 다른 포트에서 독립적인 애플리케이션으로 실행됩니다.

풀스택 앱의 기본 아키텍처

우리의 전체 스택 애플리케이션은 독립적으로 실행되는 클라이언트 및 서버 애플리케이션으로 구성되며, 클라이언트 애플리케이션의 각 구성 요소는 애플리케이션 상태의 적절한 관리를 위해 Vuex를 통해 서버 애플리케이션에서 발행한 CRUD API와 상호 작용합니다. 백엔드 애플리케이션은 모든 처방 데이터를 Sqlite 3 데이터베이스에 저장하는 동시에 CRUD API를 프론트엔드 애플리케이션에 노출합니다.

백엔드 서비스 설정

원하는 폴더에서 다음 명령을 실행하여 Rails 앱을 만듭니다.

rails new vet_clinic_api --api

이것은 Rails에게 이 프로젝트를 API로 생성하도록 지시하여 모든 프론트엔드 종속성(보기 파일)을 제거합니다.

데이터베이스 구성

Rails 애플리케이션의 기본 데이터베이스인 Sqlite 3을 사용할 것입니다.

rails g scaffold prescriptions vet_prescription:text prescribed_by:text disease:text livestock:text completed:boolean

위의 명령을 실행하여 Rails는 마이그레이션, 테스트, 모델, 컨트롤러, 경로에 대한 초기 구조를 스캐폴딩합니다.

rails db:migrate

이 명령은 데이터베이스에 테이블을 추가합니다.

데이터베이스 시드

일부 처방 데이터로 데이터베이스를 시드합시다.아래 코드 스니펫을 db/migrate/seed.rb에 추가합니다.

//db/migrate/seed.rb

Prescription.destroy_all
Prescription.create!([{
    vet_prescription:"Achyranthes aspera",
    prescribed_by:"Dr Chucks",
    disease:"Rabbies",
    livestock:"goat",
    completed:false
},
{
    vet_prescription:"Achyranthes aspera",
    prescribed_by:"Dr Rex",
    disease:"Rabbies",
    livestock:"Dog",
    completed:false
},
{
    vet_prescription:"ethnovet",
    prescribed_by:"Dr Chucks",
    disease:"Pox",
    livestock:"Sheep",
    completed:false
}])
p "Created #{Prescription.count} prescriptions"

이 파일은 데이터베이스 시드를 위한 초기 데이터를 저장하여 앱이 시작될 때 기존 처방 데이터를 갖게 됩니다.

다음 명령을 실행하여 db/migrate/seed.rb의 코드를 실행합니다. , 사전 정의된 처방 데이터로 데이터베이스에 시드:

rails db:seed

이 몇 가지 명령으로 우리는 Rails로 기능적인 CRUD API를 만들었습니다. 얼마나 쉬웠습니까? (미소)

CORS 구성

CRUD API를 프론트엔드에 노출할 것이고 프론트엔드와 백엔드 서버가 모두 다른 포트에서 실행될 것이기 때문에 프론트엔드와 백엔드 서버 사이의 데이터 공유에 대한 액세스 권한을 부여하기 위해 Rails 백엔드에서 CORS 구성을 설정해야 합니다. 백엔드.

Gemfile 찾기 프로젝트 루트에서 다음 코드 줄의 주석 처리를 제거합니다.

# gem 'rack-cors'

config/environments/initializers/cors.rb에 다음 코드를 추가합니다. :

//config/environments/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*'
    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

위의 스니펫을 사용하면 모든 포트에서 실행되는 프론트엔드 앱에서 Rails CRUD API에 액세스할 수 있습니다.

다음 명령을 실행하여 Rails 서버를 시작하십시오.

rails s

localhost:3000/prescriptions로 이동합니다. 모든 처방에 대한 JSON 응답을 받으려면.

프로젝트의 워크플로를 자주 변경하는 클라이언트와 작업하는 경우 Rails 사용을 고려하십시오. Rails를 사용하면 몇 가지 명령과 코드 줄로 기능을 쉽게 구현할 수 있습니다. 제 개인적인 생각입니다.

Vue 소개

Vue는 프로그레시브 프레임워크입니다. 사용자 인터페이스를 구축하기 위한 것입니다. Vue는 가상 DOM, 렌더링 기능 및 서버 측 렌더링 기능과 같은 개념을 도입하여 웹 애플리케이션에 상당한 성능 최적화를 제공합니다.

Vue 3에는 개발자를 위한 많은 새로운 기능과 변경 사항이 있습니다. 이러한 기능은 프레임워크의 전반적인 안정성과 속도 및 유지 관리 용이성을 개선하도록 설계되었습니다.

Vue 3에서 가장 기대되는 기능 중 하나인 구성 API를 사용할 것입니다. 코드 작성에 대한 보다 체계적이고 효율적인 접근 방식과 완전한 TypeScript 유형 검사 지원을 통해 Vue 구성 요소를 생성하는 새로운 방법입니다.

Vuex 소개

Vuex는 Vue 팀에서 만든 상태 관리 라이브러리로 Redux와 동일한 플럭스 아키텍처를 기반으로 합니다. Vue용으로 특별히 설계되었습니다. 상점을 더 잘 구성할 수 있습니다. Vue 응용 프로그램 상태가 성장함에 따라 더 복잡해지면 Vuex가 중요해집니다. Vuex의 안정적인 최신 릴리스인 v4.0.0은 Vue 3에 도입된 Composition API와 TypeScript에 대한 보다 강력한 추론을 지원합니다.

프론트엔드 애플리케이션 설정

프런트엔드는 Vue 3 및 typeScript로 설정되고 Vuex는 애플리케이션 상태 관리에 사용됩니다.

먼저 Vue-CLI 도구를 사용하여 typescript를 지원하는 Vue 3 앱을 만들어 보겠습니다.

다음 명령을 사용하여 Vue-CLI 도구를 전역적으로 설치합니다.

npm install --global @vue/cli

아래 명령을 사용하여 typescript 및 Vuex 지원으로 새 Vue 3 앱을 만듭니다.

vue create vet_clinic_frontend

수동 선택 기능 옵션을 선택하고 스페이스 키를 눌러 다음 옵션을 선택하십시오.

  • Vue 버전 선택
  • 바벨
  • 타입스크립트
  • 린터/포매터

다음으로 프로젝트 버전으로 Vue 3.x(Preview)를 선택합니다.

  • 클래스 스타일 구성요소 구문을 사용하려면 yes를 입력하세요.
  • TypeScript와 함께 Babel을 사용하려면 yes를 입력하세요.
  • 원하는 린터를 선택하세요.

Vue 3 앱이 성공적으로 생성되면 Vuex(V4.x) 및 전체 타이프스크립트 지원이 포함된 Vue 3 프로젝트 설정을 갖게 됩니다.

typescript를 사용하여 애플리케이션에 유형 안전성을 추가하겠습니다. 개발 서버를 시작하려면 터미널에서 아래 명령을 실행하고 https://localhost:8080으로 이동하세요. 브라우저에서 프로젝트를 미리 봅니다.

응용 프로그램의 스타일을 지정하기 위해 Bulma CSS 프레임워크를 사용합니다. Bulma CSS를 설치하려면 다음 명령을 실행하십시오.

npm install bulma

Bulma CSS를 가져오려면 App.vue에 다음 코드를 추가하세요. :

//App.vue
<style lang="scss">
@import "~bulma/css/bulma.css";
</style>

애플리케이션 스토어 설정

애플리케이션 스토어는 Vuex로 설정됩니다. 스토어를 변경하려면 구성 요소에서 작업이 전달되어 돌연변이를 트리거하여 스토어를 업데이트합니다.

응용 프로그램 저장소를 설정하려면 다음 단계를 따르십시오.

  1. 상태 개체를 만듭니다.
  2. 애플리케이션에서 발생할 돌연변이를 설정합니다.
  3. 이러한 후속 돌연변이에 커밋할 작업을 만듭니다.
  4. 상태 데이터를 직접 계산하기 위해 구성 요소에 대한 getter를 만듭니다.

상태는 모든 구성 요소에서 액세스해야 하는 응용 프로그램 수준 데이터를 보유하는 저장소 개체입니다.

state.ts 생성 다음 코드 스니펫을 사용하여 store 디렉토리에 있는 파일:

//src/store/state.ts
export type Prescription = {
  id: number;
  vet_prescription: string;
  prescribed_by: string;
  disease: string;
  livestock: string;
  completed: boolean;
  editing: boolean;
};
export type Data = {
  vet_prescription: string;
  prescribed_by: string;
  disease: string;
  livestock: string;
};
export type State = {
  loading: boolean;
  prescriptions: Prescription[];
  data: Data | null;
  showCreateModal: boolean;
  showEditModal: boolean;
  showPrescriptionModal: boolean;
  editModalPrescriptionId: number | undefined;
  showPrescriptionId: number | undefined;
};
export const state: State = {
  loading: false,
  prescriptions: [],
  data: null,
  showCreateModal: false,
  showEditModal: false,
  showPrescriptionModal: false,
  editModalPrescriptionId: undefined,
  showPrescriptionId: undefined,
};

여기에서 Prescription에 몇 가지 유형 안전성을 추가합니다. 및 Data . 또한 유형이 getter, mutation 및 action의 정의에 사용되기 때문에 유형을 내보냅니다. 마지막으로 State 유형을 state로 캐스팅합니다.

돌연변이

돌연변이는 트리거될 때 저장소를 수정하는 메서드입니다. 상태를 첫 번째 인수로 수신하고 페이로드를 두 번째 인수로 수신하여 결국 페이로드로 애플리케이션 상태를 수정합니다. 돌연변이를 생성하기 위해 Vuex 문서는 돌연변이 유형에 대해 상수를 사용할 것을 권장합니다.

mutations.ts 생성 다음 코드 스니펫을 사용하여 store 디렉토리에 있는 파일:

//src/store/mutations.ts
import { MutationTree } from "vuex";
import { State, Prescription, Data } from "./state";

export enum MutationType {
  CreatePrescription = "CREATE_PRESCRIPTION",
  SetPrescriptions = "SET_PRESCRIPTIONS",
  CompletePrescription = "COMPLETE_PRESCRIPTION",
  RemovePrescription = "REMOVE_PRESCRIPTION",
  EditPrescription = "EDIT_PRESCRIPTION",
  UpdatePrescription = `UPDATE_PRESCRIPTION`,

  SetLoading = "SET_LOADING",
  SetCreateModal = "SET_CREATE_MODAL",
  SetEditModal = "SET_EDIT_MODAL",
  SetPrescriptionModal = "SET_PRESCRIPTION_MODAL",
}

위의 스니펫은 열거형 기호를 사용하여 앱에서 가능한 모든 변형 이름을 보유합니다.

다음으로 각 MutationType에 대한 계약(유형)을 다음과 같이 선언합니다.

//src/store/mutation.ts
export type Mutations = {
  [MutationType.CreatePrescription](state: State, prescription: Data): void;

  [MutationType.SetPrescriptions](state: State, prescription: Prescription[]): void;

  [MutationType.CompletePrescription](state: State, prescription: Partial<Prescription> & { id: number }): void;

  [MutationType.RemovePrescription](state: State, prescription: Partial<Prescription> & { id: number }): void;

  [MutationType.EditPrescription](state: State, prescription: Partial<Prescription> & { id: number }): void;

  [MutationType.UpdatePrescription](state: State, prescription: Partial<Prescription> & { id: number }): void;

  [MutationType.SetLoading](state: State, value: boolean): void;

  [MutationType.SetCreateModal](state: State, value: boolean): void;

  [MutationType.SetEditModal](state: State, value: { showModal: boolean; prescriptionId: number | undefined }): void;

  [MutationType.SetPrescriptionModal](state: State, value: { showModal: boolean; prescriptionId: number | undefined }): void;
};

그런 다음 각 MutationType에 대해 선언된 계약을 구현합니다. , 다음과 같이:

//src/store/mutation.ts
export const mutations: MutationTree<State> & Mutations = {
  [MutationType.CreatePrescription](state, prescription) {
    state.data == prescription;
  },
  [MutationType.SetPrescriptions](state, prescriptions) {
    state.prescriptions = prescriptions;
  },
  [MutationType.CompletePrescription](state, newPrescription) {
    const prescription = state.prescriptions.findIndex((prescription) => prescription.id === newPrescription.id);
    if (prescription === -1) return;
    state.prescriptions[prescription] = { ...state.prescriptions[prescription], ...newPrescription };
  },
  [MutationType.RemovePrescription](state, Prescription) {
    const prescription = state.prescriptions.findIndex((prescription) => prescription.id === Prescription.id);
    if (prescription === -1) return;
    //If prescription exist in the state, remove it
    state.prescriptions.splice(prescription, 1);
  },
  [MutationType.EditPrescription](state, Prescription) {
    const prescription = state.prescriptions.findIndex((prescription) => prescription.id === Prescription.id);
    if (prescription === -1) return;
    //If prescription exist in the state, toggle the editing property
    state.prescriptions[prescription] = { ...state.prescriptions[prescription], editing: !state.prescriptions[prescription].editing };
    console.log("prescription", state.prescriptions[prescription]);
  },
  [MutationType.UpdatePrescription](state, Prescription) {
    state.prescriptions = state.prescriptions.map((prescription) => {
      if (prescription.id === Prescription.id) {
        return { ...prescription, ...Prescription };
      }
      return prescription;
    });
  },

  [MutationType.SetLoading](state, value) {
    state.loading = value;
  },
  [MutationType.SetCreateModal](state, value) {
    state.showCreateModal = value;
  },
  [MutationType.SetEditModal](state, value) {
    state.showEditModal = value.showModal;
    state.editModalPrescriptionId = value.prescriptionId;
  },
  [MutationType.SetPrescriptionModal](state, { showModal, prescriptionId }) {
    state.showPrescriptionModal = showModal;
    state.showPrescriptionId = prescriptionId;
  },
};

MutationTree Vuex 패키지와 함께 제공되는 일반 유형입니다. 위의 스니펫에서 이를 사용하여 돌연변이 트리 유형을 선언했습니다. 돌연변이 트리와 돌연변이는 계약이 올바르게 구현되도록 합니다. 그렇지 않으면 Typescript에서 오류가 발생합니다.

작업

조치는 돌연변이를 유발하는 메소드입니다. API에 대한 요청과 같은 비동기 작업을 처리할 때 API 응답을 페이로드로 사용하여 해당 변형을 호출하기 전에 작업이 사용됩니다. 작업을 생성하면서 이 시나리오에 대한 명확한 구현을 얻을 것입니다.

액션을 생성하기 전에 다음과 같이 Rails 서버에 대한 모든 Http 요청을 처리하기 위해 Axios를 설치할 것입니다.

npm install axios --save

actions.ts 생성 다음 코드 스니펫을 사용하여 store 디렉토리에 있는 파일:

//src/store/actions.ts
import { ActionContext, ActionTree } from "vuex";
import { Mutations, MutationType } from "./mutations";
import { State, Prescription, Data } from "./state";
import axios from "axios";
const apiUrl = "https://localhost:3000/prescriptions";
export enum ActionTypes {
  GetPrescriptions = "GET_PRESCRIPTIONS",
  SetCreateModal = "SET_CREATE_MODAL",
  SetEditModal = "SET_EDIT_MODAL",
  RemovePrescription = "REMOVE_PRESCRIPTION",
  CreatePrescription = "CREATE_PRESCRIPTION",
  UpdatePrescription = "UPDATE_PRESCRIPTION",
}

마찬가지로 위의 스니펫은 열거형 기호를 사용하여 앱에서 가능한 모든 작업 이름을 보유합니다.

다음으로 다음과 같이 각 ActionType에 대한 계약(유형)을 선언합니다.

//src/store/actions.ts
type ActionAugments = Omit<ActionContext<State, State>, "commit"> & {
  commit<K extends keyof Mutations>(key: K, payload: Parameters<Mutations[K]>[1]): ReturnType<Mutations[K]>;
};

export type Actions = {
  [ActionTypes.GetPrescriptions](context: ActionAugments): void;
  [ActionTypes.SetCreateModal](context: ActionAugments): void;
  [ActionTypes.SetEditModal](context: ActionAugments): void;
  [ActionTypes.RemovePrescription](context: ActionAugments, Prescription: { id: number }): void;
  [ActionTypes.CreatePrescription](context: ActionAugments, data: Data): void;
  [ActionTypes.UpdatePrescription](context: ActionAugments, prescription: Prescription): void;
};

ActionAugments type은 선언된 변형에만 모든 커밋을 제한하고 페이로드 유형도 확인합니다.

다음으로 각 ActionType에 대해 선언된 계약(유형)을 구현합니다. action.ts 파일에 아래 코드 추가:

//src/store/actions.ts
export const actions: ActionTree<State, State> & Actions = {
  async [ActionTypes.GetPrescriptions]({ commit }) {
    commit(MutationType.SetLoading, true);

    const response = await axios.get(apiUrl);

    commit(MutationType.SetLoading, false);
    commit(MutationType.SetPrescriptions, response.data);
  },

  async [ActionTypes.SetCreateModal]({ commit }) {
    commit(MutationType.SetCreateModal, true);
  },

  async [ActionTypes.SetEditModal]({ commit }) {
    commit(MutationType.SetEditModal, { showModal: true, prescriptionId: 1 });
  },

  //Optimistic update
  async [ActionTypes.RemovePrescription]({ commit }, Prescription) {
    if (Prescription != undefined) {
      commit(MutationType.RemovePrescription, Prescription);
    }

    const response = await axios.delete(`${apiUrl}/${Prescription.id}`);
  },

  async [ActionTypes.CreatePrescription]({ commit, dispatch }, Prescription) {
    const response = await axios.post(apiUrl, Prescription);
    dispatch(ActionTypes.GetPrescriptions);
  },

  async [ActionTypes.UpdatePrescription]({ commit, dispatch }, Prescription) {
    if (Prescription != undefined) {
      commit(MutationType.UpdatePrescription, Prescription);
      const response = await axios.patch(`${apiUrl}/${Prescription.id}`, Prescription);
      dispatch(ActionTypes.GetPrescriptions);
    }
  },
};

여기에서 구현된 모든 작업을 저장하는 작업 변수를 만들었습니다. 마찬가지로 ActionTree<State> & Actions 계약(type Actions ) 올바르게 구현되었습니다. 그렇지 않으면 Typescript에서 오류가 발생합니다.

또한 GetPrescriptions에서 Rails API 엔드포인트에 대한 비동기 호출을 설정합니다. 작업 및 트리거된 SetPrescriptions 응답 데이터를 페이로드로 사용하는 돌연변이 유형입니다. SetCreateModal도 설정했습니다. , SetEditModal , CreatePrescription , UpdatePrescription,RemovePrescription 행동.

게터

게터는 상태를 첫 번째 매개변수로 수신하고 저장 상태에서 계산된 정보를 반환하는 메서드입니다.

getters.ts 생성 다음 코드 스니펫을 사용하여 store 디렉토리에 있는 파일:

//src/store/getters.ts
import { GetterTree } from "vuex";
import { State, Prescription } from "./state";
export type Getters = {
  completedPrescriptionCount(state: State): number;
  totalPrescriptionCount(state: State): number;
  getPrescriptionById(state: State): (id: number) => Prescription | undefined;
};
export const getters: GetterTree<State, State> & Getters = {
  completedPrescriptionCount(state) {
    return state.prescriptions.filter((prescription) => prescription.completed).length;
  },
  totalPrescriptionCount(state) {
    return state.prescriptions.length;
  },
  getPrescriptionById: (state) => (id: number) => {
    return state.prescriptions.find((prescription) => prescription.id === id);
  },
};

위의 코드 조각은 다음 getter를 정의합니다.

  • completedPrescriptionCount – 우리 주에서 완료된 처방의 총 개수를 가져오는 함수입니다.
  • totalPrescriptionCount – 우리 주의 총 처방 건수를 가져오는 함수입니다.
  • getPrescriptionById – 아이디로 처방을 받는 기능입니다.

또한 Getters에 몇 가지 유형 안전성을 추가했습니다.

스토어

state를 연결해 보겠습니다. , mutations , actions , 및 getters 글로벌 Vuex 스토어로 이동합니다. store/index.ts를 업데이트하겠습니다. , 다음과 같이:

//src/store/index.ts
import { createStore, Store as VuexStore, CommitOptions, DispatchOptions, createLogger } from "vuex";
import { State, state } from "./state";
import { Mutations, mutations } from "./mutations";
import { Actions, actions } from "./actions";
import { Getters, getters } from "./getters";
export const store = createStore<State>({
  plugins: process.env.NODE_ENV === "development" ? [createLogger()] : [],
  state,
  mutations,
  actions,
  getters,
});
export function useStore() {
  return store as Store;
}
export type Store = Omit<VuexStore<State>, "getters" | "commit" | "dispatch"> & {
  commit<K extends keyof Mutations, P extends Parameters<Mutations[K]>[1]>(key: K, payload: P, options?: CommitOptions): ReturnType<Mutations[K]>;
} & {
  dispatch<K extends keyof Actions>(key: K, payload?: Parameters<Actions[K]>[1], options?: DispatchOptions): ReturnType<Actions[K]>;
} & {
  getters: {
    [K in keyof Getters]: ReturnType<Getters[K]>;
  };
};

state , mutations , actions , 및 getters 저장소를 포함하는 개체를 createStore에 전달하여 저장소를 만드는 데 필요합니다. 방법. 개발하는 동안 createLogger 플러그인은 상태(이전 상태 및 다음 상태) 및 변형을 콘솔에 기록합니다. 모든 애플리케이션 구성 요소에서 저장소에 액세스할 수 있도록 합니다. 전체 애플리케이션에 주입해야 합니다. 다행히 Vue-CLI 도구는 이미 전체 저장소를 가져와 애플리케이션의 Vue 인스턴스 내에서 전달했습니다.

Vuex Store를 구성 요소에 통합

이 튜토리얼에서는 Vue 3 구성 API를 사용하여 프론트엔드 애플리케이션의 모든 구성 요소를 생성합니다.

앱 구성요소

프론트엔드는 실행되는 즉시 처방 데이터 목록을 렌더링해야 합니다. GetPrescription 구성 요소의 mounted() 내 작업 수명 주기 후크. 구성 요소 내의 저장소에 액세스하려면 useStore 상점을 반환하는 hook이 실행됩니다.

//src/App.vue
<script lang="ts">
import { computed, defineComponent, onMounted } from "vue";
import PrescriptionList from "./components/PrescriptionList.vue";
import { useStore } from "./store";
import { ActionTypes } from "./store/actions";
export default defineComponent({
  components: { PrescriptionList },
  setup() {
    const store = useStore();
    const loading = computed(() => store.state.loading);
    onMounted(() => store.dispatch(ActionTypes.GetPrescriptions));
    const completedCount = computed(() => store.getters.completedPrescriptionCount);
    const totalCount = computed(() => store.getters.totalPrescriptionCount);
    return { loading, completedCount, totalCount };
  },
});
</script>
<template>
  <nav class="navbar" role="navigation" aria-label="main navigation">
    <div class="navbar-brand">
      <a class="navbar-item" href="https://bulma.io">
        <img src="https://bulma.io/images/bulma-logo.png" width="112" height="28" />
      </a>
    </div>
    <div id="navbarBasicExample" class="navbar-menu">
      <div class="navbar-start">
        <a class="navbar-item"> Home </a>
        <a class="navbar-item"> About </a>
      </div>
    </div>
  </nav>
  <div class="container mx-auto mt-4">
    <h1 class="is-size-3 has-text-centered p-2 has-text-weight-bold is-success">Vet clinic Frontend</h1>
    <h3 class="has-text-centered p-2">Manage records of treated livestock in your farm</h3>
    <div v-if="loading">
      <h3 class="has-text-centered mt-4">Loading...</h3>
    </div>
    <div v-else>
      <p class="has-text-centered mt-2">{{ completedCount }} of {{ totalCount }} treated.</p>
      <PrescriptionList />
    </div>
  </div>
</template>
<style>
@import "~bulma/css/bulma.css";
</style>

여기에서 세 가지 계산된 속성을 만들었습니다.

  • completedCount , completedPrescriptionCount를 호출합니다. 완료된 치료의 총 수를 검색하는 getter 메서드입니다.
  • totalCount , totalPrescriptionCount 총 처방전 수를 검색하는 getter 메소드
  • loading , 상태의 로딩 속성을 가져옵니다.

Vue 3 구성 API를 사용하면 템플릿에 필요한 메서드와 속성을 템플릿에서 액세스할 수 있도록 반환해야 합니다. loading, completedCount, and totalCount가 어떻게 반환되었는지 확인하세요. .

처방 목록 구성 요소

이 구성 요소는 백엔드에서 처방 목록을 검색하고 처방 데이터를 하위 구성 요소에 전달하는 역할을 합니다.

PrescriptionList.vue 생성 다음 코드를 사용하여 구성 요소 폴더 안에:

//src/components/PrescriptionList.vue
<template>
  <table class="table is-hoverable is-striped">
    <thead>
      <tr>
        <th><abbr title="Position">Prescription Id</abbr></th>
        <th>Treated</th>
        <th>Prescription</th>
        <th><abbr title="Won">Prescribed By</abbr></th>
        <th><abbr title="Drawn">Disease</abbr></th>
        <th><abbr title="Drawn">Livestock</abbr></th>
        <th><abbr title="Lost">Actions</abbr></th>
      </tr>
    </thead>
    <tbody v-if="prescriptions">
      <tr v-for="prescription in prescriptions" :key="prescription.id">
        <PrescriptionListItem v-bind="prescription" />
      </tr>
    </tbody>
    <tfoot>
      <CreateModal v-show="showCreateModal"></CreateModal>
      <button class="button  is-success" @click="setModal">Create Prescription</button>
    </tfoot>
  </table>
  <EditModal v-if="showEditModal" :id="editModalPrescriptionId"></EditModal>
  <Prescription v-if="showPrescriptionModal" :id="showPrescriptionId"></Prescription>
</template>
<script>
import CreateModal from "./CreateModal";
import EditModal from "./EditModal";
import Prescription from "./Prescription";
import PrescriptionListItem from "./PrescriptionListItem";
import { defineComponent, computed } from "vue";
import { useStore } from "@/store";
import { MutationType } from "@/store/mutations";
export default defineComponent({
  name: "Table",
  components: {
    CreateModal,
    PrescriptionListItem,
    Prescription,
    EditModal,
  },
  setup() {
    const store = useStore();
    const setModal = () => {
      store.commit(MutationType.SetCreateModal, true);
    };
    const showCreateModal = computed(() => store.state.showCreateModal);
    const showEditModal = computed(() => store.state.showEditModal);
    const editModalPrescriptionId = computed(() => store.state.editModalPrescriptionId);
    const showPrescriptionModal = computed(() => store.state.showPrescriptionModal);
    const showPrescriptionId = computed(() => store.state.showPrescriptionId);
    const prescriptions = computed(() => store.state.prescriptions);
    return { showCreateModal, setModal, prescriptions, showEditModal, showPrescriptionModal, editModalPrescriptionId, showPrescriptionId };
  },
});
</script>
<style scoped>
table {
  width: 100%;
}
.fa {
  font-size: 1.2rem;
  margin-left: 15px;
}
.fa:hover {
  font-size: 1.4rem;
}
</style>

setModal 메소드는 showCreateModal을 설정하는 변형을 호출합니다. 상태를 true로 설정하여 처방전 생성을 위한 모달을 시작합니다.

다음과 같은 계산된 속성을 만들었습니다.

  • showCreateModal , showCreateModal 국가의 재산입니다.
  • showEditModal , showEditModal 국가의 재산입니다.
  • showPrescriptionModal , showPrescriptionModal 국가의 재산입니다.
  • prescription , 주에서 처방전 목록을 가져옵니다.
  • showPrescriptionId , showPrescriptiond 국가의 재산입니다.
  • editPrescriptionId , editPrescriptionId를 가져옵니다. 국가의 재산입니다.

처방 성분

이 구성 요소는 PrescriptionList에서 소품으로 처방 ID를 받습니다. 요소. ID 소품은 getPrescriptionById를 통해 해당 ID로 처방전을 가져오는 데 사용됩니다. getters 메서드를 사용하고 브라우저에서 처방 속성을 렌더링합니다.

다음 코드를 사용하여 구성 요소 폴더 안에 Prescription.vue를 만듭니다.

//src/components/Prescription.vue
<template>
  <div class="modal is-active">
    <div class="modal-background"></div>
    <div class="modal-content">
      <h1>VIEW PRESCRIPTION</h1>
      <div class="card">
        <div class="card-content">
          <div class="media">
            <div class="media-content">
              <p class="title is-4">Livestock: {{ prescription.livestock }}</p>
              <p class="subtitle is-6"><b>Prescribed by:</b> {{ prescription.prescribed_by }}</p>
              <p class="subtitle is-6"><b>Disease:</b> {{ prescription.disease }}</p>
            </div>
          </div>
          <div class="content">
            <p class="subtitle is-6">Prescription: {{ prescription.vet_prescription }}</p>
          </div>
        </div>
      </div>
    </div>
    <button class="modal-close is-large" @click="closeModal" aria-label="close"></button>
  </div>
</template>
<script lang="ts">
import { computed } from "vue";
import { useStore } from "@/store";
import { MutationType } from "@/store/mutations";
export default {
  name: "PrescriptionModal",
  props: {
    id: { type: Number, required: true },
  },
  setup(props: any) {
    const store = useStore();
    const prescription = computed(() => store.getters.getPrescriptionById(Number(props.id)));
    const closeModal = () => {
      store.commit(MutationType.SetPrescriptionModal, {
        showModal: false,
        prescriptionId: undefined,
      });
    };
    return { closeModal, prescription };
  },
};
</script>
<style scoped>
h1 {
  color: #ffffff;
  text-align: center;
  font-size: 2rem;
  margin-bottom: 3rem;
}
</style>

closeModal 메소드는 SetPrescriptionModal을 커밋합니다. showModal을 설정하는 돌연변이 상태의 속성을 false로 설정하고 prescription computed property calls the getPrescriptionById getter method to retrieve a prescription by its Id.

CreateModal Component

This component is responsible for creating prescriptions.

Create CreateModal.vue inside the components folder with the following code:

//src/components/CreateModal.vue
<template>
  <div class="modal is-active">
    <div class="modal-background"></div>
    <div class="modal-content">
      <form @submit.prevent="createPrescription">
        <div class="field">
          <label class="label">Prescribed By</label>
          <div class="control">
            <input v-model="prescribedBy" class="input" type="text" placeholder="Enter prescriber's name" />
          </div>
        </div>
        <div class="field">
          <label class="label">Prescription</label>
          <div class="control">
            <textarea v-model="prescription" class="textarea" placeholder="Enter prescription"></textarea>
          </div>
        </div>
        <div class="field">
          <label class="label">Disease</label>
          <div class="control">
            <input v-model="disease" class="input" type="text" placeholder="Enter name of disease" />
          </div>
        </div>
        <div class="field">
          <label class="label">Livestock</label>
          <div class="control">
            <input v-model="livestock" class="input" type="text" placeholder="Enter livestock" />
          </div>
        </div>
        <div class="field is-grouped">
          <div class="control">
            <button type="submit" class="button is-link">Submit</button>
          </div>
          <div class="control" @click="closeModal">
            <button class="button is-link is-light">Cancel</button>
          </div>
        </div>
      </form>
    </div>
    <button class="modal-close is-large" @click="closeModal" aria-label="close"></button>
  </div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs } from "vue";
import { useStore } from "@/store";
import { Data } from "@/store/state";
import { MutationType } from "@/store/mutations";
import { ActionTypes } from "@/store/actions";
export default {
  name: "CreateModal",
  setup() {
    const state = reactive({
      prescription: "",
      prescribedBy: "",
      disease: "",
      livestock: "",
    });
    const store = useStore();
    const createPrescription = () => {
      if (state.prescription === "" || state.prescribedBy === "" || state.disease === "" || state.livestock === "") return;
      const prescription: Data = {
        vet_prescription: state.prescription,
        prescribed_by: state.prescribedBy,
        disease: state.disease,
        livestock: state.livestock,
      };
      store.dispatch(ActionTypes.CreatePrescription, prescription);
      state.prescription = "";
      state.prescribedBy = "";
      state.disease = "";
      state.livestock = "";
    };
    const closeModal = () => {
      store.commit(MutationType.SetCreateModal, false);
    };
    return { closeModal, ...toRefs(state), createPrescription };
  },
};
</script>

The createPrescription method dispatches an action that makes a post request to the server, thereby creating a new prescription, while the closeModal method commits the SetPrescriptionModal mutation (which sets the showModal property in the state to false).

Working with forms and input element requires two-way data binding, and as such, we used Vue 3’s reactive method to store values used in the input fields.

Note:When using reactive , we need to use toRefs to convert the reactive object to a plain object, where each property on the resulting object is a ref pointing to the corresponding property in the original object.

EditModal Component

This component is responsible for updating prescriptions. Its logic is similar to the CreatePrescription component we discussed in the previous section.

Create EditModal.vue inside the components folder with the following code:

//src/components/EditModal.vue
<template>
  <div class="modal is-active">
    <div class="modal-background"></div>
    <div class="modal-content">
      <form @submit.prevent="updatePrescription">
        <h1>Edit Modal</h1>
        <div class="field">
          <label class="label">Prescribed By</label>
          <div class="control">
            <input v-model="prescribedBy" class="input" type="text" placeholder="Enter prescriber's name" />
          </div>
        </div>
        <div class="field">
          <label class="label">Prescription</label>
          <div class="control">
            <textarea v-model="prescription" class="textarea" placeholder="Enter Prescription"></textarea>
          </div>
        </div>
        <div class="field">
          <label class="label">Disease</label>
          <div class="control">
            <input v-model="disease" class="input" type="text" placeholder="Enter name of disease" />
          </div>
        </div>
        <div class="field">
          <label class="label">Livestock</label>
          <div class="control">
            <input v-model="livestock" class="input" type="text" placeholder="Enter livestock" />
          </div>
        </div>
        <div class="field is-grouped">
          <div class="control">
            <button type="submit" class="button is-link">Submit</button>
          </div>
          <div class="control" @click="closeModal">
            <button class="button is-link is-light">Cancel</button>
          </div>
        </div>
      </form>
    </div>
    <button class="modal-close is-large" @click="closeModal" aria-label="close"></button>
  </div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs, computed, onMounted } from "vue";
import { useStore } from "@/store";
import { Prescription } from "@/store/state";
import { MutationType } from "@/store/mutations";
import { ActionTypes } from "@/store/actions";
export default {
  name: "EditModal",
  props: {
    id: { type: Number, required: true },
  },
  setup(props: any) {
    const state = reactive({
      prescription: "",
      prescribedBy: "",
      disease: "",
      livestock: "",
    });
    const store = useStore();
    const setFields = () => {
      const prescription = store.getters.getPrescriptionById(Number(props.id));
      if (prescription) {
        console.log("prescription si kolo", prescription);
        state.prescription = prescription.vet_prescription;
        state.prescribedBy = prescription.prescribed_by;
        state.disease = prescription.disease;
        state.livestock = prescription.livestock;
      }
    };
    onMounted(() => {
      setFields();
    });
    const updatePrescription = () => {
      if (state.prescription === "" || state.prescribedBy === "" || state.disease === "" || state.livestock === "") return;
      const prescription: Prescription = {
        id: props.id,
        vet_prescription: state.prescription,
        prescribed_by: state.prescribedBy,
        disease: state.disease,
        livestock: state.livestock,
        completed: false,
        editing: false,
      };
      store.dispatch(ActionTypes.UpdatePrescription, prescription);
      state.prescription = "";
      state.prescribedBy = "";
      state.disease = "";
      state.livestock = "";
    };
    const closeModal = () => {
      store.commit(MutationType.SetEditModal, { showModal: false, prescriptionId: undefined });
    };
    return { closeModal, ...toRefs(state), updatePrescription };
  },
};
</script>
<style scoped>
label {
  color: #ffffff;
}
h1 {
  color: #ffffff;
  text-align: center;
  font-size: 2rem;
  margin-bottom: 3rem;
}
</style>

The createPrescription method dispatches an action that makes a put request to the server, thereby updating an existing prescription by its ID, while the closeModal method commits the SetPrescriptionModal mutation that sets the showModal property in the state to false. Calling the setFields method on the onMounted lifecycle hook triggers the getPrescriptionById getters method to fetch a prescription from the store by its ID and then updates the properties in the reactive object with the fetched prescription properties as soon as the component is mounted on the DOM.

Launching the Final Project

In the root directory of your Rails API server, run the following command to start the server:

rails server

Now, you can run the frontend application with the following command:

npm run serve

Finally, your application should be as follows:

Vue, Vuex 및 Rails를 사용하여 전체 스택 애플리케이션 구축

결론

We have built a CRUD API server with Rails and our frontend application on Vue 3 composition API and Vuex, all running on different servers while integrating both stacks to build a fullstack web application. I hope you have learned a great deal from this tutorial. Please reach out in the comment section below if you have any questions or suggestions. Here is the GitHub repo for the full-stack application built in this tutorial.