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>