Frond-end/Vue

[Vue3] 의료 플랫폼 개발 기록

Nellie Kim 2024. 3. 19. 16:02
728x90

vue 를 사용하여 플랫폼을 개발하며 하는 기록.. 

 

varlet이라는 프레임워크를 사용하였으며 vue 3버전을 사용하여 개발하였다. 

 

간단히 리스트 / 검색기능 / 상세보기 기능을 구현했다. 

CSS 가 적용되지 않아 허접해보인다. 

servicesearch > list.vue

<script setup lang="ts">
import { getGuideList, GuideModel } from '@/apis/guide'
import { getCodeBaseInfo, RETURN_CODE } from '@/apis/common'
import { useRouter } from 'vue-router'

/**
|--------------------------------------------------
| 😃 1. 일반 변수
|--------------------------------------------------
*/
const departmentArr = ref<string[] | undefined>([]) // 1. 과 유형
const actsArr = ref<string[] | undefined>([]) // 2. 행위 유형

const guideResult = ref<GuideModel[]>([]) // 검색 결과 담는 변수

const selectedDept = ref<string>('') // 사용자가 선택한 과
const selectedAct = ref<string>('') // 사용자가 선택한
const keyword = ref<string>('') // 3. 키워드 - 제목 or 내용
const router = useRouter()

// const searchKeyword = ref('')

function goToDetail(idx: any) {
  if (idx !== undefined) {
    // 상세페이지 이동
    router.push(`/hospital/servicesearch/${idx}`)
  } else {
    // ID가 없는 경우 처리할 로직 추가
    console.error('Item ID is undefined!')
  }
}

/**
|--------------------------------------------------
| 🎁 3. fetch 함수
|--------------------------------------------------
*/

/** 마운트 되기 전에 가이드 리스트 조회 */
onBeforeMount(async () => {
  await fetchData()
  // await searchGuide()
})

/** 3-1. 가이드 리스트 조회 (/guide/getList) */
const fetchData = async () => {
  /** 3-1-1. 가이드 리스트 조회  */
  let response = await getGuideList({} as GuideModel) // /guide/getList

  if (response.code !== RETURN_CODE.SUCCESS) {
    console.log('예약 정보를 가져오는 중 오류가 발생했습니다.')
    return
  }

  const guideDetailList = response.data.dataList as GuideModel[]
  console.log('guideDetailList...:', guideDetailList)

  // 가이드 리스트에서 행위 목록만 중복되는 것 제거하고 추출하여 actsArr 에 세팅
  actsArr.value = Array.from(new Set(guideDetailList.map((item) => item.act as string)))
  console.log('actsArr.value : ', actsArr.value)

  const request = {
    codeA: 'GUIDE_DEPARTMENT'
  }

  /** 3-2. 코드 B 리스트 조회 */
  response = await getCodeBaseInfo(request) // /bCode/getList

  departmentArr.value = response.data.dataList.map((item: any) => item.codeName as string) // 과 이름만 추출하여 departmentArr에 세팅
  console.log('departmentArr.value : ', departmentArr.value)
}

/** 3-2. 가이드 검색  */
const searchGuide = async () => {
  const params = {
    department: selectedDept.value,
    act: selectedAct.value,
    keyword: keyword.value
  } as GuideModel

  /** 3-2-1. 가이드 리스트 조회  */
  const response = await getGuideList(params) // /guide/getList

  if (response.code !== RETURN_CODE.SUCCESS) {
    console.log('예약 정보를 가져오는 중 오류가 발생했습니다.')
    return
  }

  const guideDetailList = response.data.dataList as GuideModel[]
  // guideResult.value.push(guideDetailList)

  guideResult.value = guideDetailList.map((item) => ({
    idx: item.idx,
    department: item.department2,
    act: item.act,
    title: item.title,
    contents: item.contents,
    images: item?.images
  }))

  console.log('guideDetailList : ', guideDetailList)
  console.log('guideResult.value : ', guideResult.value)
  console.log('guideResult.value[0].images[0].url : ', guideResult.value[0].images[0].url)
}
</script>

<template>
  <div class="message">
    <var-pull-refresh>
      <app-header title="교육자료검색">
        <template #left>
          <app-back />
        </template>
        <template #right>
          <app-side-menu />
        </template>
      </app-header>

      <div class="message-list">
        <div class="message-item">
          <div class="message-item-detail">
            <div class="message-item-detail-header-centered-box">
              <var-space direction="column" size="large">
                <var-select variant="outlined" placeholder="과 유형" v-model="selectedDept">
                  <template v-for="item in departmentArr" :key="item">
                    <var-option :label="item" />
                  </template>
                </var-select>
                <var-select variant="outlined" placeholder="행위 유형" v-model="selectedAct">
                  <template v-for="item in actsArr" :key="item">
                    <var-option :label="item" />
                  </template>
                </var-select>
                <var-input variant="outlined" placeholder="제목 또는 내용을 검색하세요" v-model="keyword" />
                <var-icon @click="searchGuide" name="magnify" />
              </var-space>
            </div>
          </div>
        </div>

        <div class="message-item">
          <div class="message-list">
            <div
              v-for="(item, i) in guideResult"
              :key="i"
              class="message-item-detail-header-centered-box"
              @click="goToDetail(item.idx)"
            >
              {{ item.department }} <br />
              {{ item.act }}<br />
              -제목 : {{ item.title }}<br />
              -안내 내용 : {{ item.contents }}<br />
              -이미지url(임시) : {{ item.images?.[0]?.url }}
              <div v-if="item.images?.[0] !== undefined">
                <img src="/src/assets/images/avatar.jpg" width="100%" />
              </div>
            </div>
          </div>
        </div>
      </div>
    </var-pull-refresh>
  </div>

  <router-stack-view />
