
오늘의 실습 목표는 "설정 화면 만들어보기!" 입니다.
본 게시글에서 다루는 사항은 설정 화면에서 Backend로 연동하기 위해 REST API 연결하는 과정입니다.
설정 화면 구현하기 - REST API 호출/연동하기
사전 준비
아래 사항에 대해서 사전 준비가 완료되지 않으신 분들은 아래 링크를 참조하여 사전 준비를 진행합니다.
결과 이미지
구현하기에 앞서 본 글을 따라하시면 아래와 같이 화면과 API Server가 연동된 결과를 확인하실 수 있습니다.
▶ 초기 상태
DB (MySQL)
[TABLE] admin_user_information

[TABLE] admin_user_password_history

화면


▶ 본인 정보 변경
DB (MySQL)
[TABLE] admin_user_information

- 휴대폰 번호가 010-1234-1234에서 010-1234-5678로 변경된 것을 확인
화면

▶ 비밀번호 수정
DB (MySQL)
[TABLE] admin_user_information

- employee_pw와 employee_pw_exp_date의 값이 변경됨을 확인
[TABLE] admin_user_password_history

- 변경된 비밀번호 정보가 신규 추가(no=2)됨을 확인
화면

소스 코드
src\components\admin-web\users\AdminUsersDetail.vue
전체 코드를 확인하실 분들은 아래 더보기를 클릭해주세요.
더보기
<template>
<v-container>
<div class="text-h5 font-weight-medium ma-2">
{{ mode }}
</div>
<v-divider class="mb-5"></v-divider>
<div class="ma-md-2">
<v-row>
<v-col>
<v-combobox
outlined
dense
hide-selected
required
clearable
:disabled="isModifyMode() || isSettingMode()"
:items="companies"
:rules="[rules.required]"
v-model="employeeCompany"
label="회사"
@change="changeCompany"
></v-combobox>
</v-col>
<v-col>
<v-text-field
text
outlined
dense
clearable
:disabled="isModifyMode() || isSettingMode()"
:rules="[rules.required, rules.employeeNo]"
v-model="employeeNo"
label="직원번호"
placeholder="000000"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col>
<v-text-field
text
outlined
dense
clearable
:disabled="isSettingMode()"
:rules="[rules.required]"
v-model="employeeName"
label="국문이름"
placeholder="홍길동"
></v-text-field>
</v-col>
<v-col>
<v-text-field
text
outlined
dense
clearable
:disabled="isSettingMode()"
v-model="employeeNameEng"
label="영문이름"
placeholder="Gildong Hong"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col>
<v-text-field
text
outlined
dense
clearable
:rules="[rules.required, rules.phone]"
v-model="employeePhone"
label="휴대폰 번호"
placeholder="010-####-####"
></v-text-field>
</v-col>
<v-col>
<v-text-field
text
outlined
dense
clearable
type="email"
:rules="[rules.required, rules.email]"
v-model="employeeEmail"
label="이메일 주소"
placeholder="gildong-hong@gmail.com"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col v-show="!isSettingMode()">
<v-text-field
text
outlined
dense
clearable
:append-icon="showPw ? 'mdi-eye' : 'mdi-eye-off'"
:type="showPw ? 'text' : 'password'"
:rules="[rules.required, rules.password]"
v-model="employeePw"
label="비밀번호"
@click:append="showPw = !showPw"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col>
<v-text-field
text
outlined
dense
clearable
v-model="employeeDivision"
label="부서"
></v-text-field>
</v-col>
<v-col>
<v-text-field
text
outlined
dense
clearable
v-model="employeeTeam"
label="팀"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col>
<v-text-field
text
outlined
dense
clearable
:rules="[rules.required]"
v-model="employeePosition"
label="직책"
></v-text-field>
</v-col>
<v-col>
<v-text-field
text
outlined
dense
clearable
v-show="!isSettingMode()"
type="date"
:rules="[rules.required]"
:min="minUsageExpiryDate"
v-model="usageExpiryDate"
label="사용기한일자"
></v-text-field>
</v-col>
</v-row>
</div>
<div class="d-flex justify-end">
<v-btn
text
outlined
dense
class="mx-md-1 elevation-2"
@click="clickCancel"
>Cancel
</v-btn>
<v-btn text outlined dense class="mx-md-1 elevation-2" @click="clickOk"
>OK
</v-btn>
</div>
</v-container>
</template>
<script>
const MODE_REGISTER = "REGISTER";
const MODE_MODIFY = "MODIFY";
const MODE_SETTING = "SETTING";
export default {
props: ["employee", "mode"],
data() {
return {
rules: {
required: (value) => !!value || "Required.",
employeeNo: (value) =>
value.length == 6 || "직원번호는 6자리로 구성되어야 합니다.",
phone: (value) => {
const pattern = /^(\d{3})-(\d{4})-(\d{4})$/;
return pattern.test(value) || "휴대폰 번호 형식에 맞지 않습니다.";
},
email: (value) => {
const pattern =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return pattern.test(value) || "이메일 주소 형식에 맞지 않습니다.";
},
password: (value) => {
const pattern = /^(?=.*\d)(?=.*[a-z]).{8,}$/;
return (
pattern.test(value) ||
"영어 소문자, 숫자를 포함하여 8자리 이상으로 구성되어야 합니다."
);
},
},
minUsageExpiryDate: new Date(
Date.now() - new Date().getTimezoneOffset() * 60000
)
.toISOString()
.substring(0, 10),
companies: ["봄 회사", "여름 컴퍼니", "(주) 가을", "겨울 패밀리"],
showPw: false,
// V-MODEL
employeeCompany: [],
employeeNo: "",
employeeName: "",
employeeNameEng: "",
employeePhone: "",
employeeEmail: "",
employeePw: "",
employeeDivision: "",
employeeTeam: "",
employeePosition: "",
usageExpiryDate: "9999-12-31",
};
},
mounted: function () {
this.$nextTick(function () {
console.log(this.mode.toUpperCase());
switch (this.mode.toUpperCase()) {
case MODE_MODIFY:
case MODE_SETTING:
this.setData();
break;
default:
break;
}
});
},
watch: {
employee: {
handler() {
this.setData();
},
},
},
methods: {
setData: function () {
this.employeeCompany = this.employee.employeeCompany;
this.employeeNo = this.employee.employeeNo;
this.employeeName = this.employee.employeeName;
this.employeeNameEng = this.employee.employeeNameEng;
this.employeePw = this.employee.employeePw;
this.employeePhone = this.employee.employeePhone;
this.employeeEmail = this.employee.employeeEmail;
this.employeeDivision = this.employee.employeeDivision;
this.employeeTeam = this.employee.employeeTeam;
this.employeePosition = this.employee.employeePosition;
},
changeCompany: function () {
switch (this.employeeCompany) {
case "봄 회사": // 100000 ~ 199999
this.employeeNo = "1";
break;
case "여름 컴퍼니": // 300000 ~ 399999
this.employeeNo = "3";
break;
case "(주) 가을": // 500000 ~ 599999
this.employeeNo = "5";
break;
case "겨울 패밀리": // 700000 ~ 799999
this.employeeNo = "7";
break;
default:
// error
break;
}
},
isModifyMode: function () {
return this.mode.toUpperCase() === MODE_MODIFY;
},
isSettingMode: function () {
return this.mode.toUpperCase() === MODE_SETTING;
},
clickOk: function () {
let path = "/api/admin-web/user";
let jsonData = {
employeeNo: this.employeeNo,
registerEmployeeNo: "000000",
updateEmployeeNo: "000000",
employeeName: this.employeeName,
employeeNameEng: this.employeeNameEng,
employeePhone: this.employeePhone,
employeeEmail: this.employeeEmail,
employeePw: this.employeePw,
employeeCompany: this.employeeCompany,
employeeDivision: this.employeeDivision,
employeeTeam: this.employeeTeam,
employeePosition: this.employeePosition,
usageExpDate: this.usageExpiryDate.replaceAll("-", ""),
pwTrialState: "0",
temporaryPwState: "N",
};
switch (this.mode.toUpperCase()) {
case MODE_REGISTER:
console.log("REGISTER PROCESS");
this.$axios.post(path, jsonData).catch(function (error) {
console.log("[ERR/REG]" + error);
});
break;
case MODE_MODIFY:
case MODE_SETTING:
console.log(this.mode.toUpperCase() + " PROCESS");
path += "/update/";
this.$axios
.put(path + this.employeeNo, jsonData)
.catch(function (error) {
console.log("[ERR/MOD]" + error);
});
break;
default:
// error
console.log("[ERR/clickOk] Mode:" + this.mode);
break;
}
this.$emit("finishProcess");
},
clickCancel: function () {
console.log("press cancel button");
this.$emit("finishProcess");
},
},
};
</script>
<style scoped></style>
변경 사항
<script>
...
const MODE_SETTING = "SETTING";
export default {
...
mounted: function () {
this.$nextTick(function () {
console.log(this.mode.toUpperCase());
switch (this.mode.toUpperCase()) {
case MODE_MODIFY:
case MODE_SETTING: // 추가
this.setData();
break;
...
}
});
},
clickOk: function () {
...
case MODE_MODIFY:
case MODE_SETTING: // 추가
...
this.$axios
.put(path + this.employeeNo, jsonData)
.catch(function (error) {
console.log("[ERR/MOD]" + error);
});
break;
...
}
}
- MODE_MODIFY와 동일한 동작을 하도록 MODE_MODIFY 케이스에 MODE_SETTING 케이스 추가
src\components\common\UserPassword.vue
전체 코드를 확인하실 분들은 아래 더보기를 클릭해주세요.
더보기
<template>
<v-container>
<div class="text-h5 font-weight-medium ma-3">Password</div>
<div class="ma-md-2">
<v-row>
<v-col>
<v-text-field
text
outlined
dense
clearable
:type="'password'"
:rules="[rules.required, rules.password]"
v-model="passwordCur"
label="현재 비밀번호"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col>
<v-text-field
text
outlined
dense
clearable
:type="'password'"
:rules="[rules.required, rules.password]"
v-model="passwordTobe1"
label="변경할 비밀번호"
></v-text-field>
</v-col>
<v-col>
<v-text-field
text
outlined
dense
clearable
:type="'password'"
:rules="[rules.required, rules.password]"
v-model="passwordTobe2"
label="변경할 비밀번호"
></v-text-field>
</v-col>
</v-row>
</div>
<div class="d-flex justify-end">
<v-btn
text
outlined
dense
class="mx-md-1 elevation-2"
@click="clickCancel"
>Cancel
</v-btn>
<v-btn text outlined dense class="mx-md-1 elevation-2" @click="clickOk"
>OK
</v-btn>
</div>
</v-container>
</template>
<script>
export default {
props: ["employee"],
data() {
return {
rules: {
required: (value) => !!value || "Required.",
password: (value) => {
const pattern = /^(?=.*\d)(?=.*[a-z]).{8,}$/;
return (
pattern.test(value) ||
"영어 소문자, 숫자를 포함하여 8자리 이상으로 구성되어야 합니다."
);
},
},
passwordCur: "",
passwordTobe1: "",
passwordTobe2: "",
};
},
methods: {
clickCancel: function () {
this.passwordCur = "";
this.passwordTobe1 = "";
this.passwordTobe2 = "";
},
clickOk: function () {
let path = "/api/admin-web/user/update";
let jsonData = {
employeeNo: this.employee.employeeNo,
employeePw: this.passwordTobe1,
registerEmployeeNo: "000000",
updateEmployeeNo: "000000",
pwTrialState: "0",
temporaryPwState: "N",
};
if (this.passwordTobe1 === this.passwordTobe2) {
this.$axios
.put(path, jsonData)
.then((response) => {
if (response.statusText == "OK") {
alert("비밀번호를 변경하였습니다.");
}
})
.catch(function (error) {
console.log("[ERR/UserPwChange]" + error);
if (error.response.status == "417") {
alert("이전에 사용한 비밀번호로 변경할 수 없습니다.");
}
});
} else {
this.alert("변경할 비밀번호가 서로 일치하지 않습니다.");
}
},
},
};
</script>
<style></style>
변경 사항
<script>
export default {
props: ["employee"],
...
methods: {
clickOk: function () {
let path = "/api/admin-web/user/update";
let jsonData = {
employeeNo: this.employee.employeeNo,
employeePw: this.passwordTobe1,
registerEmployeeNo: "000000",
updateEmployeeNo: "000000",
pwTrialState: "0",
temporaryPwState: "N",
};
if (this.passwordTobe1 === this.passwordTobe2) {
this.$axios
.put(path, jsonData)
.then((response) => {
if (response.statusText == "OK") {
alert("비밀번호를 변경하였습니다.");
}
})
.catch(function (error) {
console.log("[ERR/UserPwChange]" + error);
if (error.response.status == "417") {
alert("이전에 사용한 비밀번호로 변경할 수 없습니다.");
}
});
} else {
this.alert("변경할 비밀번호가 서로 일치하지 않습니다.");
}
},
},
}
- clickOk(): 사용자 비밀번호 변경 REST API 연결 함수
src\views\admin-web\BaseAdminSettings.vue
전체 코드를 확인하실 분들은 아래 더보기를 클릭해주세요.
<template>
<v-container>
<PrivateInformation
:mode="'Setting'"
:employee="this.employee"
></PrivateInformation>
<v-divider></v-divider>
<UserPassword :employee="this.employee"></UserPassword>
</v-container>
</template>
<script>
import PrivateInformation from "./../../components/admin-web/users/AdminUsersDetail.vue";
import UserPassword from "./../../components/common/UserPassword.vue";
export default {
components: {
PrivateInformation,
UserPassword,
},
data() {
return {
employee: {},
};
},
mounted: function () {
this.$nextTick(function () {
this.getUserInformation();
});
},
methods: {
getUserInformation: function () {
// TODO: employeeNo를 로그인한 사용자의 직원 번호로 교체 필요
let employeeNo = "123456";
let path = "/api/admin-web/user/" + employeeNo;
this.$axios
.get(path)
.then((response) => {
this.employee = response.data;
})
.catch(function (error) {
console.log("[ERR/getUserInformation]" + error);
});
},
},
};
</script>
<style></style>
변경 사항
<template>
<template>
<v-container>
<PrivateInformation
:mode="'Setting'"
:employee="this.employee"
></PrivateInformation>
...
<UserPassword :employee="this.employee"></UserPassword>
</v-container>
</template>
- 사용자 정보 영역과 비밀번호 영역에 로그인한 사용자 정보 전달
<script>
<script>
...
export default {
...
data() {
return {
employee: {},
};
},
mounted: function () {
this.$nextTick(function () {
this.getUserInformation();
});
},
methods: {
getUserInformation: function () {
// TODO: employeeNo를 로그인한 사용자의 직원 번호로 교체 필요
let employeeNo = "123456";
let path = "/api/admin-web/user/" + employeeNo;
this.$axios
.get(path)
.then((response) => {
this.employee = response.data;
})
.catch(function (error) {
console.log("[ERR/getUserInformation]" + error);
});
},
},
};
</script>
- data() → employee: 로그인한 사용자 정보를 담을 변수
- mounted: 화면 로드시 로그인한 사용자 정보를 획득하여 설정하는 함수 호출
- methods: 로그인한 사용자 정보를 DB에서 획득하도록 REST API와 연결된 함수
- 현재는 특정 사용자의 직원 번호로 고정하였으나 향후 로그인 기능 구현시 변경될 예정

'Programming > Vue' 카테고리의 다른 글
[실습] 뷰(Vue)로 임시 비밀번호 발급 기능 구현하기 - UI 및 기능 개발 (0) | 2023.03.08 |
---|---|
[실습] 뷰(Vue)로 임시 비밀번호 발급 기능 구현하기 - 기획/설계 (0) | 2023.03.07 |
[실습] 뷰(Vue)로 설정 화면 구현하기 - UI/Layout 구현하기 (0) | 2023.03.02 |
[실습] 뷰(Vue)로 설정 화면 구현하기 - 기획/설계하기 (0) | 2023.03.01 |
[실습] 뷰(Vue)로 회원/사용자 관리 화면 구현하기 - Backend(REST API) 연결하기 (0) | 2023.02.20 |
댓글