Merge branch 'acct_mgmt'

This commit is contained in:
Cindy Chang
2024-08-26 13:54:13 +08:00
2 changed files with 94 additions and 61 deletions

View File

@@ -29,6 +29,7 @@
"Detail": "Detail", "Detail": "Detail",
"Edit": "Edit", "Edit": "Edit",
"Delete": "Delete", "Delete": "Delete",
"Reset": "Reset",
"AccountEdit": "Account Edit", "AccountEdit": "Account Edit",
"AccountInformation": "Account Information", "AccountInformation": "Account Information",
"Password": "Password", "Password": "Password",

View File

@@ -47,25 +47,53 @@
autocomplete="off" autocomplete="off"
/> />
</div> </div>
<div v-show="!isSSO" class="input-row w-full flex py-2 h-[40px] mb-4 items-center <div v-show="!isSSO" class="input-row w-full flex py-2 mb-4 items-center
justify-between"> justify-between">
<div class="field-label text-sm flex items-center font-medium justify-end"> <div class="field-label text-sm flex items-start font-medium justify-end"
<span class="align-right-span flex w-[122px] justify-end"> :class="{
{{ i18next.t("AcctMgmt.Password") }} 'h-[100px]': whichCurrentModal !== MODAL_CREATE_NEW,
</span> }"
</div> >
<div class="w-[454px] rounded border-[1px] border-[#64748B] flex items-center h-[40px] relative"> <span class="align-right-span flex h-full w-[122px] justify-end text-top">
<input id="input_first_pwd" class="outline-none p-1 w-[352px]" :type="isPwdEyeOn ? 'text' : 'password'" {{ i18next.t("AcctMgmt.Password") }}
v-model="inputPwd" :readonly="!isEditable" @dblclick="onInputDoubleClick" </span>
autocomplete="off" /> </div>
<img v-if="isPwdEyeOn" src='@/assets/icon-eye-open.svg' class="absolute right-[8px] cursor-pointer" <div class="reset-btn-and-input-pwd-and-error flex flex-col"
@click="togglePwdEyeBtn" alt=""/> :class="{'h-[100px]': whichCurrentModal !== MODAL_CREATE_NEW,}"
<img v-else src='@/assets/icon-eye-hide.svg' class="absolute right-[8px] cursor-pointer" @click="togglePwdEyeBtn" alt="eye"/> >
</div> <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>
<div v-show="!isSSO" id="confirm_pwd_row" class="input-row w-full grid grid-cols-2 grid-cols-[122px_1fr] gap-x-4 <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格子其沒有內容 --> 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" <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="{ :class="{
'text-[#000000]': isPwdMatched, 'text-[#000000]': isPwdMatched,
'text-[#FF3366]': !isPwdMatched, 'text-[#FF3366]': !isPwdMatched,
@@ -73,13 +101,13 @@
{{ i18next.t("AcctMgmt.ConfirmPassword") }} {{ i18next.t("AcctMgmt.ConfirmPassword") }}
</span> </span>
<div class="input-and-toggle-btn w-[454px] flex flex-row rounded border border-[1px] relative items-center <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" h-[40px] mb-4"
:class="{ :class="{
'border-[#000000]': isPwdMatched, 'border-[#000000]': isPwdMatched,
'border-[#FF3366]': !isPwdMatched, 'border-[#FF3366]': !isPwdMatched,
}"> }">
<input id="input_second_pwd" class="outline-none p-1 w-[352px]" :type="isPwdConfirmEyeOn ? 'text' : 'password'" <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" v-model="inputConfirmPwd" :readonly="!isEditable" @dblclick="onInputDoubleClick"
:class="{ :class="{
'text-[#000000]': isPwdMatched, 'text-[#000000]': isPwdMatched,
@@ -124,14 +152,14 @@
</div> </div>
</main> </main>
<footer class="flex row footer justify-end pr-[32px] pb-8"> <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] <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 flex justify-center items-center text-[#4E5969] font-medium
hover:bg-[#64748B] hover:border-[#CBD5E1] hover:text-[#FFFFFF]" hover:bg-[#64748B] hover:border-[#CBD5E1] hover:text-[#FFFFFF]"
@click="onCancelBtnClick" @click="onCancelBtnClick"
> >
{{ i18next.t("Global.Cancel") }} {{ i18next.t("Global.Cancel") }}
</button> </button>
<button class="confirm-btn rounded rounded-full w-[92px] h-10 px-2.5 py-6 <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]" flex justify-center items-center text-[#ffffff] font-medium ml-[16px]"
@click="onConfirmBtnClick" :disabled="isConfirmDisabled" @click="onConfirmBtnClick" :disabled="isConfirmDisabled"
:class="{ :class="{
@@ -146,7 +174,7 @@
</template> </template>
<script> <script>
import { defineComponent, computed, ref, watch, } from 'vue'; import { defineComponent, computed, ref, watch, onMounted, } from 'vue';
import i18next from "@/i18n/i18n.js"; import i18next from "@/i18n/i18n.js";
import { mapActions, } from 'pinia'; import { mapActions, } from 'pinia';
import { useModalStore } from '@/stores/modal.js'; import { useModalStore } from '@/stores/modal.js';
@@ -167,10 +195,9 @@ export default defineComponent({
const currentViewingUser = computed(() => acctMgmtStore.currentViewingUser); const currentViewingUser = computed(() => acctMgmtStore.currentViewingUser);
const isPwdEyeOn = ref(false); const isPwdEyeOn = ref(false);
const isPwdConfirmEyeOn = ref(false); const isConfirmDisabled = ref(false);
const isPwdMatched = ref(true);
const isPwdLengthValid = ref(true); const isPwdLengthValid = ref(true);
const isConfirmDisabled = ref(true); const isResetPwdSectionShow = ref(false);
const isSetAsAdminChecked = ref(false); const isSetAsAdminChecked = ref(false);
const isSetActivedChecked = ref(true); const isSetActivedChecked = ref(true);
@@ -184,10 +211,7 @@ export default defineComponent({
const inputUserAccount = ref(whichCurrentModal.value === MODAL_CREATE_NEW ? '' : currentViewingUser.value.username); const inputUserAccount = ref(whichCurrentModal.value === MODAL_CREATE_NEW ? '' : currentViewingUser.value.username);
const inputName = ref(whichCurrentModal.value === MODAL_CREATE_NEW ? '' : currentViewingUser.value.name); const inputName = ref(whichCurrentModal.value === MODAL_CREATE_NEW ? '' : currentViewingUser.value.name);
const inputPwd = ref(""); const inputPwd = ref("");
const inputConfirmPwd = ref("");
const isAccountUnique = ref(true); const isAccountUnique = ref(true);
const isEditable = ref(true); const isEditable = ref(true);
// 自從加入這段 watch 之後,填寫密碼欄位之時,就不會胡亂清空掉 account 或是 full name 欄位了。 // 自從加入這段 watch 之後,填寫密碼欄位之時,就不會胡亂清空掉 account 或是 full name 欄位了。
@@ -205,19 +229,12 @@ export default defineComponent({
return modalStore.whichModal === MODAL_CREATE_NEW ? i18next.t('AcctMgmt.CreateNew') : i18next.t('AcctMgmt.AccountEdit'); return modalStore.whichModal === MODAL_CREATE_NEW ? i18next.t('AcctMgmt.CreateNew') : i18next.t('AcctMgmt.AccountEdit');
}); });
const togglePwdEyeBtn = () => { const togglePwdEyeBtn = (toBeOpen) => {
isPwdEyeOn.value = !isPwdEyeOn.value; isPwdEyeOn.value = toBeOpen;
}; };
const togglePwdConfirmEyeBtn = () => {
isPwdConfirmEyeOn.value = !isPwdConfirmEyeOn.value;
};
const validateConfirmPwd = () => {
isPwdMatched.value = inputPwd.value.length > 0 && inputPwd.value === inputConfirmPwd.value;
}
const validatePwdLength = () => { const validatePwdLength = () => {
isPwdLengthValid.value = inputPwd.value.length >= PWD_VALID_LENGTH; isPwdLengthValid.value = !isResetPwdSectionShow.value || inputPwd.value.length >= PWD_VALID_LENGTH;
} }
const onInputDoubleClick = () => { const onInputDoubleClick = () => {
@@ -226,12 +243,6 @@ export default defineComponent({
} }
const onConfirmBtnClick = async () => { const onConfirmBtnClick = async () => {
// rule for matching of confirm password
validateConfirmPwd();
if(!isPwdMatched.value){
return;
}
// rule for minimum length // rule for minimum length
validatePwdLength(); validatePwdLength();
if(!isPwdLengthValid.value) { if(!isPwdLengthValid.value) {
@@ -263,13 +274,23 @@ export default defineComponent({
return; return;
} }
// 要注意的是舊的username跟新的username可以是不同的 // 要注意的是舊的username跟新的username可以是不同的
await acctMgmtStore.editAccount( // 區分有無傳入密碼的情況
currentViewingUser.value.username, { if(isResetPwdSectionShow.value) {
newUsername: inputUserAccount.value, await acctMgmtStore.editAccount(
password: inputPwd.value, currentViewingUser.value.username, {
name: inputName.value === undefined ? '' : inputName.value, newUsername: inputUserAccount.value,
is_active: true, password: inputPwd.value,
}); name: inputName.value === undefined ? '' : inputName.value,
is_active: true,
});
} else {
await acctMgmtStore.editAccount(
currentViewingUser.value.username, {
newUsername: inputUserAccount.value,
name: inputName.value === undefined ? '' : inputName.value,
is_active: true,
});
}
await toast.success(i18next.t("AcctMgmt.MsgAccountEdited")); await toast.success(i18next.t("AcctMgmt.MsgAccountEdited"));
isEditable.value = false; isEditable.value = false;
break; break;
@@ -279,6 +300,10 @@ export default defineComponent({
} }
const checkAccountIsUnique = async() => { const checkAccountIsUnique = async() => {
// 如果使用者沒有更動過欄位那就不用調用任何後端的API
if(inputUserAccount.value === username.value) {
return true;
}
const isAccountAlreadyExistAPISuccess = await acctMgmtStore.getUserDetail(inputUserAccount.value); const isAccountAlreadyExistAPISuccess = await acctMgmtStore.getUserDetail(inputUserAccount.value);
isAccountUnique.value = !isAccountAlreadyExistAPISuccess; isAccountUnique.value = !isAccountAlreadyExistAPISuccess;
return isAccountUnique.value; return isAccountUnique.value;
@@ -302,40 +327,47 @@ export default defineComponent({
} }
} }
const onResetPwdButtonClick = () => {
isResetPwdSectionShow.value = !isResetPwdSectionShow.value;
// 必須清空密碼欄位輸入的字串
inputPwd.value = '';
}
watch( watch(
[inputPwd, inputConfirmPwd, inputUserAccount, inputName], [inputPwd, inputUserAccount, inputName],
([newPwd, newConfirmPwd, newAccount, newName]) => { ([newPwd, newAccount, newName]) => {
isPwdMatched.value = newPwd === newConfirmPwd && newPwd.length;
isPwdLengthValid.value = newPwd.length >= PWD_VALID_LENGTH && newConfirmPwd.length >= PWD_VALID_LENGTH;
// 只要[確認密碼]或[密碼]欄位有更動且所有欄位都不是空的confirm 按鈕就可點選 // 只要[確認密碼]或[密碼]欄位有更動且所有欄位都不是空的confirm 按鈕就可點選
if (newPwd.length > 0 && newConfirmPwd.length > 0 && newAccount.length > 0 && newName.length > 0) { if(newAccount.length > 0 && newName.length > 0) {
isConfirmDisabled.value = false; isConfirmDisabled.value = false;
} else { }
if(isResetPwdSectionShow.value && newPwd.length < PWD_VALID_LENGTH) {
isConfirmDisabled.value = true; isConfirmDisabled.value = true;
} }
} }
); );
onMounted(() => {
});
return { return {
isConfirmDisabled, isConfirmDisabled,
username, username,
name, name,
isSSO, isSSO,
isPwdEyeOn, isPwdEyeOn,
isPwdConfirmEyeOn,
togglePwdEyeBtn, togglePwdEyeBtn,
togglePwdConfirmEyeBtn,
isPwdMatched,
isPwdLengthValid, isPwdLengthValid,
inputUserAccount, inputUserAccount,
inputName, inputName,
inputPwd, inputPwd,
inputConfirmPwd,
onConfirmBtnClick, onConfirmBtnClick,
onInputDoubleClick, onInputDoubleClick,
onInputNameFocus, onInputNameFocus,
onResetPwdButtonClick,
isSetAsAdminChecked, isSetAsAdminChecked,
isSetActivedChecked, isSetActivedChecked,
isResetPwdSectionShow,
toggleIsAdmin, toggleIsAdmin,
toggleIsActivated, toggleIsActivated,
whichCurrentModal, whichCurrentModal,