395 lines
18 KiB
Vue
395 lines
18 KiB
Vue
<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,
|
||
}"
|
||
:readonly="!isEditable" autocomplete="off"
|
||
@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"
|
||
autocomplete="off"
|
||
/>
|
||
</div>
|
||
<div v-show="!isSSO" class="input-row w-full flex py-2 mb-4 items-center
|
||
justify-between">
|
||
<div class="field-label text-sm flex items-start font-medium justify-end"
|
||
:class="{
|
||
'h-[100px]': whichCurrentModal !== MODAL_CREATE_NEW,
|
||
}"
|
||
>
|
||
<span class="align-right-span flex h-full w-[122px] justify-end text-top">
|
||
{{ i18next.t("AcctMgmt.Password") }}
|
||
</span>
|
||
</div>
|
||
<div class="reset-btn-and-input-pwd-and-error flex flex-col"
|
||
:class="{'h-[100px]': whichCurrentModal !== MODAL_CREATE_NEW,}"
|
||
>
|
||
<div v-if="whichCurrentModal !== MODAL_CREATE_NEW" class="flex account-edit-section justify-start w-[454px]">
|
||
<button class="flex w-[85px] h-[40px] reset-btn rounded-full border-[1px] border-[#666666] cursor-pointer
|
||
items-center justify-center hover:text-[#0099FF] hover:border-[#0099FF] mb-[20px]"
|
||
@click="onResetPwdButtonClick"
|
||
>
|
||
{{ i18next.t("AcctMgmt.Reset") }}
|
||
</button>
|
||
</div>
|
||
<div v-if="whichCurrentModal === MODAL_CREATE_NEW || isResetPwdSectionShow"
|
||
class="w-[454px] flex flex-col input-and-error-msg">
|
||
<div class="input-and-eye flex items-center w-full h-[40px] relative border-[1px] rounded"
|
||
:class="{'border-[#FF3366]': !isPwdLengthValid, 'border-[#64748B]': isPwdLengthValid,}"
|
||
>
|
||
<input id="input_first_pwd" class="outline-none p-1 w-[352px]" :type="isPwdEyeOn ? 'text' : 'password'"
|
||
v-model="inputPwd" :readonly="!isEditable" @dblclick="onInputDoubleClick"
|
||
autocomplete="off" :class="{'color-[#FF3366]': !isPwdLengthValid, 'color-[#64748B]': isPwdLengthValid,}"/>
|
||
<img id='eye_button' v-if="isPwdEyeOn" src='@/assets/icon-eye-open.svg' class="absolute right-[8px] cursor-pointer"
|
||
@mousedown="togglePwdEyeBtn(true)" @mouseup="togglePwdEyeBtn(false)" alt=""/>
|
||
<img v-else src='@/assets/icon-eye-hide.svg' class="absolute right-[8px] cursor-pointer"
|
||
@mousedown="togglePwdEyeBtn(true)" @mouseup="togglePwdEyeBtn(false)" alt="eye"/>
|
||
</div>
|
||
<div class="error-msg-section flex justify-start mt-4">
|
||
<img v-show="!isPwdLengthValid" src="@/assets/icon-alert.svg" alt="!" class="exclamation-img flex mr-2">
|
||
<span class="error-msg-text flex text-[#FF3366] h-[24px]">
|
||
{{ isPwdLengthValid ? "" : i18next.t("AcctMgmt.PwdLengthNotEnough") }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div v-show="false" 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 v-show="false" 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 v-show="false" 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 v-show="false" 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,
|
||
}" autocomplete="off"/>
|
||
<img v-if="isPwdConfirmEyeOn" src='@/assets/icon-eye-open.svg' class="absolute right-[8px] cursor-pointer"
|
||
@click="togglePwdConfirmEyeBtn" alt="eye"/>
|
||
<img v-else src='@/assets/icon-eye-hide.svg' class="absolute right-[8px] cursor-pointer" @click="togglePwdConfirmEyeBtn" alt="eye"/>
|
||
</div>
|
||
<div class="dummy-grid h-[24px]"></div> <!-- 透過 dummy-grid 來撐起高度-->
|
||
<div class="error-msg-section flex justify-start">
|
||
<img v-show="!isPwdMatched" src="@/assets/icon-alert.svg" alt="!" class="exclamation-img flex mr-2">
|
||
<span class="error-msg-text flex text-[#FF3366] h-[24px]">
|
||
{{ isPwdMatched ? "" : i18next.t("AcctMgmt.PwdNotMatch") }}
|
||
</span>
|
||
</div>
|
||
<div class="dummy-grid h-[24px]"></div> <!-- 透過 dummy-grid 來撐起高度-->
|
||
<div class="error-msg-section flex justify-start">
|
||
<img v-show="!isPwdLengthValid" src="@/assets/icon-alert.svg" alt="!" class="exclamation-img flex mr-2">
|
||
<span class="error-msg-text flex text-[#FF3366] h-[24px]">
|
||
{{ isPwdLengthValid ? "" : i18next.t("AcctMgmt.PwdLengthNotEnough") }}
|
||
</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-full w-[92px] h-10 px-2.5 py-6 border border-[1px] border-[#64748B]
|
||
flex justify-center items-center text-[#4E5969] font-medium
|
||
hover:bg-[#64748B] hover:border-[#CBD5E1] hover:text-[#FFFFFF]"
|
||
@click="onCancelBtnClick"
|
||
>
|
||
{{ i18next.t("Global.Cancel") }}
|
||
</button>
|
||
<button class="confirm-btn 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';
|
||
import ModalHeader from "./ModalHeader.vue";
|
||
import IconChecked from "@/components/icons/IconChecked.vue";
|
||
import { MODAL_CREATE_NEW, MODAL_ACCT_EDIT, PWD_VALID_LENGTH } 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 isConfirmDisabled = ref(false);
|
||
const isPwdLengthValid = ref(true);
|
||
const isResetPwdSectionShow = ref(false);
|
||
|
||
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 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');
|
||
});
|
||
|
||
const togglePwdEyeBtn = (toBeOpen) => {
|
||
isPwdEyeOn.value = toBeOpen;
|
||
};
|
||
|
||
const validatePwdLength = () => {
|
||
isPwdLengthValid.value = inputPwd.value.length >= PWD_VALID_LENGTH;
|
||
}
|
||
|
||
const onInputDoubleClick = () => {
|
||
// 允許編輯模式
|
||
isEditable.value = true;
|
||
}
|
||
|
||
const onConfirmBtnClick = async () => {
|
||
// rule for minimum length
|
||
validatePwdLength();
|
||
if(!isPwdLengthValid.value) {
|
||
return;
|
||
}
|
||
|
||
// rule for account uniqueness
|
||
switch(whichCurrentModal.value) {
|
||
case MODAL_CREATE_NEW:
|
||
await checkAccountIsUnique();
|
||
if(!isAccountUnique.value) {
|
||
return;
|
||
}
|
||
await acctMgmtStore.createNewAccount({
|
||
username: inputUserAccount.value,
|
||
password: inputPwd.value === undefined ? '' : 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:
|
||
await checkAccountIsUnique();
|
||
if(!isAccountUnique.value) {
|
||
return;
|
||
}
|
||
// 要注意的是舊的username跟新的username可以是不同的
|
||
await acctMgmtStore.editAccount(
|
||
currentViewingUser.value.username, {
|
||
newUsername: inputUserAccount.value,
|
||
password: inputPwd.value,
|
||
name: inputName.value === undefined ? '' : inputName.value,
|
||
is_active: true,
|
||
});
|
||
await toast.success(i18next.t("AcctMgmt.MsgAccountEdited"));
|
||
isEditable.value = false;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
const checkAccountIsUnique = async() => {
|
||
// 如果使用者沒有更動過欄位,那就不用調用任何後端的API
|
||
if(inputUserAccount.value === username.value) {
|
||
return true;
|
||
}
|
||
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 onInputNameFocus = () => {
|
||
if(isConfirmDisabled.value){
|
||
isConfirmDisabled.value = false;
|
||
}
|
||
}
|
||
|
||
const onResetPwdButtonClick = () => {
|
||
isResetPwdSectionShow.value = !isResetPwdSectionShow.value;
|
||
// 必須清空密碼欄位輸入的字串
|
||
inputPwd.value = '';
|
||
}
|
||
|
||
watch(
|
||
[inputPwd, inputUserAccount, inputName],
|
||
([newPwd, newAccount, newName]) => {
|
||
// 只要[確認密碼]或[密碼]欄位有更動,且所有欄位都不是空的,confirm 按鈕就可點選
|
||
if (newPwd.length > 0 && newAccount.length > 0 && newName.length > 0) {
|
||
isConfirmDisabled.value = false;
|
||
} else {
|
||
isConfirmDisabled.value = true;
|
||
}
|
||
}
|
||
);
|
||
|
||
onMounted(() => {
|
||
|
||
});
|
||
|
||
return {
|
||
isConfirmDisabled,
|
||
username,
|
||
name,
|
||
isSSO,
|
||
isPwdEyeOn,
|
||
togglePwdEyeBtn,
|
||
isPwdLengthValid,
|
||
inputUserAccount,
|
||
inputName,
|
||
inputPwd,
|
||
onConfirmBtnClick,
|
||
onInputDoubleClick,
|
||
onInputNameFocus,
|
||
onResetPwdButtonClick,
|
||
isSetAsAdminChecked,
|
||
isSetActivedChecked,
|
||
isResetPwdSectionShow,
|
||
toggleIsAdmin,
|
||
toggleIsActivated,
|
||
whichCurrentModal,
|
||
MODAL_CREATE_NEW,
|
||
modalTitle,
|
||
isAccountUnique,
|
||
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> |