# vue3 筆記, 使用 chatGPT 去學習復用組件(以 el-dialog 為例) ----- ###### tags: `ERP` `工作相關` `vue3` `前端學習` `element-plus` `2023/05/16` 這裡有個 vue3, element-plus UI 範本, 請你記住: ``` <el-form> <el-form-item class="add-payment-request-form"> <AddPaymentRequestForm v-model="showAddPaymentRequestForm" /> <!-- <AddPaymentRequestForm v-model="showAddPaymentRequestForm" @search="handelSearch()" /> --> <!-- 詳細內容點擊彈窗, 請款內容 --> <PaymentDetail v-model="showPaymentDetail" ref="paymentDialog" /> <!-- 請款內容彈窗 --> <el-dialog class="payment-sign-dialog" v-model="paymentSignDialog" title="請款簽收" width="22%" :before-close="handleClose" :show-close="false"> <span>請確認是否收到請款金額 新台幣 1,200元整?</span> <template #footer> <span class="payment-sign-dialog-footer flex justify-center"> <el-button class="payment-sign-check mr-6" type="primary" @click="paymentSignDialog = false"> 確認 </el-button> <el-button type="info" @click="paymentSignDialog = false">取消</el-button> </span> </template> </el-dialog> </el-form-item> </el-form> <div class="payment-request-table-container"> <!-- <el-table :data="filteredSignApplications" style="width: 100%; margin-bottom: 20px;" row-key="id" border fit highlight-current-row :expand-row-keys="expandedRowKeys" @sort-change="form.sortChange" @row-click="handleRowClick" :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"> --> <el-table :data="filteredSignApplications" style="width: 100%; margin-bottom: 20px;" row-key="id" border fit highlight-current-row :expand-row-keys="expandedRowKeys" @row-click="handleRowClick" :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"> <el-table-column class="payment-request-expand" type="expand" width="0" algin="center"> <template #default="{ row }"> <el-steps class="show-expand p-4" :active="getActiveStep(row)" align-center :process-status="newSignStateType(row.state)" finish-status="success" space="250px"> <div class="payment-request-process-text"></div> <el-step title="1.送出申請" :description="row.created_at" /> <!-- <el-step title="2.總經理" description="2023-02-24 09:30" /> <el-step title="3.部門主管" description="審核中" /> <el-step title="4.總經理" description="" /> --> <el-step v-for="(step, index) in row.sequence" :key="step.id" :title="letTitleAdd(row, index, step)" :description="step.time"> </el-step> </el-steps> </template> </el-table-column> <el-table-column prop="created_at" label="申請日期" sortable="custom" width="120"> <!-- <template #default="{ row }">{{ getApplyTime(row.created_at) }}</template> --> <template #default="{ row }">{{ row.created_at }}</template> </el-table-column> <el-table-column prop="department" label="申請部門" sortable="custom" width="120"> <!-- <template #default="{ row }">{{ getApplyTime(row.department) }}</template> --> <template #default="{ row }">{{ row.department }}</template> </el-table-column> <el-table-column prop="user_id" label="申請人" sortable="custom" width="120"> <template #default="{ row }">{{ row.user_name }}</template> </el-table-column> <!-- 最長的這欄位別設定寬度 --> <el-table-column prop="user_id" label="請款事由"> <template #default="{ row }">{{ row.reason }}</template> </el-table-column> <el-table-column prop="currency" label="幣別" width="90"> <template #default="{ row }">{{ row.currency }}</template> </el-table-column> <el-table-column prop="request_amount" label="請款金額" width="120"> <template #default="{ row }">{{ row.request_amount }}</template> </el-table-column> <el-table-column prop="payment_method" label="付款方式" sortable="custom" width="120"> <template #default="{ row }">{{ row.payment_method }}</template> </el-table-column> <el-table-column prop="payment_detail" label="詳細內容" width="120"> <template #default="{ row }"> <!-- <div class="file-name" v-for="(file, index) in row.info.files" :key="index"> <a :href="file.url" target="_blank" @click.stop>{{ file.name }}</a> </div> --> <!-- {{ row.info }} --> <template v-if="row.payment_detail == 1"> <!-- <el-icon class="payment-detail-display mt-2"> <Document /> </el-icon> --> <!-- 這裡引入 font-awesome --> <font-awesome-icon :icon="['fas', 'file-lines']" class="payment-detail-display mt-2" @click="openPaymentDetail" @click.stop /> <!-- <i class="fa fa-file-lines payment-detail-display" @click="openPaymentDetail">Hi</i> --> <!-- <div v-if="showPaymentDetail">This is a test</div> --> <!-- <el-button class="add-payment-detail-btn" type="warning" @click="createPaymentRequestFormBtn"> 簽收中 </el-button> --> </template> <template v-if="row.payment_detail !== 1"> <!-- 這裡引入 font-awesome --> <font-awesome-icon :icon="['fas', 'file-lines']" class="payment-detail-no-display mt-2" /> </template> </template> </el-table-column> <el-table-column prop="payment_process" label="進度" sortable="custom" width="100"> <template #default="{ row }">{{ row.payment_process1 }}</template> </el-table-column> <!-- 請款欄位 簽收按鈕 --> <el-table-column prop="payment_sign" label="請款" width="120"> <template #default="{ row }"> <el-button class="payment-sign-button" type="primary" @click="paymentSignDialog = true" @click.stop>{{ row.payment_sign }}</el-button> </template> </el-table-column> <el-table-column class="state-container" prop="payment_op" label="操作" width="160"> <template #default="{ row }"> <!-- <el-row v-if="row.state === 0" class="state-button-group flex flex-wrap m-0" @click.stop> <template v-if="userStore.id == row.sequence[row.index].id"> <el-button type="success" @click="sendSign(row.id, true)">核准</el-button> <el-button type="danger" @click="sendSign(row.id, false)">駁回</el-button> </template> </el-row> <div class="state-set-group flex flex-wrap justify-center"> <div class="state-set" :style="{ color: getStatusColor(row.state) }"> {{ newSignStateText(row.state) }} </div> </div> --> <div class="flex justify-around items-center"> <u>{{ '編輯' }}</u> <u>{{ '送出' }}</u> <u>{{ '撤回' }}</u> <u>{{ '刪除' }}</u> </div> </template> </el-table-column> </el-table> </div> ``` 請你記住 ChatGPT 好的,我已經記住了這個Vue3和Element-Plus的UI範本。如果你有任何問題或需要進一步的幫助,請隨時提問。 --- User 這個: ``` <AddPaymentRequestForm v-model="showAddPaymentRequestForm" /> ``` 是子層範本, 請問我要怎麼樣才能在按下: ``` <el-table-column class="state-container" prop="payment_op" label="操作" width="160"> <template #default="{ row }"> <!-- <el-row v-if="row.state === 0" class="state-button-group flex flex-wrap m-0" @click.stop> <template v-if="userStore.id == row.sequence[row.index].id"> <el-button type="success" @click="sendSign(row.id, true)">核准</el-button> <el-button type="danger" @click="sendSign(row.id, false)">駁回</el-button> </template> </el-row> <div class="state-set-group flex flex-wrap justify-center"> <div class="state-set" :style="{ color: getStatusColor(row.state) }"> {{ newSignStateText(row.state) }} </div> </div> --> <div class="flex justify-around items-center"> <u>{{ '編輯' }}</u> <u>{{ '送出' }}</u> <u>{{ '撤回' }}</u> <u>{{ '刪除' }}</u> </div> </template> </el-table-column> ``` 編輯的時候, 復用這個子層範本? --- ChatGPT 要復用 AddPaymentRequestForm 這個子組件,你需要設計一種方式來控制該組件的顯示與隱藏,以及傳遞正確的數據到該組件上。我們可以透過 v-model 控制該組件的顯示與隱藏,並通過 props 傳遞數據。 在這裡,你已經有一個 v-model 命名為 showAddPaymentRequestForm。你可以在用戶點擊編輯時改變此變量的值來顯示 AddPaymentRequestForm 組件。 以下是一個簡單的例子: html Copy code ``` <!-- 在此組件中編輯 --> <AddPaymentRequestForm v-model="showAddPaymentRequestForm" :paymentRequest="selectedPaymentRequest"/> <el-table-column class="state-container" prop="payment_op" label="操作" width="160"> <template #default="{ row }"> <div class="flex justify-around items-center"> <u @click="editPaymentRequest(row)">{{ '編輯' }}</u> <u>{{ '送出' }}</u> <u>{{ '撤回' }}</u> <u>{{ '刪除' }}</u> </div> </template> </el-table-column> 在你的Vue component中: js Copy code export default { data() { return { showAddPaymentRequestForm: false, // 控制 AddPaymentRequestForm 的顯示與隱藏 selectedPaymentRequest: null, // 選擇的付款請求 } }, methods: { editPaymentRequest(row) { this.selectedPaymentRequest = row; this.showAddPaymentRequestForm = true; // 顯示 AddPaymentRequestForm } } } 同時,你需要在 AddPaymentRequestForm 組件中接收一個 props,例如 paymentRequest,然後在組件中使用該 props 來展示或修改數據。 js Copy code export default { props: ['paymentRequest'], // ... } ``` 請注意,這僅為一個基本的例子,你可能需要根據你的應用程式的具體情況進行適當的調整。例如,你可能需要考慮如何在完成編輯後更新原始數據,或者如何處理用戶取消編輯等情況。 ---- User 但我想重複使用這個子範本, 例如我一開始使用這個子範本: ``` <AddPaymentRequestForm v-model="showAddPaymentRequestForm" ref="selectedPaymentRequest" /> ``` 是點下去可以儲存 和新增資料, 但我點操作的編輯時, 也能再調出 ``` <AddPaymentRequestForm v-model="showAddPaymentRequestForm" ref="selectedPaymentRequest" /> ``` 並使用去修改資料 --- ChatGPT 如果你想在點選編輯時使用 AddPaymentRequestForm 元件並修改資料,你可以使用 Vue 的 props 傳遞所選行的資料到 AddPaymentRequestForm 元件。然後,在 AddPaymentRequestForm 元件中,你可以根據 props 的存在與否來確定是新增還是修改資料。 讓我們假設 AddPaymentRequestForm 元件接受一個 paymentRequest 的 props,如果 paymentRequest 存在,那麼它就是一個需要編輯的請求;如果不存在,那麼就是一個新的請求。 在你的父元件中,你可以這樣做: html Copy code ``` <!-- 在這個元件中編輯或新增 --> <AddPaymentRequestForm v-model="showAddPaymentRequestForm" :paymentRequest="selectedPaymentRequest"/> <el-table-column class="state-container" prop="payment_op" label="操作" width="160"> <template #default="{ row }"> <div class="flex justify-around items-center"> <u @click="editPaymentRequest(row)">{{ '編輯' }}</u> <u>{{ '送出' }}</u> <u>{{ '撤回' }}</u> <u>{{ '刪除' }}</u> </div> </template> </el-table-column> 在你的 Vue 元件中: js Copy code export default { data() { return { showAddPaymentRequestForm: false, // 控制 AddPaymentRequestForm 的顯示與隱藏 selectedPaymentRequest: null, // 選擇的付款請求 } }, methods: { editPaymentRequest(row) { this.selectedPaymentRequest = row; this.showAddPaymentRequestForm = true; // 顯示 AddPaymentRequestForm } } } 在 AddPaymentRequestForm 元件中: js Copy code export default { props: ['paymentRequest'], // ... created() { if (this.paymentRequest) { // 如果有 paymentRequest,那麼就是編輯請求,將其資料載入到表單中 } else { // 否則,就是新的請求,清空表單 } } } ``` 這樣,當你點選編輯時,AddPaymentRequestForm 元件會顯示,並載入所選行的資料。當你想新增新的請求時,你可以再次顯示 AddPaymentRequestForm 元件,但不傳遞 paymentRequest,這樣,元件就會清空表單,準備新增新的請求。 --- ``` User 這是我父組件的 script: <script lang="ts" setup> import { provide, ref, reactive, nextTick, onMounted, computed, watch, onBeforeMount } from 'vue'; import type { PropType } from 'vue'; import moment from 'moment'; // import { punchs, punch, departments, leaveTypes, users, addLeaveApplication, exportDepartmentPunchs, signs, newSign, newLeaveApplications } from '@/api'; // import type { Department, Punch, User, SignState, NewLeaveApplication } from "@/types"; // import { useUserStore } from '@/store'; // import { httpCode } from '@/client'; import { minuteToHumanTime, signStateText, newSignStateType, newSignStateText, getStatusColor } from '@/config'; import { Calendar, Plus } from "@element-plus/icons-vue"; import { ElMessage, ElMessageBox } from "element-plus"; // import PaymentRequestTable from '@/components/PaymentRequestTable/index.vue'; import AddPaymentRequestForm from '@/components/AddPaymentRequestForm.vue'; import PaymentDetail from '@/components/PaymentDetail.vue'; const showAddPaymentRequestForm = ref(false); // 新增請款單 // 查詢請款單表單 const form = reactive({ start: moment(new Date).format('YYYY-MM-DD'), // 開始日期 end: moment(new Date).format('YYYY-MM-DD'), // 結束日期 department_id: null, selectedDepartment: '', // 申請部門 user_id: null, user_name: '', // 申請人 progress: '', // 進度 paymentRequest: '', // 請款 state: null, order: null, page: 1, last_page: 1, data: [], paymentRequestRange: [moment().hour(9).minute(0).second(0).toDate(), moment().hour(18).minute(0).toDate()], // 時間區段 rangeChange: () => { // 時間檢查 if (moment(form.paymentRequestRange[0]).hour() < 9 || moment(form.paymentRequestRange[0]).hour() > 18) form.paymentRequestRange[0] = moment(form.paymentRequestRange[0]).hour(9).toDate(); if (moment(form.paymentRequestRange[1]).hour() < 9 || moment(form.paymentRequestRange[1]).hour() > 18) form.paymentRequestRange[1] = moment(form.paymentRequestRange[1]).hour(18).toDate(); // form.search(); }, disabledDate: (date: Date) => moment().isAfter(moment(date).add(2, 'years')), // 停用時間 // search: () => { // businessTrips(form.start, form.end, form.page, form.department_id, form.user_id, form.state).then(res => { // form.data = res.data.data; // }); // }, // update: (id) => { // updateBusinessTrip(id).then(res => { // console.log(res.data); // form.search(); // }); // } // sortChange: (column) => { // let { prop, order } = column; // if (prop != null) { // 假如 column 不是 null 的情況下 // switch (order) { // case 'ascending': // order = 'asc'; // break; // case 'descending': // order = 'desc'; // break; // } // form.order = [[prop, order]]; // } // else { // form.order = null; // } // form.search(); // }, }); // 展開簽核進度 const expandedRowKeys = ref([]); const handleRowClick = (row: { id: never; }) => { const index = expandedRowKeys.value.indexOf(row.id); if (index > -1) { expandedRowKeys.value.splice(index, 1); } else { expandedRowKeys.value.push(row.id); } }; // 動態填入簽核進度時間 const getActiveStep = (row: any) => { const { index } = row; // 問題 // console.log('asdf',index,row) return index !== null ? index + 1 : 1; }; // 簽核進度條, 節點標題從1.送出申請之後+2 const letTitleAdd = (row: any, index: any, step: any) => { return index >= 0 ? `${index + 2}.${step.name}` : step.name; }; // 動態填入送出申請時間 const getApplyTime = (created_at: any) => { const applyTime = created_at.split(' ')[0]; return applyTime; }; // 點擊產生 詳細內容 const showPaymentDetail = ref(false); // 調用子層 "請款內容" 函數, 開啟/關閉對話窗 const paymentDialog = ref(); // 在欄位的詳細內容中開啟子組件 "請款內容" 視窗 const openPaymentDetail = () => { // showPaymentDetail.value = true; paymentDialog.value.openDialog(); // console.log('showPaymentDetail:', showPaymentDetail.value); }; // 欄位中的請款, 簽收彈窗 const paymentSignDialog = ref(false); // 綁定選擇的新增請款單來復用到操作欄位的編輯顯示彈窗 const selectedPaymentRequest = ref(false); // 定義存放修改的資料 // const editData = ref<businessFormDataType>() // const editPaymentRequest(row: any) => { // selectedPaymentRequest.value = row; // showAddPaymentRequestForm.value = true; // }; // 頁數取得資料 const page = reactive({ currentPage: 1, lastPage: 1 }); console.log('page', page.lastPage); const filteredSignApplications = [ { created_at: '2016-05-03', department: '總務收款處', user_name: '王小姐', reason: '收款理由很長很長很長很長很長很長很長很長很長很長很長所以試著讓這個理由框被佔滿43個字', currency: '新台幣', request_amount: '1,111,000', payment_method: '現金', info: 'CA 94114', payment_detail: 1, payment_process0: '未申請', payment_process1: '簽收中', payment_process2: '審核中', payment_sign: '簽收', payment_op0: 0, payment_op1: 1, payment_op2: 2, payment_op3: 3, } ]; console.log(filteredSignApplications); </script> ChatGPT 根據你提供的程式碼,你需要做如下的修改: 在你的 AddPaymentRequestForm 元件中,增加一個 props 參數,名為 paymentRequest: html Copy code <AddPaymentRequestForm v-model="showAddPaymentRequestForm" :paymentRequest="selectedPaymentRequest"/> 在你的 data 對象中,新增一個名為 selectedPaymentRequest 的屬性,它將儲存你選擇的付款請求: js Copy code const selectedPaymentRequest = ref(null); 在你的方法中,新增一個新的方法 editPaymentRequest,當你點選編輯時,該方法將被呼叫,並將選中的行賦值給 selectedPaymentRequest: js Copy code const editPaymentRequest = (row) => { selectedPaymentRequest.value = row; showAddPaymentRequestForm.value = true; }; 在你的 HTML 中,給 "編輯" 新增一個點選事件,呼叫 editPaymentRequest 方法: html Copy code <u @click="editPaymentRequest(row)">{{ '編輯' }}</u> 在你的 AddPaymentRequestForm 元件中,你需要接受 paymentRequest 參數,並根據它的值更新表單: js Copy code export default { props: ['paymentRequest'], created() { if (this.paymentRequest) { // 如果有 paymentRequest,那麼就是編輯請求,將其資料載入到表單中 } else { // 否則,就是新的請求,清空表單 } } } ``` 以上就是如何在點選編輯時,重複使用 AddPaymentRequestForm 元件並修改資料的方法。 --- User 可以幫我翻譯嗎: https://dev.to/gaurangdhorda/reusable-dialog-component-using-vue3-composition-api-348i ChatGPT 這篇文章主要介紹了如何使用 Vue3 的 Composition API 建立一個可復用的對話方塊元件。 以下是文章的主要內容: 設定項目 建立一個新的 Vue3 項目,然後刪除默認的 HelloWorld 元件。 建立一個新的元件 在 components 資料夾中,建立一個名為 Dialog.vue 的新檔案。這個檔案將包含對話方塊的 HTML 和 CSS。因為我們只關心如何在 Vue3 中建立可復用的對話方塊,所以 HTML 和 CSS 部分不是我們的重點。 建立 Composition API 在 setup() 函數中,我們可以定義我們的元件狀態和邏輯。在這個例子中,我們會建立一個叫 showDialog 的 ref,它的初始值是 false。我們也將建立一個 openDialog 的函數,這個函數會將 showDialog 的值設定為 true,以及一個 closeDialog 函數,這個函數會將 showDialog 的值設定為 false。 在父元件中使用對話方塊 在父元件中,我們需要一個按鈕來打開對話方塊。我們將使用 v-slot 指令來訪問對話方塊元件的 openDialog 函數。 在對話方塊元件中使用插槽 在對話方塊元件中,我們可以使用 slots 屬性來訪問任何傳入的插槽。我們可以在對話方塊的內容部分使用默認插槽,在標題部分使用名為 header 的插槽,以及在按鈕部分使用名為 footer 的插槽。 在父元件中使用插槽 在父元件中,我們可以使用 v-slot:header 和 v-slot:footer 指令來定義對話方塊的標題和按鈕。 這樣,我們就建立了一個可復用的對話方塊元件,它可以在任何元件中使用,並且可以自訂其標題,內容和按鈕。 這就是該文章的基本內容。希望對你有所幫助! ---- User 2 / 2 我將父層範本改成以下, 請你記住: ``` <el-form> <el-form-item class="add-payment-request-form"> <AddPaymentRequestForm v-model="showAddPaymentRequestForm" ref="selectedPaymentRequest" /> <!-- <AddPaymentRequestForm v-model="showAddPaymentRequestForm" @search="handelSearch()" /> --> <!-- 詳細內容點擊彈窗, 請款內容 --> <PaymentDetail v-model="showPaymentDetail" ref="paymentDialog" /> <!-- 請款內容彈窗 --> <el-dialog class="payment-sign-dialog" v-model="paymentSignDialog" title="請款簽收" width="22%" :before-close="handleClose" :show-close="false"> <span>請確認是否收到請款金額 新台幣 1,200元整?</span> <template #footer> <span class="payment-sign-dialog-footer flex justify-center"> <el-button class="payment-sign-check mr-6" type="primary" @click="paymentSignDialog = false"> 確認 </el-button> <el-button type="info" @click="paymentSignDialog = false">取消</el-button> </span> </template> </el-dialog> </el-form-item> </el-form> <div class="payment-request-table-container"> <!-- <el-table :data="filteredSignApplications" style="width: 100%; margin-bottom: 20px;" row-key="id" border fit highlight-current-row :expand-row-keys="expandedRowKeys" @sort-change="form.sortChange" @row-click="handleRowClick" :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"> --> <el-table :data="filteredSignApplications" style="width: 100%; margin-bottom: 20px;" row-key="id" border fit highlight-current-row :expand-row-keys="expandedRowKeys" @row-click="handleRowClick" :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"> <el-table-column class="payment-request-expand" type="expand" width="0" algin="center"> <template #default="{ row }"> <el-steps class="show-expand p-4" :active="getActiveStep(row)" align-center :process-status="newSignStateType(row.state)" finish-status="success" space="250px"> <div class="payment-request-process-text"></div> <el-step title="1.送出申請" :description="row.created_at" /> <!-- <el-step title="2.總經理" description="2023-02-24 09:30" /> <el-step title="3.部門主管" description="審核中" /> <el-step title="4.總經理" description="" /> --> <el-step v-for="(step, index) in row.sequence" :key="step.id" :title="letTitleAdd(row, index, step)" :description="step.time"> </el-step> </el-steps> </template> </el-table-column> <el-table-column prop="created_at" label="申請日期" sortable="custom" width="120"> <!-- <template #default="{ row }">{{ getApplyTime(row.created_at) }}</template> --> <template #default="{ row }">{{ row.created_at }}</template> </el-table-column> <el-table-column prop="department" label="申請部門" sortable="custom" width="120"> <!-- <template #default="{ row }">{{ getApplyTime(row.department) }}</template> --> <template #default="{ row }">{{ row.department }}</template> </el-table-column> <el-table-column prop="user_id" label="申請人" sortable="custom" width="120"> <template #default="{ row }">{{ row.user_name }}</template> </el-table-column> <!-- 最長的這欄位別設定寬度 --> <el-table-column prop="user_id" label="請款事由"> <template #default="{ row }">{{ row.reason }}</template> </el-table-column> <el-table-column prop="currency" label="幣別" width="90"> <template #default="{ row }">{{ row.currency }}</template> </el-table-column> <el-table-column prop="request_amount" label="請款金額" width="120"> <template #default="{ row }">{{ row.request_amount }}</template> </el-table-column> <el-table-column prop="payment_method" label="付款方式" sortable="custom" width="120"> <template #default="{ row }">{{ row.payment_method }}</template> </el-table-column> <el-table-column prop="payment_detail" label="詳細內容" width="120"> <template #default="{ row }"> <!-- <div class="file-name" v-for="(file, index) in row.info.files" :key="index"> <a :href="file.url" target="_blank" @click.stop>{{ file.name }}</a> </div> --> <!-- {{ row.info }} --> <template v-if="row.payment_detail == 1"> <!-- <el-icon class="payment-detail-display mt-2"> <Document /> </el-icon> --> <!-- 這裡引入 font-awesome --> <font-awesome-icon :icon="['fas', 'file-lines']" class="payment-detail-display mt-2" @click="openPaymentDetail" @click.stop /> <!-- <i class="fa fa-file-lines payment-detail-display" @click="openPaymentDetail">Hi</i> --> <!-- <div v-if="showPaymentDetail">This is a test</div> --> <!-- <el-button class="add-payment-detail-btn" type="warning" @click="createPaymentRequestFormBtn"> 簽收中 </el-button> --> </template> <template v-if="row.payment_detail !== 1"> <!-- 這裡引入 font-awesome --> <font-awesome-icon :icon="['fas', 'file-lines']" class="payment-detail-no-display mt-2" /> </template> </template> </el-table-column> <el-table-column prop="payment_process" label="進度" sortable="custom" width="100"> <template #default="{ row }">{{ row.payment_process1 }}</template> </el-table-column> <!-- 請款欄位 簽收按鈕 --> <el-table-column prop="payment_sign" label="請款" width="120"> <template #default="{ row }"> <el-button class="payment-sign-button" type="primary" @click="paymentSignDialog = true" @click.stop>{{ row.payment_sign }}</el-button> </template> </el-table-column> <el-table-column class="state-container" prop="payment_op" label="操作" width="160"> <template #default="{ row }"> <!-- <el-row v-if="row.state === 0" class="state-button-group flex flex-wrap m-0" @click.stop> <template v-if="userStore.id == row.sequence[row.index].id"> <el-button type="success" @click="sendSign(row.id, true)">核准</el-button> <el-button type="danger" @click="sendSign(row.id, false)">駁回</el-button> </template> </el-row> <div class="state-set-group flex flex-wrap justify-center"> <div class="state-set" :style="{ color: getStatusColor(row.state) }"> {{ newSignStateText(row.state) }} </div> </div> --> <div class="flex justify-around items-center"> <u @click="editPaymentRequest()">{{ '編輯' }}</u> <u>{{ '送出' }}</u> <u>{{ '撤回' }}</u> <u>{{ '刪除' }}</u> </div> </template> </el-table-column> </el-table> </div> ``` --- ChatGPT 好的,我已經記下了你提供的父元件範本程式碼。這個範本看起來是一個 Vue.js 元件,使用了 Element UI 庫(尤其是表格和對話方塊部分)。它主要用於顯示一些支付請求的詳細資訊,並提供了一些與支付請求相關的操作,如編輯、提交、撤回和刪除。 這裡有幾個元件,例如 AddPaymentRequestForm 和 PaymentDetail,還有一些 Element UI 的元件,如 el-form, el-form-item, el-dialog, el-table 等。 如果你有任何問題或需要關於這段程式碼的進一步解釋,歡迎提問。 ---- User 2 / 2 剛剛的父組件範本代碼是使用 vue3+element-plusUI 的範本, 以下是相關的 script 修正成這樣: ``` <script lang="ts" setup> import { provide, ref, reactive, nextTick, onMounted, computed, watch, onBeforeMount } from 'vue'; import type { PropType } from 'vue'; import moment from 'moment'; // import { punchs, punch, departments, leaveTypes, users, addLeaveApplication, exportDepartmentPunchs, signs, newSign, newLeaveApplications } from '@/api'; // import type { Department, Punch, User, SignState, NewLeaveApplication } from "@/types"; // import { useUserStore } from '@/store'; // import { httpCode } from '@/client'; import { minuteToHumanTime, signStateText, newSignStateType, newSignStateText, getStatusColor } from '@/config'; import { Calendar, Plus } from "@element-plus/icons-vue"; import { ElMessage, ElMessageBox } from "element-plus"; // import PaymentRequestTable from '@/components/PaymentRequestTable/index.vue'; import AddPaymentRequestForm from '@/components/AddPaymentRequestForm.vue'; import PaymentDetail from '@/components/PaymentDetail.vue'; const showAddPaymentRequestForm = ref(false); // 新增請款單 // 查詢請款單表單 const form = reactive({ start: moment(new Date).format('YYYY-MM-DD'), // 開始日期 end: moment(new Date).format('YYYY-MM-DD'), // 結束日期 department_id: null, selectedDepartment: '', // 申請部門 user_id: null, user_name: '', // 申請人 progress: '', // 進度 paymentRequest: '', // 請款 state: null, order: null, page: 1, last_page: 1, data: [], paymentRequestRange: [moment().hour(9).minute(0).second(0).toDate(), moment().hour(18).minute(0).toDate()], // 時間區段 rangeChange: () => { // 時間檢查 if (moment(form.paymentRequestRange[0]).hour() < 9 || moment(form.paymentRequestRange[0]).hour() > 18) form.paymentRequestRange[0] = moment(form.paymentRequestRange[0]).hour(9).toDate(); if (moment(form.paymentRequestRange[1]).hour() < 9 || moment(form.paymentRequestRange[1]).hour() > 18) form.paymentRequestRange[1] = moment(form.paymentRequestRange[1]).hour(18).toDate(); // form.search(); }, disabledDate: (date: Date) => moment().isAfter(moment(date).add(2, 'years')), // 停用時間 // search: () => { // businessTrips(form.start, form.end, form.page, form.department_id, form.user_id, form.state).then(res => { // form.data = res.data.data; // }); // }, // update: (id) => { // updateBusinessTrip(id).then(res => { // console.log(res.data); // form.search(); // }); // } // sortChange: (column) => { // let { prop, order } = column; // if (prop != null) { // 假如 column 不是 null 的情況下 // switch (order) { // case 'ascending': // order = 'asc'; // break; // case 'descending': // order = 'desc'; // break; // } // form.order = [[prop, order]]; // } // else { // form.order = null; // } // form.search(); // }, }); // 展開簽核進度 const expandedRowKeys = ref([]); const handleRowClick = (row: { id: never; }) => { const index = expandedRowKeys.value.indexOf(row.id); if (index > -1) { expandedRowKeys.value.splice(index, 1); } else { expandedRowKeys.value.push(row.id); } }; // 動態填入簽核進度時間 const getActiveStep = (row: any) => { const { index } = row; // 問題 // console.log('asdf',index,row) return index !== null ? index + 1 : 1; }; // 簽核進度條, 節點標題從1.送出申請之後+2 const letTitleAdd = (_row: any, index: any, step: any) => { return index >= 0 ? `${index + 2}.${step.name}` : step.name; }; // 動態填入送出申請時間 const getApplyTime = (created_at: any) => { const applyTime = created_at.split(' ')[0]; return applyTime; }; // 點擊產生 詳細內容 const showPaymentDetail = ref(false); // 調用子層 "請款內容" 函數, 開啟/關閉對話窗 const paymentDialog = ref(); // 在欄位的詳細內容中開啟子組件 "請款內容" 視窗 const openPaymentDetail = () => { // showPaymentDetail.value = true; paymentDialog.value.openDialog(); // console.log('showPaymentDetail:', showPaymentDetail.value); }; // 欄位中的請款, 簽收彈窗 const paymentSignDialog = ref(false); // 綁定選擇的新增請款單來復用到操作欄位的編輯顯示彈窗 const selectedPaymentRequest = ref(null); // 定義存放修改的資料 // const editData = ref<businessFormDataType>() const editPaymentRequest = () => { showAddPaymentRequestForm.value = true; }; // 頁數取得資料 const page = reactive({ currentPage: 1, lastPage: 1 }); console.log('page', page.lastPage); const filteredSignApplications = [ { created_at: '2016-05-03', department: '總務收款處', user_name: '王小姐', reason: '收款理由很長很長很長很長很長很長很長很長很長很長很長所以試著讓這個理由框被佔滿43個字', currency: '新台幣', request_amount: '1,111,000', payment_method: '現金', info: 'CA 94114', payment_detail: 1, payment_process0: '未申請', payment_process1: '簽收中', payment_process2: '審核中', payment_sign: '簽收', payment_op0: 0, payment_op1: 1, payment_op2: 2, payment_op3: 3, } ]; console.log(filteredSignApplications); </script> ``` --- ChatGPT 你的程式碼已經使用了 Vue 3 的 <script setup> 風格,使用了 reactive 和 ref 建立了響應式資料,並且導入了需要的組件和函數。 其中有一些方法或 API 呼叫被註解掉了,這可能是因為你在重構你的程式碼,或者你正在等待實現這些功能。如果你需要幫忙實現某些功能,或者對於某些部分的實現有問題,請告訴我,我會很樂意幫忙。 另外,我注意到你在程式碼中使用了一些尚未定義的變數和函數,例如 businessTrips、updateBusinessTrip,如果你需要在你的元件中使用這些變數和函數,你需要在你的程式碼中定義它們,或者將它們導入到你的程式碼中。 最後,我注意到你在程式碼中使用了 console.log 來列印出一些變數的值,這是一種很好的除錯方法,但在你的程式碼最終版中,你可能想要移除這些語句,因為它們可能會導致不必要的性能開銷,並且可能會洩露不應該公開的資訊。 ---- User 請問為什麼我按下 ``` <u @click="editPaymentRequest()">{{ '編輯' }}</u> ``` 不會 彈出子範本: ``` <AddPaymentRequestForm v-model="showAddPaymentRequestForm" ref="selectedPaymentRequest" /> ``` ChatGPT 從你給出的代碼來看,你的 ``` <u @click="editPaymentRequest()">{{ '編輯' }}</u> ``` 是使用 Vue 的 @click 監聽點擊事件並執行 editPaymentRequest 方法。這個方法將 showAddPaymentRequestForm 的值設為 true,這應該能夠顯示你的 AddPaymentRequestForm 子組件。 然而,你在 AddPaymentRequestForm 組件上使用了 v-model="showAddPaymentRequestForm",這可能是問題所在。v-model 在 Vue 3 中用於雙向數據綁定,通常用於輸入元素,例如 <input> 或 <select>。對於自定義組件,你需要在組件中明確定義 emits 選項來發送 update:modelValue 事件。 在你的情況下,你可能想要使用 v-if 或者 v-show 來根據 showAddPaymentRequestForm 的值來顯示或隱藏組件: vue Copy code ``` <AddPaymentRequestForm v-if="showAddPaymentRequestForm" ref="selectedPaymentRequest" /> ``` 另外,ref="selectedPaymentRequest" 不會將選定的支付請求綁定到你之前定義的 selectedPaymentRequest 參考中。在 Vue 3 中,ref 屬性被用來在範本中引用 DOM 元素或子組件,你可以通過 setup 函數中的 ref 方法來訪問它們。如果你需要將選定的支付請求存儲在 selectedPaymentRequest 中,你可能需要在 AddPaymentRequestForm 組件中新增一個 prop 或者發出一個自定義事件來實現。 --- User 這是我的子範本, AddPaymentRequestForm : ``` <template> <div class="add-payment-request-form-dialog-container"> <el-form> <el-form-item> <el-button class="add-payment-request-form-btn" type="primary" @click="createPaymentRequestFormBtn">+ 新增請款單 </el-button> </el-form-item> </el-form> <!-- 新增請款單 --> <!-- <el-dialog class="add-business-trip-form-dialog" v-model="addBusinessTripForm.value" :width="`${dialogWidth}px`" @close="closeOnDelete" @open="showLeaveDay"> --> <el-dialog class="add-payment-request-form-dialog" v-model="addPaymentRequestForm" :width="`${dialogWidth}px`" @close="closeOnDelete"> <el-row :gutter="20"> <el-col :sm="24"> <el-form-item class="add-payment-request-form-title" label="新增請款單" label-width="100px"></el-form-item> </el-col> </el-row> </el-form> </el-dialog> </div> </template> ``` 以及子範本的 script: ``` <script lang='ts' setup> import { provide, ref, reactive, nextTick, onMounted, computed, watch, onBeforeMount } from 'vue'; import moment from 'moment'; // import { punchs, punch, departments, leaveTypes, users, addLeaveApplication, exportDepartmentPunchs, signs, newSign, newLeaveApplications } from '@/api'; // import type { Department, Punch, User, SignState, NewLeaveApplication } from "@/types"; // import { useUserStore } from '@/store'; // import { httpCode } from '@/client'; // import { minuteToHumanTime, signType, newSignStateText } from '@/config'; import { Calendar, Plus } from "@element-plus/icons-vue"; import { ElMessage } from "element-plus"; import PaymentRequestTable from '@/components/PaymentRequestTable/index.vue'; const props = defineProps({ paymentRequest: null, }); // 新增請款單彈窗顯示 const addPaymentRequestForm = ref(false); const createPaymentRequestFormBtn = () => { addPaymentRequestForm.value = true; }; // const paymentRequestStyle = ({ // labelStyle: {'background-color: 'white'} // }) // 新增請款單表單 const create_form = reactive({ // user_id: userStore.id, // user_name: userStore.name, user_name: '', // 申請人 selectedDepartment: '', // 申請部門 paymentMatter: '', // 請款事項 paymentInfo: '', // 付款資訊 companyName: '', // 廠商名稱 uniformNumbers: '', // 統一編號 contact: '', // 聯絡人 phone: '', // 聯絡電話 files: [] as Array<any>, // 上傳憑證 proof: '', // 憑證 payee: '', // 收款人 uniOrEmployeeNumbers: '', // 統一編號/員編 selectedPaymentMethod: '', // 付款方式 paymentDate: moment(new Date).format('YYYY-MM-DD'), // 指定付款日期 paymentDateRange: [moment().hour(9).minute(0).second(0).toDate(), moment().hour(18).minute(0).toDate()], // 時間區段 rangeChange: () => { // 時間檢查 if (moment(create_form.paymentDateRange[0]).hour() < 9 || moment(create_form.paymentDateRange[0]).hour() > 18) create_form.paymentDateRange[0] = moment(create_form.paymentDateRange[0]).hour(9).toDate(); if (moment(create_form.paymentDateRange[1]).hour() < 9 || moment(create_form.paymentDateRange[1]).hour() > 18) create_form.paymentDateRange[1] = moment(create_form.paymentDateRange[1]).hour(18).toDate(); // create_form.search(); }, disabledDate: (date: Date) => moment().isAfter(moment(date).add(2, 'years')), // 停用時間 bankDeposit: '', // 開戶銀行 bankAccount: '', // 銀行帳號 paymentAmount: '', // 付款金額 selectedCurrency: '', // 幣別 storePayment: () => { }, createPayment: () => { const date_str = moment(create_form.paymentDate).format('YYYY-MM-DD HH:mm:ss'); // addBusinessTrip(date_str, create_form.place, create_form.reason).then(res => { // console.log(res.data); // form.search(); // }).catch(err => { // }); }, dateChange: () => { console.log('asd', create_form.paymentDate); // 時間檢查 if (moment(create_form.paymentDate).hour() < 9) create_form.paymentDate = moment(create_form.paymentDate).set({ hour: 9 }).toDate().toString(); // 之後傳值要注意一下 else if ((moment(create_form.paymentDate).hour() > 18)) create_form.paymentDate = moment(create_form.paymentDate).set({ hour: 18 }).toDate().toString(); }, }); // 上傳圖片設定 const changePaymentProofImg = (e: Event) => { if (e.target) { const t = e.target as HTMLInputElement; if (t.files && t.files.length) { // 檔案限制在 5 個以內 // console.log(t.files); if (t.files && t.files.length > 5) { ElMessage.error('最多隻能上傳5個檔案'); return; } // 移除舊的檔案預覽 create_form.files = []; // userFormData.files = []; // 創建新的檔案預覽 for (let i = 0; i < t.files.length; i++) { // userFormData.files![i] = window.URL.createObjectURL(t.files![i]); create_form.files[i] = t.files[i].name; } } // avatar.value = t.files; } }; // 將檔案名稱取前五個字母, 加上檔名 const ctrlFileName = (file: any) => { return file.slice(0, 5) + file.substring(file.lastIndexOf('.')); }; // 上傳憑證, 刪除按鈕控制 const removeFile = (index: any) => { create_form.files.splice(index, 1); const fileProofInput = document.querySelector("input[type='file']") as HTMLInputElement; if (fileProofInput) { fileProofInput.value = ''; } }; // 離開請款單時清空表格 const closeOnDelete = () => { // userFormData.leave_type.id = 5; // 代理人顯示為 無 // create_form.place = ''; // 右上 XX 後地點清空 // create_form.reason = ''; // 右上 XX 後理由清空 // 清空上傳圖片的預覽,檢查 userFormData.files 是否為 null 或 undefined // if (userFormData.files) userFormData.files = []; // 清空檔案上傳 // avatar.value = null; // 清空檔案輸入元素的值 // if (files.value) files.value.value = ""; // 重設驗證訊息 // addForm.value?.resetFields(); }; // 返回, 關閉對話視窗 const cancelDialog = () => { addPaymentRequestForm.value = false; // create_form.place = ''; // 右上 XX 後地點清空 // create_form.reason = ''; // 右上 XX 後理由清空 }; const dialogWidth = ref(860); // 新增請款單整體高度 // const dialogHeigh = ref<number>(750); // 新增請款單整體寬度 </script> ``` --- 請問我該怎麼做? ChatGPT 這個子組件 AddPaymentRequestForm 包含一個名為 addPaymentRequestForm 的 ref,該 ref 控制著 el-dialog 元件的顯示和隱藏。 要在點擊“編輯”時顯示這個子組件,你需要在父組件中調用 createPaymentRequestFormBtn 函數,這個函數會設置 addPaymentRequestForm 為 true,從而顯示對話框。你可以透過使用 ref 方法來訪問子組件的方法。這是一個例子: 在你的父組件中: vue Copy code ``` <template> <div> <u @click="editPaymentRequest()">{{ '編輯' }}</u> <AddPaymentRequestForm ref="addPaymentRequestFormRef" /> </div> </template> <script> import { ref } from 'vue'; import AddPaymentRequestForm from './AddPaymentRequestForm.vue'; export default { components: { AddPaymentRequestForm }, setup() { const addPaymentRequestFormRef = ref(null); const editPaymentRequest = () => { addPaymentRequestFormRef.value.createPaymentRequestFormBtn(); }; return { addPaymentRequestFormRef, editPaymentRequest } } } </script> 在你的 AddPaymentRequestForm 子組件中: vue Copy code <script lang='ts' setup> // ... const createPaymentRequestFormBtn = () => { addPaymentRequestForm.value = true; }; // ... </script> <script> // ... export default { methods: { createPaymentRequestFormBtn } } </script> ``` 在上述的例子中,我們首先在父組件中創建了一個 ref 來參照子組件。然後,我們在 editPaymentRequest 函數中調用了子組件的 createPaymentRequestFormBtn 方法,這個方法會顯示 el-dialog。在子組件中,我們需要將 createPaymentRequestFormBtn 方法導出,使得父組件能夠訪問它。 需要注意的是,ref 在組件創建時可能是 null,你需要確保在調用 createPaymentRequestFormBtn 之前,子組件已經被創建並且已經被分配給 ref。你也需要確保子組件在被卸載之前不會調用這個方法。在實際應用中,你可能需要更複雜的邏輯來處理這些邊界情況。 ---- User 我一開始是想去按下父組件中的: ``` <AddPaymentRequestForm v-model="showAddPaymentRequestForm" ref="selectedPaymentRequest" /> ``` 去跳出新增請款單, 也就是 AddPaymentRequestForm 這個子組件, 而我想在父組件按下: <u @click="editPaymentRequest()">{{ '編輯' }}</u> 的時候來進行編輯 AddPaymentRequestForm 這個子組件 請問我該怎麼做? ChatGPT 瞭解您的需求,那麼在父組件中,我們需要讓 editPaymentRequest() 方法去改變 showAddPaymentRequestForm 的值,這樣一來, v-model 就會觸發對話框的顯示或隱藏。 首先,讓我們在子組件中設置一個 v-model,讓我們可以控制對話框的顯示和隱藏: vue Copy code ``` <template> <!-- ... --> <el-dialog class="add-payment-request-form-dialog" v-model="dialogVisible" :width="`${dialogWidth}px`" @close="closeOnDelete"> <!-- ... --> </template> <script lang='ts' setup> // ... const dialogVisible = ref(false); const props = withDefaults(defineProps<{ modelValue: boolean; }>(), { modelValue: false }); watchEffect(() => { dialogVisible.value = props.modelValue; }); const emit = defineEmits(['update:modelValue']); watch(dialogVisible, (newVal) => { emit('update:modelValue', newVal); }); // ... </script> ``` 這裡,我們在子組件的 props 中增加了一個 modelValue 屬性,並且設定了一個 watchEffect 來同步父組件的值到 dialogVisible。當 dialogVisible 改變時,我們使用 emit 來通知父組件。 然後,在父組件中,我們可以在 editPaymentRequest() 方法中改變 showAddPaymentRequestForm 的值: vue Copy code ``` <template> <div> <u @click="editPaymentRequest()">{{ '編輯' }}</u> <AddPaymentRequestForm v-model="showAddPaymentRequestForm" /> </div> </template> <script> import { ref } from 'vue'; import AddPaymentRequestForm from './AddPaymentRequestForm.vue'; export default { components: { AddPaymentRequestForm }, setup() { const showAddPaymentRequestForm = ref(false); const editPaymentRequest = () => { showAddPaymentRequestForm.value = true; }; return { showAddPaymentRequestForm, editPaymentRequest } } } </script> ``` 現在,當你在父組件中點擊 "編輯" 時, showAddPaymentRequestForm 的值會變成 true,然後通過 v-model 將這個值傳給子組件,並且會顯示對話框。當對話框被關閉時, dialogVisible 的值會變成 false,並且通過 emit 將這個值傳給父組件,然後更新 showAddPaymentRequestForm 的值。 User 請問為什麼要在 子組件的 props 中增加了一個 modelValue 屬性 ChatGPT 在 Vue 中,v-model 是一個語法糖,它實際上是將 modelValue 作為 prop 傳入元件,並監聽 update:modelValue 事件。這是 Vue 3 的新特性,使得你可以在任意元件上使用 v-model,而不僅僅是表單控制項。 對於一個元件,當你使用 v-model 時,你其實做了兩件事: 你將父元件中的某個值作為 prop(在 Vue 3 中是 modelValue)傳入子元件。 你告訴子元件,當這個值需要改變時,子元件需要觸發一個 update:modelValue 事件。 所以,在子元件中,我們需要定義一個接收 modelValue 的 prop,並在適當的時候觸發 update:modelValue 事件。這樣,子元件就能讀取到父元件傳入的值,並能通知父元件更新這個值。 在你的例子中,我們希望能夠控制對話方塊的顯示和隱藏。所以,我們需要在子元件中定義一個 modelValue prop,並在對話方塊的顯示狀態改變時,觸發 update:modelValue 事件。這樣,父元件就能夠使用 v-model 來控制對話方塊的顯示和隱藏了。 --- User 那為什麼要使用 watchEffect(()?? ChatGPT 在 Vue 中,watchEffect 是一種更加聲明式的偵聽響應式依賴的方法。它會立即運行傳入的函數,並響應性地重新運行該函數每次其依賴項更改時。 在你的例子中,你希望當 modelValue(對話方塊的顯示狀態)改變時,更新子元件的狀態 addPaymentRequestForm。你可以使用 watchEffect 來偵聽 modelValue,並在 modelValue 改變時更新 addPaymentRequestForm。 watchEffect 的好處是,你不需要顯式地列出你要偵聽的所有依賴項。Vue 會自動識別你在 watchEffect 函數中訪問的所有響應式依賴項,並在這些依賴項改變時重新運行 watchEffect 函數。這使得你的程式碼更加簡潔,更加聲明式。 總的來說,watchEffect 是一個非常有用的工具,可以幫助你更好地管理你的響應式狀態和副作用。 ---- User 那為什麼要使用 const emit = defineEmits(['update:modelValue']); ChatGPT defineEmits 函數用於定義一個元件可以觸發的事件類型。在你的情況下,你想要觸發一個名為 update:modelValue 的事件。這是 Vue 的一個特殊事件,用於雙向繫結。當你觸發這個事件時,你可以通知父元件更新其繫結到 v-model 的值。 在 Vue 3 中,元件需要顯式聲明它們可以觸發的事件。你可以使用 defineEmits 函數在元件的 setup 函數中做到這一點。 當你的元件想要關閉對話方塊時(即將 addPaymentRequestForm 設定為 false),你可以使用 emit 函數觸發 update:modelValue 事件,並將新的值(false)作為參數傳遞。這將通知父元件更新其 showAddPaymentRequestForm 的值,因為它是繫結到 v-model 的。 這就是為什麼你需要使用 const emit = defineEmits(['update:modelValue']); 的原因。這樣你的元件就可以觸發 update:modelValue 事件,並通過 v-model 雙向繫結來更新父元件的狀態。 --- User 最後, 想請教為什麼要用 watch(addPaymentRequestForm, (newVal) => { emit('update:modelValue', newVal); }); ChatGPT watch 是 Vue 3 的響應式 API 的一部分,用於觀察和響應 Vue 實例上的資料變化。 在這裡,我們正在觀察 addPaymentRequestForm 這個響應式引用的值。當 addPaymentRequestForm 的值改變時,回呼函數將被觸發。 在你的回呼函數中,你觸發了一個 update:modelValue 事件,並將新的值作為參數傳遞。update:modelValue 事件是 Vue 用於實現 v-model 雙向繫結的特殊事件。通過觸發這個事件,你的元件可以通知父元件更新其 v-model 繫結的值。這就是為什麼你需要使用 watch 的原因。 這樣,當 addPaymentRequestForm 的值在元件內部改變時(例如,當對話方塊被關閉或打開時),父元件的 showAddPaymentRequestForm 也會相應地更新,保持兩者的同步。 綜上,你使用 watch 來確保當子元件的狀態變化時,能夠及時通知並更新父元件的狀態。 --- 其他學習途徑: https://juejin.cn/post/7213171664711073851 https://juejin.cn/post/7092412488725037087