Feat: Add chatbot component
This commit is contained in:
parent
0c5ad07f0e
commit
8770fd9f38
@ -1,58 +1,11 @@
|
|||||||
// import axios from 'axios';
|
|
||||||
|
|
||||||
// const apiKey = import.meta.env.VITE_OPENAI_API_KEY;
|
|
||||||
|
|
||||||
// const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
||||||
|
|
||||||
// export const sendMessageToChatGPT = async (message, retries = 3) => {
|
|
||||||
// let attempt = 0;
|
|
||||||
// const maxDelay = 32000; // 최대 지연 시간 (32초)
|
|
||||||
|
|
||||||
// while (attempt < retries) {
|
|
||||||
// try {
|
|
||||||
// console.log(apiKey)
|
|
||||||
// const response = await axios.post(
|
|
||||||
// 'https://api.openai.com/v1/chat/completions',
|
|
||||||
// {
|
|
||||||
// model: 'gpt-3.5-turbo', // 또는 다른 사용 가능한 모델로 변경
|
|
||||||
// messages: [
|
|
||||||
// { role: 'system', content: 'You are a tour guide for those who want to travel to Korea. Answer only questions related to your trip to Korea.' },
|
|
||||||
// { role: 'user', content: message }
|
|
||||||
// ],
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// headers: {
|
|
||||||
// 'Content-Type': 'application/json',
|
|
||||||
// 'Authorization': `Bearer ${apiKey}`,
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
|
|
||||||
// return response.data.choices[0].message.content;
|
|
||||||
// } catch (error) {
|
|
||||||
// if (error.response && error.response.status === 429) {
|
|
||||||
// attempt++;
|
|
||||||
// const backoffTime = Math.min(1000 * 2 ** attempt, maxDelay); // 지수 백오프
|
|
||||||
// console.warn(`Rate limit exceeded. Retrying in ${backoffTime / 1000} seconds...`);
|
|
||||||
// await delay(backoffTime);
|
|
||||||
// } else {
|
|
||||||
// console.error('Error calling OpenAI API:', error);
|
|
||||||
// throw error;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// throw new Error('Exceeded maximum retry attempts');
|
|
||||||
// };
|
|
||||||
|
|
||||||
import { localAxios } from '@/utils/http-commons';
|
import { localAxios } from '@/utils/http-commons';
|
||||||
|
|
||||||
const local = localAxios;
|
const local = localAxios;
|
||||||
|
|
||||||
const sendMessageToChatGPT = async (message) => {
|
const sendMessageToChatGPT = async (message) => {
|
||||||
try {
|
try {
|
||||||
console.log(message)
|
|
||||||
const response = await local.post('/api/chatgpt/message', { message });
|
const response = await local.post('/api/chatgpt/message', { message });
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error sending message to ChatGPT:', error);
|
console.error('Error sending message to ChatGPT:', error);
|
||||||
|
54
src/components/chatbot/ChatBotButton.vue
Normal file
54
src/components/chatbot/ChatBotButton.vue
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import FilledButton from '../common/FilledButton.vue';
|
||||||
|
import TravelChatBot from './TravelChatBot.vue';
|
||||||
|
|
||||||
|
const isOpen = ref(false);
|
||||||
|
|
||||||
|
function toggleChatbot() {
|
||||||
|
isOpen.value = !isOpen.value;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<FilledButton class="chatbot-button" @click="toggleChatbot">채팅</FilledButton>
|
||||||
|
<div v-if="isOpen" class="chatbot-wrapper">
|
||||||
|
<TravelChatBot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.chatbot-button {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 2px solid var(--color-primary);
|
||||||
|
color: var(--color-primary);
|
||||||
|
z-index: 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes show {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate3d(0, 20px, 0) scale(0.95);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chatbot-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 104px;
|
||||||
|
right: 20px;
|
||||||
|
background-color: var(--color-background);
|
||||||
|
width: 390px;
|
||||||
|
height: calc(100% - 124px);
|
||||||
|
max-height: 600px;
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
animation: show 0.35s ease;
|
||||||
|
transform-origin: bottom right;
|
||||||
|
z-index: 9;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,183 +1,160 @@
|
|||||||
<!-- <template>
|
<script setup>
|
||||||
<div class="chat-container">
|
import sendMessageToChatGPT from '@/api/chatbot';
|
||||||
<div class="messages">
|
import { nextTick, ref } from 'vue';
|
||||||
<div v-for="(msg, index) in messages" :key="index" :class="msg.role">
|
import SearchBox from '../common/SearchBox.vue';
|
||||||
{{ msg.content }}
|
|
||||||
</div>
|
const userInput = ref('');
|
||||||
</div>
|
const messages = ref([]);
|
||||||
<div class="input-container">
|
const scrollRef = ref(null);
|
||||||
<input
|
const pending = ref(false);
|
||||||
v-model="userInput"
|
|
||||||
@keyup.enter="handleSend"
|
function handleSubmit() {
|
||||||
type="text"
|
if (!userInput.value?.trim()) return;
|
||||||
placeholder="Type your travel query..."
|
|
||||||
/>
|
messages.value.push({ role: 'user', content: userInput.value });
|
||||||
<button @click="handleSend">Send</button>
|
pending.value = true;
|
||||||
|
nextTick().then(() => {
|
||||||
|
scrollRef.value.scrollTop = scrollRef.value.scrollHeight;
|
||||||
|
});
|
||||||
|
sendMessageToChatGPT(userInput.value)
|
||||||
|
.then((response) => {
|
||||||
|
pending.value = false;
|
||||||
|
messages.value.push({ role: 'assistant', content: response });
|
||||||
|
})
|
||||||
|
.then(() => nextTick())
|
||||||
|
.then(() => {
|
||||||
|
scrollRef.value.scrollTop = scrollRef.value.scrollHeight;
|
||||||
|
});
|
||||||
|
|
||||||
|
userInput.value = '';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="chat-container">
|
||||||
|
<div class="messages" ref="scrollRef">
|
||||||
|
<div v-for="(msg, index) in messages" :key="index" :class="['message', msg.role]">
|
||||||
|
{{ msg.content }}
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="pending" class="message assistant"><div class="skeleton"></div></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<div class="input-wrapper">
|
||||||
|
<SearchBox v-model="userInput" :onSubmit="handleSubmit">
|
||||||
<script>
|
<svg
|
||||||
import { ref } from 'vue';
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
import { sendMessageToChatGPT } from "@/api/chatbot";
|
class="icon"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
export default {
|
fill="none"
|
||||||
name: 'TravelChatBot',
|
stroke-width="2"
|
||||||
setup() {
|
stroke-linecap="round"
|
||||||
const messages = ref([]);
|
stroke-linejoin="round"
|
||||||
const userInput = ref('');
|
>
|
||||||
|
<polygon points="3,11 22,2 13,21 11,13 3,11" />
|
||||||
const handleSend = async () => {
|
</svg>
|
||||||
if (userInput.value.trim() === '') return;
|
</SearchBox>
|
||||||
|
|
||||||
const userMessage = { role: 'user', content: userInput.value };
|
|
||||||
console.log(userMessage)
|
|
||||||
messages.value.push(userMessage);
|
|
||||||
|
|
||||||
userInput.value = '';
|
|
||||||
|
|
||||||
const botMessageContent = await sendMessageToChatGPT(userMessage.content);
|
|
||||||
const botMessage = { role: 'assistant', content: botMessageContent };
|
|
||||||
|
|
||||||
messages.value.push(botMessage);
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
messages,
|
|
||||||
userInput,
|
|
||||||
handleSend,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.chat-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100vh;
|
|
||||||
max-width: 600px;
|
|
||||||
margin: 0 auto;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages .user {
|
|
||||||
text-align: right;
|
|
||||||
margin: 10px 0;
|
|
||||||
color: blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages .assistant {
|
|
||||||
text-align: left;
|
|
||||||
margin: 10px 0;
|
|
||||||
color: green;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-container {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="text"] {
|
|
||||||
flex: 1;
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
padding: 10px;
|
|
||||||
margin-left: 10px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: #007bff;
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="chat-container">
|
|
||||||
<div class="chat-box">
|
|
||||||
<div class="messages">
|
|
||||||
<div v-for="(msg, index) in messages" :key="index" :class="['message', msg.role]">
|
|
||||||
<span>{{ msg.content }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input v-model="userInput" @keyup.enter="sendMessage" placeholder="Ask me anything about travel" />
|
|
||||||
<button @click="sendMessage">Send</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<script>
|
<style scoped>
|
||||||
import sendMessageToChatGPT from "@/api/chatbot";
|
@keyframes slideUp {
|
||||||
|
0% {
|
||||||
export default {
|
opacity: 0;
|
||||||
data() {
|
transform: translateY(4px);
|
||||||
return {
|
|
||||||
userInput: '',
|
|
||||||
messages: [],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async sendMessage() {
|
|
||||||
if (this.userInput.trim() === '') return;
|
|
||||||
|
|
||||||
this.messages.push({ role: 'user', content: this.userInput });
|
|
||||||
|
|
||||||
console.log(this.userInput)
|
|
||||||
const response = await sendMessageToChatGPT(this.userInput);
|
|
||||||
|
|
||||||
this.messages.push({ role: 'assistant', content: response });
|
|
||||||
|
|
||||||
this.userInput = '';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.chat-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 100vh;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.chat-box {
|
@keyframes skeleton {
|
||||||
width: 400px;
|
100% {
|
||||||
border: 1px solid #ccc;
|
background-position: -100% 0;
|
||||||
padding: 20px;
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.messages {
|
.chat-container {
|
||||||
max-height: 300px;
|
display: flex;
|
||||||
overflow-y: auto;
|
flex-direction: column;
|
||||||
margin-bottom: 20px;
|
align-items: stretch;
|
||||||
}
|
position: relative;
|
||||||
|
padding: 20px;
|
||||||
|
padding-right: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.message {
|
.chat-container::-webkit-scrollbar {
|
||||||
padding: 10px;
|
width: 6px;
|
||||||
border-radius: 5px;
|
}
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user {
|
.chat-container::-webkit-scrollbar-thumb {
|
||||||
background-color: #e0f7fa;
|
background-color: var(--color-text-secondary);
|
||||||
align-self: flex-end;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.assistant {
|
.icon {
|
||||||
background-color: #fff9c4;
|
display: block;
|
||||||
}
|
width: 24px;
|
||||||
</style>
|
height: 24px;
|
||||||
|
stroke: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 100%;
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages::-webkit-scrollbar-thumb {
|
||||||
|
background-color: var(--color-text-secondary);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 16px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
animation: slideUp 0.25s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton {
|
||||||
|
width: 100%;
|
||||||
|
height: 1em;
|
||||||
|
background: linear-gradient(
|
||||||
|
120deg,
|
||||||
|
var(--color-border) 30%,
|
||||||
|
var(--color-background-soft) 38%,
|
||||||
|
var(--color-background-soft) 42%,
|
||||||
|
var(--color-border) 50%
|
||||||
|
);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
background-position: 100% 0;
|
||||||
|
animation: skeleton 1.5s infinite;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user {
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
color: white;
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.assistant {
|
||||||
|
background-color: var(--color-background-soft);
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-wrapper {
|
||||||
|
justify-self: flex-end;
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
align-items: center;
|
||||||
|
padding-right: 20px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -40,12 +40,12 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div id="map" ref="mapDiv" />
|
<div id="map" ref="mapDiv"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
#map {
|
#map {
|
||||||
width: calc(100% - 400px);
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -11,21 +11,10 @@ const model = defineModel();
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<form @submit.prevent="onSubmit" class="search-box">
|
<form @submit.prevent="onSubmit" class="search-box">
|
||||||
<input type="text" :name="name" v-model.lazy="model" placeholder="검색어를 입력하세요" />
|
<input type="text" :name="name" v-model="model" placeholder="검색어를 입력하세요" />
|
||||||
|
|
||||||
<TextButton @click="onSubmit">
|
<TextButton @click="onSubmit">
|
||||||
<svg
|
<slot></slot>
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
fill="currentColor"
|
|
||||||
class="bi bi-search"
|
|
||||||
viewBox="0 0 16 16"
|
|
||||||
part="svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</TextButton>
|
</TextButton>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
@ -32,6 +32,7 @@ watch(sido, ({ sidoCode }) => {
|
|||||||
axios.get(`//localhost:8000/area/gugun?sidoCode=${sidoCode}`).then(({ data }) => {
|
axios.get(`//localhost:8000/area/gugun?sidoCode=${sidoCode}`).then(({ data }) => {
|
||||||
gugunList.value = [{ gugunCode: 0, gugunName: '전체' }, ...data];
|
gugunList.value = [{ gugunCode: 0, gugunName: '전체' }, ...data];
|
||||||
});
|
});
|
||||||
|
console.log(keyword.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (sidoList.value.length === 0) {
|
if (sidoList.value.length === 0) {
|
||||||
@ -48,9 +49,14 @@ function handleSubmit() {
|
|||||||
const contentTypeQuery = `contentTypeId=${contentType.value?.value ?? 0}`;
|
const contentTypeQuery = `contentTypeId=${contentType.value?.value ?? 0}`;
|
||||||
const queryString = [areaQuery, keywordQuery, contentTypeQuery].join('&');
|
const queryString = [areaQuery, keywordQuery, contentTypeQuery].join('&');
|
||||||
|
|
||||||
|
console.log(queryString);
|
||||||
setQueryString(queryString);
|
setQueryString(queryString);
|
||||||
search();
|
search();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(keyword, () => {
|
||||||
|
console.log('change');
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -71,7 +77,21 @@ function handleSubmit() {
|
|||||||
optionName="name"
|
optionName="name"
|
||||||
/>
|
/>
|
||||||
</nav>
|
</nav>
|
||||||
<SearchBox name="keyword" v-model.lazy="keyword" :onSubmit="handleSubmit" />
|
<SearchBox name="keyword" v-model="keyword" :onSubmit="handleSubmit">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
fill="currentColor"
|
||||||
|
class="bi bi-search"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
part="svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</SearchBox>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -1,31 +1,31 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from "vue";
|
import { reactive, ref } from 'vue';
|
||||||
import { registMember } from "@/api/member";
|
import { registMember } from '@/api/member';
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const email_id = ref("")
|
const email_id = ref('');
|
||||||
const email_domain = ref("")
|
const email_domain = ref('');
|
||||||
|
|
||||||
const member = ref({
|
const member = reactive({
|
||||||
id: "",
|
id: '',
|
||||||
name: "",
|
name: '',
|
||||||
password: "",
|
password: '',
|
||||||
email: email_id.value + "@" + email_domain.value,
|
email: email_id.value + '@' + email_domain.value,
|
||||||
})
|
});
|
||||||
|
|
||||||
function onSubmit() {
|
function onSubmit() {
|
||||||
member.value.email = email_id.value + "@" + email_domain.value
|
member.value.email = email_id.value + '@' + email_domain.value;
|
||||||
console.log(member.value)
|
console.log(member.value);
|
||||||
registMember(
|
registMember(
|
||||||
member.value,
|
member.value,
|
||||||
(response) => {
|
(response) => {
|
||||||
if (response.status == 200) console.log("회원가입 성공!")
|
if (response.status == 200) console.log('회원가입 성공!');
|
||||||
router.push({ name : "user-login"})
|
router.push({ name: 'user-login' });
|
||||||
},
|
},
|
||||||
(error) => console.error(error)
|
(error) => console.error(error)
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -41,15 +41,20 @@ function onSubmit() {
|
|||||||
<form>
|
<form>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="username" class="form-label">이름 : </label>
|
<label for="username" class="form-label">이름 : </label>
|
||||||
<input type="text" class="form-control" placeholder="이름..." v-model="member.name"/>
|
<input type="text" class="form-control" placeholder="이름..." v-model="member.name" />
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="userid" class="form-label">아이디 : </label>
|
<label for="userid" class="form-label">아이디 : </label>
|
||||||
<input type="text" class="form-control" placeholder="아이디..." v-model="member.id"/>
|
<input type="text" class="form-control" placeholder="아이디..." v-model="member.id" />
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="userpwd" class="form-label">비밀번호 : </label>
|
<label for="userpwd" class="form-label">비밀번호 : </label>
|
||||||
<input type="text" class="form-control" placeholder="비밀번호..." v-model="member.password"/>
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="비밀번호..."
|
||||||
|
v-model="member.password"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="pwdcheck" class="form-label">비밀번호확인 : </label>
|
<label for="pwdcheck" class="form-label">비밀번호확인 : </label>
|
||||||
@ -58,7 +63,12 @@ function onSubmit() {
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="emailid" class="form-label">이메일 : </label>
|
<label for="emailid" class="form-label">이메일 : </label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" class="form-control" placeholder="이메일아이디" v-model="email_id"/>
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="이메일아이디"
|
||||||
|
v-model="email_id"
|
||||||
|
/>
|
||||||
<span class="input-group-text">@</span>
|
<span class="input-group-text">@</span>
|
||||||
<select class="form-select" aria-label="이메일 도메인 선택" v-model="email_domain">
|
<select class="form-select" aria-label="이메일 도메인 선택" v-model="email_domain">
|
||||||
<option selected>선택</option>
|
<option selected>선택</option>
|
||||||
@ -70,7 +80,9 @@ function onSubmit() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto text-center">
|
<div class="col-auto text-center">
|
||||||
<button type="button" class="btn btn-outline-primary mb-3" @click="onSubmit">회원가입</button>
|
<button type="button" class="btn btn-outline-primary mb-3" @click="onSubmit">
|
||||||
|
회원가입
|
||||||
|
</button>
|
||||||
<button type="button" class="btn btn-outline-success ms-1 mb-3">초기화</button>
|
<button type="button" class="btn btn-outline-success ms-1 mb-3">초기화</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -2,7 +2,7 @@ import './assets/main.css';
|
|||||||
|
|
||||||
import { createApp } from 'vue';
|
import { createApp } from 'vue';
|
||||||
import { createPinia } from 'pinia';
|
import { createPinia } from 'pinia';
|
||||||
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
|
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
|
||||||
|
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import router from './router';
|
import router from './router';
|
||||||
@ -15,7 +15,6 @@ pinia.use(piniaPluginPersistedstate);
|
|||||||
app.use(createPinia());
|
app.use(createPinia());
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
// app.mount('#app');
|
|
||||||
router.isReady().then(() => {
|
router.isReady().then(() => {
|
||||||
app.mount("#app");
|
app.mount('#app');
|
||||||
});
|
});
|
||||||
|
@ -1,24 +1,31 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import AppHeader from '@/components/AppHeader.vue';
|
import AppHeader from '@/components/AppHeader.vue';
|
||||||
|
import ChatBotButton from '@/components/chatbot/ChatBotButton.vue';
|
||||||
import KakaoMap from '@/components/common/KakaoMap.vue';
|
import KakaoMap from '@/components/common/KakaoMap.vue';
|
||||||
import SideBar from '@/components/search/SideBar.vue';
|
import SideBar from '@/components/search/SideBar.vue';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AppHeader />
|
<AppHeader />
|
||||||
<main>
|
<div class="view-wrapper">
|
||||||
<SideBar />
|
<SideBar />
|
||||||
<KakaoMap />
|
<main>
|
||||||
</main>
|
<KakaoMap />
|
||||||
|
<ChatBotButton />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
main {
|
.view-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: absolute;
|
position: relative;
|
||||||
top: 80px;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc(100% - 80px);
|
height: calc(100% - 80px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
Reference in New Issue
Block a user