Migrate all Vue components from Options API to <script setup>

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 17:10:06 +08:00
parent a619be7881
commit 3b7b6ae859
61 changed files with 10835 additions and 11750 deletions

View File

@@ -2,7 +2,7 @@
<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">
@@ -12,7 +12,7 @@
</span>
</div>
<div class="input-and-error flex flex-col">
<input id="input_account_field"
<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="{
@@ -23,14 +23,14 @@
:readonly="!isEditable" autocomplete="off"
@dblclick="onInputDoubleClick"
/>
<div v-show="!isAccountUnique" class="error-wrapper my-2">
<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>
</div>
<div class="input-row w-full flex py-2 h-[40px] mb-4 items-center
@@ -108,7 +108,7 @@
}">
{{ 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="{
@@ -148,7 +148,7 @@
<IconChecked :isChecked="isSetAsAdminChecked" @click="toggleIsAdmin"/>
<span class="flex checkbox-text">
{{ i18next.t("AcctMgmt.SetAsAdmin") }}
</span>
</span>
</div>
<div class="checkbox-and-text flex">
<IconChecked :isChecked="isSetActivedChecked" @click="toggleIsActivated"/>
@@ -159,7 +159,7 @@
</section>
</div>
</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-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]"
@@ -177,14 +177,13 @@
>
{{ i18next.t("Global.Confirm") }}
</button>
</footer>
</footer>
</div>
</template>
<script>
import { defineComponent, computed, ref, watch, onMounted, } from 'vue';
<script setup>
import { computed, ref, watch } from 'vue';
import i18next from "@/i18n/i18n.js";
import { mapActions, } from 'pinia';
import { useModalStore } from '@/stores/modal';
import { useRouter } from 'vue-router';
import { useToast } from 'vue-toast-notification';
@@ -193,228 +192,178 @@ 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 acctMgmtStore = useAcctMgmtStore();
const modalStore = useModalStore();
const router = useRouter();
const toast = useToast();
const router = useRouter();
const toast = useToast();
const currentViewingUser = computed(() => acctMgmtStore.currentViewingUser);
const isPwdEyeOn = ref(false);
const isConfirmDisabled = ref(true);
const isPwdLengthValid = ref(true);
const isResetPwdSectionShow = ref(false);
const currentViewingUser = computed(() => acctMgmtStore.currentViewingUser);
const isPwdEyeOn = ref(false);
const isConfirmDisabled = ref(true);
const isPwdLengthValid = ref(true);
const isResetPwdSectionShow = ref(false);
const isSetAsAdminChecked = ref(false);
const isSetActivedChecked = ref(true);
const isSetAsAdminChecked = ref(false);
const isSetActivedChecked = ref(true);
const whichCurrentModal = computed(() => modalStore.whichModal);
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);
const isSSO = computed(() => acctMgmtStore.currentViewingUser.is_sso);
const username = computed(() => acctMgmtStore.currentViewingUser.username);
const name = computed(() => acctMgmtStore.currentViewingUser.name);
// 自從加入這段 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 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);
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 = !isResetPwdSectionShow.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可以是不同的
// 區分有無傳入密碼的情況
if(isResetPwdSectionShow.value) {
await acctMgmtStore.editAccount(
currentViewingUser.value.username, {
newUsername: inputUserAccount.value,
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"));
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(newAccount.length > 0 && newName.length > 0) {
isConfirmDisabled.value = false;
}
if(whichCurrentModal.value !== MODAL_CREATE_NEW) {
if(isResetPwdSectionShow.value && newPwd.length < PWD_VALID_LENGTH) {
isConfirmDisabled.value = true;
}
}else {
if(newPwd.length < PWD_VALID_LENGTH) {
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']),
// 自從加入這段 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 = !isResetPwdSectionShow.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可以是不同的
// 區分有無傳入密碼的情況
if(isResetPwdSectionShow.value) {
await acctMgmtStore.editAccount(
currentViewingUser.value.username, {
newUsername: inputUserAccount.value,
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"));
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(newAccount.length > 0 && newName.length > 0) {
isConfirmDisabled.value = false;
}
if(whichCurrentModal.value !== MODAL_CREATE_NEW) {
if(isResetPwdSectionShow.value && newPwd.length < PWD_VALID_LENGTH) {
isConfirmDisabled.value = true;
}
}else {
if(newPwd.length < PWD_VALID_LENGTH) {
isConfirmDisabled.value = true;
}
}
}
);
function onCancelBtnClick(){
modalStore.closeModal();
}
</script>
<style>
#modal_account_edit {
background-color: #ffffff;
backdrop-filter: opacity(1); /*防止子元件繼承父元件的透明度 */
}
</style>
</style>