Files
lucia-frontend/src/views/AccountManagement/ModalAccountEditCreate.vue
2024-07-04 15:21:07 +08:00

367 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div id="modal_account_edit_or_create_new" class="w-[640px] bg-[#FFFFFF] rounded
shadow-lg flex flex-col">
<ModalHeader :headerText="modalTitle"/>
<main class="flex row min-h-[96px] my-[32px] flex-col px-6">
<div class="input-row w-full flex py-2 h-[40px] mb-4 items-center
justify-between">
<div class="field-label text-sm flex items-center font-medium justify-end">
<span class="align-right-span flex w-[122px] justify-end">
{{ i18next.t("AcctMgmt.Account") }}
</span>
</div>
<div class="input-and-error flex flex-col">
<input id="input_account_field"
class="w-[454px] rounded p-1 border border-[1px] border-[#64748B] flex items-center h-[40px] outline-none"
v-model="inputUserAccount"
:class="{
'text-[#000000]': isAccountUnique,
'text-[#FF3366]': !isAccountUnique,
'border-[#FF3366]': !isAccountUnique,
}"
@focus="onInputAccountFocus" :readonly="!isEditable"
@dblclick="onInputDoubleClick"
/>
<div v-show="!isAccountUnique" class="error-wrapper my-2">
<div class="error-msg-section flex justify-start">
<img src="@/assets/icon-alert.svg" alt="!" class="exclamation-img flex mr-2">
<span class="error-msg-text flex text-[#FF3366] h-[24px]">
{{ i18next.t("AcctMgmt.AccountNotUnique") }}
</span>
</div>
</div>
</div>
</div>
<div class="input-row w-full flex py-2 h-[40px] mb-4 items-center
justify-between">
<div class="field-label text-sm flex items-center font-medium justify-end">
<span class="align-right-span flex w-[122px] justify-end">
{{ i18next.t("AcctMgmt.FullName") }}
</span>
</div>
<input id="input_name_field"
class="w-[454px] rounded p-1 border border-[1px] border-[#64748B] flex items-center h-[40px] outline-none"
v-model="inputName" :readonly="!isEditable" @dblclick="onInputDoubleClick" @focus="onInputNameFocus"
/>
</div>
<div v-show="!isSSO" class="input-row w-full flex py-2 h-[40px] mb-4 items-center
justify-between">
<div class="field-label text-sm flex items-center font-medium justify-end">
<span class="align-right-span flex w-[122px] justify-end">
{{ i18next.t("AcctMgmt.Password") }}
</span>
</div>
<div class="w-[454px] rounded border-[1px] border-[#64748B] flex items-center h-[40px] relative">
<input id="input_first_pwd" class="outline-none p-1 w-[352px]" :type="isPwdEyeOn ? 'text' : 'password'"
v-model="inputPwd" :readonly="!isEditable" @dblclick="onInputDoubleClick"/>
<img v-if="isPwdEyeOn" src='@/assets/icon-eye-open.svg' class="absolute right-[8px] cursor-pointer"
@click="togglePwdEyeBtn"/>
<img v-else src='@/assets/icon-eye-hide.svg' class="absolute right-[8px] cursor-pointer" @click="togglePwdEyeBtn"/>
</div>
</div>
<div v-show="!isSSO" id="confirm_pwd_row" class="input-row w-full grid grid-cols-2 grid-cols-[122px_1fr] gap-x-4
mb-4 flex items-center"> <!-- 2-by-2 的格子其中左下角是一個dummy格子其沒有內容 -->
<span class="field-label w-[122px] text-sm flex items-center justify-end text-right font-medium mr-4 whitespace-nowrap"
:class="{
'text-[#000000]': isPwdMatched,
'text-[#FF3366]': !isPwdMatched,
}">
{{ i18next.t("AcctMgmt.ConfirmPassword") }}
</span>
<div class="input-and-toggle-btn w-[454px] flex flex-row rounded border border-[1px] relative items-center
h-[40px] mb-4"
:class="{
'border-[#000000]': isPwdMatched,
'border-[#FF3366]': !isPwdMatched,
}">
<input id="input_second_pwd" class="outline-none p-1 w-[352px]" :type="isPwdConfirmEyeOn ? 'text' : 'password'"
v-model="inputConfirmPwd" :readonly="!isEditable" @dblclick="onInputDoubleClick"
:class="{
'text-[#000000]': isPwdMatched,
'text-[#FF3366]': !isPwdMatched,
}"/>
<img v-if="isPwdConfirmEyeOn" src='@/assets/icon-eye-open.svg' class="absolute right-[8px] cursor-pointer"
@click="togglePwdConfirmEyeBtn"/>
<img v-else src='@/assets/icon-eye-hide.svg' class="absolute right-[8px] cursor-pointer" @click="togglePwdConfirmEyeBtn"/>
</div>
<div class="dummy-grid h-[24px]"></div> <!-- 透過 dummy-grid 來撐起高度-->
<div v-show="!isPwdMatched" class="error-msg-section flex justify-start">
<img src="@/assets/icon-alert.svg" alt="!" class="exclamation-img flex mr-2">
<span class="error-msg-text flex text-[#FF3366] h-[24px]">
{{ i18next.t("AcctMgmt.PwdNotMatch") }}
</span>
</div>
</div>
<div v-if="whichCurrentModal === MODAL_CREATE_NEW" class="checkbox-row w-full grid-cols-[122px_1fr] gap-x-4 flex py-2 h-[40px] my-4 items-center">
<div class="dummy field-label flex items-center w-[122px]">
</div> <!--這裡也使用了dummy欄位去撐起空間-->
<section id="account_create_checkboxes_section" class="flex flex-col">
<div class="checkbox-and-text flex">
<IconChecked :isChecked="isSetAsAdminChecked" @click="toggleIsAdmin"/>
<span class="flex checkbox-text">
{{ i18next.t("AcctMgmt.SetAsAdmin") }}
</span>
</div>
<div class="checkbox-and-text flex">
<IconChecked :isChecked="isSetActivedChecked" @click="toggleIsActivated"/>
<span class="flex checkbox-text">
{{ i18next.t("AcctMgmt.ActivateNow") }}
</span>
</div>
</section>
</div>
</main>
<footer class="flex row footer justify-end pr-[32px] pb-8">
<button class="cancel-btn rounded rounded-full w-[92px] h-10 px-2.5 py-6 border border-[1px] border-[#64748B]
flex justify-center items-center text-[#4E5969] font-medium"
@click="onCancelBtnClick"
>
{{ i18next.t("Global.Cancel") }}
</button>
<button class="confirm-btn rounded rounded-full w-[92px] h-10 px-2.5 py-6
flex justify-center items-center text-[#ffffff] font-medium ml-[16px]"
@click="onConfirmBtnClick" :disabled="isConfirmDisabled"
:class="{
'bg-[#0099FF]': !isConfirmDisabled,
'bg-[#E2E8F0]': isConfirmDisabled,
}"
>
{{ i18next.t("Global.Confirm") }}
</button>
</footer>
</div>
</template>
<script>
import { defineComponent, computed, ref, watch, onMounted, } from 'vue';
import i18next from "@/i18n/i18n.js";
import { mapActions, } from 'pinia';
import { useModalStore } from '@/stores/modal.js';
import { useRouter } from 'vue-router';
import { useToast } from 'vue-toast-notification';
import useAcctMgmtStore from '@/stores/acctMgmt.js';
import ModalHeader from "./ModalHeader.vue";
import IconChecked from "@/components/icons/IconChecked.vue";
import { MODAL_CREATE_NEW, MODAL_ACCT_EDIT, } from '@/constants/constants.js';
export default defineComponent({
setup() {
const acctMgmtStore = useAcctMgmtStore();
const modalStore = useModalStore();
const router = useRouter();
const toast = useToast();
const currentViewingUser = computed(() => acctMgmtStore.currentViewingUser);
const isPwdEyeOn = ref(false);
const isPwdConfirmEyeOn = ref(false);
const isPwdMatched = ref(true);
const isConfirmDisabled = ref(true);
const isSetAsAdminChecked = ref(false);
const isSetActivedChecked = ref(true);
const whichCurrentModal = computed(() => modalStore.whichModal);
const isSSO = computed(() => acctMgmtStore.currentViewingUser.is_sso);
const username = computed(() => acctMgmtStore.currentViewingUser.username);
const name = computed(() => acctMgmtStore.currentViewingUser.name);
const inputUserAccount = ref(whichCurrentModal.value === MODAL_CREATE_NEW ? '' : currentViewingUser.value.username);
const inputName = ref(whichCurrentModal.value === MODAL_CREATE_NEW ? '' : currentViewingUser.value.name);
const inputPwd = ref("");
const inputConfirmPwd = ref("");
const isAccountUnique = ref(true);
const isEditable = ref(true);
// 自從加入這段 watch 之後,填寫密碼欄位之時,就不會胡亂清空掉 account 或是 full name 蘭為了。
watch(whichCurrentModal, (newVal) => {
if (newVal === MODAL_CREATE_NEW) {
inputUserAccount.value = '';
inputName.value = '';
} else {
inputUserAccount.value = currentViewingUser.value.username;
inputName.value = currentViewingUser.value.name;
}
});
const modalTitle = computed(() => {
return modalStore.whichModal === MODAL_CREATE_NEW ? i18next.t('AcctMgmt.CreateNew') : i18next.t('AcctMgmt.AccountEdit');
});
watch(
[inputUserAccount, inputName, inputPwd, inputConfirmPwd, isAccountUnique,
isPwdMatched,
],
([newAccount, newName, newPwd, newConfirmPwd, newIsAccountUnique,
newPwdMatched,
]) => {
if (!newAccount?.length || !newName?.length || !newPwd?.length
|| !newConfirmPwd?.length || !newIsAccountUnique
|| !newPwdMatched
) {
isConfirmDisabled.value = true;
} else {
isConfirmDisabled.value = false;
}
},
{ immediate: true }
);
const togglePwdEyeBtn = () => {
isPwdEyeOn.value = !isPwdEyeOn.value;
};
const togglePwdConfirmEyeBtn = () => {
isPwdConfirmEyeOn.value = !isPwdConfirmEyeOn.value;
};
const validateConfirmPwd = () => {
isPwdMatched.value = inputPwd.value.length > 0 && inputPwd.value === inputConfirmPwd.value;
}
const onInputDoubleClick = () => {
// 允許編輯模式
isEditable.value = true;
}
const onConfirmBtnClick = async () => {
validateConfirmPwd();
if(!isPwdMatched.value){
return;
}
switch(whichCurrentModal.value) {
case MODAL_CREATE_NEW:
await checkAccountIsUnique();
if(!isAccountUnique.value) {
return;
}
await acctMgmtStore.createNewAccount({
username: inputUserAccount.value,
password: inputPwd.value,
name: inputName.value,
is_admin: isSetAsAdminChecked.value,
is_active: isSetActivedChecked.value,
});
await toast.success(i18next.t("AcctMgmt.MsgAccountAdded"));
await modalStore.closeModal();
acctMgmtStore.setShouldUpdateList(true);
await router.push('/account-admin');
break;
case MODAL_ACCT_EDIT:
// 要注意的是舊的username跟新的username可以是不同的
await acctMgmtStore.editAccount(
currentViewingUser.value.username, {
newUsername: inputUserAccount.value,
password: inputPwd.value,
name: inputName.value,
is_active: true,
// is_active: isSetActivedChecked.value, //TODO: 待確認需求規格
});
await toast.success(i18next.t("AcctMgmt.MsgAccountEdited"));
isEditable.value = false;
default:
break;
}
}
const checkAccountIsUnique = async() => {
const isAccountAlreadyExistAPISuccess = await acctMgmtStore.getUserDetail(inputUserAccount.value);
isAccountUnique.value = !isAccountAlreadyExistAPISuccess;
return isAccountUnique.value;
};
const toggleIsAdmin = () => {
if(isEditable){
isSetAsAdminChecked.value = !isSetAsAdminChecked.value;
}
}
const toggleIsActivated = () => {
if(isEditable){
isSetActivedChecked.value = !isSetActivedChecked.value;
}
}
const onInputAccountFocus = () => {
if(!isAccountUnique.value) {
// 之所以要轉回true是為了讓使用者可以繼續填寫不被阻擋
isAccountUnique.value = true;
}
if(isConfirmDisabled.value){
isConfirmDisabled.value = false;
}
}
const onInputNameFocus = () => {
if(isConfirmDisabled.value){
isConfirmDisabled.value = false;
}
}
onMounted(() => {
});
return {
isConfirmDisabled,
username,
name,
isSSO,
isPwdEyeOn,
isPwdConfirmEyeOn,
togglePwdEyeBtn,
togglePwdConfirmEyeBtn,
isPwdMatched,
inputUserAccount,
inputName,
inputPwd,
inputConfirmPwd,
onConfirmBtnClick,
onInputDoubleClick,
onInputNameFocus,
isSetAsAdminChecked,
isSetActivedChecked,
toggleIsAdmin,
toggleIsActivated,
whichCurrentModal,
MODAL_CREATE_NEW,
modalTitle,
isAccountUnique,
onInputAccountFocus,
isEditable,
};
},
data() {
return {
i18next: i18next,
};
},
components: {
ModalHeader,
IconChecked,
},
methods: {
onCloseBtnClick(){
this.closeModal();
},
onCancelBtnClick(){
this.closeModal();
},
...mapActions(useModalStore, ['closeModal']),
}
});
</script>
<style>
#modal_account_edit {
background-color: #ffffff;
backdrop-filter: opacity(1); /*防止子元件繼承父元件的透明度 */
}
</style>