
오늘의 실습 목표는 "설정 화면 만들어보기!" 입니다.
본 게시글에서 다루는 사항은 설정 화면의 UI/Layout을 구현하는 과정입니다.
설정 화면 구현하기 - UI/Layout
사전 준비
아래 사항에 대해서 사전 준비가 완료되지 않으신 분들은 아래 링크를 참조하여 사전 준비를 진행합니다.
- [실습] 뷰(Vue)로 회원/사용자 관리 화면 구현하기 - UI/Layout 구현하기
- [실습] 뷰(Vue)로 회원/사용자 관리 화면 구현하기 - Backend(REST API) 연결하기
결과 이미지
구현하기에 앞서 본 글을 따라하시면 아래와 같이 화면의 레이아웃을 구성하실 수 있습니다.


소스 코드
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="company"
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="nameKor"
label="국문이름"
placeholder="홍길동"
></v-text-field>
</v-col>
<v-col>
<v-text-field
text
outlined
dense
clearable
:disabled="isSettingMode()"
v-model="nameEng"
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="phone"
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="email"
label="이메일 주소"
placeholder="gildong-hong@gmail.com"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col v-show="!isSettingMode() && !isModifyMode()">
<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="password"
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="division"
label="부서"
></v-text-field>
</v-col>
<v-col>
<v-text-field
text
outlined
dense
clearable
v-model="team"
label="팀"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col>
<v-text-field
text
outlined
dense
clearable
:rules="[rules.required]"
v-model="position"
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
company: [],
employeeNo: "",
nameKor: "",
nameEng: "",
phone: "",
email: "",
password: "",
division: "",
team: "",
position: "",
usageExpiryDate: "9999-12-31",
};
},
mounted: function () {
this.$nextTick(function () {
if (this.mode === MODE_MODIFY) {
this.setData();
}
});
},
watch: {
employee: {
handler() {
this.setData();
},
},
},
methods: {
setData: function () {
this.company = this.employee.company;
this.employeeNo = this.employee.employeeNo;
this.nameKor = this.employee.nameKor;
this.nameEng = this.employee.nameEng;
this.password = this.employee.password;
this.phone = this.employee.phone;
this.email = this.employee.email;
this.division = this.employee.division;
this.team = this.employee.team;
this.position = this.employee.position;
},
changeCompany: function () {
switch (this.company) {
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/users";
switch (this.mode) {
case MODE_REGISTER:
console.log("REGISTER PROCESS");
this.$axios
.post(path, {
employeeNo: this.employeeNo,
registerEmployeeNo: "000000",
updateEmployeeNo: "000000",
employeeName: this.nameKor,
employeeNameEng: this.nameEng,
employeePhone: this.phone,
employeeEmail: this.email,
employeePw: this.password,
employeeCompany: this.company,
employeeDivision: this.division,
employeeTeam: this.team,
employeePosition: this.position,
usageExpDate: this.usageExpiryDate.replaceAll("-", ""),
})
.catch(function (error) {
console.log("[ERR/REG]" + error);
});
break;
case MODE_MODIFY:
console.log("MODIFY PROCESS");
path += "/update/";
this.$axios
.put(path + this.employeeNo, {
employeeNo: this.employeeNo,
updateEmployeeNo: "000000",
employeeName: this.nameKor,
employeeNameEng: this.nameEng,
employeePhone: this.phone,
employeeEmail: this.email,
employeePw: this.password,
employeeCompany: this.company,
employeeDivision: this.division,
employeeTeam: this.team,
employeePosition: this.position,
usageExpDate: this.usageExpiryDate.replaceAll("-", ""),
})
.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>
본 코드를 재사용하여 설정 화면에서도 사용할 수 있도록 아래에 대해서만 일부 수정하였습니다.
<template>
...
<div class="text-h5 font-weight-medium ma-2">
{{ mode }}
</div>
<div class="ma-md-2">
<v-row>
<v-col>
<v-combobox
...
:disabled="isModifyMode() || isSettingMode()"
label="회사"
></v-combobox>
</v-col>
<v-col>
<v-text-field
...
:disabled="isModifyMode() || isSettingMode()"
label="직원번호"
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col>
<v-text-field
...
:disabled="isSettingMode()"
label="국문이름"
></v-text-field>
</v-col>
<v-col>
<v-text-field
...
:disabled="isSettingMode()"
label="영문이름"
></v-text-field>
</v-col>
</v-row>
...
<v-row>
<v-col v-show="!isSettingMode() && !isModifyMode()">
<v-text-field
...
label="비밀번호"
></v-text-field>
</v-col>
</v-row>
...
<v-row>
...
<v-text-field
...
v-show="!isSettingMode()""
label="사용기한일자"
></v-text-field>
...
</v-row>
...
</div>
...
- 기존 구현한 관리자 포털 사용자 추가/수정 화면에 추가적으로 "설정" 화면인 경우에만 Hide 혹은 Disable되는 필드에 대해서 조건에 맞게 사용자에게 보여지도록 구현
- Hide: 비밀번호, 사용기한 일자
- Disable: 회사, 직원번호, 직원 국문이름, 직원 영문이름
<script>
<script>
const MODE_SETTING = "SETTING";
export default {
...
methods: {
...
isSettingMode: function () {
return this.title.toUpperCase() === MODE_SETTING;
},
...
}
};
</script>
- 전역 변수(MODE_SETTING): 설정 화면에 대한 제어 모드 추가 정의
- isSettingMode (): 현재 오픈된 화면의 모드가 설정 화면인지 확인하는 함수
src\components\common\UserPassword.vue
로그인한 사용자의 비밀번호 변경 컴포넌트 파일입니다.
기존에 구현한 'AdminUsersDetail.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 {
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 () {
// TODO: SOMETHINGS
},
},
};
</script>
<style></style>
src\views\admin-web\BaseAdminSettings.vue
메뉴/사이드바의 라우팅 결과로 표시될 최상위 컴포넌트 파일입니다.
<template>
<v-container>
<PrivateInformation :mode="'Setting'"></PrivateInformation>
<v-divider></v-divider>
<UserPassword></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,
},
};
</script>
<style></style>
- PrivateInformation: 개인 정보 표시/수정 영역 컴포넌트
- 'AdminUsersDetail' 컴포넌트를 재사용함에 따른 title을 props로 전달
- UserPassword: 로그인한 사용자의 비밀번호 변경 컴포넌트

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