From 0756bf12d10cf1b7f78c571de0a9ad69cbaeb7ca Mon Sep 17 00:00:00 2001
From: curtis <curtis@i-mps.com>
Date: 星期一, 30 三月 2026 14:24:17 +0800
Subject: [PATCH] fix: 更新內部引用方法參照
---
uiOutput/index.html | 408 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 408 insertions(+), 0 deletions(-)
diff --git a/uiOutput/index.html b/uiOutput/index.html
new file mode 100644
index 0000000..0c61824
--- /dev/null
+++ b/uiOutput/index.html
@@ -0,0 +1,408 @@
+<!DOCTYPE html>
+<html lang="zh-TW">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>DFM to Vue Preview</title>
+
+ <!-- 引入 Vue 3 -->
+ <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
+
+ <!-- 引入 Tailwind CSS 用於即時預覽 -->
+ <script src="https://cdn.tailwindcss.com"></script>
+
+ <!-- 使用 vue3-sfc-loader 來動態編譯 .vue 檔案 -->
+ <script src="https://cdn.jsdelivr.net/npm/vue3-sfc-loader@0.8.4/dist/vue3-sfc-loader.js"></script>
+
+ <!-- 加入 TypeScript 的 Babel transpiler 用於即時編譯 .ts -->
+ <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
+
+ <style>
+ body, html {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ background-color: #f3f4f6; /* Tailwind gray-100 */
+ font-family: sans-serif;
+ }
+
+ #app {
+ display: flex;
+ flex-direction: column;
+ height: 100vh;
+ }
+
+ /* 上方清單區塊 (1.4.1.1) */
+ .list-section {
+ background-color: #ffffff;
+ border-bottom: 1px solid #d1d5db; /* gray-300 */
+ padding: 16px;
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
+ z-index: 10;
+ }
+
+ .component-btn {
+ background-color: #3b82f6; /* blue-500 */
+ color: white;
+ padding: 8px 16px;
+ border-radius: 4px;
+ border: none;
+ cursor: pointer;
+ font-size: 14px;
+ margin-right: 8px;
+ transition: background-color 0.2s;
+ }
+
+ .component-btn:hover {
+ background-color: #2563eb; /* blue-600 */
+ }
+
+ .component-btn.active {
+ background-color: #1e40af; /* blue-800 */
+ box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.2);
+ }
+
+ /* 下方預覽區塊 (1.4.1.2) */
+ .preview-section {
+ flex: 1;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 24px;
+ overflow: auto;
+ background-color: #e5e7eb; /* Tailwind gray-200 */
+ }
+
+ /* 模擬 Windows 視窗外框 */
+ .preview-container {
+ background-color: white;
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+ border: 1px solid #9ca3af;
+ border-radius: 4px;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ /* 讓內部的子元件決定自己的寬高,但不能超過螢幕 */
+ max-width: 100%;
+ max-height: 100%;
+ }
+
+ .window-title {
+ background-color: #0058b0;
+ color: white;
+ padding: 6px 10px;
+ font-size: 12px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ user-select: none;
+ }
+
+ .window-controls span {
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ background-color: #ccc;
+ margin-left: 4px;
+ border-radius: 2px;
+ text-align: center;
+ line-height: 14px;
+ color: black;
+ cursor: pointer;
+ font-size: 10px;
+ font-weight: bold;
+ }
+
+ .window-controls span:hover {
+ background-color: #e81123;
+ color: white;
+ }
+
+ /* 載入中動畫 */
+ .loader {
+ border: 4px solid #f3f3f3;
+ border-top: 4px solid #3b82f6;
+ border-radius: 50%;
+ width: 30px;
+ height: 30px;
+ animation: spin 1s linear infinite;
+ }
+
+ @keyframes spin {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+ }
+ </style>
+</head>
+<body>
+
+<div id="app">
+ <!-- 1.4.1.1: List button 區塊 -->
+ <div class="list-section">
+ <h2 class="text-lg font-bold text-gray-700 mb-3">Converted Vue Components</h2>
+ <div class="flex flex-wrap gap-2">
+ <button
+ v-for="comp in availableComponents"
+ :key="comp.id"
+ @click="loadComponent(comp)"
+ :class="['component-btn', { active: currentComponent && currentComponent.id === comp.id }]"
+ >
+ {{ comp.name }}
+ </button>
+ </div>
+ </div>
+
+ <!-- 1.4.1.2: Preview viewer 區塊 -->
+ <div class="preview-section relative">
+ <div v-if="isLoading"
+ class="absolute inset-0 flex flex-col items-center justify-center bg-gray-200 bg-opacity-75 z-20">
+ <div class="loader mb-4"></div>
+ <div class="text-gray-600 font-medium">Loading component...</div>
+ </div>
+
+ <div v-else-if="!currentComponent" class="text-gray-500 text-lg">
+ Please select a component from the top menu to preview.
+ </div>
+
+ <div v-else class="preview-container">
+ <!-- 模擬 Windows 視窗標題列 -->
+ <div class="window-title">
+ <span>{{ currentComponent.windowTitle }}</span>
+ <div class="window-controls">
+ <span>_</span>
+ <span>□</span>
+ <span @click="closePreview">x</span>
+ </div>
+ </div>
+
+ <!-- 動態載入的 Vue 元件將會渲染在這裡 -->
+ <component :is="dynamicComponent"></component>
+ </div>
+ </div>
+</div>
+
+<script type="module">
+ const {loadModule} = window['vue3-sfc-loader'];
+
+ // 用來暫存修改過的檔案內容
+ const virtualFileSystem = {};
+
+ // 為了避免重複宣告 `ref`, `onMounted` 導致 SyntaxError
+ // 我們在全域環境中先宣告好 Vue 的依賴,只宣告一次
+ if (!window.__VUE_SETUP__) {
+ window.__VUE_SETUP__ = true;
+ const script = document.createElement('script');
+ script.textContent = `
+ // 全域提供 Vue API,避免在每個動態腳本中重複 let/const
+ window.ref = Vue.ref;
+ window.reactive = Vue.reactive;
+ window.computed = Vue.computed;
+ window.onMounted = Vue.onMounted;
+ window.onUnmounted = Vue.onUnmounted;
+ window.watch = Vue.watch;
+ window.defineComponent = Vue.defineComponent;
+ `;
+ document.head.appendChild(script);
+ }
+
+ // SFC Loader 設定,加入對 TS 的編譯支援
+ const options = {
+ moduleCache: {
+ vue: Vue
+ },
+ async getFile(url) {
+ // 處理虛擬 URL
+ if (url.startsWith('virtual-url-')) {
+ const content = virtualFileSystem[url];
+ if (content) {
+ return {
+ getContentData: asBinary => asBinary ? new TextEncoder().encode(content) : content,
+ type: '.vue'
+ };
+ }
+ }
+
+ // 處理相依的 .ts / .js 邏輯檔案
+ if (url.endsWith('.ts') || url.endsWith('.js')) {
+ const res = await fetch(url);
+ if (!res.ok) throw Object.assign(new Error(res.statusText + ' ' + url), {res});
+ const code = await res.text();
+
+ // 用 Babel 拔除 TypeScript 型別,並轉成 ES5
+ const transpiled = Babel.transform(code, {
+ presets: ['typescript'],
+ filename: url // 加入 filename 參數解決 Babel 錯誤
+ }).code;
+
+ // 將 export 轉化為掛載到 window
+ let modifiedCode = transpiled.replace(/export\s+(?:function|const)\s+(use\w+Logic)/g, 'window.$1 = function');
+
+ return {
+ getContentData: asBinary => asBinary ? new TextEncoder().encode(modifiedCode) : modifiedCode,
+ type: '.js'
+ };
+ }
+
+ const res = await fetch(url);
+ if (!res.ok) throw Object.assign(new Error(res.statusText + ' ' + url), {res});
+
+ return {
+ getContentData: asBinary => asBinary ? res.arrayBuffer() : res.text(),
+ }
+ },
+ addStyle(textContent) {
+ const style = Object.assign(document.createElement('style'), {textContent});
+ const ref = document.head.getElementsByTagName('style')[0] || null;
+ document.head.insertBefore(style, ref);
+ },
+ // 設定處理 `<script lang="ts">` 的勾子
+ handleModule(type, getContentData, path, options) {
+ switch (type) {
+ case '.ts':
+ const code = getContentData(false);
+ const jsCode = Babel.transform(code, {
+ presets: ['typescript'],
+ filename: path || 'file.ts' // 加入 filename 參數解決 Babel 錯誤
+ }).code;
+ return {getContentData: _ => jsCode, type: '.js'};
+ }
+ }
+ };
+
+ const app = Vue.createApp({
+ data() {
+ 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',
+ jsPath: './ErrList/ErrList.ts',
+ windowTitle: '檢核失敗原因畫面'
+ },
+ {
+ id: 'OldCaseInfo',
+ name: 'OldCaseInfo.vue',
+ vuePath: './OldCaseInfo/OldCaseInfo.vue',
+ jsPath: './OldCaseInfo/OldCaseInfo.ts',
+ windowTitle: '舊案引用畫面'
+ },
+ {
+ id: 'PatchFom',
+ name: 'PatchFom.vue',
+ vuePath: './PatchFom/PatchFom.vue',
+ jsPath: './PatchFom/PatchFom.ts',
+ windowTitle: '空白頁設定'
+ },
+ {
+ id: 'CB_IMGPSScanImp',
+ name: 'CB_IMGPSScanImp.vue',
+ vuePath: './CB_IMGPSScanImp/CB_IMGPSScanImp.vue',
+ jsPath: './CB_IMGPSScanImp/CB_IMGPSScanImp.ts',
+ windowTitle: 'CB_IMGPSScanX'
+ }
+ ],
+ currentComponent: null,
+ dynamicComponent: null,
+ isLoading: false
+ }
+ },
+ methods: {
+ async loadComponent(comp) {
+ if (this.currentComponent && this.currentComponent.id === comp.id) return;
+
+ this.isLoading = true;
+ this.currentComponent = comp;
+ this.dynamicComponent = null;
+
+ try {
+ // 1. 載入並執行 TypeScript 邏輯檔案
+ const res = await fetch(comp.jsPath);
+ if (!res.ok) throw new Error(`Failed to load ${comp.jsPath}`);
+ const tsCode = await res.text();
+
+ // 透過 Babel 將 TypeScript 轉譯為 JavaScript
+ const jsCode = Babel.transform(tsCode, {
+ presets: ['typescript'],
+ filename: comp.jsPath
+ }).code;
+
+ // 移除 import vue,將 export 改為 window 掛載
+ // 注意:這裡不宣告 const { ref, ... } = Vue,因為我們在全域已經掛載了 window.ref
+ // 只需要把 ts 轉譯後的 var _vue = require("vue"); 之類的給清除
+ let modifiedCode = jsCode
+ .replace(/import\s+\{.*?\}\s+from\s+['"]vue['"];/g, '')
+ .replace(/(var|let|const)\s+\w+\s*=\s*require\(['"]vue['"]\);/g, '')
+ // 將 babel 轉譯後可能產生的 _vue.ref 換回全域的 ref
+ .replace(/_vue\./g, '')
+ .replace(/export\s+(?:function|const)\s+(use\w+Logic)/g, 'window.$1 = function');
+
+ const scriptId = `script-${comp.id}`;
+ let oldScript = document.getElementById(scriptId);
+ if (oldScript) oldScript.remove();
+
+ const script = document.createElement('script');
+ script.id = scriptId;
+
+ // 使用 IIFE (立即執行函式) 包裝,避免區域變數互相污染
+ script.textContent = `
+ (function() {
+ ${modifiedCode}
+ })();
+ `;
+ document.head.appendChild(script);
+
+ // 2. 載入 Vue SFC
+ this.dynamicComponent = Vue.markRaw(Vue.defineAsyncComponent(() => {
+ return fetch(comp.vuePath)
+ .then(res => res.text())
+ .then(content => {
+ // 替換 .vue 裡面的 import
+ const modifiedContent = content.replace(
+ /import\s+\{\s*(use\w+Logic)\s*\}\s+from\s+['"].*?\.ts['"];/g,
+ 'const { $1 } = window;'
+ );
+
+ const virtualUrl = `virtual-url-${comp.id}-${Date.now()}.vue`;
+ virtualFileSystem[virtualUrl] = modifiedContent;
+
+ return loadModule(virtualUrl, options);
+ });
+ }));
+ } catch (err) {
+ console.error(`Error loading component ${comp.name}:`, err);
+ alert(`載入元件失敗: ${err.message}`);
+ this.currentComponent = null;
+ } finally {
+ this.isLoading = false;
+ }
+ },
+ closePreview() {
+ this.currentComponent = null;
+ this.dynamicComponent = null;
+ }
+ }
+ });
+
+ app.mount('#app');
+</script>
+</body>
+</html>
--
Gitblit v1.8.0