| | |
| | | ### 2.1 DFM 轉換需求: .dfm Delphi 轉換為 vue 實作 |
| | | @/CB_IMGPSScanImp.dfm 為 Delphi 的 ui 介面定義, 請幫我轉換為相對應的 /uiOutput/ErrList/CB_IMGPSScanImp.vue |
| | | @/CB_IMGPSScanImp.pas 為 Delphi 的 ui 介面定義實作, 也請幫我轉換為相應的 /uiOutput/ErrList/CB_IMGPSScanImp.ts |
| | | @/doc/curtis/screenShot/CB_IMGPSScanImp.png 則是 @/CB_IMGPSScanImp.dfm 介面的截圖, 轉換後的 /uiOutput/ErrList/CB_IMGPSScanImp.vue 必須與 @/doc/curtis/screenShot/CB_IMGPSScanImp.png 一致 |
| | | @/CB_IMGPSScanImp.dfm 為 Delphi 的 ui 介面定義, 請幫我轉換為相對應的 /uiOutput/CB_IMGPSScanImp/CB_IMGPSScanImp.vue |
| | | @/CB_IMGPSScanImp.pas 為 Delphi 的 ui 介面定義實作, 也請幫我轉換為相應的 /uiOutput/CB_IMGPSScanImp/CB_IMGPSScanImp.ts |
| | | @/doc/curtis/screenShot/CB_IMGPSScanImp.png 則是 @/CB_IMGPSScanImp.dfm 介面的截圖, 轉換後的 /uiOutput/CB_IMGPSScanImp/CB_IMGPSScanImp.vue 必須與 @/doc/curtis/screenShot/CB_IMGPSScanImp.png 一致 |
| | | |
| | | #### 2.2 轉換原則 |
| | | ##### 2.2.1 CB_IMGPSScanImp.vue 轉換原則 |
| | |
| | | - 2.2.1 於實作 CB_IMGPSScanImp.vue 時能引用 CB_IMGPSScanImp.ts 用相關方法 |
| | | |
| | | #### 2.3 生成後輸出 |
| | | - 生成後的檔案請輸出至對應的 /uiOutput/ErrList/CB_IMGPSScanImp.vue 和 /uiOutput/ErrList/CB_IMGPSScanImp.ts 路徑 |
| | | - 生成後的檔案請輸出至對應的 /uiOutput/CB_IMGPSScanImp/CB_IMGPSScanImp.vue 和 /uiOutput/CB_IMGPSScanImp/CB_IMGPSScanImp.ts 路徑 |
| | | |
| | | #### 2.4 更新 preview 入口 |
| | | - 2.4.1 preview 入口功能定義: |
| 比對新檔案 |
| | |
| | | ### 2.1 DFM 轉換需求: .dfm Delphi 轉換為 vue 實作 |
| | | @/DocList.dfm 為 Delphi 的 ui 介面定義, 請幫我轉換為相對應的 /uiOutput/DocList/DocList.vue |
| | | @/DocList.pas 為 Delphi 的 ui 介面定義實作, 也請幫我轉換為相應的 /uiOutput/DocList/DocList.ts |
| | | @/doc/curtis/screenShot/DocList.png 則是 @/DocList.dfm 介面的截圖, 轉換後的 /uiOutput/DocList/DocList.vue 必須與 @/doc/curtis/screenShot/DocList.png 一致 |
| | | |
| | | #### 2.2 轉換原則 |
| | | ##### 2.2.1 DocList.vue 轉換原則 |
| | | - ui layout 及風格請完全參照 DocList.png, 因為這是使用者要的設計, 大小定義請完全參照 DocList.dfm |
| | | - Delphi DFM 結構轉換為使用 Tailwind CSS, 請參考其 Left, Top, Width, Height 屬性來推斷相對位置 |
| | | - dfm 檔案內所指向的物件進行轉換時請生成相對應的 vue 元件, 如下 TPanel 在轉換成 vue 時請生成對應的 TPanel元件 |
| | | ```dfm |
| | | object ErrlistForm: TErrlistForm |
| | | Left = 0 |
| | | Top = 0 |
| | | ... |
| | | object Panel2: TPanel |
| | | Left = 0 |
| | | Top = 0 |
| | | Width = 841 |
| | | Height = 636 |
| | | Align = alClient |
| | | Caption = 'Panel2' |
| | | ``` |
| | | - DocList.dfm 參照 DocList.png 的截圖回推 vue 的實作方式,並 以 vuejs 實作 DocList.vue, |
| | | |
| | | ##### 2.2.2 DocList.ts 轉換原則 |
| | | - 將 DocList.pas 的實作轉換為 ts 實作, 並完全保留其介面及大小寫, 將其 transpile 為 DocList.ts |
| | | - 若 DocList.pas 找不到相依方法的實作方式則於 ts 應完全保留該方法及界面,讓使用者另行實作 |
| | | - 2.2.1 於實作 DocList.vue 時能引用 DocList.ts 用相關方法 |
| | | |
| | | #### 2.3 生成後輸出 |
| | | - 生成後的檔案請輸出至對應的 /uiOutput/DocList/DocList.vue 和 /uiOutput/DocList/DocList.ts 路徑 |
| | | |
| | | #### 2.4 更新 preview 入口 |
| | | - 2.4.1 preview 入口功能定義: |
| | | - 2.4.1.1 確保 @/uiOutput/index.html 中的 `availableComponents` 陣列包含此新轉換的元件 |
| | | - 2.4.1.2 此新元件的設定應包含 id, name, vuePath, jsPath (指向 ts 檔案) 和 windowTitle |
| 比對新檔案 |
| | |
| | | ### 2.1 DFM 轉換需求: .dfm Delphi 轉換為 vue 實作 |
| | | @/DocPrt.dfm 為 Delphi 的 ui 介面定義, 請幫我轉換為相對應的 /uiOutput/DocPrt/DocPrt.vue |
| | | @/DocPrt.pas 為 Delphi 的 ui 介面定義實作, 也請幫我轉換為相應的 /uiOutput/DocPrt/DocPrt.ts |
| | | @/doc/curtis/screenShot/DocPrt.png 則是 @/DocPrt.dfm 介面的截圖, 轉換後的 /uiOutput/DocPrt/DocPrt.vue 必須與 @/doc/curtis/screenShot/DocPrt.png 一致 |
| | | |
| | | #### 2.2 轉換原則 |
| | | ##### 2.2.1 DocPrt.vue 轉換原則 |
| | | - ui layout 及風格請完全參照 DocPrt.png, 因為這是使用者要的設計, 大小定義請完全參照 DocPrt.dfm |
| | | - Delphi DFM 結構轉換為使用 Tailwind CSS, 請參考其 Left, Top, Width, Height 屬性來推斷相對位置 |
| | | - dfm 檔案內所指向的物件進行轉換時請生成相對應的 vue 元件, 如下 TPanel 在轉換成 vue 時請生成對應的 TPanel元件 |
| | | ```dfm |
| | | object ErrlistForm: TErrlistForm |
| | | Left = 0 |
| | | Top = 0 |
| | | ... |
| | | object Panel2: TPanel |
| | | Left = 0 |
| | | Top = 0 |
| | | Width = 841 |
| | | Height = 636 |
| | | Align = alClient |
| | | Caption = 'Panel2' |
| | | ``` |
| | | - DocPrt.dfm 參照 DocPrt.png 的截圖回推 vue 的實作方式,並 以 vuejs 實作 DocPrt.vue, |
| | | |
| | | ##### 2.2.2 DocPrt.ts 轉換原則 |
| | | - 將 DocPrt.pas 的實作轉換為 ts 實作, 並完全保留其介面及大小寫, 將其 transpile 為 DocPrt.ts |
| | | - 若 DocPrt.pas 找不到相依方法的實作方式則於 ts 應完全保留該方法及界面,讓使用者另行實作 |
| | | - 2.2.1 於實作 DocPrt.vue 時能引用 DocPrt.ts 用相關方法 |
| | | |
| | | #### 2.3 生成後輸出 |
| | | - 生成後的檔案請輸出至對應的 /uiOutput/DocPrt/DocPrt.vue 和 /uiOutput/DocPrt/DocPrt.ts 路徑 |
| | | |
| | | #### 2.4 更新 preview 入口 |
| | | - 2.4.1 preview 入口功能定義: |
| | | - 2.4.1.1 確保 @/uiOutput/index.html 中的 `availableComponents` 陣列包含此新轉換的元件 |
| | | - 2.4.1.2 此新元件的設定應包含 id, name, vuePath, jsPath (指向 ts 檔案) 和 windowTitle |
| | |
| | | |
| | | ### 2.1 DFM 轉換需求: .dfm Delphi 轉換為 vue 實作 |
| | | @ErrList.dfm 為 Delphi 的 ui 介面定義, 請幫我轉換為相對應的 ErrList.vue |
| | | @ErrList.pas 為 Delphi 的 ui 介面定義實, 也請幫我轉換為相應的 ErrList.ts |
| | | @CB_ErrList.png 則是 @ErrList.dfm 介面的截圖, 轉換後的 ErrList.vue 必須與 @CB_ErrList.png 一致 |
| | | @/ErrList.dfm 為 Delphi 的 ui 介面定義, 請幫我轉換為相對應的 /uiOutput/ErrList/ErrList.vue |
| | | @/ErrList.pas 為 Delphi 的 ui 介面定義實作, 也請幫我轉換為相應的 /uiOutput/ErrList/ErrList.ts |
| | | @/doc/curtis/screenShot/ErrList.png 則是 @/ErrList.dfm 介面的截圖, 轉換後的 /uiOutput/ErrList/ErrList.vue 必須與 @/doc/curtis/screenShot/ErrList.png 一致 |
| | | |
| | | #### 2.2 轉換原則 |
| | | ##### 2.2.1 ErrList.vue 轉換原則 |
| | |
| | | Align = alClient |
| | | Caption = 'Panel2' |
| | | ``` |
| | | - ErrList.dfm 參照 CB_ErrList.png 的截圖回推 vue 的實作方式,並 以 vuejs 實作 ErrList.vue, |
| | | - |
| | | - ErrList.dfm 參照 ErrList.png 的截圖回推 vue 的實作方式,並 以 vuejs 實作 ErrList.vue, |
| | | |
| | | ##### 2.2.2 ErrList.ts 轉換原則 |
| | | - 將 ErrList.pas 的實作轉換為 ts 實作, 並完全保留其介面及大小寫, 將其 transpile 為 ErrList.ts |
| | | - 若 ErrList.pas 找不到相依方法的實作方式則於 ts 應完全保留該方法及界面,讓使用者另行實作 |
| | | - 2.2.1 於實作 ErrList.vue 時能引用 ErrList.ts 用相關方法 |
| | | |
| | | #### 2.3 生成後輸出 |
| | | - 生成後的檔案請輸出至 uiOutput/ErrList |
| | | - 生成後的檔案請輸出至對應的 /uiOutput/ErrList/ErrList.vue 和 /uiOutput/ErrList/ErrList.ts 路徑 |
| | | |
| | | #### 2.4 生成 preview 入口 |
| | | #### 2.4 更新 preview 入口 |
| | | - 2.4.1 preview 入口功能定義: |
| | | - 2.4.1.1 preview 入口上方為 list button 區塊, 該區塊用來放致所有於 2.3 轉換生成的 vue 連結 |
| | | - 2.4.1.2 preview 入口下方為 preview viewer, 當點擊 2.4.1.1 的 vue 連結時, viewer 能載入相應的 vue 元件內容 |
| | | - 2.4.2 prview 入口請輪出至 uiOutput/index.html |
| | | - 2.4.1.1 確保 @/uiOutput/index.html 中的 `availableComponents` 陣列包含此新轉換的元件 |
| | | - 2.4.1.2 此新元件的設定應包含 id, name, vuePath, jsPath (指向 ts 檔案) 和 windowTitle |
| 比對新檔案 |
| | |
| | | ### 2.1 DFM 轉換需求: .dfm Delphi 轉換為 vue 實作 |
| | | @/OldCaseInfo.dfm 為 Delphi 的 ui 介面定義, 請幫我轉換為相對應的 /uiOutput/OldCaseInfo/OldCaseInfo.vue |
| | | @/OldCaseInfo.pas 為 Delphi 的 ui 介面定義實作, 也請幫我轉換為相應的 /uiOutput/OldCaseInfo/OldCaseInfo.ts |
| | | @/doc/curtis/screenShot/OldCaseInfo.png 則是 @/OldCaseInfo.dfm 介面的截圖, 轉換後的 /uiOutput/OldCaseInfo/OldCaseInfo.vue 必須與 @/doc/curtis/screenShot/OldCaseInfo.png 一致 |
| | | |
| | | #### 2.2 轉換原則 |
| | | ##### 2.2.1 OldCaseInfo.vue 轉換原則 |
| | | - ui layout 及風格請完全參照 OldCaseInfo.png, 因為這是使用者要的設計, 大小定義請完全參照 OldCaseInfo.dfm |
| | | - Delphi DFM 結構轉換為使用 Tailwind CSS, 請參考其 Left, Top, Width, Height 屬性來推斷相對位置 |
| | | - dfm 檔案內所指向的物件進行轉換時請生成相對應的 vue 元件, 如下 TPanel 在轉換成 vue 時請生成對應的 TPanel元件 |
| | | ```dfm |
| | | object ErrlistForm: TErrlistForm |
| | | Left = 0 |
| | | Top = 0 |
| | | ... |
| | | object Panel2: TPanel |
| | | Left = 0 |
| | | Top = 0 |
| | | Width = 841 |
| | | Height = 636 |
| | | Align = alClient |
| | | Caption = 'Panel2' |
| | | ``` |
| | | - OldCaseInfo.dfm 參照 OldCaseInfo.png 的截圖回推 vue 的實作方式,並 以 vuejs 實作 OldCaseInfo.vue, |
| | | |
| | | ##### 2.2.2 OldCaseInfo.ts 轉換原則 |
| | | - 將 OldCaseInfo.pas 的實作轉換為 ts 實作, 並完全保留其介面及大小寫, 將其 transpile 為 OldCaseInfo.ts |
| | | - 若 OldCaseInfo.pas 找不到相依方法的實作方式則於 ts 應完全保留該方法及界面,讓使用者另行實作 |
| | | - 2.2.1 於實作 OldCaseInfo.vue 時能引用 OldCaseInfo.ts 用相關方法 |
| | | |
| | | #### 2.3 生成後輸出 |
| | | - 生成後的檔案請輸出至對應的 /uiOutput/OldCaseInfo/OldCaseInfo.vue 和 /uiOutput/OldCaseInfo/OldCaseInfo.ts 路徑 |
| | | |
| | | #### 2.4 更新 preview 入口 |
| | | - 2.4.1 preview 入口功能定義: |
| | | - 2.4.1.1 確保 @/uiOutput/index.html 中的 `availableComponents` 陣列包含此新轉換的元件 |
| | | - 2.4.1.2 此新元件的設定應包含 id, name, vuePath, jsPath (指向 ts 檔案) 和 windowTitle |
| | |
| | | "png": "/doc/curtis/screenShot/CB_IMGPSScanImp.png", |
| | | "vue": "/uiOutput/CB_IMGPSScanImp/CB_IMGPSScanImp.vue", |
| | | "ts": "/uiOutput/CB_IMGPSScanImp/CB_IMGPSScanImp.ts" |
| | | }, |
| | | { |
| | | "dfm": "/DocList.dfm", |
| | | "pas": "/DocList.pas", |
| | | "png": "/doc/curtis/screenShot/DocList.png", |
| | | "vue": "/uiOutput/DocList/DocList.vue", |
| | | "ts": "/uiOutput/DocList/DocList.ts" |
| | | }, |
| | | { |
| | | "dfm": "/ErrList.dfm", |
| | | "pas": "/ErrList.pas", |
| | | "png": "/doc/curtis/screenShot/ErrList.png", |
| | | "vue": "/uiOutput/ErrList/ErrList.vue", |
| | | "ts": "/uiOutput/ErrList/ErrList.ts" |
| | | }, |
| | | { |
| | | "dfm": "/DocPrt.dfm", |
| | | "pas": "/DocPrt.pas", |
| | | "png": "/doc/curtis/screenShot/DocPrt.png", |
| | | "vue": "/uiOutput/DocPrt/DocPrt.vue", |
| | | "ts": "/uiOutput/DocPrt/DocPrt.ts" |
| | | }, |
| | | { |
| | | "dfm": "/OldCaseInfo.dfm", |
| | | "pas": "/OldCaseInfo.pas", |
| | | "png": "/doc/curtis/screenShot/OldCaseInfo.png", |
| | | "vue": "/uiOutput/OldCaseInfo/OldCaseInfo.vue", |
| | | "ts": "/uiOutput/OldCaseInfo/OldCaseInfo.ts" |
| | | } |
| | | ] |
| 比對新檔案 |
| | |
| | | import { ref, computed, onMounted } from 'vue'; |
| | | |
| | | interface DocListItem { |
| | | formId: string; |
| | | docName: string; |
| | | } |
| | | |
| | | export function useDocListLogic() { |
| | | // --- State Mapped from Delphi UI Components --- |
| | | |
| | | const searchText = ref<string>(''); |
| | | const isCustomDoc = ref<boolean>(false); |
| | | const selectedIndex = ref<number>(-1); |
| | | const sortColumn = ref<number>(0); |
| | | const sortOrder = ref<'asc' | 'desc'>('asc'); |
| | | |
| | | // FormIDList: TStringList equivalent |
| | | // Sample data format from Pascal: 'FormID#@#DocName' |
| | | const formIDList = ref<string[]>([]); |
| | | |
| | | // --- Computed --- |
| | | |
| | | const filteredItems = computed(() => { |
| | | const items: DocListItem[] = formIDList.value |
| | | .filter(line => !searchText.value || line.toUpperCase().includes(searchText.value.toUpperCase())) |
| | | .map(line => { |
| | | const parts = line.split('#@#'); |
| | | return { |
| | | formId: parts[0] || '', |
| | | docName: parts[1] || '' |
| | | }; |
| | | }); |
| | | |
| | | // Sort items |
| | | items.sort((a, b) => { |
| | | let valA = sortColumn.value === 0 ? a.formId : a.docName; |
| | | let valB = sortColumn.value === 0 ? b.formId : b.docName; |
| | | |
| | | const comparison = valA.localeCompare(valB); |
| | | return sortOrder.value === 'asc' ? comparison : -comparison; |
| | | }); |
| | | |
| | | return items; |
| | | }); |
| | | |
| | | // --- Methods --- |
| | | |
| | | const FormCreate = () => { |
| | | // PostMessage(Handle,WM_ACTIVATE,WA_CLICKACTIVE,0); |
| | | console.log('DocListForm created'); |
| | | |
| | | // Mock initialization for demo |
| | | formIDList.value = [ |
| | | 'F001#@#測試文件一', |
| | | 'F002#@#測試文件二', |
| | | 'A003#@#合約書', |
| | | 'B004#@#申請書' |
| | | ]; |
| | | }; |
| | | |
| | | const OkBtClick = () => { |
| | | if (selectedIndex.value === -1 && !isCustomDoc.value) { |
| | | // Showmessage(_Msg('請選擇一個種類')); |
| | | alert('請選擇一個種類'); |
| | | return; |
| | | } |
| | | console.log('Form confirmed:', selectedIndex.value !== -1 ? filteredItems.value[selectedIndex.value] : 'Custom'); |
| | | closeForm('ok'); |
| | | }; |
| | | |
| | | const CancelBtClick = () => { |
| | | closeForm('cancel'); |
| | | }; |
| | | |
| | | const Edit1Change = () => { |
| | | // Logic handled by computed filteredItems |
| | | selectedIndex.value = -1; |
| | | }; |
| | | |
| | | const DocLVColumnClick = (index: number) => { |
| | | if (sortColumn.value === index) { |
| | | sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'; |
| | | } else { |
| | | sortColumn.value = index; |
| | | sortOrder.value = 'asc'; |
| | | } |
| | | }; |
| | | |
| | | const DocLVDblClick = (index: number) => { |
| | | selectedIndex.value = index; |
| | | OkBtClick(); |
| | | }; |
| | | |
| | | const selectItem = (index: number) => { |
| | | selectedIndex.value = index; |
| | | }; |
| | | |
| | | const closeForm = (result: string) => { |
| | | console.log(`DocListForm closing with result: ${result}`); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | FormCreate(); |
| | | }); |
| | | |
| | | return { |
| | | // State |
| | | searchText, |
| | | isCustomDoc, |
| | | selectedIndex, |
| | | filteredItems, |
| | | sortColumn, |
| | | sortOrder, |
| | | |
| | | // Actions |
| | | OkBtClick, |
| | | CancelBtClick, |
| | | Edit1Change, |
| | | DocLVColumnClick, |
| | | DocLVDblClick, |
| | | selectItem |
| | | }; |
| | | } |
| 比對新檔案 |
| | |
| | | <template> |
| | | <div class="doc-list-form flex flex-col bg-gray-100 font-sans w-[594px] h-[486px] border border-gray-400 relative overflow-hidden"> |
| | | |
| | | <!-- Main Content Panel (Panel 2) --> |
| | | <div class="flex-1 flex flex-col border-b border-gray-300 overflow-hidden"> |
| | | <!-- Search Panel (Panel 3) --> |
| | | <div class="h-[41px] border-b border-gray-300 flex items-center px-4 space-x-4 bg-gray-50 flex-none"> |
| | | <label class="text-base">索引</label> |
| | | <input type="text" |
| | | class="w-[306px] h-[24px] border border-gray-400 px-1 uppercase" |
| | | v-model="searchText" |
| | | @input="Edit1Change" /> |
| | | |
| | | <label class="flex items-center space-x-1 cursor-pointer"> |
| | | <input type="checkbox" v-model="isCustomDoc" /> |
| | | <span class="text-sm">分類為自訂文件</span> |
| | | </label> |
| | | </div> |
| | | |
| | | <!-- List View (DocLV) --> |
| | | <div class="flex-1 overflow-auto bg-white"> |
| | | <table class="w-full border-collapse"> |
| | | <thead class="sticky top-0 bg-gray-200 shadow-sm"> |
| | | <tr class="text-left text-sm font-medium border-b border-gray-300"> |
| | | <th class="p-2 border-r border-gray-300 w-[140px] cursor-pointer hover:bg-gray-300" @click="DocLVColumnClick(0)"> |
| | | FormID |
| | | <span v-if="sortColumn === 0">{{ sortOrder === 'asc' ? '▲' : '▼' }}</span> |
| | | </th> |
| | | <th class="p-2 cursor-pointer hover:bg-gray-300" @click="DocLVColumnClick(1)"> |
| | | 文件名稱 |
| | | <span v-if="sortColumn === 1">{{ sortOrder === 'asc' ? '▲' : '▼' }}</span> |
| | | </th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | <tr v-for="(item, index) in filteredItems" |
| | | :key="index" |
| | | :class="['text-sm hover:bg-blue-50 cursor-pointer', selectedIndex === index ? 'bg-blue-600 text-white hover:bg-blue-700' : '']" |
| | | @click="selectItem(index)" |
| | | @dblclick="DocLVDblClick(index)"> |
| | | <td class="p-2 border-r border-gray-200">{{ item.formId }}</td> |
| | | <td class="p-2">{{ item.docName }}</td> |
| | | </tr> |
| | | </tbody> |
| | | </table> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- Bottom Action Panel (Panel 1) --> |
| | | <div class="h-[41px] flex items-center justify-center space-x-6 bg-gray-50 flex-none border-t border-gray-300"> |
| | | <button class="w-[75px] h-[25px] border border-gray-400 bg-gray-200 hover:bg-gray-300 flex items-center justify-center shadow-sm" @click="OkBtClick"> |
| | | 確定 |
| | | </button> |
| | | <button class="w-[75px] h-[25px] border border-gray-400 bg-gray-200 hover:bg-gray-300 flex items-center justify-center shadow-sm" @click="CancelBtClick"> |
| | | 取消 |
| | | </button> |
| | | </div> |
| | | |
| | | <!-- Visual Reference Overlay --> |
| | | <div class="doc-list-layout opacity-20 pointer-events-none absolute left-0 top-0"> |
| | | <img src="assets/DocList.png" class="w-[594px] h-[486px]" /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script lang="ts"> |
| | | import { defineComponent } from 'vue'; |
| | | import { useDocListLogic } from './DocList.ts'; |
| | | |
| | | export default defineComponent({ |
| | | name: 'DocListForm', |
| | | setup() { |
| | | const logic = useDocListLogic(); |
| | | return { ...logic }; |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | /* Ensure standard Delphi font appearance where appropriate */ |
| | | .doc-list-form { |
| | | user-select: none; |
| | | } |
| | | .doc-list-layout { |
| | | z-index: 1000; |
| | | } |
| | | </style> |
| 比對新檔案 |
| | |
| | | import { ref, onMounted } from 'vue'; |
| | | |
| | | interface PrintItem { |
| | | id: string; |
| | | text: string; |
| | | checked: boolean; |
| | | } |
| | | |
| | | export function useDocPrtLogic() { |
| | | // --- State Mapped from Delphi UI Components --- |
| | | |
| | | const items = ref<PrintItem[]>([]); |
| | | const selectedIndex = ref<number>(-1); |
| | | |
| | | // --- Methods --- |
| | | |
| | | const FormCreate = () => { |
| | | // PostMessage(Handle,WM_ACTIVATE,WA_CLICKACTIVE,0); |
| | | console.log('PrintForm created'); |
| | | |
| | | // Mock data |
| | | items.value = Array.from({ length: 20 }, (_, i) => ({ |
| | | id: (i + 1).toString(), |
| | | text: `影像檔 ${i + 1}.jpg`, |
| | | checked: false |
| | | })); |
| | | }; |
| | | |
| | | const SelecAllBtClick = () => { |
| | | items.value.forEach(item => item.checked = true); |
| | | }; |
| | | |
| | | const EraseBtClick = () => { |
| | | items.value.forEach(item => item.checked = false); |
| | | }; |
| | | |
| | | const ExitBtClick = () => { |
| | | closeForm('cancel'); |
| | | }; |
| | | |
| | | const PrtBtClick = () => { |
| | | const selected = items.value.filter(i => i.checked); |
| | | if (selected.length === 0) { |
| | | alert('請至少選擇一張影像進行列印'); |
| | | return; |
| | | } |
| | | console.log('Printing items:', selected); |
| | | closeForm('ok'); |
| | | }; |
| | | |
| | | const CheckListBox1Click = (index: number) => { |
| | | selectedIndex.value = index; |
| | | }; |
| | | |
| | | const ListBox1Click = (index: number) => { |
| | | selectedIndex.value = index; |
| | | }; |
| | | |
| | | const closeForm = (result: string) => { |
| | | console.log(`PrintForm closing with result: ${result}`); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | FormCreate(); |
| | | }); |
| | | |
| | | return { |
| | | // State |
| | | items, |
| | | selectedIndex, |
| | | |
| | | // Actions |
| | | SelecAllBtClick, |
| | | EraseBtClick, |
| | | ExitBtClick, |
| | | PrtBtClick, |
| | | CheckListBox1Click, |
| | | ListBox1Click |
| | | }; |
| | | } |
| 比對新檔案 |
| | |
| | | <template> |
| | | <div class="print-form flex flex-col bg-gray-100 font-sans w-[368px] h-[484px] border border-gray-400 relative overflow-hidden"> |
| | | |
| | | <!-- Document Image GroupBox (DocGB) --> |
| | | <fieldset class="flex-1 flex flex-col border border-gray-400 m-1 p-0 overflow-hidden"> |
| | | <legend class="text-sm px-1 ml-2">文件影像</legend> |
| | | |
| | | <div class="flex-1 flex overflow-hidden"> |
| | | <!-- Index List (ListBox1) --> |
| | | <div class="w-[37px] bg-white border-r border-gray-300 overflow-y-auto"> |
| | | <div v-for="(item, index) in items" |
| | | :key="'lb'+index" |
| | | :class="['h-[20px] text-xs flex items-center justify-center cursor-pointer border-b border-gray-100', selectedIndex === index ? 'bg-blue-600 text-white' : '']" |
| | | @click="ListBox1Click(index)"> |
| | | {{ index + 1 }} |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- Check List (CheckListBox1) --> |
| | | <div class="flex-1 bg-white overflow-y-auto"> |
| | | <div v-for="(item, index) in items" |
| | | :key="'clb'+index" |
| | | :class="['h-[20px] text-sm flex items-center px-2 space-x-2 cursor-pointer border-b border-gray-100', selectedIndex === index ? 'bg-blue-600 text-white' : '']" |
| | | @click="CheckListBox1Click(index)"> |
| | | <input type="checkbox" v-model="item.checked" class="cursor-pointer" @click.stop /> |
| | | <span class="truncate">{{ item.text }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </fieldset> |
| | | |
| | | <!-- Bottom Action Panel (Panel 1) --> |
| | | <div class="h-[51px] flex items-center px-2 space-x-2 bg-gray-50 flex-none border-t border-gray-300"> |
| | | <button class="w-[50px] h-[35px] border border-gray-400 bg-gray-200 hover:bg-gray-300 flex flex-col items-center justify-center text-[10px]" @click="SelecAllBtClick" title="全選"> |
| | | <span class="text-lg">✓</span> |
| | | 全選 |
| | | </button> |
| | | <button class="w-[50px] h-[35px] border border-gray-400 bg-gray-200 hover:bg-gray-300 flex flex-col items-center justify-center text-[10px]" @click="EraseBtClick" title="清除"> |
| | | <span class="text-lg">✗</span> |
| | | 清除 |
| | | </button> |
| | | <button class="w-[50px] h-[35px] border border-gray-400 bg-gray-200 hover:bg-gray-300 flex flex-col items-center justify-center text-[10px]" @click="PrtBtClick" title="列印"> |
| | | <span class="text-lg">🖨️</span> |
| | | 列印 |
| | | </button> |
| | | <div class="flex-1"></div> |
| | | <button class="w-[50px] h-[35px] border border-gray-400 bg-gray-200 hover:bg-gray-300 flex flex-col items-center justify-center text-[10px]" @click="ExitBtClick" title="離開"> |
| | | <span class="text-lg">🚪</span> |
| | | 離開 |
| | | </button> |
| | | </div> |
| | | |
| | | <!-- Visual Reference Overlay --> |
| | | <div class="print-layout opacity-20 pointer-events-none absolute left-0 top-0"> |
| | | <img src="assets/DocPrt.png" class="w-[368px] h-[484px]" /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script lang="ts"> |
| | | import { defineComponent } from 'vue'; |
| | | import { useDocPrtLogic } from './DocPrt.ts'; |
| | | |
| | | export default defineComponent({ |
| | | name: 'PrintForm', |
| | | setup() { |
| | | const logic = useDocPrtLogic(); |
| | | return { ...logic }; |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .print-form { |
| | | user-select: none; |
| | | } |
| | | .print-layout { |
| | | z-index: 1000; |
| | | } |
| | | </style> |
| | |
| | | import { ref, onMounted, onUnmounted } from 'vue'; |
| | | import { ref, onMounted, computed } from 'vue'; |
| | | |
| | | // Interfaces mapping from Delphi structures |
| | | interface TPoint { |
| | | X: number; |
| | | Y: number; |
| | | } |
| | | |
| | | interface TErrListItem { |
| | | content: string; |
| | | interface ErrItem { |
| | | reason: string; |
| | | index: string; |
| | | } |
| | | |
| | | export function useErrListLogic() { |
| | | // State from Delphi variables |
| | | const errListItems = ref<TErrListItem[]>([ |
| | | // Mock data for display |
| | | { content: '測試錯誤原因1', index: 'Item_1' }, |
| | | { content: '測試錯誤原因2', index: 'Item_2' } |
| | | ]); |
| | | const selectedItemIndex = ref<number>(-1); |
| | | // --- State Mapped from Delphi UI Components --- |
| | | |
| | | const errorItems = ref<ErrItem[]>([]); |
| | | const selectedIndex = ref<number>(-1); |
| | | |
| | | const primaryImage = ref<string>(''); |
| | | const relatedImage = ref<string>(''); |
| | | |
| | | const siteIdx = ref<number>(1); |
| | | const siteCount = ref<number>(0); |
| | | const relaSiteIdx = ref<number>(1); |
| | | const relaSiteCount = ref<number>(0); |
| | | |
| | | const isDeleteEnabled = ref<boolean>(false); |
| | | |
| | | const siteIndex = ref<number>(0); |
| | | const siteTotal = ref<number>(0); |
| | | const isSitePreEnabled = ref<boolean>(false); |
| | | const isSiteNextEnabled = ref<boolean>(false); |
| | | const SiteList = ref<string[]>([]); // TStringlist |
| | | |
| | | const relaSiteIndex = ref<number>(0); |
| | | const relaSiteTotal = ref<number>(0); |
| | | const isRelaPreEnabled = ref<boolean>(false); |
| | | const isRelaNextEnabled = ref<boolean>(false); |
| | | const RelaSiteList = ref<string[]>([]); // TStringlist |
| | | |
| | | const showPanel6 = ref<boolean>(true); // Splitter visibility for related images |
| | | const showNoteBtn = ref<boolean>(false); |
| | | const showRejectBtn = ref<boolean>(false); |
| | | |
| | | const Ingnore = ref<boolean>(false); |
| | | const NowIndex = ref<string>(''); |
| | | |
| | | const iniPath = ref<string>(''); // Should be injected or configured |
| | | const MyHotkeyid1 = ref<number>(0); |
| | | |
| | | // Point structures |
| | | const UpLPoint = ref<TPoint>({ X: 0, Y: 0 }); |
| | | const UpRPoint = ref<TPoint>({ X: 0, Y: 0 }); |
| | | const DownLPoint = ref<TPoint>({ X: 0, Y: 0 }); |
| | | const DownRPoint = ref<TPoint>({ X: 0, Y: 0 }); |
| | | const isImmediateEnabled = ref<boolean>(false); |
| | | |
| | | const UpLPoint_Rela = ref<TPoint>({ X: 0, Y: 0 }); |
| | | const UpRPoint_Rela = ref<TPoint>({ X: 0, Y: 0 }); |
| | | const DownLPoint_Rela = ref<TPoint>({ X: 0, Y: 0 }); |
| | | const DownRPoint_Rela = ref<TPoint>({ X: 0, Y: 0 }); |
| | | // --- Methods --- |
| | | |
| | | // Hotkey simulation mapped from WMHotKey |
| | | const WMHotKey = (e: KeyboardEvent) => { |
| | | if (e.ctrlKey && e.key.toLowerCase() === 'd' && isDeleteEnabled.value) { |
| | | e.preventDefault(); |
| | | DeleteBtClick(); |
| | | const FormCreate = () => { |
| | | console.log('ErrlistForm created'); |
| | | // Mock initialization |
| | | errorItems.value = [ |
| | | { reason: 'OMR 檢查失敗: 區域 1 未填寫', index: 'ERR001' }, |
| | | { reason: 'OMR 檢查失敗: 區域 5 重複填寫', index: 'ERR005' }, |
| | | { reason: '條碼讀取失敗', index: 'BAR002' } |
| | | ]; |
| | | |
| | | if (errorItems.value.length > 0) { |
| | | selectedIndex.value = 0; |
| | | loadErrorDetails(errorItems.value[0].index); |
| | | } |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | window.addEventListener('keydown', WMHotKey); |
| | | InitialData(); |
| | | }); |
| | | |
| | | onUnmounted(() => { |
| | | window.removeEventListener('keydown', WMHotKey); |
| | | }); |
| | | |
| | | const selectItem = (index: number) => { |
| | | selectedItemIndex.value = index; |
| | | ErrListLVClick(); |
| | | const loadErrorDetails = (id: string) => { |
| | | console.log('Loading error details for:', id); |
| | | // Mock loading from INI |
| | | primaryImage.value = 'assets/CB_IMGPSScanImp.png'; // Mock URL |
| | | relatedImage.value = 'assets/DocList.png'; // Mock URL |
| | | |
| | | siteCount.value = 3; |
| | | relaSiteCount.value = 2; |
| | | siteIdx.value = 1; |
| | | relaSiteIdx.value = 1; |
| | | |
| | | isDeleteEnabled.value = true; |
| | | isImmediateEnabled.value = errorItems.value.length === 0; |
| | | }; |
| | | |
| | | // Delphi Methods |
| | | const InitialData = () => { |
| | | isSitePreEnabled.value = false; |
| | | isSiteNextEnabled.value = false; |
| | | isRelaPreEnabled.value = false; |
| | | isRelaNextEnabled.value = false; |
| | | |
| | | SiteList.value = []; |
| | | RelaSiteList.value = []; |
| | | |
| | | siteIndex.value = 1; |
| | | siteTotal.value = 0; |
| | | relaSiteIndex.value = 1; |
| | | relaSiteTotal.value = 0; |
| | | |
| | | isDeleteEnabled.value = false; |
| | | }; |
| | | |
| | | const GetOMRErrini = (Index: string) => { |
| | | console.log(`Getting OMR Err INI for index: ${Index}`); |
| | | |
| | | // Mock data population based on index |
| | | SiteList.value = ['Site1', 'Site2']; |
| | | RelaSiteList.value = ['Rela1']; |
| | | Ingnore.value = true; |
| | | |
| | | siteTotal.value = SiteList.value.length; |
| | | relaSiteTotal.value = RelaSiteList.value.length; |
| | | |
| | | if (RelaSiteList.value.length > 0) { |
| | | showPanel6.value = true; |
| | | } else { |
| | | showPanel6.value = false; |
| | | } |
| | | |
| | | if (SiteList.value.length > 1) isSiteNextEnabled.value = true; |
| | | if (RelaSiteList.value.length > 1) isRelaNextEnabled.value = true; |
| | | |
| | | isDeleteEnabled.value = Ingnore.value; |
| | | |
| | | // Mock showing first errors |
| | | if (SiteList.value.length > 0) ShowOMRErr(siteIndex.value); |
| | | if (RelaSiteList.value.length > 0) ShowRelaOMRErr(relaSiteIndex.value); |
| | | }; |
| | | |
| | | const ErrListLVClick = () => { |
| | | InitialData(); |
| | | if (selectedItemIndex.value === -1 || !errListItems.value[selectedItemIndex.value]) return; |
| | | |
| | | NowIndex.value = errListItems.value[selectedItemIndex.value].index; |
| | | GetOMRErrini(NowIndex.value); |
| | | }; |
| | | |
| | | const ErrListLVKeyUp = (e: KeyboardEvent) => { |
| | | if (e.key === 'ArrowUp' || e.key === 'ArrowDown') { |
| | | ErrListLVClick(); |
| | | } |
| | | }; |
| | | |
| | | const ErrListLVMouseDown = (e: MouseEvent) => { |
| | | // Right click |
| | | if (e.button === 2) { |
| | | ErrListLVClick(); |
| | | } |
| | | const ErrListLVClick = (index: number) => { |
| | | selectedIndex.value = index; |
| | | loadErrorDetails(errorItems.value[index].index); |
| | | }; |
| | | |
| | | const DeleteBtClick = () => { |
| | | InitialData(); |
| | | if (Ingnore.value) { |
| | | console.log(`Writing Del=True to INI for ${NowIndex.value}`); |
| | | |
| | | const idx = selectedItemIndex.value; |
| | | if (idx !== -1) { |
| | | errListItems.value.splice(idx, 1); |
| | | |
| | | if (errListItems.value.length === 0) { |
| | | console.log('All errors deleted, enabling Immediate button and writing OMRCheckOk.dat'); |
| | | } else { |
| | | selectedItemIndex.value = Math.min(idx, errListItems.value.length - 1); |
| | | ErrListLVClick(); |
| | | } |
| | | } |
| | | if (selectedIndex.value === -1) return; |
| | | |
| | | errorItems.value.splice(selectedIndex.value, 1); |
| | | if (errorItems.value.length === 0) { |
| | | isImmediateEnabled.value = true; |
| | | selectedIndex.value = -1; |
| | | } else { |
| | | alert('此項目不可刪除或已無可刪除項目'); |
| | | if (selectedIndex.value >= errorItems.value.length) { |
| | | selectedIndex.value = errorItems.value.length - 1; |
| | | } |
| | | loadErrorDetails(errorItems.value[selectedIndex.value].index); |
| | | } |
| | | }; |
| | | |
| | | const EnforceBtClick = () => { |
| | | InitialData(); |
| | | let deletedAny = false; |
| | | |
| | | for (let i = errListItems.value.length - 1; i >= 0; i--) { |
| | | const itemIgnorable = true; |
| | | |
| | | if (itemIgnorable) { |
| | | console.log(`Writing Del=True for ${errListItems.value[i].index}`); |
| | | errListItems.value.splice(i, 1); |
| | | deletedAny = true; |
| | | } |
| | | } |
| | | |
| | | if (errListItems.value.length === 0 && deletedAny) { |
| | | console.log('All errors enforced, enabling Immediate button and writing OMRCheckOk.dat'); |
| | | if (confirm('是否確定強制送件?')) { |
| | | errorItems.value = []; |
| | | isImmediateEnabled.value = true; |
| | | selectedIndex.value = -1; |
| | | } |
| | | }; |
| | | |
| | | const ImmediateBtClick = () => { |
| | | console.log('ImmediateBtClick: ' + logTimeString() + ' 立即傳送'); |
| | | console.log('Immediate send triggered'); |
| | | closeForm('ok'); |
| | | }; |
| | | |
| | |
| | | closeForm('cancel'); |
| | | }; |
| | | |
| | | const NoteBtClick = () => { |
| | | console.log('Ch_WriteNote = True'); |
| | | closeForm('note'); |
| | | }; |
| | | |
| | | const RejectBtClick = () => { |
| | | if (confirm('是否確定退件??')) { |
| | | console.log('RejectCase = True'); |
| | | closeForm('reject'); |
| | | } |
| | | }; |
| | | |
| | | const Button1Click = () => { |
| | | alert(`UpLPoint=${UpLPoint.value.X},${UpLPoint.value.Y}\n` + |
| | | `UpRPoint=${UpRPoint.value.X},${UpRPoint.value.Y}\n` + |
| | | `DownLPoint=${DownLPoint.value.X},${DownLPoint.value.Y}\n` + |
| | | `DownRPoint=${DownRPoint.value.X},${DownRPoint.value.Y}`); |
| | | const SitePreBtClick = () => { |
| | | if (siteIdx.value > 1) siteIdx.value--; |
| | | }; |
| | | |
| | | const SiteNextBtClick = () => { |
| | | siteIndex.value++; |
| | | ShowOMRErr(siteIndex.value); |
| | | |
| | | isSitePreEnabled.value = false; |
| | | isSiteNextEnabled.value = false; |
| | | if (siteIndex.value > 1) isSitePreEnabled.value = true; |
| | | if (siteIndex.value < siteTotal.value) isSiteNextEnabled.value = true; |
| | | }; |
| | | |
| | | const SitePreBtClick = () => { |
| | | siteIndex.value--; |
| | | ShowOMRErr(siteIndex.value); |
| | | |
| | | isSitePreEnabled.value = false; |
| | | isSiteNextEnabled.value = false; |
| | | if (siteIndex.value > 1) isSitePreEnabled.value = true; |
| | | if (siteIndex.value < siteTotal.value) isSiteNextEnabled.value = true; |
| | | }; |
| | | |
| | | const RelaNextBtClick = () => { |
| | | relaSiteIndex.value++; |
| | | ShowRelaOMRErr(relaSiteIndex.value); |
| | | |
| | | isRelaPreEnabled.value = false; |
| | | isRelaNextEnabled.value = false; |
| | | if (relaSiteIndex.value > 1) isRelaPreEnabled.value = true; |
| | | if (relaSiteIndex.value < relaSiteTotal.value) isRelaNextEnabled.value = true; |
| | | if (siteIdx.value < siteCount.value) siteIdx.value++; |
| | | }; |
| | | |
| | | const RelaPreBtClick = () => { |
| | | relaSiteIndex.value--; |
| | | ShowRelaOMRErr(relaSiteIndex.value); |
| | | |
| | | isRelaPreEnabled.value = false; |
| | | isRelaNextEnabled.value = false; |
| | | if (relaSiteIndex.value > 1) isRelaPreEnabled.value = true; |
| | | if (relaSiteIndex.value < relaSiteTotal.value) isRelaNextEnabled.value = true; |
| | | if (relaSiteIdx.value > 1) relaSiteIdx.value--; |
| | | }; |
| | | |
| | | const ShowOMRErr = (Idx: number) => { |
| | | if (Idx > SiteList.value.length) return; |
| | | console.log(`Showing OMR Error shape for index ${Idx}`); |
| | | }; |
| | | |
| | | const ShowRelaOMRErr = (RelaIdx: number) => { |
| | | if (RelaIdx > RelaSiteList.value.length) return; |
| | | console.log(`Showing Related OMR Error shape for index ${RelaIdx}`); |
| | | }; |
| | | |
| | | // Missing or external methods mapped |
| | | const logTimeString = (): string => { |
| | | const now = new Date(); |
| | | const pad = (n: number) => (n < 10 ? '0' + n : n); |
| | | return `${now.getFullYear()}${pad(now.getMonth()+1)}${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())} `; |
| | | const RelaNextBtClick = () => { |
| | | if (relaSiteIdx.value < relaSiteCount.value) relaSiteIdx.value++; |
| | | }; |
| | | |
| | | const closeForm = (result: string) => { |
| | | console.log(`Form closing with result: ${result}`); |
| | | console.log(`ErrlistForm closing with result: ${result}`); |
| | | }; |
| | | |
| | | const startResize = (e: MouseEvent) => { |
| | | console.log('Splitter moving started'); |
| | | }; |
| | | onMounted(() => { |
| | | FormCreate(); |
| | | }); |
| | | |
| | | return { |
| | | errListItems, |
| | | selectedItemIndex, |
| | | // State |
| | | errorItems, |
| | | selectedIndex, |
| | | primaryImage, |
| | | relatedImage, |
| | | siteIdx, |
| | | siteCount, |
| | | relaSiteIdx, |
| | | relaSiteCount, |
| | | isDeleteEnabled, |
| | | isImmediateEnabled, |
| | | |
| | | siteIndex, |
| | | siteTotal, |
| | | isSitePreEnabled, |
| | | isSiteNextEnabled, |
| | | |
| | | relaSiteIndex, |
| | | relaSiteTotal, |
| | | isRelaPreEnabled, |
| | | isRelaNextEnabled, |
| | | |
| | | showPanel6, |
| | | showNoteBtn, |
| | | showRejectBtn, |
| | | |
| | | selectItem, |
| | | // Actions |
| | | ErrListLVClick, |
| | | ErrListLVKeyUp, |
| | | ErrListLVMouseDown, |
| | | DeleteBtClick, |
| | | EnforceBtClick, |
| | | ImmediateBtClick, |
| | |
| | | SitePreBtClick, |
| | | SiteNextBtClick, |
| | | RelaPreBtClick, |
| | | RelaNextBtClick, |
| | | NoteBtClick, |
| | | RejectBtClick, |
| | | Button1Click, |
| | | startResize |
| | | RelaNextBtClick |
| | | }; |
| | | } |
| | |
| | | <template> |
| | | <div class="err-list-form flex flex-col bg-gray-100 font-sans" style="width: 841px; height: 636px;"> |
| | | <!-- Top Panel: Error Reason & Details --> |
| | | <div class="panel-3 border border-gray-300 flex-none" style="height: 212px;"> |
| | | <fieldset class="err-reason-gb m-2 border border-gray-400 p-2 h-[calc(100%-16px)]"> |
| | | <legend class="text-black text-base px-1">錯誤原因</legend> |
| | | <div class="relative h-full"> |
| | | <!-- ListView for Error Items --> |
| | | <div class="err-list-lv bg-white border border-gray-300 w-full h-[calc(100%-20px)] overflow-auto cursor-pointer" |
| | | @click="errListLVClick" @keyup="errListLVKeyUp" @mousedown="errListLVMouseDown"> |
| | | <table class="w-full text-left text-base border-collapse"> |
| | | <thead> |
| | | <tr class="bg-gray-200"> |
| | | <th class="p-1 border border-gray-300 font-normal" style="width: 550px;">內容</th> |
| | | <th class="p-1 border border-gray-300 font-normal" style="width: 80px;">index</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | <tr v-for="(item, index) in errListItems" :key="index" |
| | | @click="selectItem(index)" |
| | | :class="{'bg-blue-600 text-white': selectedItemIndex === index, 'hover:bg-gray-100': selectedItemIndex !== index}"> |
| | | <td class="p-1 border border-gray-300">{{ item.content }}</td> |
| | | <td class="p-1 border border-gray-300">{{ item.index }}</td> |
| | | </tr> |
| | | </tbody> |
| | | </table> |
| | | <div class="err-list-form flex flex-col bg-gray-100 font-sans w-[841px] h-[636px] border border-gray-400 relative overflow-hidden text-sm"> |
| | | |
| | | <!-- Top Error Reason Panel (Panel 3) --> |
| | | <div class="h-[212px] flex-none border-b border-gray-300 p-1"> |
| | | <fieldset class="w-full h-full border border-gray-400 p-0 flex flex-col"> |
| | | <legend class="px-1 ml-2 text-base text-black">錯誤原因</legend> |
| | | |
| | | <div class="flex-1 overflow-auto bg-white m-1 border border-gray-300"> |
| | | <table class="w-full border-collapse"> |
| | | <thead class="sticky top-0 bg-gray-100"> |
| | | <tr class="text-left border-b border-gray-300"> |
| | | <th class="p-1 border-r border-gray-200">內容</th> |
| | | <th class="p-1 w-[80px]">index</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | <tr v-for="(item, index) in errorItems" |
| | | :key="index" |
| | | :class="['hover:bg-blue-50 cursor-pointer', selectedIndex === index ? 'bg-blue-600 text-white hover:bg-blue-700' : '']" |
| | | @click="ErrListLVClick(index)"> |
| | | <td class="p-1 border-r border-gray-200">{{ item.reason }}</td> |
| | | <td class="p-1">{{ item.index }}</td> |
| | | </tr> |
| | | </tbody> |
| | | </table> |
| | | <div v-if="errorItems.length === 0" class="flex items-center justify-center h-full text-red-600 font-bold text-lg"> |
| | | 檢核失敗原因已全部清除 |
| | | </div> |
| | | |
| | | <!-- Hidden ImageScrollBox3 --> |
| | | <div v-show="false" class="image-scroll-box-3 absolute top-[94px] left-[720px] w-[100px] h-[100px] border"></div> |
| | | </div> |
| | | </fieldset> |
| | | </div> |
| | | |
| | | <!-- Splitter 3 --> |
| | | <div class="splitter-3 h-[5px] w-full bg-gray-300 cursor-row-resize flex-none"></div> |
| | | |
| | | <!-- Action Buttons Panel --> |
| | | <div class="panel-1 border border-gray-300 bg-gray-100 flex items-center px-4 space-x-2 flex-none" style="height: 41px;"> |
| | | <button class="delete-bt px-4 py-1.5 border border-gray-400 bg-gray-200 hover:bg-gray-300 text-sm disabled:opacity-50" |
| | | :disabled="!isDeleteEnabled" @click="DeleteBtClick">忽略</button> |
| | | <button class="enforce-bt px-4 py-1.5 border border-gray-400 bg-gray-200 hover:bg-gray-300 text-sm" |
| | | @click="EnforceBtClick">強制傳送</button> |
| | | <button class="immediate-bt px-4 py-1.5 border border-gray-400 bg-gray-200 hover:bg-gray-300 text-sm" |
| | | @click="ImmediateBtClick">立即傳送</button> |
| | | |
| | | <!-- Hidden buttons --> |
| | | <button v-show="showNoteBtn" class="note-bt px-4 py-1.5 border border-gray-400 bg-gray-200" @click="NoteBtClick">備註</button> |
| | | <button v-show="showRejectBtn" class="reject-bt px-4 py-1.5 border border-gray-400 bg-gray-200" @click="RejectBtClick">退件</button> |
| | | <button v-show="false" class="button-1 px-4 py-1.5 border border-gray-400 bg-gray-200" @click="Button1Click">Button1</button> |
| | | |
| | | <div class="flex-grow"></div> <!-- Spacer --> |
| | | <button class="exit-bt px-4 py-1.5 border border-gray-400 bg-gray-200 hover:bg-gray-300 text-sm" |
| | | @click="ExitBtClick">離開</button> |
| | | <!-- Action Buttons Panel (Panel 1) --> |
| | | <div class="h-[41px] flex-none border-b border-gray-300 flex items-center px-4 space-x-2 bg-gray-50"> |
| | | <button class="w-[84px] h-[34px] border border-gray-400 bg-gray-200 hover:bg-gray-300 disabled:opacity-50" |
| | | :disabled="!isDeleteEnabled" @click="DeleteBtClick"> |
| | | 忽略 |
| | | </button> |
| | | <button class="w-[79px] h-[34px] border border-gray-400 bg-gray-200 hover:bg-gray-300" @click="EnforceBtClick"> |
| | | 強制送件 |
| | | </button> |
| | | <div class="flex-1"></div> |
| | | <button class="w-[89px] h-[34px] border border-gray-400 bg-gray-200 hover:bg-gray-300 disabled:opacity-50" |
| | | :disabled="!isImmediateEnabled" @click="ImmediateBtClick"> |
| | | 立即傳送 |
| | | </button> |
| | | <button class="w-[84px] h-[34px] border border-gray-400 bg-gray-200 hover:bg-gray-300" @click="ExitBtClick"> |
| | | 離開 |
| | | </button> |
| | | </div> |
| | | |
| | | <!-- Bottom Panel: Image Viewers --> |
| | | <div class="panel-4 flex flex-1 overflow-hidden border border-gray-300"> |
| | | |
| | | <!-- Left Image Viewer Panel --> |
| | | <div class="panel-5 flex flex-col h-full w-[407px]"> |
| | | <div class="panel-8 flex items-center bg-gray-200 border-b border-gray-300 px-2 h-[28px] flex-none"> |
| | | <button class="site-pre-bt p-0.5 border border-gray-400 bg-gray-100 disabled:opacity-50 mr-1" |
| | | :disabled="!isSitePreEnabled" @click="SitePreBtClick"> |
| | | <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg> |
| | | </button> |
| | | <span class="label-3 text-sm ml-2">{{ siteIndex }}/{{ siteTotal }}</span> |
| | | <button class="site-next-bt p-0.5 border border-gray-400 bg-gray-100 disabled:opacity-50 ml-1" |
| | | :disabled="!isSiteNextEnabled" @click="SiteNextBtClick"> |
| | | <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg> |
| | | </button> |
| | | <!-- Image Viewing Area (Panel 4) --> |
| | | <div class="flex-1 flex overflow-hidden"> |
| | | <!-- Primary Image View (Panel 5) --> |
| | | <div class="flex-1 flex flex-col border-r border-gray-400 overflow-hidden"> |
| | | <div class="h-[28px] bg-gray-200 border-b border-gray-300 flex items-center px-2 space-x-4"> |
| | | <button class="w-[29px] h-[23px] border border-gray-400 bg-gray-100 flex items-center justify-center text-xs" |
| | | :disabled="siteIdx <= 1" @click="SitePreBtClick">◀</button> |
| | | <span class="text-xs font-mono">{{ siteIdx }}/{{ siteCount }}</span> |
| | | <button class="w-[29px] h-[23px] border border-gray-400 bg-gray-100 flex items-center justify-center text-xs" |
| | | :disabled="siteIdx >= siteCount" @click="SiteNextBtClick">▶</button> |
| | | </div> |
| | | <div class="image-scroll-box-1 flex-1 bg-white border border-gray-300 overflow-auto relative"> |
| | | <!-- Placeholder for Main Image --> |
| | | <div class="text-gray-400 absolute inset-0 flex items-center justify-center">Image 1</div> |
| | | <div class="flex-1 bg-gray-800 overflow-auto relative p-2 flex items-center justify-center"> |
| | | <img :src="primaryImage" class="max-w-none shadow-xl border border-gray-400" /> |
| | | <!-- Mock OMR Error Overlay --> |
| | | <div v-if="siteIdx > 0" class="absolute left-1/2 top-1/4 w-[100px] h-[40px] border-2 border-yellow-400 bg-yellow-200 opacity-40 animate-pulse pointer-events-none"></div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- Splitter 1 --> |
| | | <div class="splitter-1 w-[5px] h-full bg-gray-300 cursor-col-resize flex-none hover:bg-gray-400" @mousedown="startResize"></div> |
| | | |
| | | <!-- Right Image Viewer Panel (Conditional Visibility in original code based on RelaSite) --> |
| | | <div class="panel-6 flex flex-col h-full flex-1" v-show="showPanel6"> |
| | | <div class="panel-9 flex items-center bg-gray-200 border-b border-gray-300 px-2 h-[28px] flex-none"> |
| | | <button class="rela-pre-bt p-0.5 border border-gray-400 bg-gray-100 disabled:opacity-50 mr-1" |
| | | :disabled="!isRelaPreEnabled" @click="RelaPreBtClick"> |
| | | <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg> |
| | | </button> |
| | | <span class="label-4 text-sm ml-2">{{ relaSiteIndex }}/{{ relaSiteTotal }}</span> |
| | | <button class="rela-next-bt p-0.5 border border-gray-400 bg-gray-100 disabled:opacity-50 ml-1" |
| | | :disabled="!isRelaNextEnabled" @click="RelaNextBtClick"> |
| | | <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg> |
| | | </button> |
| | | <!-- Related Image View (Panel 6) --> |
| | | <div class="w-[427px] flex-none flex flex-col overflow-hidden"> |
| | | <div class="h-[28px] bg-gray-200 border-b border-gray-300 flex items-center px-2 space-x-4"> |
| | | <button class="w-[31px] h-[23px] border border-gray-400 bg-gray-100 flex items-center justify-center text-xs" |
| | | :disabled="relaSiteIdx <= 1" @click="RelaPreBtClick">◀</button> |
| | | <span class="text-xs font-mono">{{ relaSiteIdx }}/{{ relaSiteCount }}</span> |
| | | <button class="w-[31px] h-[23px] border border-gray-400 bg-gray-100 flex items-center justify-center text-xs" |
| | | :disabled="relaSiteIdx >= relaSiteCount" @click="RelaNextBtClick">▶</button> |
| | | </div> |
| | | <div class="image-scroll-box-2 flex-1 bg-white border border-gray-300 overflow-auto relative"> |
| | | <!-- Placeholder for Related Image --> |
| | | <div class="text-gray-400 absolute inset-0 flex items-center justify-center">Image 2 (Related)</div> |
| | | <div class="flex-1 bg-gray-800 overflow-auto relative p-2 flex items-center justify-center"> |
| | | <img :src="relatedImage" class="max-w-none shadow-xl border border-gray-400" /> |
| | | <!-- Mock Related Error Overlay --> |
| | | <div v-if="relaSiteIdx > 0" class="absolute left-1/3 top-1/3 w-[80px] h-[30px] border-2 border-red-400 bg-red-200 opacity-40 pointer-events-none"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- Visual Reference Overlay --> |
| | | <div class="err-list-layout opacity-20 pointer-events-none absolute left-0 top-0"> |
| | | <img src="assets/ErrList.png" class="w-[841px] h-[636px]" /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | |
| | | export default defineComponent({ |
| | | name: 'ErrlistForm', |
| | | setup() { |
| | | const { |
| | | // State |
| | | errListItems, |
| | | selectedItemIndex, |
| | | isDeleteEnabled, |
| | | siteIndex, |
| | | siteTotal, |
| | | isSitePreEnabled, |
| | | isSiteNextEnabled, |
| | | relaSiteIndex, |
| | | relaSiteTotal, |
| | | isRelaPreEnabled, |
| | | isRelaNextEnabled, |
| | | showPanel6, |
| | | showNoteBtn, |
| | | showRejectBtn, |
| | | |
| | | // Actions |
| | | selectItem, |
| | | ErrListLVClick, |
| | | ErrListLVKeyUp, |
| | | ErrListLVMouseDown, |
| | | DeleteBtClick, |
| | | EnforceBtClick, |
| | | ImmediateBtClick, |
| | | ExitBtClick, |
| | | SitePreBtClick, |
| | | SiteNextBtClick, |
| | | RelaPreBtClick, |
| | | RelaNextBtClick, |
| | | NoteBtClick, |
| | | RejectBtClick, |
| | | Button1Click, |
| | | startResize |
| | | } = useErrListLogic(); |
| | | |
| | | return { |
| | | errListItems, |
| | | selectedItemIndex, |
| | | isDeleteEnabled, |
| | | siteIndex, |
| | | siteTotal, |
| | | isSitePreEnabled, |
| | | isSiteNextEnabled, |
| | | relaSiteIndex, |
| | | relaSiteTotal, |
| | | isRelaPreEnabled, |
| | | isRelaNextEnabled, |
| | | showPanel6, |
| | | showNoteBtn, |
| | | showRejectBtn, |
| | | |
| | | selectItem, |
| | | ErrListLVClick, |
| | | ErrListLVKeyUp, |
| | | ErrListLVMouseDown, |
| | | DeleteBtClick, |
| | | EnforceBtClick, |
| | | ImmediateBtClick, |
| | | ExitBtClick, |
| | | SitePreBtClick, |
| | | SiteNextBtClick, |
| | | RelaPreBtClick, |
| | | RelaNextBtClick, |
| | | NoteBtClick, |
| | | RejectBtClick, |
| | | Button1Click, |
| | | startResize |
| | | }; |
| | | const logic = useErrListLogic(); |
| | | return { ...logic }; |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | /* Optional: Add custom scrollbar styling to match Delphi look if desired */ |
| | | .err-list-lv::-webkit-scrollbar { |
| | | width: 16px; |
| | | .err-list-form { |
| | | user-select: none; |
| | | } |
| | | .err-list-lv::-webkit-scrollbar-track { |
| | | background: #f1f1f1; |
| | | } |
| | | .err-list-lv::-webkit-scrollbar-thumb { |
| | | background: #c1c1c1; |
| | | border: 1px solid #f1f1f1; |
| | | } |
| | | .err-list-lv::-webkit-scrollbar-thumb:hover { |
| | | background: #a8a8a8; |
| | | .err-list-layout { |
| | | z-index: 1000; |
| | | } |
| | | </style> |
| 比對新檔案 |
| | |
| | | import { ref, onMounted } from 'vue'; |
| | | |
| | | interface OldCase { |
| | | id: string; |
| | | year: string; |
| | | type: string; |
| | | isOld: string; |
| | | } |
| | | |
| | | interface DocGroup { |
| | | text: string; |
| | | checked: boolean; |
| | | pages: string[]; |
| | | } |
| | | |
| | | export function useOldCaseInfoLogic() { |
| | | // --- State Mapped from Delphi UI Components --- |
| | | |
| | | const currentPage = ref<'CaseInfo' | 'View'>('CaseInfo'); |
| | | const oldCases = ref<OldCase[]>([]); |
| | | const selectedCaseIndex = ref<number>(-1); |
| | | |
| | | const docGroups = ref<DocGroup[]>([]); |
| | | const selectedGroupIndex = ref<number>(-1); |
| | | |
| | | const previewImage = ref<string>(''); |
| | | |
| | | // --- Methods --- |
| | | |
| | | const FormCreate = () => { |
| | | console.log('OldCaseInfoForm created'); |
| | | // Mock data for OldCaseLV |
| | | oldCases.value = [ |
| | | { id: 'CASE2023001', year: '2023', type: 'HLN', isOld: 'Y' }, |
| | | { id: 'CASE2022045', year: '2022', type: 'HLN', isOld: 'Y' }, |
| | | { id: 'CASE2024012', year: '2024', type: 'NORMAL', isOld: 'N' } |
| | | ]; |
| | | }; |
| | | |
| | | const LoadBtClick = () => { |
| | | if (selectedCaseIndex.value === -1) { |
| | | alert('請先選擇一個案件編號'); |
| | | return; |
| | | } |
| | | |
| | | console.log('Loading case:', oldCases.value[selectedCaseIndex.value].id); |
| | | currentPage.value = 'View'; |
| | | |
| | | // Mock doc groups loading |
| | | docGroups.value = [ |
| | | { text: '身份證{正本}-1', checked: false, pages: ['assets/DocList.png'] }, |
| | | { text: '申請書{複本}-2', checked: false, pages: ['assets/CB_IMGPSScanImp.png', 'assets/DocPrt.png'] }, |
| | | { text: '財力證明{附件}-1', checked: false, pages: ['assets/ErrList.png'] } |
| | | ]; |
| | | }; |
| | | |
| | | const ImportBtClick = () => { |
| | | const selected = docGroups.value.filter(g => g.checked); |
| | | if (selected.length === 0) { |
| | | alert('請至少選擇一個要引用之文件'); |
| | | return; |
| | | } |
| | | console.log('Importing groups:', selected); |
| | | closeForm('ok'); |
| | | }; |
| | | |
| | | const ExitBtClick = () => { |
| | | currentPage.value = 'CaseInfo'; |
| | | }; |
| | | |
| | | const OldExitBtClick = () => { |
| | | closeForm('cancel'); |
| | | }; |
| | | |
| | | const CheckListBox1Click = (index: number) => { |
| | | selectedGroupIndex.value = index; |
| | | if (docGroups.value[index].pages.length > 0) { |
| | | previewImage.value = docGroups.value[index].pages[0]; |
| | | } |
| | | }; |
| | | |
| | | const selectCase = (index: number) => { |
| | | selectedCaseIndex.value = index; |
| | | }; |
| | | |
| | | const setThumbnailAsPreview = (url: string) => { |
| | | previewImage.value = url; |
| | | }; |
| | | |
| | | const closeForm = (result: string) => { |
| | | console.log(`OldCaseInfoForm closing with result: ${result}`); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | FormCreate(); |
| | | }); |
| | | |
| | | return { |
| | | // State |
| | | currentPage, |
| | | oldCases, |
| | | selectedCaseIndex, |
| | | docGroups, |
| | | selectedGroupIndex, |
| | | previewImage, |
| | | |
| | | // Actions |
| | | LoadBtClick, |
| | | ImportBtClick, |
| | | ExitBtClick, |
| | | OldExitBtClick, |
| | | CheckListBox1Click, |
| | | selectCase, |
| | | setThumbnailAsPreview |
| | | }; |
| | | } |
| 比對新檔案 |
| | |
| | | <template> |
| | | <div class="old-case-info-form flex flex-col bg-gray-100 font-sans w-[740px] h-[554px] border border-gray-400 relative overflow-hidden text-sm"> |
| | | |
| | | <!-- Page 1: CaseInfo --> |
| | | <div v-if="currentPage === 'CaseInfo'" class="flex-1 flex flex-col overflow-hidden"> |
| | | <!-- Toolbar (Panel 1) --> |
| | | <div class="h-[41px] flex items-center px-4 space-x-4 bg-gray-50 border-b border-gray-300"> |
| | | <button class="w-[75px] h-[25px] border border-gray-400 bg-gray-200 hover:bg-gray-300" @click="LoadBtClick"> |
| | | 載入影像 |
| | | </button> |
| | | <button class="w-[75px] h-[25px] border border-gray-400 bg-gray-200 hover:bg-gray-300" @click="OldExitBtClick"> |
| | | 離開 |
| | | </button> |
| | | </div> |
| | | |
| | | <!-- Case List (OldCaseLV) --> |
| | | <div class="flex-1 bg-white overflow-auto"> |
| | | <table class="w-full border-collapse"> |
| | | <thead class="sticky top-0 bg-gray-100"> |
| | | <tr class="text-left border-b border-gray-300"> |
| | | <th class="p-2 border-r border-gray-200 w-[200px]">掛報書編號</th> |
| | | <th class="p-2 border-r border-gray-200 w-[120px]">年度</th> |
| | | <th class="p-2 border-r border-gray-200 w-[120px]">業務別</th> |
| | | <th class="p-2">是否舊案</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | <tr v-for="(item, index) in oldCases" |
| | | :key="index" |
| | | :class="['hover:bg-blue-50 cursor-pointer border-b border-gray-100', selectedCaseIndex === index ? 'bg-blue-600 text-white hover:bg-blue-700' : '']" |
| | | @click="selectCase(index)" |
| | | @dblclick="LoadBtClick"> |
| | | <td class="p-2 border-r border-gray-200">{{ item.id }}</td> |
| | | <td class="p-2 border-r border-gray-200">{{ item.year }}</td> |
| | | <td class="p-2 border-r border-gray-200">{{ item.type }}</td> |
| | | <td class="p-2">{{ item.isOld }}</td> |
| | | </tr> |
| | | </tbody> |
| | | </table> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- Page 2: View --> |
| | | <div v-else class="flex-1 flex flex-col overflow-hidden"> |
| | | <!-- Toolbar (Panel 3) --> |
| | | <div class="h-[49px] flex items-center px-4 space-x-4 bg-gray-50 border-b border-gray-300"> |
| | | <button class="w-[75px] h-[25px] border border-gray-400 bg-gray-200 hover:bg-gray-300" @click="ImportBtClick"> |
| | | 引用 |
| | | </button> |
| | | <button class="w-[75px] h-[25px] border border-gray-400 bg-gray-200 hover:bg-gray-300" @click="ExitBtClick"> |
| | | 離開 |
| | | </button> |
| | | <div class="flex-1"></div> |
| | | <span class="text-xs text-gray-500">案件編號: {{ oldCases[selectedCaseIndex].id }}</span> |
| | | </div> |
| | | |
| | | <div class="flex-1 flex overflow-hidden"> |
| | | <!-- Left Sidebar: Document Groups (Panel 4) --> |
| | | <div class="w-[249px] flex-none border-r border-gray-400 p-1 flex flex-col bg-gray-50"> |
| | | <fieldset class="flex-1 border border-gray-400 p-0 flex flex-col"> |
| | | <legend class="px-1 ml-2 text-sm">案件編號</legend> |
| | | <div class="flex-1 overflow-y-auto bg-white m-1 border border-gray-300"> |
| | | <div v-for="(group, index) in docGroups" |
| | | :key="index" |
| | | :class="['flex items-center px-2 py-1 space-x-2 cursor-pointer hover:bg-blue-50', selectedGroupIndex === index ? 'bg-blue-100' : '']" |
| | | @click="CheckListBox1Click(index)"> |
| | | <input type="checkbox" v-model="group.checked" @click.stop /> |
| | | <span class="text-sm truncate">{{ group.text }}</span> |
| | | </div> |
| | | </div> |
| | | </fieldset> |
| | | </div> |
| | | |
| | | <!-- Main Preview Area (Panel 5) --> |
| | | <div class="flex-1 flex overflow-hidden"> |
| | | <!-- Thumbnails (Panel 6) --> |
| | | <div class="w-[183px] flex-none border-r border-gray-400 overflow-y-auto bg-gray-300 p-1 space-y-2"> |
| | | <div v-for="(page, pIdx) in (selectedGroupIndex !== -1 ? docGroups[selectedGroupIndex].pages : [])" |
| | | :key="pIdx" |
| | | class="w-[150px] mx-auto border-2 border-transparent hover:border-blue-500 cursor-pointer bg-white shadow-sm" |
| | | @click="setThumbnailAsPreview(page)"> |
| | | <img :src="page" class="w-full" /> |
| | | <div class="text-[10px] text-center bg-gray-100 py-1">第 {{ pIdx + 1 }} 頁</div> |
| | | </div> |
| | | </div> |
| | | <!-- Large Preview (Panel 7) --> |
| | | <div class="flex-1 bg-gray-800 flex items-center justify-center p-2 overflow-auto"> |
| | | <img v-if="previewImage" :src="previewImage" class="max-w-none shadow-2xl border-4 border-white" /> |
| | | <div v-else class="text-white text-lg opacity-30">請選擇影像進行預覽</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- Visual Reference Overlay --> |
| | | <div class="old-case-layout opacity-20 pointer-events-none absolute left-0 top-0"> |
| | | <img src="assets/OldCaseInfo.png" class="w-[740px] h-[554px]" /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script lang="ts"> |
| | | import { defineComponent } from 'vue'; |
| | | import { useOldCaseInfoLogic } from './OldCaseInfo.ts'; |
| | | |
| | | export default defineComponent({ |
| | | name: 'OldCaseInfoForm', |
| | | setup() { |
| | | const logic = useOldCaseInfoLogic(); |
| | | return { ...logic }; |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .old-case-info-form { |
| | | user-select: none; |
| | | } |
| | | .old-case-layout { |
| | | z-index: 1000; |
| | | } |
| | | </style> |
| | |
| | | return { |
| | | availableComponents: [ |
| | | { |
| | | id: 'DocList', |
| | | name: 'DocList.vue', |
| | | vuePath: './DocList/DocList.vue', |
| | | jsPath: './DocList/DocList.ts', |
| | | windowTitle: '歷史類畫面' |
| | | }, |
| | | { |
| | | id: 'DocPrt', |
| | | name: 'DocPrt.vue', |
| | | vuePath: './DocPrt/DocPrt.vue', |
| | | jsPath: './DocPrt/DocPrt.ts', |
| | | windowTitle: '列印畫面' |
| | | }, |
| | | { |
| | | id: 'ErrList', |
| | | name: 'ErrList.vue', |
| | | vuePath: './ErrList/ErrList.vue', |
| | |
| | | windowTitle: '檢核失敗原因畫面' |
| | | }, |
| | | { |
| | | id: 'OldCaseInfo', |
| | | name: 'OldCaseInfo.vue', |
| | | vuePath: './OldCaseInfo/OldCaseInfo.vue', |
| | | jsPath: './OldCaseInfo/OldCaseInfo.ts', |
| | | windowTitle: '舊案引用畫面' |
| | | }, |
| | | { |
| | | id: 'PatchFom', |
| | | name: 'PatchFom.vue', |
| | | vuePath: './PatchFom/PatchFom.vue', |