parent
8afa3673cb
commit
1c49ceb6f6
@ -0,0 +1,27 @@
|
||||
export interface UserSearch {
|
||||
searchType: string;
|
||||
searchText: string;
|
||||
pageSize: number;
|
||||
pageNum: number;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
userId: string;
|
||||
userPw: string;
|
||||
userNm: string;
|
||||
userAuth: string;
|
||||
delYn: string;
|
||||
regDate: string;
|
||||
regUserId: string;
|
||||
regUserNm: string;
|
||||
modDate: string;
|
||||
modUserId: string;
|
||||
modUserNm: string;
|
||||
logingDate: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export interface UserProjectMap {
|
||||
userId: string;
|
||||
prjCd: string;
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
export interface ApiProject {
|
||||
id: number | null;
|
||||
prjCd: string;
|
||||
prjNm: string;
|
||||
prjDesc: string;
|
||||
prjStartDt: string;
|
||||
prjEndDt: string;
|
||||
delYn: string;
|
||||
regDate: string;
|
||||
regUserId: string;
|
||||
regUserNm: string;
|
||||
modDate: string;
|
||||
modUserId: string;
|
||||
modUserNm: string;
|
||||
}
|
||||
|
||||
export interface UiProject {
|
||||
id: number;
|
||||
title: string;
|
||||
creator: string;
|
||||
date: string;
|
||||
description: string;
|
||||
}
|
||||
@ -0,0 +1,131 @@
|
||||
import axios from "axios";
|
||||
import { commonStore, loadingStore } from "@/stores/commonStore";
|
||||
import { storage } from "@/utils/storage";
|
||||
import router from "@/router";
|
||||
|
||||
const loading = loadingStore();
|
||||
|
||||
const API_URL = import.meta.env.VITE_APP_API_SERVER_URL;
|
||||
console.log("API URL:", API_URL);
|
||||
export const request = {
|
||||
post: (uri: string, param: any): any => {
|
||||
return axios.post(`${API_URL}${uri}`, param);
|
||||
},
|
||||
get: (uri: string, param: any): any => {
|
||||
return axios.get(`${API_URL}${uri}`, { params: param });
|
||||
},
|
||||
delete: (uri: string, param: any): any => {
|
||||
return axios.delete(`${API_URL}${uri}`, param);
|
||||
},
|
||||
put: (uri: string, param: any): any => {
|
||||
return axios.put(`${API_URL}${uri}`, param);
|
||||
},
|
||||
postFile: (uri: string, param: any, attachment: any, progress: any): any => {
|
||||
const formData = new FormData();
|
||||
|
||||
for (const key in attachment) {
|
||||
if (Object.prototype.hasOwnProperty.call(attachment, key)) {
|
||||
const value = attachment[key];
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
for (const file of value) {
|
||||
formData.append(key, file);
|
||||
}
|
||||
} else {
|
||||
formData.append(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
formData.append("param", JSON.stringify(param));
|
||||
|
||||
return axios.post(`${API_URL}${uri}`, formData, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
onUploadProgress: progress,
|
||||
});
|
||||
},
|
||||
postResponseFile: (
|
||||
uri: string,
|
||||
param: any,
|
||||
responseParam: any,
|
||||
attachment: any,
|
||||
progress: any,
|
||||
): any => {
|
||||
const formData = new FormData();
|
||||
for (const key in attachment) {
|
||||
if (Object.prototype.hasOwnProperty.call(attachment, key)) {
|
||||
const value = attachment[key];
|
||||
if (Array.isArray(value)) {
|
||||
for (const file of value) {
|
||||
formData.append(key, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
formData.append("param", JSON.stringify(param));
|
||||
formData.append("responseParam", JSON.stringify(responseParam));
|
||||
|
||||
return axios.post(`${API_URL}${uri}`, formData, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
onUploadProgress: progress,
|
||||
});
|
||||
},
|
||||
// postOptimization: (uri: string, param: any): any => {
|
||||
// return axios.post(`${PYTHON_API_URL}${uri}`, param);
|
||||
// },
|
||||
|
||||
// postPython: (uri: string, param: any): Promise<any> => {
|
||||
// return axios.post(`${PYTHON_API_URL}${uri}`, param);
|
||||
// },
|
||||
};
|
||||
|
||||
// axios.defaults.withCredentials = true;
|
||||
axios.defaults.headers.common["Access-Control-Allow-Origin"] = "*";
|
||||
axios.interceptors.request.use(
|
||||
(config) => {
|
||||
loading.setLoading(true);
|
||||
|
||||
const token = storage.getToken();
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
loading.setLoading(false);
|
||||
|
||||
console.log("request error", error);
|
||||
|
||||
const store = commonStore();
|
||||
store.setSnackbarMsg({
|
||||
text: "에러가 발생하였습니다.",
|
||||
color: "red",
|
||||
result: 500,
|
||||
});
|
||||
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
|
||||
axios.interceptors.response.use(
|
||||
(response) => {
|
||||
loading.setLoading(false);
|
||||
return response;
|
||||
},
|
||||
(error) => {
|
||||
loading.setLoading(false);
|
||||
|
||||
const store = commonStore();
|
||||
store.setSnackbarMsg({
|
||||
text: "에러가 발생하였습니다.",
|
||||
color: "red",
|
||||
result: 500,
|
||||
});
|
||||
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
@ -0,0 +1,27 @@
|
||||
import { request } from "@/components/service/index";
|
||||
import { UserSearch, User } from "@/components/models/management/User";
|
||||
|
||||
export const UserManagerService = {
|
||||
signIn: (payload: User) => {
|
||||
return request.post("/api/auth/signin", payload);
|
||||
},
|
||||
signUp: (payload: User) => {
|
||||
return request.post("/api/auth/signup", payload);
|
||||
},
|
||||
signOut: () => request.post("/api/auth/signout", {}),
|
||||
|
||||
getAll: () => request.get("/api/auth/users", {}),
|
||||
|
||||
select: (param: User) => {
|
||||
return request.post("/management/user/select", param);
|
||||
},
|
||||
add: (param: User) => {
|
||||
return request.post("/management/user/add", param);
|
||||
},
|
||||
update: (param: User) => {
|
||||
return request.post("/management/user/update", param);
|
||||
},
|
||||
delete: (param: User) => {
|
||||
return request.post("/management/user/delete", param);
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,13 @@
|
||||
import { request } from "@/components/service/index";
|
||||
import { ApiProject } from "@/components/models/project/Project";
|
||||
|
||||
export const ProjectService = {
|
||||
search: () => request.get("/api/projects", {}),
|
||||
add: (payload: ApiProject) => {
|
||||
return request.post("/api/projects", payload);
|
||||
},
|
||||
update: (id: number, payload: ApiProject) => {
|
||||
return request.put(`/api/projects/${id}`, payload);
|
||||
},
|
||||
delete: (id: number) => request.delete(`/api/projects/${id}`, {}),
|
||||
};
|
||||
@ -0,0 +1,210 @@
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { storage } from "@/utils/storage";
|
||||
import logo from "@/assets/wordmark.png";
|
||||
|
||||
import logo2 from "@/assets/workflow.png";
|
||||
import { UserManagerService } from "@/components/service/management/userManagerService";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const data = ref({
|
||||
form: false,
|
||||
username: "",
|
||||
email: "",
|
||||
role: [],
|
||||
password: "",
|
||||
loading: false,
|
||||
snackbar: false,
|
||||
snackbarText: "",
|
||||
snackbarColor: "",
|
||||
});
|
||||
|
||||
const resetForm = () => {
|
||||
data.value.userId = "";
|
||||
data.value.userPw = "";
|
||||
data.value.loading = false;
|
||||
};
|
||||
|
||||
const resetLogin = () => {
|
||||
data.value.userId = "";
|
||||
data.value.userPw = "";
|
||||
data.value.loading = false;
|
||||
};
|
||||
const resetSignup = () => {
|
||||
data.value.username = "";
|
||||
data.value.email = "";
|
||||
data.value.role = [];
|
||||
data.value.password = "";
|
||||
data.value.loading = false;
|
||||
};
|
||||
|
||||
const signUp = () => {
|
||||
const payload = {
|
||||
username: data.value.username,
|
||||
email: data.value.email,
|
||||
role: data.value.role,
|
||||
password: data.value.password,
|
||||
};
|
||||
console.log("회원가입 호출 payload:", payload);
|
||||
|
||||
UserManagerService.signUp(payload)
|
||||
.then((res) => {
|
||||
if (res.data.success) {
|
||||
router.push("/login");
|
||||
} else {
|
||||
data.value.snackbarText =
|
||||
res.data.message || "회원가입에 실패했습니다.";
|
||||
data.value.snackbarColor = "red";
|
||||
data.value.snackbar = true;
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
data.value.snackbarText =
|
||||
"비밀번호는 6자 이상, 40자 이하로 입력해야 합니다.";
|
||||
data.value.snackbarColor = "red";
|
||||
data.value.snackbar = true;
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container :fluid="true" class="pa-0">
|
||||
<v-sheet
|
||||
class="background-image w-100 h-screen d-flex align-center justify-center flex-column"
|
||||
>
|
||||
<v-card flat class="bg-transparent d-flex align-center flex-column">
|
||||
<v-card
|
||||
class="mx-auto pa-10 mb-4 login-box rounded-lg d-flex flex-column"
|
||||
style="min-width: 450px !important"
|
||||
density="comfortable"
|
||||
>
|
||||
<div class="mb-4 w-100 d-flex justify-center">
|
||||
<div
|
||||
class="bg-transparent rounded-circle pa-2"
|
||||
style="border: 1.8px solid #398bec"
|
||||
>
|
||||
<v-icon class="text-primary">mdi-shield-key-outline</v-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-h5 pb-2 text-center font-weight-bold text-primary">
|
||||
Autoflow Web Console
|
||||
</div>
|
||||
|
||||
<v-form v-model="data.form" @submit.prevent="signUp" class="mt-3">
|
||||
<v-text-field
|
||||
v-model="data.username"
|
||||
:readonly="data.loading"
|
||||
class="mb-2"
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
placeholder="아이디를 입력해주세요."
|
||||
></v-text-field>
|
||||
<v-text-field
|
||||
v-model="data.email"
|
||||
:readonly="data.loading"
|
||||
class="mb-2"
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
placeholder="이메일을 입력해주세요."
|
||||
></v-text-field>
|
||||
<v-select
|
||||
v-model="data.role"
|
||||
:items="['ROLE_USER', 'ROLE_MODERATOR', 'ROLE_ADMIN']"
|
||||
multiple
|
||||
variant="outlined"
|
||||
placeholder="Role"
|
||||
class="mb-2"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="data.password"
|
||||
:readonly="data.loading"
|
||||
type="password"
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
class="mb-2"
|
||||
placeholder="비밀번호를 입력해주세요."
|
||||
></v-text-field>
|
||||
|
||||
<v-btn
|
||||
:disabled="!data.form"
|
||||
:loading="data.loading"
|
||||
block
|
||||
color="primary"
|
||||
size="large"
|
||||
type="submit"
|
||||
variant="flat"
|
||||
>
|
||||
SignUp
|
||||
</v-btn>
|
||||
</v-form>
|
||||
|
||||
<div class="mt-4 text-center">
|
||||
<span>계정이 있으십니까?</span>
|
||||
<router-link
|
||||
to="/login"
|
||||
class="ml-2 font-weight-medium"
|
||||
style="color: #90caf9; text-decoration: none"
|
||||
>
|
||||
Login
|
||||
</router-link>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-card>
|
||||
|
||||
<v-sheet
|
||||
class="position-absolute w-100 bg-transparent pa-4"
|
||||
style="bottom: 0; left: 0"
|
||||
>
|
||||
<v-sheet class="bg-shades-transparent d-flex align-end"
|
||||
>Copyright © 2025 Autoflow Web Console
|
||||
</v-sheet>
|
||||
</v-sheet>
|
||||
<v-sheet
|
||||
class="position-absolute w-100 bg-transparent pa-4"
|
||||
style="bottom: 0; right: 0"
|
||||
>
|
||||
<v-sheet class="bg-shades-transparent d-flex justify-end">
|
||||
<img :src="logo" style="width: 5%" />
|
||||
</v-sheet>
|
||||
</v-sheet>
|
||||
</v-sheet>
|
||||
</v-container>
|
||||
|
||||
<v-snackbar
|
||||
v-model="data.snackbar"
|
||||
timeout="2000"
|
||||
location="button"
|
||||
:color="data.snackbarColor"
|
||||
>
|
||||
{{ data.snackbarText }}
|
||||
<template #actions>
|
||||
<v-btn variant="text" @click="data.snackbar = false"> 닫기 </v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.login-box {
|
||||
background-color: rgba(18, 18, 18, 0.4); /* 흰색 배경에 30% 불투명도 */
|
||||
backdrop-filter: blur(10px); /* 배경 블러 효과 */
|
||||
}
|
||||
|
||||
.background-image {
|
||||
background-image:
|
||||
linear-gradient(
|
||||
90deg,
|
||||
rgba(19, 18, 18, 0.5),
|
||||
rgba(19, 18, 18, 0.3),
|
||||
rgba(19, 18, 18, 0.3),
|
||||
rgba(19, 18, 18, 0.5)
|
||||
),
|
||||
url("@/assets/4117551.jpg"); /* 배경 이미지 경로 */
|
||||
background-size: cover; /* 이미지가 화면을 꽉 채우도록 설정 */
|
||||
background-position: center; /* 이미지 중앙 정렬 */
|
||||
background-repeat: no-repeat; /* 이미지 반복 방지 */
|
||||
min-height: 100vh; /* 화면 전체 높이 설정 */
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in new issue