</template>

<style lang="less" scoped>
.message {
  padding: calc(var(--app-bar-height)) 0 0;

  &-list {
    padding: 10px 0;
    overflow-y: auto; /* 세로 스크롤 활성화 */
    max-height: calc(100vh - var(--app-bar-height)); /* 스크롤 높이 조정 */
  }

  &-item {
    position: relative;
    display: flex;
    padding-top: 3px;

    &-avatar {
      flex-shrink: 0;
    }

    &-detail {
      width: 100%;
      border-bottom: thin solid var(--divider-color);
      padding-bottom: 5px;

      &-header {
        display: flex;
        justify-content: space-between;
        align-items: center;

        &-name {
          color: var(--app-title-color);
          font-size: 16px;
          width: 190px;
        }

        &-date {
          color: var(--app-subtitle-color);
          font-size: 14px;
          margin-bottom: 2px;
        }

        &-centered-box {
          /* 위아래 여백 조절 */
          margin: 10px;
          padding: 17px;
          border: 1.5px solid rgb(222, 218, 218);
          border-radius: 10px;
          color: rgb(49, 49, 165);
        }
      }

      &-description {
        color: var(--app-subtitle-color);
        font-size: 15px;
        margin-top: 6px;
      }
    }
  }

  .var-steps {
    margin: 10px;
  }

  .var-step {
    margin: 15px;
  }
}
.var-step__vertical-content {
  margin: 15px !important;
  border: 1.5px solid rgb(222, 218, 218) !important;
  border-radius: 10px !important;
}

.var-step__vertical-tag {
  margin: 100px !important; /* 원하는 간격을 지정합니다. */
}

/* 교육자료 검색 */
.full-height-title {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* 과유형 */
.ripple-parent {
  position: relative;
  overflow: hidden;
  border: 1px solid #ddd;
  padding: 1vh;
}

/* select box */
.select-box-style {
  width: 100%;
  background-color: #f1f3f5;
  border-radius: 0.5rem;
  margin-bottom: 1vh;
  padding: 0vh 2vh;
}
/* 돋보기 */
.searchBox {
  width: 100%;
  margin-top: 2vh;
  margin-bottom: 2vh;
  border-color: #dee2e6;
  border-radius: 0.5rem;
  outline: none;
  box-shadow: none;
  width: calc(100% - 4vh);
  margin-right: 1vh;
}
.search-box-style {
  height: 30%;
  width: 100%;
  margin-bottom: 1vh;
  float: left;
}
input .icon-search {
  font-size: 2em;
  color: #dee2e6;
}
ion-card-content {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
</style>

 

 

 

servicesearch > [idx].vue

<script setup lang="ts">
import { onMounted } from 'vue'
import { getGuideDetail, GuideModel } from '@/apis/guide'
import { useRouter } from 'vue-router'
import { ImagePreview } from '@varlet/ui'

const router = useRouter()
const props = defineProps<{ idx: number }>()
const data = ref<GuideModel>()

function preview(url: string) {
  ImagePreview(url)
}

/** 가이드 상세보기 호출 */
async function fetchData() {
  const idx = props.idx

  console.log('idx,,,,,,,,??', idx)
  if (idx == 0) return

  const request = {
    idx: idx
  }

  const response = await getGuideDetail(request)

  tokenError(response.code)

  if (response.status === 200) {
    data.value = response.data.dataList
    console.log('data.value,,,,,,,,,,,,,,,,,,,,,,,???', data.value)
  }
}
function tokenError(code: string) {
  if (/^30002\d{3}$/.test(code)) {
    Snackbar.error('로그인 후 이용 부탁드립니다')
    router.replace('/login')
  }
}

onMounted(() => {
  fetchData()
})
</script>

<template>
  <div class="message">
    <app-header title="교육자료검색">
      <template #left>
        <app-back />
      </template>
      <template #right>
        <app-side-menu />
      </template>
    </app-header>
    <div class="block"></div>
    <div class="content">
      <div class="message-item">
        <div class="message-list">
          <div v-for="(item, i) in data" :key="i" class="message-item-detail-header-centered-box">
            -과 : {{ item.department2 }} <br />
            -행위 : {{ item.act }}<br />
            -제목 : {{ item.title }}<br />
            -안내 내용 : {{ item.contents }}<br />

            <div v-for="(img, i) in item.images" :key="i">
              <br />-이미지url(임시) {{ i + 1 }} : <br />
              <img :src="img.url" @click="preview(img.url)" />
              <div>
                {{ img.url }}
              </div>
              <img
                src="/src/assets/images/avatar.jpg"
                @click="preview('/src/assets/images/avatar.jpg')"
                width="100%"
              /><br />
            </div>
            <br />
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<style lang="less" scoped>
.container {
  display: flex;
  flex-direction: column;
}

.content {
  padding: 20px;
}

.centered-submit {
  display: flex;
  justify-content: center;
}

.request-title {
  font-weight: bold;
  margin-bottom: 10px;
}

.nurse-textarea {
  width: 100%;
  min-height: 100px;
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 5px;
  margin-bottom: 20px;
}

.submit-btn {
  padding: 10px 20px;
  background-color: var(--color-primary);
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  width: 90%;
}

.request-title {
  font-weight: bold;
  margin-bottom: 10px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.request-date {
  font-weight: normal;
  font-size: 0.9em;
}

.block {
  height: 50px;
}
</style>