prc 1 年間 前
コミット
b314f80d8c
53 ファイル変更2760 行追加1663 行削除
  1. 2 6
      client/electron-builder.json5
  2. 319 133
      client/package-lock.json
  3. 1 3
      client/package.json
  4. 16 15
      client/src/renderer/App.vue
  5. 236 180
      client/src/renderer/pages/ListNum/ColorfulProgress.vue
  6. 341 257
      client/src/renderer/pages/ListNum/ListNum1.vue
  7. 574 568
      client/src/renderer/pages/ListNum/ListNum2.vue
  8. 649 32
      client/src/renderer/pages/ListNum/ListNum3.vue
  9. 429 422
      client/src/renderer/pages/ListNum/ListNum4.vue
  10. 1 1
      client/src/renderer/pages/ListNum/ListNumNone.vue
  11. 0 0
      client/src/renderer/public/iconlib/google/delete_black.png
  12. 0 0
      client/src/renderer/public/iconlib/google/delete_black.svg
  13. 0 0
      client/src/renderer/public/iconlib/google/delete_white.png
  14. 0 0
      client/src/renderer/public/iconlib/google/delete_white.svg
  15. 0 0
      client/src/renderer/public/iconlib/google/folder_open_black.png
  16. 0 0
      client/src/renderer/public/iconlib/google/folder_open_black.svg
  17. 0 0
      client/src/renderer/public/iconlib/google/folder_open_white.png
  18. 0 0
      client/src/renderer/public/iconlib/google/folder_open_white.svg
  19. 0 0
      client/src/renderer/public/iconlib/google/pause_black.png
  20. 0 0
      client/src/renderer/public/iconlib/google/pause_black.svg
  21. 0 0
      client/src/renderer/public/iconlib/google/pause_white.png
  22. 0 0
      client/src/renderer/public/iconlib/google/pause_white.svg
  23. 0 0
      client/src/renderer/public/iconlib/google/play_arrow_black.png
  24. 0 0
      client/src/renderer/public/iconlib/google/play_arrow_black.svg
  25. 0 0
      client/src/renderer/public/iconlib/google/play_arrow_white.png
  26. 0 0
      client/src/renderer/public/iconlib/google/play_arrow_white.svg
  27. 0 0
      client/src/renderer/public/iconlib/google/save_black.png
  28. 0 0
      client/src/renderer/public/iconlib/google/save_black.svg
  29. 0 0
      client/src/renderer/public/iconlib/google/save_white.png
  30. 0 0
      client/src/renderer/public/iconlib/google/save_white.svg
  31. 0 0
      client/src/renderer/public/iconlib/google/settings_black.png
  32. 0 0
      client/src/renderer/public/iconlib/google/settings_black.svg
  33. 0 0
      client/src/renderer/public/iconlib/google/settings_white.png
  34. 0 0
      client/src/renderer/public/iconlib/google/settings_white.svg
  35. 0 0
      client/src/renderer/public/iconlib/google/stop_black.png
  36. 0 0
      client/src/renderer/public/iconlib/google/stop_black.svg
  37. 0 0
      client/src/renderer/public/iconlib/google/stop_white.png
  38. 0 0
      client/src/renderer/public/iconlib/google/stop_white.svg
  39. 0 0
      client/src/renderer/public/iconlib/google/upload_black.png
  40. 0 0
      client/src/renderer/public/iconlib/google/upload_black.svg
  41. 0 0
      client/src/renderer/public/iconlib/google/upload_white.png
  42. 0 0
      client/src/renderer/public/iconlib/google/upload_white.svg
  43. BIN
      export/OSC.20.20.05.png
  44. BIN
      export/OSC.20.20.08.png
  45. BIN
      export/OSC.20.20.09.png
  46. BIN
      export/OSC.20.20.11.png
  47. BIN
      export/OSC.20.20.12.png
  48. 2 2
      launcher.py
  49. BIN
      server/__pycache__/instrument_controller.cpython-311.pyc
  50. BIN
      server/__pycache__/serial_device_controller.cpython-311.pyc
  51. 17 5
      server/instrument_controller.py
  52. 64 0
      server/serial_device_controller.py
  53. 109 39
      server/server.py

+ 2 - 6
client/electron-builder.json5

@@ -1,6 +1,6 @@
 {
-  productName: 'Vutron',
-  appId: 'com.vutron.vutron',
+  productName: 'PLC-SIM',
+  appId: 'com.plcsim.electrical-line-carrier-communication-simulator',
   asar: true,
   extends: null,
   compression: 'maximum',
@@ -53,10 +53,6 @@
   win: {
     icon: 'buildAssets/icons/icon.ico',
     target: [
-      {
-        target: 'appx',
-        arch: 'x64'
-      },
       {
         target: 'zip',
         arch: 'x64'

ファイルの差分が大きいため隠しています
+ 319 - 133
client/package-lock.json


+ 1 - 3
client/package.json

@@ -36,10 +36,8 @@
   "dependencies": {
     "axios": "^1.5.0",
     "brew": "^0.0.8",
-    "canvas": "^2.11.2",
     "chart.js": "^3.0.0",
     "chartjs": "^0.3.24",
-    "pdfjs-dist": "^3.9.179",
     "pinia": "^2.1.6",
     "socket.io-client": "^4.7.2",
     "vue": "^3.3.4",
@@ -59,13 +57,13 @@
     "cross-env": "^7.0.3",
     "electron": "^25.4.0",
     "electron-builder": "^24.6.3",
-    "eslint": "^8.46.0",
     "eslint-config-prettier": "^8.9.0",
     "eslint-config-standard": "^17.1.0",
     "eslint-friendly-formatter": "^4.0.1",
     "eslint-plugin-import": "^2.28.0",
     "eslint-plugin-n": "^16.0.1",
     "eslint-plugin-promise": "^6.1.1",
+    "eslint-plugin-react": "^7.33.2",
     "eslint-plugin-vue": "^9.16.1",
     "less": "^4.2.0",
     "playwright": "^1.36.2",

+ 16 - 15
client/src/renderer/App.vue

@@ -24,7 +24,7 @@ const languages = ref(['en'])
 const appVersion = ref('Unknown')
 
 const leftButtonList = () => {
-  const leftButtons = ref(['干扰控制', '衰减控制', '负载控制', '功能测试', '帮助文档'])
+  const leftButtons = ref(['功能测试', '干扰控制', '衰减控制', '负载控制', '帮助文档'])
   const selectedItem = ref('')
 
   return {
@@ -40,7 +40,8 @@ const getSelectedComponent = (getSelectedItem) => {
     case '':
       return ListNumNone
     case '干扰控制':
-      return ListNum1
+      //   return ListNum1
+      return ListNumNone
     case '衰减控制':
       return ListNum2
     case '负载控制':
@@ -75,17 +76,17 @@ const updateSelectedLeftButton = (buttonName: string) => {
 
 const getIconURL = () => {
   return {
-    iconMinURL: './public/icons/icon-min-white.png',
-    iconMaxURL: './public/icons/icon-max-white.png',
-    iconCloseURL: './public/icons/icon-close-white.png',
-    iconRestoreURL: './public/icons/icon-max-white.png'
+    iconMinURL: './icons/icon-min-white.png',
+    iconMaxURL: './icons/icon-max-white.png',
+    iconCloseURL: './icons/icon-close-white.png',
+    iconRestoreURL: './icons/icon-max-white.png'
   }
 }
 
 const iconURL = getIconURL()
 
-const iconSaveBlack = './pages/iconlib/google/save_black.png'
-const iconSaveWhite = './pages/iconlib/google/save_white.png'
+const iconSaveBlack = './iconlib/google/save_black.png'
+const iconSaveWhite = './iconlib/google/save_white.png'
 const iconSave = ref(iconSaveWhite)
 
 const iconSaveUseBlack = () => {
@@ -96,8 +97,8 @@ const iconSaveUseWhite = () => {
   iconSave.value = iconSaveWhite
 }
 
-const iconOpenBlack = './pages/iconlib/google/folder_open_black.png'
-const iconOpenWhite = './pages/iconlib/google/folder_open_white.png'
+const iconOpenBlack = './iconlib/google/folder_open_black.png'
+const iconOpenWhite = './iconlib/google/folder_open_white.png'
 const iconOpen = ref(iconOpenWhite)
 
 const iconOpenUseBlack = () => {
@@ -108,8 +109,8 @@ const iconOpenUseWhite = () => {
   iconOpen.value = iconOpenWhite
 }
 
-const iconSettingBlack = './pages/iconlib/google/settings_black.png'
-const iconSettingWhite = './pages/iconlib/google/settings_white.png'
+const iconSettingBlack = './iconlib/google/settings_black.png'
+const iconSettingWhite = './iconlib/google/settings_white.png'
 const iconSetting = ref(iconSettingWhite)
 
 const iconSettingUseBlack = () => {
@@ -141,7 +142,7 @@ onBeforeUnmount(() => {
 </script>
 
 <template>
-  <div class="main-screen" id="main-screen-app">
+  <div id="main-screen-app" class="main-screen">
     <div class="top-bar">
       <div class="title">
         <div class="title-text"> </div>
@@ -161,7 +162,7 @@ onBeforeUnmount(() => {
             @mouseup="iconSaveUseWhite"
             @mouseleave="iconSaveUseWhite"
           >
-            <img :src="iconSave" class="icon" id="icon-save" />
+            <img :src="iconSave" id="icon-save" class="icon" />
             <div class="text">保存项目</div>
           </button>
           <div class="middle"></div>
@@ -171,7 +172,7 @@ onBeforeUnmount(() => {
             @mouseup="iconSettingUseWhite"
             @mouseleave="iconSettingUseWhite"
           >
-            <img :src="iconSetting" class="icon" id="icon-setting" />
+            <img :src="iconSetting" id="icon-setting" class="icon" />
             <div class="text">偏好设置</div>
           </button>
         </div>

+ 236 - 180
client/src/renderer/pages/ListNum/ColorfulProgress.vue

@@ -1,230 +1,286 @@
 <script setup lang="ts">
 import { computed } from 'vue'
 interface Gradient {
-    '0%'?: string
-    '100%'?: string
-    from?: string
-    to?: string
-    direction?: 'right' | 'left'
+  '0%'?: string
+  '100%'?: string
+  from?: string
+  to?: string
+  direction?: 'right' | 'left'
 }
 interface Props {
-    width?: number | string // 进度条总宽度
-    percent?: number // 当前进度百分比
-    strokeColor?: string | Gradient // 进度条的色彩,传入 string 时为纯色,传入 object 时为渐变
-    strokeWidth?: number // 进度条线的宽度,单位px
-    showInfo?: boolean // 是否显示进度数值或状态图标
-    type?: 'line' | 'circle' // 进度条类型
+  width?: number | string // 进度条总宽度
+  percent?: number // 当前进度百分比
+  strokeColor?: string | Gradient // 进度条的色彩,传入 string 时为纯色,传入 object 时为渐变
+  strokeWidth?: number // 进度条线的宽度,单位px
+  showInfo?: boolean // 是否显示进度数值或状态图标
+  type?: 'line' | 'circle' // 进度条类型
 }
 const props = withDefaults(defineProps<Props>(), {
-    width: '100%',
-    percent: 0,
-    strokeColor: '#1677FF',
-    strokeWidth: 8,
-    showInfo: true,
-    type: 'line'
+  width: '100%',
+  percent: 0,
+  strokeColor: '#1677FF',
+  strokeWidth: 8,
+  showInfo: true,
+  type: 'line'
 })
-const totalWidth = computed(() => { // 进度条总宽度
-    if (typeof props.width === 'number') {
-        return props.width + 'px'
-    } else {
-        return props.width
-    }
+const totalWidth = computed(() => {
+  // 进度条总宽度
+  if (typeof props.width === 'number') {
+    return props.width + 'px'
+  } else {
+    return props.width
+  }
 })
-const perimeter = computed(() => { // 圆条周长
-    return (100 - props.strokeWidth) * Math.PI
+const perimeter = computed(() => {
+  // 圆条周长
+  return (100 - props.strokeWidth) * Math.PI
 })
-const path = computed(() => { // 圆条轨道路径指令
-    const long = (100 - props.strokeWidth)
-    return `M 50,50 m 0,-${(long / 2)}
-   a ${(long / 2)},${(long / 2)} 0 1 1 0,${long}
-   a ${(long / 2)},${(long / 2)} 0 1 1 0,-${long}`
+const path = computed(() => {
+  // 圆条轨道路径指令
+  const long = 100 - props.strokeWidth
+  return `M 50,50 m 0,-${long / 2}
+   a ${long / 2},${long / 2} 0 1 1 0,${long}
+   a ${long / 2},${long / 2} 0 1 1 0,-${long}`
 })
 const lineColor = computed(() => {
-    if (typeof props.strokeColor === 'string') {
-        return props.strokeColor
-    } else {
-        return `linear-gradient(to ${props.strokeColor.direction || 'right'}, ${props.strokeColor['0%'] || props.strokeColor.from}, ${props.strokeColor['100%'] || props.strokeColor.to})`
-    }
+  if (typeof props.strokeColor === 'string') {
+    return props.strokeColor
+  } else {
+    return `linear-gradient(to ${props.strokeColor.direction || 'right'}, ${
+      props.strokeColor['0%'] || props.strokeColor.from
+    }, ${props.strokeColor['100%'] || props.strokeColor.to})`
+  }
 })
 </script>
 <template>
-    <div v-if="type === 'line'" class="m-progress-line"
-        :style="`width: ${totalWidth}; height: ${strokeWidth < 24 ? 24 : strokeWidth}px;`">
-        <div class="m-progress-inner">
-            <div :class="['u-progress-bg', { 'u-success-bg': percent >= 100 }]"
-                :style="`background: ${lineColor}; width: ${percent >= 100 ? 100 : percent}%; height: ${strokeWidth}px;`">
-            </div>
-        </div>
-        <template v-if="showInfo">
-            <Transition mode="out-in">
-                <span v-if="percent >= 100" class="m-success">
-                    <svg focusable="false" class="u-icon" data-icon="check-circle" width="1em" height="1em"
-                        fill="currentColor" aria-hidden="true" viewBox="64 64 896 896">
-                        <path
-                            d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm193.5 301.7l-210.6 292a31.8 31.8 0 01-51.7 0L318.5 484.9c-3.8-5.3 0-12.7 6.5-12.7h46.9c10.2 0 19.9 4.9 25.9 13.3l71.2 98.8 157.2-218c6-8.3 15.6-13.3 25.9-13.3H699c6.5 0 10.3 7.4 6.5 12.7z">
-                        </path>
-                    </svg>
-                </span>
-                <p class="u-progress-text" v-else>{{ percent >= 100 ? 100 : percent }}%</p>
-            </Transition>
-        </template>
+  <div
+    v-if="type === 'line'"
+    class="m-progress-line"
+    :style="`width: ${totalWidth}; height: ${strokeWidth < 24 ? 24 : strokeWidth}px;`"
+  >
+    <div class="m-progress-inner">
+      <div
+        :class="['u-progress-bg', { 'u-success-bg': percent >= 100 }]"
+        :style="`background: ${lineColor}; width: ${
+          percent >= 100 ? 100 : percent
+        }%; height: ${strokeWidth}px;`"
+      >
+      </div>
     </div>
-    <div v-else class="m-progress-circle" :style="`width: ${totalWidth}; height: ${totalWidth};`">
-        <svg class="u-progress-circle" viewBox="0 0 100 100">
-            <path :d="path" stroke-linecap="round" class="u-progress-circle-trail" :stroke-width="strokeWidth"
-                :style="`stroke-dasharray: ${perimeter}px, ${perimeter}px;`" fill-opacity="0"></path>
-            <path :d="path" stroke-linecap="round" class="u-progress-circle-path" :class="{ success: percent >= 100 }"
-                :stroke-width="strokeWidth" :stroke="lineColor"
-                :style="`stroke-dasharray: ${(percent / 100) * perimeter}px, ${perimeter}px;`"
-                :opacity="percent === 0 ? 0 : 1" fill-opacity="0"></path>
+    <template v-if="showInfo">
+      <Transition mode="out-in">
+        <span v-if="percent >= 100" class="m-success">
+          <svg
+            focusable="false"
+            class="u-icon"
+            data-icon="check-circle"
+            width="1em"
+            height="1em"
+            fill="currentColor"
+            aria-hidden="true"
+            viewBox="64 64 896 896"
+          >
+            <path
+              d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm193.5 301.7l-210.6 292a31.8 31.8 0 01-51.7 0L318.5 484.9c-3.8-5.3 0-12.7 6.5-12.7h46.9c10.2 0 19.9 4.9 25.9 13.3l71.2 98.8 157.2-218c6-8.3 15.6-13.3 25.9-13.3H699c6.5 0 10.3 7.4 6.5 12.7z"
+            ></path>
+          </svg>
+        </span>
+        <p class="u-progress-text" v-else>{{ percent >= 100 ? 100 : percent }}%</p>
+      </Transition>
+    </template>
+  </div>
+  <div v-else class="m-progress-circle" :style="`width: ${totalWidth}; height: ${totalWidth};`">
+    <svg class="u-progress-circle" viewBox="0 0 100 100">
+      <path
+        :d="path"
+        stroke-linecap="round"
+        class="u-progress-circle-trail"
+        :stroke-width="strokeWidth"
+        :style="`stroke-dasharray: ${perimeter}px, ${perimeter}px;`"
+        fill-opacity="0"
+      ></path>
+      <path
+        :d="path"
+        stroke-linecap="round"
+        class="u-progress-circle-path"
+        :class="{ success: percent >= 100 }"
+        :stroke-width="strokeWidth"
+        :stroke="lineColor"
+        :style="`stroke-dasharray: ${(percent / 100) * perimeter}px, ${perimeter}px;`"
+        :opacity="percent === 0 ? 0 : 1"
+        fill-opacity="0"
+      ></path>
+    </svg>
+    <template v-if="showInfo">
+      <Transition mode="out-in">
+        <svg
+          v-if="percent >= 100"
+          class="u-icon"
+          focusable="false"
+          data-icon="check"
+          width="1em"
+          height="1em"
+          fill="currentColor"
+          aria-hidden="true"
+          viewBox="64 64 896 896"
+        >
+          <path
+            d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 00-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"
+          ></path>
         </svg>
-        <template v-if="showInfo">
-            <Transition mode="out-in">
-                <svg v-if="percent >= 100" class="u-icon" focusable="false" data-icon="check" width="1em" height="1em"
-                    fill="currentColor" aria-hidden="true" viewBox="64 64 896 896">
-                    <path
-                        d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 00-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z">
-                    </path>
-                </svg>
-                <p class="u-progress-text" v-else>{{ percent >= 100 ? 100 : percent }}%</p>
-            </Transition>
-        </template>
-    </div>
+        <p class="u-progress-text" v-else>{{ percent >= 100 ? 100 : percent }}%</p>
+      </Transition>
+    </template>
+  </div>
 </template>
 <style lang="less" scoped>
 .v-enter-active,
 .v-leave-active {
-    transition: opacity 0.2s;
+  transition: opacity 0.2s;
 }
 
 .v-enter-from,
 .v-leave-to {
-    opacity: 0;
+  opacity: 0;
 }
 
-@success: #52C41A;
+@success: #52c41a;
 
 .m-progress-line {
-    display: flex;
-    align-items: center;
+  display: flex;
+  align-items: center;
+
+  .m-progress-inner {
+    width: 100%;
+    background: #f5f5f5;
+    border-radius: 100px;
+
+    .u-progress-bg {
+      position: relative;
+      background-color: #1677ff;
+      border-radius: 100px;
+      transition: height 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
+
+      &::after {
+        content: '';
+        background-image: linear-gradient(
+          90deg,
+          rgba(255, 255, 255, 0.3) 0%,
+          rgba(255, 255, 255, 0.5) 100%
+        );
+        animation: progressRipple 2s cubic-bezier(0.4, 0, 0.2, 1) infinite;
+      }
 
-    .m-progress-inner {
-        width: 100%;
-        background: #f5f5f5;
-        border-radius: 100px;
-
-        .u-progress-bg {
-            position: relative;
-            background-color: #1677ff;
-            border-radius: 100px;
-            transition: height .3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
-
-            &::after {
-                content: "";
-                background-image: linear-gradient(90deg, rgba(255, 255, 255, .3) 0%, rgba(255, 255, 255, .5) 100%);
-                animation: progressRipple 2s cubic-bezier(.4, 0, .2, 1) infinite;
-            }
-
-            @keyframes progressRipple {
-                0% {
-                    position: absolute;
-                    inset: 0;
-                    right: 100%;
-                    opacity: 1;
-                }
-
-                66% {
-                    position: absolute;
-                    inset: 0;
-                    opacity: 0;
-                }
-
-                100% {
-                    position: absolute;
-                    inset: 0;
-                    opacity: 0;
-                }
-            }
+      @keyframes progressRipple {
+        0% {
+          position: absolute;
+          inset: 0;
+          right: 100%;
+          opacity: 1;
         }
 
-        .u-success-bg {
-            background: @success !important;
+        66% {
+          position: absolute;
+          inset: 0;
+          opacity: 0;
         }
-    }
 
-    .m-success {
-        width: 40px;
-        text-align: center;
-        display: inline-flex;
-        align-items: center;
-        justify-content: center;
-        padding-left: 4px;
-        flex-shrink: 0; // 默认 1.即空间不足时,项目将缩小
-
-        .u-icon {
-            display: inline-block;
-            width: 16px;
-            height: 16px;
-            fill: @success;
+        100% {
+          position: absolute;
+          inset: 0;
+          opacity: 0;
         }
+      }
+    }
+
+    .u-success-bg {
+      background: @success !important;
+    }
+  }
+
+  .m-success {
+    width: 40px;
+    text-align: center;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    padding-left: 4px;
+    flex-shrink: 0; // 默认 1.即空间不足时,项目将缩小
+
+    .u-icon {
+      display: inline-block;
+      width: 16px;
+      height: 16px;
+      fill: @success;
     }
+  }
 
-    .u-progress-text {
-        /*
+  .u-progress-text {
+    /*
       如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小
       如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小。
     */
-        flex-shrink: 0; // 默认 1.即空间不足时,项目将缩小
-        width: 40px;
-        text-align: center;
-        font-size: 14px;
-        padding-left: 4px;
-        color: rgba(0, 0, 0, .88);
-    }
+    flex-shrink: 0; // 默认 1.即空间不足时,项目将缩小
+    width: 40px;
+    text-align: center;
+    font-size: 14px;
+    padding-left: 4px;
+    color: rgba(0, 0, 0, 0.88);
+  }
 }
 
 .m-progress-circle {
-    display: inline-block;
-    position: relative;
+  display: inline-block;
+  position: relative;
 
-    .u-progress-circle {
-        .u-progress-circle-trail {
-            stroke: #f5f5f5;
-            stroke-dashoffset: 0;
-            transition: stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s ease 0s, stroke-width .06s ease .3s, opacity .3s ease 0s;
-        }
-
-        .u-progress-circle-path {
-            stroke-dashoffset: 0;
-            transition: stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s ease 0s, stroke-width .06s ease .3s, opacity .3s ease 0s;
-        }
-
-        .success {
-            stroke: @success !important;
-        }
+  .u-progress-circle {
+    .u-progress-circle-trail {
+      stroke: #f5f5f5;
+      stroke-dashoffset: 0;
+      transition:
+        stroke-dashoffset 0.3s ease 0s,
+        stroke-dasharray 0.3s ease 0s,
+        stroke 0.3s ease 0s,
+        stroke-width 0.06s ease 0.3s,
+        opacity 0.3s ease 0s;
     }
 
-    .u-icon {
-        position: absolute;
-        top: 50%;
-        left: 50%;
-        transform: translate(-50%, -50%);
-        display: inline-block;
-        width: 30px;
-        height: 30px;
-        fill: @success;
+    .u-progress-circle-path {
+      stroke-dashoffset: 0;
+      transition:
+        stroke-dashoffset 0.3s ease 0s,
+        stroke-dasharray 0.3s ease 0s,
+        stroke 0.3s ease 0s,
+        stroke-width 0.06s ease 0.3s,
+        opacity 0.3s ease 0s;
     }
 
-    .u-progress-text {
-        position: absolute;
-        top: 50%;
-        left: 50%;
-        transform: translate(-50%, -50%);
-        width: 100%;
-        font-size: 27px;
-        line-height: 1;
-        text-align: center;
-        color: rgba(0, 0, 0, .85);
+    .success {
+      stroke: @success !important;
     }
+  }
+
+  .u-icon {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    display: inline-block;
+    width: 30px;
+    height: 30px;
+    fill: @success;
+  }
+
+  .u-progress-text {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    width: 100%;
+    font-size: 27px;
+    line-height: 1;
+    text-align: center;
+    color: rgba(0, 0, 0, 0.85);
+  }
 }
-</style>
+</style>

+ 341 - 257
client/src/renderer/pages/ListNum/ListNum1.vue

@@ -1,28 +1,124 @@
 <script setup lang="tsx">
-import { useI18n } from 'vue-i18n'
-import { useTheme } from 'vuetify'
-import { openExternal } from '@/renderer/utils'
-import { useCounterStore } from '@/renderer/store/counter'
-import { storeToRefs } from 'pinia'
-import { onMounted, ref } from 'vue'
-
-const { locale, availableLocales } = useI18n()
-const { counterIncrease } = useCounterStore()
-const { counter } = storeToRefs(useCounterStore())
-const theme = useTheme()
-const languages = ref(['en'])
-const appVersion = ref('Unknown')
-
-onMounted((): void => {
-  languages.value = availableLocales
-
-  // Get application version from package.json version string (Using IPC communication)
-  window.mainApi.receive('msgReceivedVersion', (event: Event, version: string) => {
-    appVersion.value = version
+import { onMounted, ref, watch, onBeforeUnmount } from 'vue'
+import axios from 'axios'
+
+import Progress from '@/renderer/pages/ListNum/ColorfulProgress.vue'
+
+const httpServerPort = ref<number | null>(null)
+
+onMounted(() => {
+  window.mainApi.send('msgRequestGetServerPort')
+
+  window.mainApi.receive('msgReceivedServerPort', (event, port) => {
+    httpServerPort.value = port
   })
-  window.mainApi.send('msgRequestGetVersion')
 })
 
+class ProgressBarHandler {
+  fixedNumber = 3
+  percent = ref(0)
+  maxNumber = ref(100)
+  progressWidth = ref(10)
+  targetValue = ref(0.0)
+  targetValueString = ref(this.percent.value.toFixed(this.fixedNumber))
+
+  constructor(setMaxNumber: number, setDefaultPercent: number) {
+    this.maxNumber.value = setMaxNumber
+    this.percent.value = setDefaultPercent
+    this.updatePercentToTargetValue()
+
+    watch(this.targetValue, (newPercent) => {
+      this.updateTargetValueString(newPercent)
+    })
+
+    watch(this.percent, (newPercent) => {
+      this.updateTargetValue(newPercent)
+    })
+  }
+
+  updatePercentToTargetValue() {
+    this.updateTargetValueString(this.percent.value)
+    this.updateTargetValue(this.targetValue.value)
+  }
+
+  updateTargetValue(newValue: number) {
+    this.targetValue.value = this.processValue(0.01 * this.maxNumber.value * newValue)
+  }
+
+  updateTargetValueString(newValue: number) {
+    this.targetValueString.value = this.processValueStringFormat(newValue)
+  }
+
+  processValue(rawValue: number): number {
+    const processedValue = Math.round(rawValue / 0.5) * 0.5
+    return +processedValue.toFixed(this.fixedNumber)
+  }
+
+  processValueStringFormat(num: number): string {
+    const integerPart = Math.floor(num)
+    const decimalPart = num - integerPart
+    const integerString = integerPart < 10 ? '0' + integerPart : '' + integerPart
+    const decimalString = decimalPart.toFixed(this.fixedNumber).slice(2)
+    return integerString + '.' + decimalString
+  }
+
+  progressMouseUp() {
+    this.progressWidth.value = 10
+  }
+
+  progressMouseOver() {
+    this.progressWidth.value = 20
+  }
+
+  progressMouseLeave() {
+    this.progressWidth.value = 10
+  }
+
+  progressOnIncrease(scale: number) {
+    const res = this.percent.value + scale
+    this.percent.value = res > 100 ? 100 : res
+  }
+
+  progressOnDecline(scale: number) {
+    const res = this.percent.value - scale
+    this.percent.value = res < 0 ? 0 : res
+  }
+
+  progressMouseDown(event: MouseEvent) {
+    const adjustLine = event.currentTarget as HTMLElement
+    const rect = adjustLine.getBoundingClientRect()
+    const initialX = event.clientX
+
+    const updatePercent = (e: MouseEvent) => {
+      let x = e.clientX - rect.left
+      const dragRange = 0.8 * rect.width
+      const dragStart = 0.1 * rect.width
+      x = Math.max(Math.min(x, dragStart + dragRange), dragStart)
+      this.percent.value = ((x - dragStart) / dragRange) * 100
+    }
+
+    const moveHandler = (e: MouseEvent) => {
+      if (e.clientX > initialX) {
+        this.progressOnIncrease(((e.clientX - initialX) / rect.width) * 100)
+      } else {
+        this.progressOnDecline(((initialX - e.clientX) / rect.width) * 100)
+      }
+      updatePercent(e)
+    }
+
+    const upHandler = () => {
+      document.removeEventListener('mousemove', moveHandler)
+      document.removeEventListener('mouseup', upHandler)
+    }
+
+    document.addEventListener('mousemove', moveHandler)
+    document.addEventListener('mouseup', upHandler)
+    updatePercent(event)
+  }
+}
+
+const valueBarHandler = new ProgressBarHandler(100, 10)
+
 const height = ref(200)
 let initialY = 0 // 初始的鼠标Y坐标
 let initialHeight = 0 // 开始调整时的元素高度
@@ -49,44 +145,34 @@ const stopResize = () => {
   isDragging.value = false // 设置为停止拖动
 }
 
-const startButtonActived = ref(false)
-const startButtonTextList = ref(['开始', '停止'])
-const startButtonText = ref('开始')
-
-const iconStartBlack = './pages/iconlib/google/play_arrow_black.png'
-const iconStartWhite = './pages/iconlib/google/pause_white.png'
-const iconStartList = ref([iconStartBlack, iconStartWhite])
-const iconStart = ref(iconStartBlack)
+const terminalOutput = ref<string[]>([])
 
-const updateStartButton = () => {
-  startButtonActived.value = !startButtonActived.value
-  const useIndex = startButtonActived.value ? 1 : 0
-  startButtonText.value = startButtonTextList.value[useIndex]
-  iconStart.value = iconStartList.value[useIndex]
+// 定义一个方法,允许在组件内部推送内容到terminalOutput数组
+const terminalPush = (content: string) => {
+  terminalOutput.value.push(content)
 }
 
-const iconStopBlack = './pages/iconlib/google/stop_black.png'
-const iconStopWhite = './pages/iconlib/google/stop_white.png'
-const iconStop = ref(iconStopBlack)
-
-const iconStopUseBlack = () => {
-  iconStop.value = iconStopBlack
-}
-
-const iconStopUseWhite = () => {
-  iconStop.value = iconStopWhite
-}
-
-const iconExportBlack = './pages/iconlib/google/upload_black.png'
-const iconExportWhite = './pages/iconlib/google/upload_white.png'
-const iconExport = ref(iconExportBlack)
-
-const iconExportUseBlack = () => {
-  iconExport.value = iconExportBlack
+const setDeviceValue = async (targetDeviceValue: string) => {
+  try {
+    terminalPush(`[干扰注入器] 尝试将干扰设置为 ${targetDeviceValue} Ω`)
+    const params = {
+      value: targetDeviceValue
+    }
+    const url = `http://127.0.0.1:${httpServerPort.value}/serial_device/cnc/set`
+    const response = await axios.get(url, { params })
+    const ifSetSuccess = response.data
+    if (ifSetSuccess === 'True' || ifSetSuccess === 'true') {
+      terminalPush(`[干扰注入器] 干扰成功调整为 ${targetDeviceValue} Ω`)
+    } else {
+      terminalPush(`[干扰注入器] 干扰器调整失败`)
+    }
+  } catch (error) {
+    terminalPush(`[干扰注入器] 干扰器调整失败:${error}`)
+  }
 }
 
-const iconExportUseWhite = () => {
-  iconExport.value = iconExportWhite
+const updateDeviceValue = () => {
+  setDeviceValue(valueBarHandler.targetValueString.value)
 }
 </script>
 
@@ -94,67 +180,36 @@ const iconExportUseWhite = () => {
   <div class="list-page-1">
     <div class="page">
       <div class="page-up">
-        <div class="page-left">
-          <div class="button-bar">
-            <button
-              class="button-lock"
-              :class="{ active: startButtonActived }"
-              @click="updateStartButton()"
-            >
-              <img :src="iconStart" class="icon" />
-              <div class="text">{{ startButtonText }}</div>
-            </button>
-            <button
-              class="button-unlock"
-              @mousedown="iconStopUseWhite"
-              @mouseup="iconStopUseBlack"
-              @mouseleave="iconStopUseBlack"
-            >
-              <img :src="iconStop" class="icon" />
-              <div class="text">中止</div>
-            </button>
-            <button
-              class="button-unlock"
-              @mousedown="iconExportUseWhite"
-              @mouseup="iconExportUseBlack"
-              @mouseleave="iconExportUseBlack"
-            >
-              <img :src="iconExport" class="icon" />
-              <div class="text">导出</div>
-            </button>
-          </div>
-          <div class="card">
-            <div class="title">测试配置</div>
+        <div class="control-zone">
+          <div class="title">
+            <div class="text">电磁干扰注入器: {{ valueBarHandler.targetValueString.value }} Ω</div>
           </div>
-          <div class="card">
-            <div class="title">通信内容统计</div>
-            <div v-for="i in 6" :key="i">
-              <div class="text">发送: 12 34 56 78 90 21 43 65 87</div>
-              <div class="text">接收: 23 45 67 89 01 23 45 67 89 --- [标志]: 成功</div>
+          <div class="config-zone">
+            <div class="text-line">调整:个位 / 十位</div>
+            <div
+              class="adjust-line"
+              @mouseup="valueBarHandler.progressMouseUp"
+              @mousedown="valueBarHandler.progressMouseDown"
+              @mouseover="valueBarHandler.progressMouseOver"
+              @mouseleave="valueBarHandler.progressMouseLeave"
+            >
+              <Progress
+                :width="'80%'"
+                :percent="valueBarHandler.percent.value"
+                :stroke-width="valueBarHandler.progressWidth.value"
+                :stroke-color="{
+                  '0%': '#108ee9',
+                  '100%': '#87d068',
+                  direction: 'right'
+                }"
+                :show-info="false"
+              />
             </div>
-          </div>
-        </div>
-        <div class="page-right">
-          <div class="card">
-            <div class="text">示波器</div>
-            <div class="output-img">示波器输出图</div>
-            <div class="text"> 电源 </div>
-            <div class="output-value-group">
-              <div class="output-value">
-                <div class="text">
-                  <div>电压</div>
-                </div>
-                <div class="value"></div>
-              </div>
-              <div class="output-value">
-                <div class="text">
-                  <div>电流</div>
-                </div>
-                <div class="value"></div>
+            <div class="btn-zone">
+              <div @click="updateDeviceValue" class="btn">
+                <div class="text">应用配置</div>
               </div>
             </div>
-            <div class="text">万用表</div>
-            <div class="output-img">万用表输出图</div>
           </div>
         </div>
       </div>
@@ -166,25 +221,22 @@ const iconExportUseWhite = () => {
             </div>
             <div class="middle"></div>
             <div class="last-time">
-              <div class="text">测试持续时间: 00:00:00</div>
+              <div class="text"></div>
             </div>
           </div>
         </div>
         <div :style="{ height: height + 'px' }" class="content">
           <div class="terminal-zone">
-            <div class="terminal-left">
-              <div class="text" v-for="n in 50" :key="n"> 第{{ n }}次通信测试成功 </div>
-            </div>
+            <!-- <div class="terminal-left">
+                            <div class="select-area">
+                            </div>
+                        </div> -->
             <div class="terminal-dividing"></div>
             <div class="terminal-right">
-              <div class="title">测试结果统计</div>
-              <div class="line">
-                <div class="item">信噪比</div>
-                <div class="item">传输速率</div>
-              </div>
-              <div class="line">
-                <div class="item">电平</div>
-                <div class="item">误码率</div>
+              <div class="terminal-output">
+                <div class="text" v-for="(text, index) in terminalOutput" :key="index">
+                  {{ text }}
+                </div>
               </div>
             </div>
           </div>
@@ -210,26 +262,120 @@ const iconExportUseWhite = () => {
     flex-direction: column;
     justify-content: flex-start;
     align-items: flex-start;
-  }
-
-  .icon {
-    width: 30px;
-    height: 30px;
-    margin: 0px;
-    margin-left: -5px;
-    padding: 0px;
+    background-color: white;
+    padding: 0;
   }
 
   .page-up {
     width: 100%;
+    height: 100%;
     flex: 1;
-    overflow-y: hidden;
+    overflow-x: hidden;
+    overflow-y: auto;
     display: flex;
     flex-direction: row;
     justify-content: flex-start;
     align-items: flex-start;
-    margin: 20px;
-    margin-bottom: 0px;
+
+    .control-zone {
+      width: 100%;
+      height: 100%;
+      display: flex;
+      flex-direction: column;
+      justify-content: flex-start;
+      align-items: center;
+      margin-top: 10vh;
+    }
+
+    .title {
+      font-size: 35px;
+      font-weight: 500;
+      margin-bottom: 0;
+      margin-bottom: 3vh;
+    }
+
+    .config-zone {
+      width: 100%;
+      display: flex;
+      flex-direction: column;
+      justify-content: flex-start;
+      align-items: center;
+    }
+
+    .text-line {
+      font-size: 25px;
+      font-weight: 500;
+      margin-top: 2vh;
+      margin-bottom: -2vh;
+    }
+
+    .adjust-line {
+      width: 100%;
+      height: auto;
+      padding-top: 3vh;
+      padding-bottom: 3vh;
+      padding-left: 0;
+      padding-right: 0;
+      display: flex;
+      flex-direction: column;
+      justify-content: flex-start;
+      align-items: center;
+    }
+
+    .btn-zone {
+      margin-top: 3vh;
+      display: flex;
+      flex-direction: row;
+      justify-content: flex-start;
+      align-items: center;
+
+      .btn {
+        width: 105px;
+        height: 35px;
+        overflow: hidden;
+        border: 1px solid #dfdfdf;
+        border-radius: 5px;
+        display: flex;
+        flex-direction: row;
+        justify-content: center;
+        align-items: center;
+        color: black;
+        position: relative;
+        cursor: pointer;
+        transition: all 0.1s cubic-bezier(0.78, 0.14, 0.15, 0.86);
+      }
+
+      .btn::after {
+        content: '';
+        position: absolute;
+        width: 100%;
+        height: 100%;
+        background-color: rgba(255, 255, 255, 1);
+        z-index: 3;
+        transition: all 0.1s cubic-bezier(0.78, 0.14, 0.15, 0.86);
+      }
+
+      .btn:hover {
+        background: linear-gradient(to right, #108ee9 0%, #87d068 100%);
+        background-color: white;
+        color: white;
+      }
+
+      .btn:hover::after {
+        content: '';
+        position: absolute;
+        width: 100%;
+        height: 100%;
+        background-color: rgba(255, 255, 255, 0);
+        z-index: 3;
+      }
+
+      .text {
+        font-size: 20px;
+        font-weight: 400;
+        z-index: 5;
+      }
+    }
   }
 
   .page-down {
@@ -322,8 +468,14 @@ const iconExportUseWhite = () => {
     justify-content: flex-start;
     align-items: flex-start;
 
-    .terminal-left {
+    .terminal-right {
       flex: 5;
+      height: 100%;
+      overflow-x: hidden;
+      overflow-y: hidden;
+    }
+
+    .terminal-output {
       height: 100%;
       overflow-x: auto;
       overflow-y: scroll;
@@ -339,7 +491,7 @@ const iconExportUseWhite = () => {
       border-right: 1px solid #dfdfdf;
     }
 
-    .terminal-right {
+    .terminal-left {
       flex: 3;
       height: 100%;
       background-color: white;
@@ -350,6 +502,7 @@ const iconExportUseWhite = () => {
       justify-content: flex-start;
       align-items: flex-start;
       padding: 25px;
+      padding-top: 40px;
 
       .text {
         color: black;
@@ -358,152 +511,83 @@ const iconExportUseWhite = () => {
       .title {
         font-size: 20px;
         font-weight: 500;
-        margin-top: 10px;
-        margin-bottom: 5px;
+        margin-top: 15px;
+        margin-bottom: 10px;
       }
 
       .line {
         width: 100%;
         height: auto;
         display: flex;
-        flex-direction: row;
+        flex-direction: column;
         justify-content: flex-start;
         align-items: flex-start;
         margin-bottom: 6px;
 
         .item {
+          font-size: 18px;
+          font-weight: 500;
           flex: 1;
+          margin-bottom: 10px;
         }
       }
-    }
-  }
 
-  .page-left {
-    flex: 5;
-    // width: 100%;
-    height: 100%;
-    overflow-y: auto;
-    display: flex;
-    flex-direction: column;
-    justify-content: flex-start;
-    align-items: center;
-    padding-right: 10px;
-  }
-
-  .page-right {
-    flex: 3;
-    // width: 500px;
-    height: 100%;
-    overflow-y: auto;
-    margin-left: 10px;
-    margin-right: 25px;
-    padding-right: 10px;
-
-    .text {
-      font-size: 18px;
-    }
-
-    .output-img {
-      height: 200px;
-      width: 100%;
-      border: 1px solid #dfdfdf;
-      display: flex;
-      flex-direction: row;
-      justify-content: center;
-      align-items: center;
-      margin-top: 15px;
-      margin-bottom: 25px;
-      background-color: #dfdfdf;
-    }
-
-    .output-value-group {
-      width: 100%;
-      height: auto;
-      margin-bottom: 25px;
-    }
-
-    .output-value {
-      display: flex;
-      flex-direction: row;
-      justify-content: flex-start;
-      align-items: center;
-      margin-top: 15px;
-
-      .text {
+      .select-area {
         display: flex;
         flex-direction: row;
         justify-content: flex-start;
         align-items: center;
-        font-size: 16px;
-        width: 55px;
-        padding-left: 5px;
-        padding-right: 5px;
-      }
-
-      .value {
-        height: 45px;
-        width: 100%;
-        border: 1px solid #dfdfdf;
-        display: flex;
-        flex-direction: row;
-        justify-content: center;
-        align-items: center;
-        background-color: #dfdfdf;
-      }
-    }
-  }
 
-  .button-bar {
-    width: 100%;
-    height: 50px;
-    display: flex;
-    flex-direction: row;
-    justify-content: flex-start;
-    align-items: center;
-    margin-bottom: 20px;
-  }
+        .text {
+          font-size: 20px;
+          font-weight: 500;
+        }
 
-  .button-bar .button-lock,
-  .button-bar .button-unlock {
-    width: auto;
-    height: 42px;
-    padding-left: 12px;
-    padding-right: 12px;
-    border-radius: 5px;
-    background-color: white;
-    margin-right: 15px;
-    display: flex;
-    flex-direction: row;
-    justify-content: flex-start;
-    align-items: center;
-  }
+        .select-box {
+          margin-top: 6px;
+          margin-bottom: 6px;
+
+          .select-button {
+            padding: 3px;
+            padding-left: 9px;
+            width: 100px;
+            border: 1px solid rgba(0, 0, 0, 0.35);
+            border-radius: 5px;
+            margin-left: 10px;
+            margin-right: 20px;
+          }
+        }
 
-  .button-bar .button-lock:hover,
-  .button-bar .button-unlock:hover {
-    font-weight: 500;
-  }
+        .button {
+          margin-top: 12px;
+          font-size: 18px;
+          border: 1px solid rgba(0, 0, 0, 0.35);
+          border-radius: 5px;
+          padding: 3px;
+          padding-left: 8px;
+          padding-right: 8px;
+        }
 
-  .button-bar .button-lock.active,
-  .button-bar .button-unlock:active {
-    color: white;
-    background-color: #2673e3;
-    font-weight: 500;
-  }
+        .button:hover {
+          background-color: #2673e3;
+          color: white;
+          border: 1px solid rgba($color: #2673e3, $alpha: 1);
+        }
 
-  .card {
-    margin-bottom: 20px;
-    width: 100%;
-    height: auto;
-    background-color: white;
-    padding: 35px;
-    border-radius: 5px;
-    color: black;
-  }
+        .button:active {
+          background-color: rgba($color: #2673e3, $alpha: 0.5);
+          border: 1px solid rgba($color: #2673e3, $alpha: 0.5);
+          color: white;
+        }
 
-  .card .title {
-    font-size: 20px;
-    font-weight: 500;
-    margin-bottom: 15px;
+        .tip {
+          margin-top: 12px;
+          font-size: 18px;
+          margin-left: 16px;
+          font-weight: 500;
+        }
+      }
+    }
   }
 }
 </style>

+ 574 - 568
client/src/renderer/pages/ListNum/ListNum2.vue

@@ -12,144 +12,144 @@ import Progress from '@/renderer/pages/ListNum/ColorfulProgress.vue'
 const httpServerPort = ref<number | null>(null)
 
 onMounted(() => {
-    window.mainApi.send('msgRequestGetServerPort')
+  window.mainApi.send('msgRequestGetServerPort')
 
-    window.mainApi.receive('msgReceivedServerPort', (event, port) => {
-        httpServerPort.value = port
-        getComPort()
-    })
+  window.mainApi.receive('msgReceivedServerPort', (event, port) => {
+    httpServerPort.value = port
+    getComPort()
+  })
 })
 
 let intervalId: number
 
 onMounted(() => {
-    intervalId = setInterval(doEvery500ms, 500) as unknown as number
+  intervalId = setInterval(doEvery500ms, 500) as unknown as number
 })
 
 let intervalId300ms: number
 
 onBeforeUnmount(() => {
-    clearInterval(intervalId)
+  clearInterval(intervalId)
 })
 
 const getComPort = async () => {
-    try {
-        const response = await axios.get(
-            `http://127.0.0.1:${httpServerPort.value}/serial_device/port_list`
-        )
-        const parts = response.data.split(':')
-        const valuesFromResponse = parts[1].trim().split(' ')
-        optionsComPorts.value = [' '].concat(valuesFromResponse)
-        selected1.value = optionsComPorts.value[0] || '' // 如果有选项,将第一个选项设置为选中
-    } catch (error) {
-        console.error('An error occurred while fetching the data:', error)
-    }
+  try {
+    const response = await axios.get(
+      `http://127.0.0.1:${httpServerPort.value}/serial_device/port_list`
+    )
+    const parts = response.data.split(':')
+    const valuesFromResponse = parts[1].trim().split(' ')
+    optionsComPorts.value = [' '].concat(valuesFromResponse)
+    selected1.value = optionsComPorts.value[0] || '' // 如果有选项,将第一个选项设置为选中
+  } catch (error) {
+    console.error('An error occurred while fetching the data:', error)
+  }
 }
 
 const percent = ref(15)
 const progressWidth = ref(10)
 const cncValue = ref(0.0)
-const cncValueString = ref(percent.value.toFixed(2));
+const cncValueString = ref(percent.value.toFixed(2))
 
 watch(cncValue, (newPercent) => {
-    cncValueString.value = processCNCValueStringFormat(newPercent);
-});
+  cncValueString.value = processCNCValueStringFormat(newPercent)
+})
 
 watch(percent, (newPercent) => {
-    cncValue.value = processCNCValue(0.36 * newPercent);
-});
+  cncValue.value = processCNCValue(0.36 * newPercent)
+})
 
 function processCNCValue(rawValue: number): number {
-    // 四舍五入到最近的0.05
-    // rawValue / 0.05 通过除以0.05来“扩大”我们的精度
-    // Math.round(...) 四舍五入到最近的整数
-    // ... * 0.05 通过乘以0.05“缩小”我们的精度,将数字变回原来的范围
-    const processedValue = Math.round(rawValue / 0.5) * 0.5;
-
-    // 将processedValue保留两位有效数字
-    // +processedValue.toFixed(2) 将字符串转换为数字,以避免字符串结果
-    return +processedValue.toFixed(2);
+  // 四舍五入到最近的0.05
+  // rawValue / 0.05 通过除以0.05来“扩大”我们的精度
+  // Math.round(...) 四舍五入到最近的整数
+  // ... * 0.05 通过乘以0.05“缩小”我们的精度,将数字变回原来的范围
+  const processedValue = Math.round(rawValue / 0.5) * 0.5
+
+  // 将processedValue保留两位有效数字
+  // +processedValue.toFixed(2) 将字符串转换为数字,以避免字符串结果
+  return +processedValue.toFixed(2)
 }
 
 function processCNCValueStringFormat(num: number): string {
-    // 获取整数和小数部分
-    const integerPart = Math.floor(num);
-    const decimalPart = num - integerPart;
+  // 获取整数和小数部分
+  const integerPart = Math.floor(num)
+  const decimalPart = num - integerPart
 
-    // 将整数部分转换为至少两位数的字符串
-    const integerString = integerPart < 10 ? '0' + integerPart : '' + integerPart;
+  // 将整数部分转换为至少两位数的字符串
+  const integerString = integerPart < 10 ? '0' + integerPart : '' + integerPart
 
-    // 转换并修剪/舍入小数部分,确保两位
-    const decimalString = decimalPart.toFixed(2).slice(2); // slice(2) 删除“0.”前缀
+  // 转换并修剪/舍入小数部分,确保两位
+  const decimalString = decimalPart.toFixed(2).slice(2) // slice(2) 删除“0.”前缀
 
-    return integerString + '.' + decimalString;
+  return integerString + '.' + decimalString
 }
 
 const progressMouseUp = () => {
-    progressWidth.value = 10
+  progressWidth.value = 10
 }
 
 const progressMouseOver = () => {
-    progressWidth.value = 20
+  progressWidth.value = 20
 }
 
 const progressMouseLeave = () => {
-    progressWidth.value = 10
+  progressWidth.value = 10
 }
 
 function progressOnIncrease(scale: number) {
-    const res = percent.value + scale
-    if (res > 100) {
-        percent.value = 100
-    } else {
-        percent.value = res
-    }
+  const res = percent.value + scale
+  if (res > 100) {
+    percent.value = 100
+  } else {
+    percent.value = res
+  }
 }
 
 function progressOnDecline(scale: number) {
-    const res = percent.value - scale
-    if (res < 0) {
-        percent.value = 0
-    } else {
-        percent.value = res
-    }
+  const res = percent.value - scale
+  if (res < 0) {
+    percent.value = 0
+  } else {
+    percent.value = res
+  }
 }
 
 const progressMouseDown = (event: MouseEvent) => {
-    const adjustLine = event.currentTarget as HTMLElement;
-    const rect = adjustLine.getBoundingClientRect();
-    const initialX = event.clientX;
-
-    const updatePercent = (e: MouseEvent) => {
-        let x = e.clientX - rect.left;
-        const dragRange = 0.8 * rect.width; // 80% of the line width
-        const dragStart = 0.1 * rect.width; // Start from 10% of the line width
-
-        // Clamp x within the draggable range
-        x = Math.max(Math.min(x, dragStart + dragRange), dragStart);
-
-        // Calculate the percent within the 10% to 90% range
-        percent.value = ((x - dragStart) / dragRange) * 100;
-    };
-
-    const moveHandler = (e: MouseEvent) => {
-        if (e.clientX > initialX) {
-            progressOnIncrease((e.clientX - initialX) / rect.width * 100);
-        } else {
-            progressOnDecline((initialX - e.clientX) / rect.width * 100);
-        }
-        updatePercent(e); // Update the percent directly based on the cursor position
-    };
+  const adjustLine = event.currentTarget as HTMLElement
+  const rect = adjustLine.getBoundingClientRect()
+  const initialX = event.clientX
 
-    const upHandler = () => {
-        document.removeEventListener('mousemove', moveHandler);
-        document.removeEventListener('mouseup', upHandler);
-    };
+  const updatePercent = (e: MouseEvent) => {
+    let x = e.clientX - rect.left
+    const dragRange = 0.8 * rect.width // 80% of the line width
+    const dragStart = 0.1 * rect.width // Start from 10% of the line width
 
-    document.addEventListener('mousemove', moveHandler);
-    document.addEventListener('mouseup', upHandler);
-    updatePercent(event);
-};
+    // Clamp x within the draggable range
+    x = Math.max(Math.min(x, dragStart + dragRange), dragStart)
+
+    // Calculate the percent within the 10% to 90% range
+    percent.value = ((x - dragStart) / dragRange) * 100
+  }
+
+  const moveHandler = (e: MouseEvent) => {
+    if (e.clientX > initialX) {
+      progressOnIncrease(((e.clientX - initialX) / rect.width) * 100)
+    } else {
+      progressOnDecline(((initialX - e.clientX) / rect.width) * 100)
+    }
+    updatePercent(e) // Update the percent directly based on the cursor position
+  }
+
+  const upHandler = () => {
+    document.removeEventListener('mousemove', moveHandler)
+    document.removeEventListener('mouseup', upHandler)
+  }
+
+  document.addEventListener('mousemove', moveHandler)
+  document.addEventListener('mouseup', upHandler)
+  updatePercent(event)
+}
 
 const { locale, availableLocales } = useI18n()
 const { counterIncrease } = useCounterStore()
@@ -159,13 +159,13 @@ const languages = ref(['en'])
 const appVersion = ref('Unknown')
 
 onMounted((): void => {
-    languages.value = availableLocales
+  languages.value = availableLocales
 
-    // Get application version from package.json version string (Using IPC communication)
-    window.mainApi.receive('msgReceivedVersion', (event: Event, version: string) => {
-        appVersion.value = version
-    })
-    window.mainApi.send('msgRequestGetVersion')
+  // Get application version from package.json version string (Using IPC communication)
+  window.mainApi.receive('msgReceivedVersion', (event: Event, version: string) => {
+    appVersion.value = version
+  })
+  window.mainApi.send('msgRequestGetVersion')
 })
 
 const height = ref(200)
@@ -175,46 +175,46 @@ let initialHeight = 0 // 开始调整时的元素高度
 const isDragging = ref(false)
 
 const startResize = (event: MouseEvent) => {
-    initialY = event.clientY
-    initialHeight = height.value
-    isDragging.value = true // 设置为正在拖动
+  initialY = event.clientY
+  initialHeight = height.value
+  isDragging.value = true // 设置为正在拖动
 
-    document.addEventListener('mousemove', onResize)
-    document.addEventListener('mouseup', stopResize)
+  document.addEventListener('mousemove', onResize)
+  document.addEventListener('mouseup', stopResize)
 }
 
 const onResize = (event: MouseEvent) => {
-    const deltaY = initialY - event.clientY // 计算鼠标的垂直移动距离
-    height.value = initialHeight + deltaY // 调整元素的高度
+  const deltaY = initialY - event.clientY // 计算鼠标的垂直移动距离
+  height.value = initialHeight + deltaY // 调整元素的高度
 }
 
 const stopResize = () => {
-    document.removeEventListener('mousemove', onResize)
-    document.removeEventListener('mouseup', stopResize)
-    isDragging.value = false // 设置为停止拖动
+  document.removeEventListener('mousemove', onResize)
+  document.removeEventListener('mouseup', stopResize)
+  isDragging.value = false // 设置为停止拖动
 }
 
 const terminalOutput = ref<string[]>([])
 
 // 定义一个方法,允许在组件内部推送内容到terminalOutput数组
 const terminalPush = (content: string) => {
-    terminalOutput.value.push(content)
+  terminalOutput.value.push(content)
 }
 
 function doEvery500ms() {
-    if (optionsComPorts.value.length < 2) {
-        getComPort()
-    } else {
-        if (optionsComPorts.value[1] === '') {
-            getComPort()
-        }
+  if (optionsComPorts.value.length < 2) {
+    getComPort()
+  } else {
+    if (optionsComPorts.value[1] === '') {
+      getComPort()
     }
+  }
 }
 
 // 定义接口来表述选项
 interface Option {
-    label: string
-    value: string
+  label: string
+  value: string
 }
 
 const optionsComPorts = ref<string[]>([])
@@ -224,45 +224,45 @@ const selected1 = ref(optionsComPorts.value[0])
 
 // 监听下拉框值的变化
 watch([selected1], ([newVal1], [oldVal1]) => {
-    submitComDataIsCompleted = false
-    updateSelectAreaTipWord([newVal1])
+  submitComDataIsCompleted = false
+  updateSelectAreaTipWord([newVal1])
 })
 
 const updateSelectAreaTipWord = ([newVal1]) => {
-    if (submitComDataIsProcessing.value === true) {
-        updateSelectAreaTipWordIfSubmitIsProcessing()
+  if (submitComDataIsProcessing.value === true) {
+    updateSelectAreaTipWordIfSubmitIsProcessing()
+  } else {
+    if (submitComDataIsCompleted === true) {
+      updateSelectAreaTipWordIfSubmitIsCompleted()
     } else {
-        if (submitComDataIsCompleted === true) {
-            updateSelectAreaTipWordIfSubmitIsCompleted()
-        } else {
-            if (newVal1 !== ' ') {
-                selectComAreaTipWord.value = tipWordRememberToClickOK
-                selectComAreaTipWordColor.value = selectComAreaTipWordColorBlack
-                selectComAreaIsCorrect = true
-            } else {
-                selectComAreaTipWord.value = tipWordEmptyValueExpection
-                selectComAreaTipWordColor.value = selectComAreaTipWordColorRed
-                selectComAreaIsCorrect = false
-            }
-        }
+      if (newVal1 !== ' ') {
+        selectComAreaTipWord.value = tipWordRememberToClickOK
+        selectComAreaTipWordColor.value = selectComAreaTipWordColorBlack
+        selectComAreaIsCorrect = true
+      } else {
+        selectComAreaTipWord.value = tipWordEmptyValueExpection
+        selectComAreaTipWordColor.value = selectComAreaTipWordColorRed
+        selectComAreaIsCorrect = false
+      }
     }
+  }
 }
 
 const updateSelectAreaTipWordIfSubmitIsProcessing = () => {
-    selectComAreaTipWord.value = tipWordProgramIsSubmiting
+  selectComAreaTipWord.value = tipWordProgramIsSubmiting
 }
 
 const updateSelectAreaTipWordIfSubmitIsCompleted = () => {
-    if (submitComDataIfSuccess === false) {
-        selectComAreaTipWord.value = tipWordSubmitFailed
-        selectComAreaTipWordColor.value = selectComAreaTipWordColorRed
-    } else {
-        selectComAreaTipWord.value = tipWordSubmitSuccess
-        selectComAreaTipWordColor.value = selectComAreaTipWordColorBlack
-    }
+  if (submitComDataIfSuccess === false) {
+    selectComAreaTipWord.value = tipWordSubmitFailed
+    selectComAreaTipWordColor.value = selectComAreaTipWordColorRed
+  } else {
+    selectComAreaTipWord.value = tipWordSubmitSuccess
+    selectComAreaTipWordColor.value = selectComAreaTipWordColorBlack
+  }
 }
 
-var selectComAreaIsCorrect: Boolean = true
+let selectComAreaIsCorrect: Boolean = true
 
 const tipWordSubmitFailed = '配置失败!'
 const tipWordSubmitSuccess = '配置成功!'
@@ -275,19 +275,18 @@ const selectComAreaTipWordColorBlack = 'black'
 const selectComAreaTipWordColorRed = 'red'
 const selectComAreaTipWordColor = ref(selectComAreaTipWordColorBlack)
 
-
 const comSenderReceiverSubmit = () => {
-    if (selectComAreaIsCorrect === true) {
-        doSubmitComConfigData()
-    }
+  if (selectComAreaIsCorrect === true) {
+    doSubmitComConfigData()
+  }
 }
 
 const doSubmitComConfigData = () => {
-    updateSelectAreaTipWordIfSubmitIsProcessing()
-    submitComDataIsProcessing.value = true
-    submitComDataIsCompleted = false
-    submitComDataIfSuccess = false
-    submitConfigCNC(selected1.value)
+  updateSelectAreaTipWordIfSubmitIsProcessing()
+  submitComDataIsProcessing.value = true
+  submitComDataIsCompleted = false
+  submitComDataIfSuccess = false
+  submitConfigCNC(selected1.value)
 }
 
 // 创建一个响应式的引用
@@ -298,495 +297,502 @@ let timer: number | null = null
 
 // 观察变量的变化
 watch(submitComDataIsProcessing, (newVal, oldVal) => {
-    if (newVal === true) {
-        // 当变量变为 true,启动一个计时器
-        timer = window.setTimeout(() => {
-            submitComDataIsCompleted = true
-            submitComDataIfSuccess = false
-            submitIsAllCompleted()
-            terminalPush('[衰减器] 配置失败:后台服务响应超时,建议重启程序')
-        }, 6000) // 5000毫秒 = 5秒
-    } else if (newVal === false && timer !== null) {
-        // 当变量变为 false,清除计时器
-        window.clearTimeout(timer)
-        timer = null
-    }
+  if (newVal === true) {
+    // 当变量变为 true,启动一个计时器
+    timer = window.setTimeout(() => {
+      submitComDataIsCompleted = true
+      submitComDataIfSuccess = false
+      submitIsAllCompleted()
+      terminalPush('[衰减器] 配置失败:后台服务响应超时,建议重启程序')
+    }, 6000) // 5000毫秒 = 5秒
+  } else if (newVal === false && timer !== null) {
+    // 当变量变为 false,清除计时器
+    window.clearTimeout(timer)
+    timer = null
+  }
 })
 
 // 确保在组件卸载时清除计时器,防止内存泄漏
 onBeforeUnmount(() => {
-    if (timer !== null) {
-        window.clearTimeout(timer)
-    }
+  if (timer !== null) {
+    window.clearTimeout(timer)
+  }
 })
 
-var submitComDataIsCompleted: Boolean = false
-var submitComDataIfSuccess: Boolean = false
+let submitComDataIsCompleted: Boolean = false
+let submitComDataIfSuccess: Boolean = false
 
 const submitIsAllCompleted = () => {
-    if (submitComDataIsCompleted === true) {
-        submitComDataIsProcessing.value = false
-        updateSelectAreaTipWordIfSubmitIsCompleted()
-    }
+  if (submitComDataIsCompleted === true) {
+    submitComDataIsProcessing.value = false
+    updateSelectAreaTipWordIfSubmitIsCompleted()
+  }
 }
 
 const setCNCValue = async (targetValueCNC: string) => {
-    try {
-        terminalPush(`[衰减器] 尝试将衰减设置为 ${targetValueCNC} dB`)
-        const params = {
-            value: targetValueCNC,
-        }
-        const url = `http://127.0.0.1:${httpServerPort.value}/serial_device/cnc/set`
-        const response = await axios.get(url, { params })
-        const ifSetSuccess = response.data
-        if (ifSetSuccess === 'True' || ifSetSuccess === 'true') {
-            terminalPush(`[衰减器] 衰减成功调整为 ${targetValueCNC} dB`)
-        } else {
-            terminalPush(`[衰减器] 衰减调整失败`)
-        }
-    } catch (error) {
-        terminalPush(`[衰减器] 衰减调整失败:${error}`)
+  try {
+    terminalPush(`[衰减器] 尝试将衰减设置为 ${targetValueCNC} dB`)
+    const params = {
+      value: targetValueCNC
+    }
+    const url = `http://127.0.0.1:${httpServerPort.value}/serial_device/cnc/set`
+    const response = await axios.get(url, { params })
+    const ifSetSuccess = response.data
+    if (ifSetSuccess === 'True' || ifSetSuccess === 'true') {
+      terminalPush(`[衰减器] 衰减成功调整为 ${targetValueCNC} dB`)
+    } else {
+      terminalPush(`[衰减器] 衰减调整失败`)
     }
+  } catch (error) {
+    terminalPush(`[衰减器] 衰减调整失败:${error}`)
+  }
 }
 
 const submitConfigCNC = async (name: string) => {
-    try {
-        terminalPush(`[衰减器] 尝试将端口配置为 [${name}]`)
-        const params = {
-            port_name: name,
-        }
-        const url = `http://127.0.0.1:${httpServerPort.value}/serial_device/cnc/config_connection`
-        const response = await axios.get(url, { params })
-        const ifSuccess = response.data
-        if (ifSuccess === 'True' || ifSuccess === 'true') {
-            submitComDataIfSuccess = true
-            terminalPush(`[衰减器] 配置成功:配置为 [${name}]`)
-        } else {
-            submitComDataIfSuccess = false
-            terminalPush(
-                `[衰减器] 配置失败:尝试配置 [${name}] 时出现错误`
-            )
-            terminalPush(`[衰减器] 配置失败:目标端口发送错误或无响应`)
-        }
-    } catch (error) {
-        console.error('An error occurred while fetching the data:', error)
-        submitComDataIfSuccess = false
-        terminalPush(`[衰减器] 配置失败:发生未知错误`)
-        terminalPush(`[衰减器] 错误信息:${error}`)
-    } finally {
-        submitComDataIsCompleted = true
-        submitIsAllCompleted()
+  try {
+    terminalPush(`[衰减器] 尝试将端口配置为 [${name}]`)
+    const params = {
+      port_name: name
+    }
+    const url = `http://127.0.0.1:${httpServerPort.value}/serial_device/cnc/config_connection`
+    const response = await axios.get(url, { params })
+    const ifSuccess = response.data
+    if (ifSuccess === 'True' || ifSuccess === 'true') {
+      submitComDataIfSuccess = true
+      terminalPush(`[衰减器] 配置成功:配置为 [${name}]`)
+    } else {
+      submitComDataIfSuccess = false
+      terminalPush(`[衰减器] 配置失败:尝试配置 [${name}] 时出现错误`)
+      terminalPush(`[衰减器] 配置失败:目标端口发送错误或无响应`)
     }
+  } catch (error) {
+    console.error('An error occurred while fetching the data:', error)
+    submitComDataIfSuccess = false
+    terminalPush(`[衰减器] 配置失败:发生未知错误`)
+    terminalPush(`[衰减器] 错误信息:${error}`)
+  } finally {
+    submitComDataIsCompleted = true
+    submitIsAllCompleted()
+  }
 }
-
 </script>
 
 <template>
-    <div class="list-page-2">
-        <div class="page">
-            <div class="page-up">
-                <div class="control-zone">
-                    <div class="title">
-                        <div class="text">衰减器: {{ cncValueString }} dB</div>
-                    </div>
-                    <div class="config-zone">
-                        <div class="adjust-line" @mouseup="progressMouseUp" @mousedown="progressMouseDown"
-                            @mouseover="progressMouseOver" @mouseleave="progressMouseLeave">
-                            <Progress :width="'80%'" :percent="percent" :stroke-width="progressWidth" :stroke-color="{
-                                '0%': '#108ee9',
-                                '100%': '#87d068',
-                                direction: 'right'
-                            }" :show-info="false" />
-                        </div>
-                        <div class="btn-zone">
-                            <div @click="setCNCValue(cncValueString)" class="btn">
-                                <div class="text">应用配置</div>
-                            </div>
-                        </div>
-                    </div>
-                </div>
+  <div class="list-page-2">
+    <div class="page">
+      <div class="page-up">
+        <div class="control-zone">
+          <div class="title">
+            <div class="text">衰减器: {{ cncValueString }} dB</div>
+          </div>
+          <div class="config-zone">
+            <div
+              class="adjust-line"
+              @mouseup="progressMouseUp"
+              @mousedown="progressMouseDown"
+              @mouseover="progressMouseOver"
+              @mouseleave="progressMouseLeave"
+            >
+              <Progress
+                :width="'80%'"
+                :percent="percent"
+                :stroke-width="progressWidth"
+                :stroke-color="{
+                  '0%': '#108ee9',
+                  '100%': '#87d068',
+                  direction: 'right'
+                }"
+                :show-info="false"
+              />
+            </div>
+            <div class="btn-zone">
+              <div @click="setCNCValue(cncValueString)" class="btn">
+                <div class="text">应用配置</div>
+              </div>
             </div>
-            <div class="page-down">
-                <div class="top-bar">
-                    <div @mousedown="startResize" class="resizer" :class="{ dragging: isDragging }">
-                        <div class="flag-bar">
-                            <div class="text">测试输出终端</div>
-                        </div>
-                        <div class="middle"></div>
-                        <div class="last-time">
-                            <div class="text"></div>
-                        </div>
-                    </div>
+          </div>
+        </div>
+      </div>
+      <div class="page-down">
+        <div class="top-bar">
+          <div class="resizer" :class="{ dragging: isDragging }" @mousedown="startResize">
+            <div class="flag-bar">
+              <div class="text">测试输出终端</div>
+            </div>
+            <div class="middle"></div>
+            <div class="last-time">
+              <div class="text"></div>
+            </div>
+          </div>
+        </div>
+        <div :style="{ height: height + 'px' }" class="content">
+          <div class="terminal-zone">
+            <div class="terminal-left">
+              <div class="select-area">
+                <div class="text">衰减器端口</div>
+                <div class="select-box">
+                  <select v-model="selected1" class="select-button">
+                    <option v-for="option in optionsComPorts" :key="option" :value="option">
+                      {{ option }}
+                    </option>
+                  </select>
                 </div>
-                <div :style="{ height: height + 'px' }" class="content">
-                    <div class="terminal-zone">
-                        <div class="terminal-left">
-                            <div class="select-area">
-                                <div class="text">衰减器端口</div>
-                                <div class="select-box">
-                                    <select v-model="selected1" class="select-button">
-                                        <option v-for="option in optionsComPorts" :key="option" :value="option">
-                                            {{ option }}
-                                        </option>
-                                    </select>
-                                </div>
-                            </div>
-                            <div class="select-area">
-                                <button @click="comSenderReceiverSubmit" class="button">确定</button>
-                                <div :style="{ color: selectComAreaTipWordColor }" class="tip">{{
-                                    selectComAreaTipWord
-                                }}</div>
-                            </div>
-                        </div>
-                        <div class="terminal-dividing"></div>
-                        <div class="terminal-right">
-                            <div class="terminal-output">
-                                <div class="text" v-for="(text, index) in terminalOutput" :key="index">
-                                    {{ text }}
-                                </div>
-                            </div>
-                        </div>
-                    </div>
+              </div>
+              <div class="select-area">
+                <button @click="comSenderReceiverSubmit" class="button">确定</button>
+                <div :style="{ color: selectComAreaTipWordColor }" class="tip">{{
+                  selectComAreaTipWord
+                }}</div>
+              </div>
+            </div>
+            <div class="terminal-dividing"></div>
+            <div class="terminal-right">
+              <div class="terminal-output">
+                <div class="text" v-for="(text, index) in terminalOutput" :key="index">
+                  {{ text }}
                 </div>
+              </div>
             </div>
+          </div>
         </div>
+      </div>
     </div>
+  </div>
 </template>
 
 <style lang="scss">
 .list-page-2 {
+  width: 100%;
+  height: 100%;
+  overflow-x: hidden;
+  overflow-y: hidden;
+
+  .page {
     width: 100%;
     height: 100%;
     overflow-x: hidden;
     overflow-y: hidden;
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-start;
+    align-items: flex-start;
+    background-color: white;
+    padding: 0;
+  }
+
+  .page-up {
+    width: 100%;
+    height: 100%;
+    flex: 1;
+    overflow-x: hidden;
+    overflow-y: auto;
+    display: flex;
+    flex-direction: row;
+    justify-content: flex-start;
+    align-items: flex-start;
+
+    .control-zone {
+      width: 100%;
+      height: 100%;
+      display: flex;
+      flex-direction: column;
+      justify-content: flex-start;
+      align-items: center;
+      margin-top: 15vh;
+    }
+
+    .title {
+      font-size: 35px;
+      font-weight: 500;
+      margin-bottom: 0;
+    }
 
-    .page {
+    .config-zone {
+      width: 100%;
+      display: flex;
+      flex-direction: column;
+      justify-content: flex-start;
+      align-items: center;
+    }
+
+    .adjust-line {
+      width: 100%;
+      height: auto;
+      padding-top: 6vh;
+      padding-bottom: 6vh;
+      padding-left: 0;
+      padding-right: 0;
+      display: flex;
+      flex-direction: column;
+      justify-content: flex-start;
+      align-items: center;
+    }
+
+    .btn-zone {
+      display: flex;
+      flex-direction: row;
+      justify-content: flex-start;
+      align-items: center;
+
+      .btn {
+        width: 105px;
+        height: 35px;
+        overflow: hidden;
+        border: 1px solid #dfdfdf;
+        border-radius: 5px;
+        display: flex;
+        flex-direction: row;
+        justify-content: center;
+        align-items: center;
+        color: black;
+        position: relative;
+        cursor: pointer;
+        transition: all 0.1s cubic-bezier(0.78, 0.14, 0.15, 0.86);
+      }
+
+      .btn::after {
+        content: '';
+        position: absolute;
         width: 100%;
         height: 100%;
-        overflow-x: hidden;
-        overflow-y: hidden;
-        display: flex;
-        flex-direction: column;
-        justify-content: flex-start;
-        align-items: flex-start;
+        background-color: rgba(255, 255, 255, 1);
+        z-index: 3;
+        transition: all 0.1s cubic-bezier(0.78, 0.14, 0.15, 0.86);
+      }
+
+      .btn:hover {
+        background: linear-gradient(to right, #108ee9 0%, #87d068 100%);
         background-color: white;
-        padding: 0;
-    }
+        color: white;
+      }
 
-    .page-up {
+      .btn:hover::after {
+        content: '';
+        position: absolute;
         width: 100%;
         height: 100%;
-        flex: 1;
-        overflow-x: hidden;
+        background-color: rgba(255, 255, 255, 0);
+        z-index: 3;
+      }
+
+      .text {
+        font-size: 20px;
+        font-weight: 400;
+        z-index: 5;
+      }
+    }
+  }
+
+  .page-down {
+    width: 100%;
+    // height: 200px;
+    background-color: white;
+    border-top: 1px solid #dfdfdf;
+    position: relative;
+
+    .content {
+      width: 100%;
+      height: 100%;
+      overflow: hidden;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+
+    .top-bar {
+      height: auto;
+      overflow-y: hidden;
+
+      .resizer {
+        cursor: ns-resize; // 调整为垂直调整的鼠标样式
+        background-color: white;
+        height: 22px; // 调整为较小的高度
+        width: 100%; // 确保宽度与父 div 一致
+        position: absolute;
+        top: 0;
+        border-top: 1px solid #dfdfdf;
+        border-bottom: 1px solid #dfdfdf;
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        justify-content: flex-start;
         overflow-y: hidden;
+      }
+
+      .text {
+        color: black;
+        font-size: 15px;
+      }
+
+      .flag-bar {
+        background-color: #2673e3;
+        width: 125px;
+        height: 32px;
+        padding-left: 10px;
         display: flex;
         flex-direction: row;
+        align-items: center;
         justify-content: flex-start;
-        align-items: flex-start;
+        clip-path: polygon(0% 0%, 100% 0%, 90% 100%, 0% 100%);
 
-        .control-zone {
-            width: 100%;
-            height: 100%;
-            display: flex;
-            flex-direction: column;
-            justify-content: flex-start;
-            align-items: center;
-            margin-top: 15vh;
+        .text {
+          color: white;
         }
+      }
 
-        .title {
-            font-size: 35px;
-            font-weight: 500;
-            margin-bottom: 0;
-        }
+      .middle {
+        flex: 1;
+        background-color: red;
+      }
 
-        .config-zone {
-            width: 100%;
-            display: flex;
-            flex-direction: column;
-            justify-content: flex-start;
-            align-items: center;
-        }
+      .last-time {
+        width: auto;
+        height: 100%;
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        justify-content: flex-start;
+        padding-right: 15px;
+        font-weight: 500;
+      }
+
+      .dragging {
+        border-top: 6px solid #2673e3;
+        height: 34px;
+      }
+    }
+  }
 
-        .adjust-line {
-            width: 100%;
-            height: auto;
-            padding-top: 6vh;
-            padding-bottom: 6vh;
-            padding-left: 0;
-            padding-right: 0;
-            display: flex;
-            flex-direction: column;
-            justify-content: flex-start;
-            align-items: center;
-        }
+  .terminal-zone {
+    width: 100%;
+    height: 100%;
+    overflow-x: hidden;
+    overflow-y: hidden;
+    display: flex;
+    flex-direction: row;
+    justify-content: flex-start;
+    align-items: flex-start;
+
+    .terminal-right {
+      flex: 5;
+      height: 100%;
+      overflow-x: hidden;
+      overflow-y: hidden;
+    }
 
-        .btn-zone {
-            display: flex;
-            flex-direction: row;
-            justify-content: flex-start;
-            align-items: center;
-
-            .btn {
-                width: 105px;
-                height: 35px;
-                overflow: hidden;
-                border: 1px solid #dfdfdf;
-                border-radius: 5px;
-                display: flex;
-                flex-direction: row;
-                justify-content: center;
-                align-items: center;
-                color: black;
-                position: relative;
-                cursor: pointer;
-                transition: all .1s cubic-bezier(0.78, 0.14, 0.15, 0.86);
-            }
-
-            .btn::after {
-                content: "";
-                position: absolute;
-                width: 100%;
-                height: 100%;
-                background-color: rgba(255, 255, 255, 1);
-                z-index: 3;
-                transition: all .1s cubic-bezier(0.78, 0.14, 0.15, 0.86);
-            }
-
-            .btn:hover {
-                background: linear-gradient(to right, #108ee9 0%, #87d068 100%);
-                background-color: white;
-                color: white;
-            }
-
-            .btn:hover::after {
-                content: "";
-                position: absolute;
-                width: 100%;
-                height: 100%;
-                background-color: rgba(255, 255, 255, 0);
-                z-index: 3;
-            }
-
-            .text {
-                font-size: 20px;
-                font-weight: 400;
-                z-index: 5;
-            }
-        }
+    .terminal-output {
+      height: 100%;
+      overflow-x: auto;
+      overflow-y: scroll;
+      background-color: white;
+      padding: 25px;
+      padding-top: 35px;
     }
 
-    .page-down {
-        width: 100%;
-        // height: 200px;
-        background-color: white;
-        border-top: 1px solid #dfdfdf;
-        position: relative;
+    .terminal-dividing {
+      width: 0px;
+      height: 100%;
+      border: 0;
+      border-right: 1px solid #dfdfdf;
+    }
 
-        .content {
-            width: 100%;
-            height: 100%;
-            overflow: hidden;
-            display: flex;
-            align-items: center;
-            justify-content: center;
-        }
+    .terminal-left {
+      flex: 3;
+      height: 100%;
+      background-color: white;
+      overflow-y: auto;
+      overflow-x: auto;
+      display: flex;
+      flex-direction: column;
+      justify-content: flex-start;
+      align-items: flex-start;
+      padding: 25px;
+      padding-top: 40px;
+
+      .text {
+        color: black;
+      }
+
+      .title {
+        font-size: 20px;
+        font-weight: 500;
+        margin-top: 15px;
+        margin-bottom: 10px;
+      }
+
+      .line {
+        width: 100%;
+        height: auto;
+        display: flex;
+        flex-direction: column;
+        justify-content: flex-start;
+        align-items: flex-start;
+        margin-bottom: 6px;
 
-        .top-bar {
-            height: auto;
-            overflow-y: hidden;
-
-            .resizer {
-                cursor: ns-resize; // 调整为垂直调整的鼠标样式
-                background-color: white;
-                height: 22px; // 调整为较小的高度
-                width: 100%; // 确保宽度与父 div 一致
-                position: absolute;
-                top: 0;
-                border-top: 1px solid #dfdfdf;
-                border-bottom: 1px solid #dfdfdf;
-                display: flex;
-                flex-direction: row;
-                align-items: center;
-                justify-content: flex-start;
-                overflow-y: hidden;
-            }
-
-            .text {
-                color: black;
-                font-size: 15px;
-            }
-
-            .flag-bar {
-                background-color: #2673e3;
-                width: 125px;
-                height: 32px;
-                padding-left: 10px;
-                display: flex;
-                flex-direction: row;
-                align-items: center;
-                justify-content: flex-start;
-                clip-path: polygon(0% 0%, 100% 0%, 90% 100%, 0% 100%);
-
-                .text {
-                    color: white;
-                }
-            }
-
-            .middle {
-                flex: 1;
-                background-color: red;
-            }
-
-            .last-time {
-                width: auto;
-                height: 100%;
-                display: flex;
-                flex-direction: row;
-                align-items: center;
-                justify-content: flex-start;
-                padding-right: 15px;
-                font-weight: 500;
-            }
-
-            .dragging {
-                border-top: 6px solid #2673e3;
-                height: 34px;
-            }
+        .item {
+          font-size: 18px;
+          font-weight: 500;
+          flex: 1;
+          margin-bottom: 10px;
         }
-    }
+      }
 
-    .terminal-zone {
-        width: 100%;
-        height: 100%;
-        overflow-x: hidden;
-        overflow-y: hidden;
+      .select-area {
         display: flex;
         flex-direction: row;
         justify-content: flex-start;
-        align-items: flex-start;
+        align-items: center;
+
+        .text {
+          font-size: 20px;
+          font-weight: 500;
+        }
+
+        .select-box {
+          margin-top: 6px;
+          margin-bottom: 6px;
+
+          .select-button {
+            padding: 3px;
+            padding-left: 9px;
+            width: 100px;
+            border: 1px solid rgba(0, 0, 0, 0.35);
+            border-radius: 5px;
+            margin-left: 10px;
+            margin-right: 20px;
+          }
+        }
 
-        .terminal-right {
-            flex: 5;
-            height: 100%;
-            overflow-x: hidden;
-            overflow-y: hidden;
+        .button {
+          margin-top: 12px;
+          font-size: 18px;
+          border: 1px solid rgba(0, 0, 0, 0.35);
+          border-radius: 5px;
+          padding: 3px;
+          padding-left: 8px;
+          padding-right: 8px;
         }
 
-        .terminal-output {
-            height: 100%;
-            overflow-x: auto;
-            overflow-y: scroll;
-            background-color: white;
-            padding: 25px;
-            padding-top: 35px;
+        .button:hover {
+          background-color: #2673e3;
+          color: white;
+          border: 1px solid rgba($color: #2673e3, $alpha: 1);
         }
 
-        .terminal-dividing {
-            width: 0px;
-            height: 100%;
-            border: 0;
-            border-right: 1px solid #dfdfdf;
+        .button:active {
+          background-color: rgba($color: #2673e3, $alpha: 0.5);
+          border: 1px solid rgba($color: #2673e3, $alpha: 0.5);
+          color: white;
         }
 
-        .terminal-left {
-            flex: 3;
-            height: 100%;
-            background-color: white;
-            overflow-y: auto;
-            overflow-x: auto;
-            display: flex;
-            flex-direction: column;
-            justify-content: flex-start;
-            align-items: flex-start;
-            padding: 25px;
-            padding-top: 40px;
-
-            .text {
-                color: black;
-            }
-
-            .title {
-                font-size: 20px;
-                font-weight: 500;
-                margin-top: 15px;
-                margin-bottom: 10px;
-            }
-
-            .line {
-                width: 100%;
-                height: auto;
-                display: flex;
-                flex-direction: column;
-                justify-content: flex-start;
-                align-items: flex-start;
-                margin-bottom: 6px;
-
-                .item {
-                    font-size: 18px;
-                    font-weight: 500;
-                    flex: 1;
-                    margin-bottom: 10px;
-                }
-            }
-
-            .select-area {
-                display: flex;
-                flex-direction: row;
-                justify-content: flex-start;
-                align-items: center;
-
-                .text {
-                    font-size: 20px;
-                    font-weight: 500;
-                }
-
-                .select-box {
-                    margin-top: 6px;
-                    margin-bottom: 6px;
-
-                    .select-button {
-                        padding: 3px;
-                        padding-left: 9px;
-                        width: 100px;
-                        border: 1px solid rgba(0, 0, 0, 0.35);
-                        border-radius: 5px;
-                        margin-left: 10px;
-                        margin-right: 20px;
-                    }
-                }
-
-                .button {
-                    margin-top: 12px;
-                    font-size: 18px;
-                    border: 1px solid rgba(0, 0, 0, 0.35);
-                    border-radius: 5px;
-                    padding: 3px;
-                    padding-left: 8px;
-                    padding-right: 8px;
-                }
-
-                .button:hover {
-                    background-color: #2673e3;
-                    color: white;
-                    border: 1px solid rgba($color: #2673e3, $alpha: 1);
-                }
-
-                .button:active {
-                    background-color: rgba($color: #2673e3, $alpha: 0.5);
-                    border: 1px solid rgba($color: #2673e3, $alpha: 0.5);
-                    color: white;
-                }
-
-                .tip {
-                    margin-top: 12px;
-                    font-size: 18px;
-                    margin-left: 16px;
-                    font-weight: 500;
-                }
-
-            }
+        .tip {
+          margin-top: 12px;
+          font-size: 18px;
+          margin-left: 16px;
+          font-weight: 500;
         }
+      }
     }
+  }
 }
 </style>

+ 649 - 32
client/src/renderer/pages/ListNum/ListNum3.vue

@@ -1,35 +1,336 @@
 <script setup lang="tsx">
-import { useI18n } from 'vue-i18n'
-import { useTheme } from 'vuetify'
-import { openExternal } from '@/renderer/utils'
-import { useCounterStore } from '@/renderer/store/counter'
-import { storeToRefs } from 'pinia'
-import { onMounted, ref } from 'vue'
-
-const { locale, availableLocales } = useI18n()
-const { counterIncrease } = useCounterStore()
-const { counter } = storeToRefs(useCounterStore())
-const theme = useTheme()
-const languages = ref(['en'])
-const appVersion = ref('Unknown')
-
-onMounted((): void => {
-  languages.value = availableLocales
-
-  // Get application version from package.json version string (Using IPC communication)
-  window.mainApi.receive('msgReceivedVersion', (event: Event, version: string) => {
-    appVersion.value = version
+import { onMounted, ref, watch, onBeforeUnmount } from 'vue'
+import axios from 'axios'
+
+import Progress from '@/renderer/pages/ListNum/ColorfulProgress.vue'
+
+const httpServerPort = ref<number | null>(null)
+
+onMounted(() => {
+  window.mainApi.send('msgRequestGetServerPort')
+
+  window.mainApi.receive('msgReceivedServerPort', (event, port) => {
+    httpServerPort.value = port
   })
-  window.mainApi.send('msgRequestGetVersion')
 })
+
+class ProgressBarHandler {
+  fixedNumber = 3
+  percent = ref(0)
+  maxNumber = ref(100)
+  progressWidth = ref(10)
+  targetValue = ref(0.0)
+  targetValueString = ref(this.percent.value.toFixed(this.fixedNumber))
+
+  constructor(setMaxNumber: number, setDefaultPercent: number) {
+    this.maxNumber.value = setMaxNumber
+    this.percent.value = setDefaultPercent
+    this.updatePercentToTargetValue()
+
+    watch(this.targetValue, (newPercent) => {
+      this.updateTargetValueString(newPercent)
+    })
+
+    watch(this.percent, (newPercent) => {
+      this.updateTargetValue(newPercent)
+    })
+  }
+
+  updatePercentToTargetValue() {
+    this.updateTargetValueString(this.percent.value)
+    this.updateTargetValue(this.targetValue.value)
+  }
+
+  updateTargetValue(newValue: number) {
+    this.targetValue.value = this.processValue(0.01 * this.maxNumber.value * newValue)
+  }
+
+  updateTargetValueString(newValue: number) {
+    this.targetValueString.value = this.processValueStringFormat(newValue)
+  }
+
+  processValue(rawValue: number): number {
+    const processedValue = Math.round(rawValue)
+    return +processedValue.toFixed(this.fixedNumber)
+  }
+
+  processValueStringFormat(num: number): string {
+    const integerPart = Math.floor(num)
+    const decimalPart = num - integerPart
+    const integerString = integerPart < 10 ? '0' + integerPart : '' + integerPart
+    const decimalString = decimalPart.toFixed(this.fixedNumber).slice(2)
+    return integerString + '.' + decimalString
+  }
+
+  progressMouseUp() {
+    this.progressWidth.value = 10
+  }
+
+  progressMouseOver() {
+    this.progressWidth.value = 20
+  }
+
+  progressMouseLeave() {
+    this.progressWidth.value = 10
+  }
+
+  progressOnIncrease(scale: number) {
+    const res = this.percent.value + scale
+    this.percent.value = res > 100 ? 100 : res
+  }
+
+  progressOnDecline(scale: number) {
+    const res = this.percent.value - scale
+    this.percent.value = res < 0 ? 0 : res
+  }
+
+  progressMouseDown(event: MouseEvent) {
+    const adjustLine = event.currentTarget as HTMLElement
+    const rect = adjustLine.getBoundingClientRect()
+    const initialX = event.clientX
+
+    const updatePercent = (e: MouseEvent) => {
+      let x = e.clientX - rect.left
+      const dragRange = 0.8 * rect.width
+      const dragStart = 0.1 * rect.width
+      x = Math.max(Math.min(x, dragStart + dragRange), dragStart)
+      this.percent.value = ((x - dragStart) / dragRange) * 100
+    }
+
+    const moveHandler = (e: MouseEvent) => {
+      if (e.clientX > initialX) {
+        this.progressOnIncrease(((e.clientX - initialX) / rect.width) * 100)
+      } else {
+        this.progressOnDecline(((initialX - e.clientX) / rect.width) * 100)
+      }
+      updatePercent(e)
+    }
+
+    const upHandler = () => {
+      document.removeEventListener('mousemove', moveHandler)
+      document.removeEventListener('mouseup', upHandler)
+    }
+
+    document.addEventListener('mousemove', moveHandler)
+    document.addEventListener('mouseup', upHandler)
+    updatePercent(event)
+  }
+}
+
+const defaultBarHandler100 = 5
+const defaultBarHandler15000 = 0
+const defaultBarHandlerFloat = 0
+
+const BarHandler100 = new ProgressBarHandler(100, defaultBarHandler100)
+const BarHandler15000 = new ProgressBarHandler(150, defaultBarHandler15000)
+const BarHandlerFloat = new ProgressBarHandler(100, defaultBarHandlerFloat)
+
+const getDeviceValue = () => {
+  return computeDeviceValue(
+    BarHandler15000.targetValue.value,
+    BarHandler100.targetValue.value,
+    BarHandlerFloat.targetValue.value
+  )
+}
+
+const computeDeviceValue = (value15000: number, value100: number, valueFloat: number) => {
+  return value15000 * 100 + value100 + valueFloat / 100
+}
+
+const getDeviceValueString = () => {
+  return deviceValue.value.toFixed(3)
+}
+
+const initDeviceValue = () => {
+  return computeDeviceValue(defaultBarHandler15000, defaultBarHandler100, defaultBarHandlerFloat)
+}
+
+const initDeviceValueString = () => {
+  return initDeviceValue().toFixed(3)
+}
+
+const deviceValue = ref(initDeviceValue())
+const deviceValueString = ref(initDeviceValueString())
+
+watch(BarHandler100.targetValueString, (_) => {
+  updateDeviceValueData()
+})
+
+watch(BarHandler15000.targetValueString, (_) => {
+  updateDeviceValueData()
+})
+
+watch(BarHandlerFloat.targetValueString, (_) => {
+  updateDeviceValueData()
+})
+
+const updateDeviceValueData = () => {
+  deviceValue.value = getDeviceValue()
+  deviceValueString.value = getDeviceValueString()
+}
+
+const height = ref(200)
+let initialY = 0 // 初始的鼠标Y坐标
+let initialHeight = 0 // 开始调整时的元素高度
+
+const isDragging = ref(false)
+
+const startResize = (event: MouseEvent) => {
+  initialY = event.clientY
+  initialHeight = height.value
+  isDragging.value = true // 设置为正在拖动
+
+  document.addEventListener('mousemove', onResize)
+  document.addEventListener('mouseup', stopResize)
+}
+
+const onResize = (event: MouseEvent) => {
+  const deltaY = initialY - event.clientY // 计算鼠标的垂直移动距离
+  height.value = initialHeight + deltaY // 调整元素的高度
+}
+
+const stopResize = () => {
+  document.removeEventListener('mousemove', onResize)
+  document.removeEventListener('mouseup', stopResize)
+  isDragging.value = false // 设置为停止拖动
+}
+
+const terminalOutput = ref<string[]>([])
+
+// 定义一个方法,允许在组件内部推送内容到terminalOutput数组
+const terminalPush = (content: string) => {
+  terminalOutput.value.push(content)
+}
+
+const setDeviceValue = async (targetDeviceValue: string) => {
+  try {
+    terminalPush(`[模拟负载] 尝试将负载设置为 ${targetDeviceValue} Ω`)
+    const params = {
+      set_resistance: targetDeviceValue
+    }
+    const url = `http://127.0.0.1:${httpServerPort.value}/instrument/analog_electronic_load/set_resistance`
+    const response = await axios.get(url, { params })
+    const ifSetSuccess = response.data
+    if (ifSetSuccess === 'True' || ifSetSuccess === 'true') {
+      terminalPush(`[模拟负载] 负载成功调整为 ${targetDeviceValue} Ω`)
+    } else {
+      terminalPush(`[模拟负载] 负载调整失败`)
+    }
+  } catch (error) {
+    terminalPush(`[模拟负载] 负载调整失败:${error}`)
+  }
+}
+
+const updateDeviceValue = () => {
+  setDeviceValue(deviceValueString.value)
+}
 </script>
 
 <template>
   <div class="list-page-3">
     <div class="page">
-      <div class="card">
-        <div class="title">帮助文档</div>
-        <div class="text"> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa </div>
+      <div class="page-up">
+        <div class="control-zone">
+          <div class="title">
+            <div class="text">机上模拟负载: {{ deviceValueString }} Ω</div>
+          </div>
+          <div class="config-zone">
+            <div class="text-line">调整:个位 / 十位</div>
+            <div
+              class="adjust-line"
+              @mouseup="BarHandler100.progressMouseUp"
+              @mousedown="BarHandler100.progressMouseDown"
+              @mouseover="BarHandler100.progressMouseOver"
+              @mouseleave="BarHandler100.progressMouseLeave"
+            >
+              <Progress
+                :width="'80%'"
+                :percent="BarHandler100.percent.value"
+                :stroke-width="BarHandler100.progressWidth.value"
+                :stroke-color="{
+                  '0%': '#108ee9',
+                  '100%': '#87d068',
+                  direction: 'right'
+                }"
+                :show-info="false"
+              />
+            </div>
+            <div class="text-line">调整:小数位</div>
+            <div
+              class="adjust-line"
+              @mouseup="BarHandlerFloat.progressMouseUp"
+              @mousedown="BarHandlerFloat.progressMouseDown"
+              @mouseover="BarHandlerFloat.progressMouseOver"
+              @mouseleave="BarHandlerFloat.progressMouseLeave"
+            >
+              <Progress
+                :width="'80%'"
+                :percent="BarHandlerFloat.percent.value"
+                :stroke-width="BarHandlerFloat.progressWidth.value"
+                :stroke-color="{
+                  '0%': '#108ee9',
+                  '100%': '#87d068',
+                  direction: 'right'
+                }"
+                :show-info="false"
+              />
+            </div>
+            <div class="text-line">调整:百位 / 千位</div>
+            <div
+              class="adjust-line"
+              @mouseup="BarHandler15000.progressMouseUp"
+              @mousedown="BarHandler15000.progressMouseDown"
+              @mouseover="BarHandler15000.progressMouseOver"
+              @mouseleave="BarHandler15000.progressMouseLeave"
+            >
+              <Progress
+                :width="'80%'"
+                :percent="BarHandler15000.percent.value"
+                :stroke-width="BarHandler15000.progressWidth.value"
+                :stroke-color="{
+                  '0%': '#108ee9',
+                  '100%': '#87d068',
+                  direction: 'right'
+                }"
+                :show-info="false"
+              />
+            </div>
+            <div class="btn-zone">
+              <div @click="updateDeviceValue" class="btn">
+                <div class="text">应用配置</div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="page-down">
+        <div class="top-bar">
+          <div @mousedown="startResize" class="resizer" :class="{ dragging: isDragging }">
+            <div class="flag-bar">
+              <div class="text">测试输出终端</div>
+            </div>
+            <div class="middle"></div>
+            <div class="last-time">
+              <div class="text"></div>
+            </div>
+          </div>
+        </div>
+        <div :style="{ height: height + 'px' }" class="content">
+          <div class="terminal-zone">
+            <!-- <div class="terminal-left">
+                            <div class="select-area">
+                            </div>
+                        </div> -->
+            <div class="terminal-dividing"></div>
+            <div class="terminal-right">
+              <div class="terminal-output">
+                <div class="text" v-for="(text, index) in terminalOutput" :key="index">
+                  {{ text }}
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
       </div>
     </div>
   </div>
@@ -39,28 +340,344 @@ onMounted((): void => {
 .list-page-3 {
   width: 100%;
   height: 100%;
+  overflow-x: hidden;
+  overflow-y: hidden;
 
   .page {
     width: 100%;
     height: 100%;
-    overflow-y: auto;
+    overflow-x: hidden;
+    overflow-y: hidden;
     display: flex;
     flex-direction: column;
     justify-content: flex-start;
-    align-items: center;
+    align-items: flex-start;
+    background-color: white;
+    padding: 0;
   }
 
-  .card {
+  .page-up {
     width: 100%;
     height: 100%;
+    flex: 1;
+    overflow-x: hidden;
+    overflow-y: auto;
+    display: flex;
+    flex-direction: row;
+    justify-content: flex-start;
+    align-items: flex-start;
+
+    .control-zone {
+      width: 100%;
+      height: 100%;
+      display: flex;
+      flex-direction: column;
+      justify-content: flex-start;
+      align-items: center;
+      margin-top: 10vh;
+    }
+
+    .title {
+      font-size: 35px;
+      font-weight: 500;
+      margin-bottom: 0;
+      margin-bottom: 3vh;
+    }
+
+    .config-zone {
+      width: 100%;
+      display: flex;
+      flex-direction: column;
+      justify-content: flex-start;
+      align-items: center;
+    }
+
+    .text-line {
+      font-size: 25px;
+      font-weight: 500;
+      margin-top: 2vh;
+      margin-bottom: -2vh;
+    }
+
+    .adjust-line {
+      width: 100%;
+      height: auto;
+      padding-top: 3vh;
+      padding-bottom: 3vh;
+      padding-left: 0;
+      padding-right: 0;
+      display: flex;
+      flex-direction: column;
+      justify-content: flex-start;
+      align-items: center;
+    }
+
+    .btn-zone {
+      margin-top: 3vh;
+      display: flex;
+      flex-direction: row;
+      justify-content: flex-start;
+      align-items: center;
+
+      .btn {
+        width: 105px;
+        height: 35px;
+        overflow: hidden;
+        border: 1px solid #dfdfdf;
+        border-radius: 5px;
+        display: flex;
+        flex-direction: row;
+        justify-content: center;
+        align-items: center;
+        color: black;
+        position: relative;
+        cursor: pointer;
+        transition: all 0.1s cubic-bezier(0.78, 0.14, 0.15, 0.86);
+      }
+
+      .btn::after {
+        content: '';
+        position: absolute;
+        width: 100%;
+        height: 100%;
+        background-color: rgba(255, 255, 255, 1);
+        z-index: 3;
+        transition: all 0.1s cubic-bezier(0.78, 0.14, 0.15, 0.86);
+      }
+
+      .btn:hover {
+        background: linear-gradient(to right, #108ee9 0%, #87d068 100%);
+        background-color: white;
+        color: white;
+      }
+
+      .btn:hover::after {
+        content: '';
+        position: absolute;
+        width: 100%;
+        height: 100%;
+        background-color: rgba(255, 255, 255, 0);
+        z-index: 3;
+      }
+
+      .text {
+        font-size: 20px;
+        font-weight: 400;
+        z-index: 5;
+      }
+    }
+  }
+
+  .page-down {
+    width: 100%;
+    // height: 200px;
     background-color: white;
-    padding: 35px;
-    color: black;
+    border-top: 1px solid #dfdfdf;
+    position: relative;
+
+    .content {
+      width: 100%;
+      height: 100%;
+      overflow: hidden;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+
+    .top-bar {
+      height: auto;
+      overflow-y: hidden;
+
+      .resizer {
+        cursor: ns-resize; // 调整为垂直调整的鼠标样式
+        background-color: white;
+        height: 22px; // 调整为较小的高度
+        width: 100%; // 确保宽度与父 div 一致
+        position: absolute;
+        top: 0;
+        border-top: 1px solid #dfdfdf;
+        border-bottom: 1px solid #dfdfdf;
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        justify-content: flex-start;
+        overflow-y: hidden;
+      }
+
+      .text {
+        color: black;
+        font-size: 15px;
+      }
+
+      .flag-bar {
+        background-color: #2673e3;
+        width: 125px;
+        height: 32px;
+        padding-left: 10px;
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        justify-content: flex-start;
+        clip-path: polygon(0% 0%, 100% 0%, 90% 100%, 0% 100%);
+
+        .text {
+          color: white;
+        }
+      }
+
+      .middle {
+        flex: 1;
+        background-color: red;
+      }
+
+      .last-time {
+        width: auto;
+        height: 100%;
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        justify-content: flex-start;
+        padding-right: 15px;
+        font-weight: 500;
+      }
+
+      .dragging {
+        border-top: 6px solid #2673e3;
+        height: 34px;
+      }
+    }
   }
 
-  .card .title {
-    font-size: 20px;
-    font-weight: 500;
+  .terminal-zone {
+    width: 100%;
+    height: 100%;
+    overflow-x: hidden;
+    overflow-y: hidden;
+    display: flex;
+    flex-direction: row;
+    justify-content: flex-start;
+    align-items: flex-start;
+
+    .terminal-right {
+      flex: 5;
+      height: 100%;
+      overflow-x: hidden;
+      overflow-y: hidden;
+    }
+
+    .terminal-output {
+      height: 100%;
+      overflow-x: auto;
+      overflow-y: scroll;
+      background-color: white;
+      padding: 25px;
+      padding-top: 35px;
+    }
+
+    .terminal-dividing {
+      width: 0px;
+      height: 100%;
+      border: 0;
+      border-right: 1px solid #dfdfdf;
+    }
+
+    .terminal-left {
+      flex: 3;
+      height: 100%;
+      background-color: white;
+      overflow-y: auto;
+      overflow-x: auto;
+      display: flex;
+      flex-direction: column;
+      justify-content: flex-start;
+      align-items: flex-start;
+      padding: 25px;
+      padding-top: 40px;
+
+      .text {
+        color: black;
+      }
+
+      .title {
+        font-size: 20px;
+        font-weight: 500;
+        margin-top: 15px;
+        margin-bottom: 10px;
+      }
+
+      .line {
+        width: 100%;
+        height: auto;
+        display: flex;
+        flex-direction: column;
+        justify-content: flex-start;
+        align-items: flex-start;
+        margin-bottom: 6px;
+
+        .item {
+          font-size: 18px;
+          font-weight: 500;
+          flex: 1;
+          margin-bottom: 10px;
+        }
+      }
+
+      .select-area {
+        display: flex;
+        flex-direction: row;
+        justify-content: flex-start;
+        align-items: center;
+
+        .text {
+          font-size: 20px;
+          font-weight: 500;
+        }
+
+        .select-box {
+          margin-top: 6px;
+          margin-bottom: 6px;
+
+          .select-button {
+            padding: 3px;
+            padding-left: 9px;
+            width: 100px;
+            border: 1px solid rgba(0, 0, 0, 0.35);
+            border-radius: 5px;
+            margin-left: 10px;
+            margin-right: 20px;
+          }
+        }
+
+        .button {
+          margin-top: 12px;
+          font-size: 18px;
+          border: 1px solid rgba(0, 0, 0, 0.35);
+          border-radius: 5px;
+          padding: 3px;
+          padding-left: 8px;
+          padding-right: 8px;
+        }
+
+        .button:hover {
+          background-color: #2673e3;
+          color: white;
+          border: 1px solid rgba($color: #2673e3, $alpha: 1);
+        }
+
+        .button:active {
+          background-color: rgba($color: #2673e3, $alpha: 0.5);
+          border: 1px solid rgba($color: #2673e3, $alpha: 0.5);
+          color: white;
+        }
+
+        .tip {
+          margin-top: 12px;
+          font-size: 18px;
+          margin-left: 16px;
+          font-weight: 500;
+        }
+      }
+    }
   }
 }
 </style>

ファイルの差分が大きいため隠しています
+ 429 - 422
client/src/renderer/pages/ListNum/ListNum4.vue


+ 1 - 1
client/src/renderer/pages/ListNum/ListNumNone.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="list-page-none">
     <div class="page">
-      <div class="tip"> 选择一个测试项目 </div>
+      <div class="tip"> 选择一个测试功能 </div>
     </div>
   </div>
 </template>

+ 0 - 0
client/src/renderer/pages/iconlib/google/delete_black.png → client/src/renderer/public/iconlib/google/delete_black.png


+ 0 - 0
client/src/renderer/pages/iconlib/google/delete_black.svg → client/src/renderer/public/iconlib/google/delete_black.svg


+ 0 - 0
client/src/renderer/pages/iconlib/google/delete_white.png → client/src/renderer/public/iconlib/google/delete_white.png


+ 0 - 0
client/src/renderer/pages/iconlib/google/delete_white.svg → client/src/renderer/public/iconlib/google/delete_white.svg


+ 0 - 0
client/src/renderer/pages/iconlib/google/folder_open_black.png → client/src/renderer/public/iconlib/google/folder_open_black.png


+ 0 - 0
client/src/renderer/pages/iconlib/google/folder_open_black.svg → client/src/renderer/public/iconlib/google/folder_open_black.svg


+ 0 - 0
client/src/renderer/pages/iconlib/google/folder_open_white.png → client/src/renderer/public/iconlib/google/folder_open_white.png


+ 0 - 0
client/src/renderer/pages/iconlib/google/folder_open_white.svg → client/src/renderer/public/iconlib/google/folder_open_white.svg


+ 0 - 0
client/src/renderer/pages/iconlib/google/pause_black.png → client/src/renderer/public/iconlib/google/pause_black.png


+ 0 - 0
client/src/renderer/pages/iconlib/google/pause_black.svg → client/src/renderer/public/iconlib/google/pause_black.svg


+ 0 - 0
client/src/renderer/pages/iconlib/google/pause_white.png → client/src/renderer/public/iconlib/google/pause_white.png


+ 0 - 0
client/src/renderer/pages/iconlib/google/pause_white.svg → client/src/renderer/public/iconlib/google/pause_white.svg


+ 0 - 0
client/src/renderer/pages/iconlib/google/play_arrow_black.png → client/src/renderer/public/iconlib/google/play_arrow_black.png


+ 0 - 0
client/src/renderer/pages/iconlib/google/play_arrow_black.svg → client/src/renderer/public/iconlib/google/play_arrow_black.svg


+ 0 - 0
client/src/renderer/pages/iconlib/google/play_arrow_white.png → client/src/renderer/public/iconlib/google/play_arrow_white.png


+ 0 - 0
client/src/renderer/pages/iconlib/google/play_arrow_white.svg → client/src/renderer/public/iconlib/google/play_arrow_white.svg


+ 0 - 0
client/src/renderer/pages/iconlib/google/save_black.png → client/src/renderer/public/iconlib/google/save_black.png


+ 0 - 0
client/src/renderer/pages/iconlib/google/save_black.svg → client/src/renderer/public/iconlib/google/save_black.svg


+ 0 - 0
client/src/renderer/pages/iconlib/google/save_white.png → client/src/renderer/public/iconlib/google/save_white.png


+ 0 - 0
client/src/renderer/pages/iconlib/google/save_white.svg → client/src/renderer/public/iconlib/google/save_white.svg


+ 0 - 0
client/src/renderer/pages/iconlib/google/settings_black.png → client/src/renderer/public/iconlib/google/settings_black.png


+ 0 - 0
client/src/renderer/pages/iconlib/google/settings_black.svg → client/src/renderer/public/iconlib/google/settings_black.svg


+ 0 - 0
client/src/renderer/pages/iconlib/google/settings_white.png → client/src/renderer/public/iconlib/google/settings_white.png


+ 0 - 0
client/src/renderer/pages/iconlib/google/settings_white.svg → client/src/renderer/public/iconlib/google/settings_white.svg


+ 0 - 0
client/src/renderer/pages/iconlib/google/stop_black.png → client/src/renderer/public/iconlib/google/stop_black.png


+ 0 - 0
client/src/renderer/pages/iconlib/google/stop_black.svg → client/src/renderer/public/iconlib/google/stop_black.svg


+ 0 - 0
client/src/renderer/pages/iconlib/google/stop_white.png → client/src/renderer/public/iconlib/google/stop_white.png


+ 0 - 0
client/src/renderer/pages/iconlib/google/stop_white.svg → client/src/renderer/public/iconlib/google/stop_white.svg


+ 0 - 0
client/src/renderer/pages/iconlib/google/upload_black.png → client/src/renderer/public/iconlib/google/upload_black.png


+ 0 - 0
client/src/renderer/pages/iconlib/google/upload_black.svg → client/src/renderer/public/iconlib/google/upload_black.svg


+ 0 - 0
client/src/renderer/pages/iconlib/google/upload_white.png → client/src/renderer/public/iconlib/google/upload_white.png


+ 0 - 0
client/src/renderer/pages/iconlib/google/upload_white.svg → client/src/renderer/public/iconlib/google/upload_white.svg


BIN
export/OSC.20.20.05.png


BIN
export/OSC.20.20.08.png


BIN
export/OSC.20.20.09.png


BIN
export/OSC.20.20.11.png


BIN
export/OSC.20.20.12.png


+ 2 - 2
launcher.py

@@ -14,7 +14,7 @@ class Launcher(object):
         print(f"[Launcher] {message}")
 
     def run(self):
-        server_process = subprocess.Popen(["python", "./server/server.py"], stdout=subprocess.PIPE,
+        server_process = subprocess.Popen(["./server/server.exe"], stdout=subprocess.PIPE,
                                           stderr=subprocess.STDOUT, text=True, bufsize=1)
         server_ready = False
         port: int = 1024
@@ -35,7 +35,7 @@ class Launcher(object):
         print("Server is ready, starting client...")
         client_env = os.environ.copy()
         client_env["PLC_SIM_PORT"] = f"{port}"
-        client_process = subprocess.Popen(["powershell", "npm run dev"], env=client_env, cwd="./client")
+        client_process = subprocess.Popen(["./PLC-SIM.exe"], env=client_env)
         try:
             while True:
                 return_code = client_process.poll()

BIN
server/__pycache__/instrument_controller.cpython-311.pyc


BIN
server/__pycache__/serial_device_controller.cpython-311.pyc


+ 17 - 5
server/instrument_controller.py

@@ -62,7 +62,7 @@ class InstrumentController(object):
         self.__pubtools: ProgramPublicTools = pubtools
         self.__scpi_instrument_list: list[ScpiInstrument] = []
         self.services: InstrumentServices = InstrumentServices()
-        self.__retry_times: int = 0
+        self.__retry_times: int = 1
 
     @staticmethod
     def __init_scpi_manager() -> scpiManager:
@@ -622,9 +622,18 @@ class AnalogElectronicLoadService(object):
         self.__pubtools: ProgramPublicTools = pubtools
         self.__config: InstrumentControllerConfig = config
         self.name: str = "Analog Electronic Load"
+        self.__mode_set_time: int = 0
 
-    def set(self):
-        pass
+    def set_mode(self):
+        if self.__mode_set_time <= 2:
+            self.__mode_set_time += 1
+            cmd_set = f":SOUR:FUNC RES"
+            self.scpi_instrument.connection.write(cmd_set)
+
+    def set_resistance(self, value: str):
+        self.set_mode()
+        cmd_set = f":SOUR:RES:LEV:IMM {value}"
+        self.scpi_instrument.connection.write(cmd_set)
 
 
 def run_as_main():
@@ -633,8 +642,11 @@ def run_as_main():
     instrument_controller.auto_connect()
     # _, _, server_url_multimeter = instrument_controller.services.digital_multimeter.keep_listening()
     # print("digital_multimeter", server_url_multimeter)
-    _, _, server_url_oscilloscope = instrument_controller.services.digital_oscilloscope.keep_listening()
-    print("digital_oscilloscope", server_url_oscilloscope)
+    # _, _, server_url_oscilloscope = instrument_controller.services.digital_oscilloscope.keep_listening()
+    # print("digital_oscilloscope", server_url_oscilloscope)
+    while True:
+        input_value = input("input: ")
+        instrument_controller.services.analog_electronic_load.set_resistance(input_value)
 
 
 if __name__ == "__main__":

+ 64 - 0
server/serial_device_controller.py

@@ -135,6 +135,7 @@ class PLCDeviceSender(PLCDevice):
         self.sleep_sec_every_ten_step: float = 0.01
         self.__ten_step_counter: int = 0
         self.pull_up_signal_when_sending: bool = False
+        self.__is_resetting: bool = False
 
     def clear(self):
         self.send_pkg_number = 0
@@ -151,6 +152,15 @@ class PLCDeviceSender(PLCDevice):
     def pause(self):
         self.__pause = True
 
+    def pause_and_reset_frame_std_sender(self, frame_std_new):
+        if self.__is_resetting is False:
+            self.__is_resetting = True
+            self.pause()
+            self.frame_std = frame_std_new
+            self.send_pkg_length = len(self.frame_std)
+            self.__is_resetting = False
+
+
     @staticmethod
     def __start_server():
         return None
@@ -208,6 +218,8 @@ class PLCDeviceReceiver(PLCDevice):
         self.__pause: bool = False
         self.pull_up_signal_when_receiving: bool = False
         self.auto_pause_when_receiving = False
+        self.__update_frame_std: bool = False
+        self.__is_resetting: bool = False
 
     def clear(self):
         self.error_count = 0
@@ -237,10 +249,22 @@ class PLCDeviceReceiver(PLCDevice):
     def __receiver_server_threading_main(self, port):
         self.__r_server.run("0.0.0.0", port)
 
+    def pause_and_reset_frame_std_receiver(self, frame_std_new):
+        if self.__is_resetting is False:
+            self.__is_resetting = True
+            self.frame_std = frame_std_new
+            self.pause()
+            self.__update_frame_std = True
+
     def __receive_thread_main(self):
         frame_std: bytes = self.frame_std
         frame_std_len: int = len(frame_std)
         while True:
+            if self.__update_frame_std is True:
+                frame_std: bytes = self.frame_std
+                frame_std_len: int = len(frame_std)
+                self.__update_frame_std = False
+                self.__is_resetting = False
             if self.__pause is False:
                 self.receive_bytes_this_circle = 0
                 line = self.com.connection.readline()
@@ -363,6 +387,14 @@ class TestService(object):
         self.speed_kbps2: float = 0
         self.__rev_timer_sec: float = time.time()
         self.__rev_timeout_sec: float = 2
+        self.__is_resetting_frame_std_and_restarting: bool = False
+        self.send_package_1kbit: bytes = 25 * b'12345' + b'\n'
+        self.send_package_16kbit: bytes = 400 * b'12345' + b'\n'
+        self.test_service_init_complete_flag: bool = False
+        self.have_test_service_task_in_circle: bool = False
+        self.have_task_reset_frame_std: bool = False
+        self.task_lock_reset_frame_std_and_restart: bool = False
+        self.__frame_std_new: bytes = self.send_package_16kbit
 
     def run_as_main(self):
         run_mode = ""
@@ -496,6 +528,31 @@ class TestService(object):
     def pause_test_service(self):
         self.__pause_tester_signal = True
 
+    def reset_frame_std_and_restart(self, frame_std_new: bytes):
+        if self.task_lock_reset_frame_std_and_restart is False:
+            self.task_lock_reset_frame_std_and_restart = True
+            self.__frame_std_new = frame_std_new
+            self.have_test_service_task_in_circle = True
+            self.have_task_reset_frame_std = True
+
+    def __do_reset_frame_std_and_restart(self):
+        if self.__is_resetting_frame_std_and_restarting is False:
+            self.__is_resetting_frame_std_and_restarting = True
+            self.pause_test_service()
+            self.__sender.pause_and_reset_frame_std_sender(self.__frame_std_new)
+            self.__receiver.pause_and_reset_frame_std_receiver(self.__frame_std_new)
+            if self.__receiver2 is not None:
+                self.__receiver2.pause_and_reset_frame_std_receiver(self.__frame_std_new)
+            self.task_add_reset_error_rate()
+            self.start_test_service(self.__sender, self.__receiver, self.__receiver2)
+            self.__is_resetting_frame_std_and_restarting = False
+
+    def __test_service_task_in_circle(self):
+        if self.have_task_reset_frame_std is True:
+            self.__do_reset_frame_std_and_restart()
+            self.have_task_reset_frame_std = False
+            self.task_lock_reset_frame_std_and_restart = False
+
     def __start_tester_threading(self, sender: PLCDeviceSender, receiver: PLCDeviceReceiver,
                                  receiver2: Optional[PLCDeviceReceiver] = None):
         self.__threading = threading.Thread(target=self.test_service, args=(sender, receiver, receiver2)).start()
@@ -557,7 +614,11 @@ class TestService(object):
         for _ in range(0, 3):
             self.__test_circle(if_print=False)
         self.task_add_reset_error_rate()
+        self.test_service_init_complete_flag = True
         while True:
+            if self.have_test_service_task_in_circle is True:
+                self.__test_service_task_in_circle()
+                self.have_test_service_task_in_circle = False
             if self.__pause_tester_signal is False:
                 self.__test_circle(if_print=True)
             else:
@@ -583,6 +644,9 @@ class TestService(object):
             self.__tester_output(f"DBG: 发包={self.__sender.send_pkg_number} 收包={self.__receiver.receive_pkg}",
                                  if_print)
             self.__tester_output(f"speed: {self.speed_kbps} kbps", if_print)
+            if self.__sender.send_pkg_number % 5 == 0:
+                self.__tester_output(f"[Sender] Package Length: {len(self.__sender.frame_std)}", if_print)
+                self.__tester_output(f"[Receiver] Package Length: {len(self.__receiver.frame_std)}", if_print)
             self.__receiver.pull_up_signal_when_receiving = False
             time.sleep(self.__sender.sleep_sec_every_step)
             if self.__sender.send_pkg_number % 10 == 0:

+ 109 - 39
server/server.py

@@ -137,23 +137,35 @@ class Server:
 
         @self.__app.route("/instrument/waveform_generator/output_start", methods=['GET'])
         def instrument_waveform_generator_output_start():
-            self.__core.instrument_controller.services.waveform_generator.output_start()
-            return f"output_start"
+            if self.__core.instrument_controller.services.waveform_generator is not None:
+                self.__core.instrument_controller.services.waveform_generator.output_start()
+                return f"output_start"
+            else:
+                return "None"
 
         @self.__app.route("/instrument/waveform_generator/output_restart", methods=['GET'])
         def instrument_waveform_generator_output_restart():
-            self.__core.instrument_controller.services.waveform_generator.output_restart()
-            return f"output_restart"
+            if self.__core.instrument_controller.services.waveform_generator is not None:
+                self.__core.instrument_controller.services.waveform_generator.output_restart()
+                return f"output_restart"
+            else:
+                return "None"
 
         @self.__app.route("/instrument/waveform_generator/output_pause", methods=['GET'])
         def instrument_waveform_generator_output_pause():
-            self.__core.instrument_controller.services.waveform_generator.output_pause()
-            return f"output_pause"
+            if self.__core.instrument_controller.services.waveform_generator is not None:
+                self.__core.instrument_controller.services.waveform_generator.output_pause()
+                return f"output_pause"
+            else:
+                return "None"
 
         @self.__app.route("/instrument/waveform_generator/config/apply_config", methods=['GET'])
         def instrument_waveform_generator_config_apply_config():
-            self.__core.instrument_controller.services.waveform_generator.apply_config(True)
-            return "apply_config"
+            if self.__core.instrument_controller.services.waveform_generator is not None:
+                self.__core.instrument_controller.services.waveform_generator.apply_config(True)
+                return "apply_config"
+            else:
+                return "None"
 
         @self.__app.route("/instrument/waveform_generator/config/set_enable", methods=['GET'])
         def instrument_waveform_generator_config_set_enable():
@@ -163,81 +175,123 @@ class Server:
                 enable: bool = True
             else:
                 enable: bool = False
-            channel = self.__core.instrument_controller.services.waveform_generator.set_enable(channel_string, enable)
-            return f"set_enable: {channel} {enable}"
+            if self.__core.instrument_controller.services.waveform_generator is not None:
+                channel = self.__core.instrument_controller.services.waveform_generator.set_enable(channel_string, enable)
+                return f"set_enable: {channel} {enable}"
+            else:
+                return f"set_enable: None None"
 
         @self.__app.route("/instrument/waveform_generator/config/get_type", methods=['GET'])
         def instrument_waveform_generator_config_get_type():
-            res: str = self.__core.instrument_controller.services.waveform_generator.config_channel_1.get_type_string()
-            return f"get_type: " + res
+            if self.__core.instrument_controller.services.waveform_generator is not None:
+                res: str = self.__core.instrument_controller.services.waveform_generator.config_channel_1.get_type_string()
+                return f"get_type: " + res
+            else:
+                return f"get_type: " + "None"
 
         @self.__app.route("/instrument/waveform_generator/config/set_type", methods=['GET'])
         def instrument_waveform_generator_config_set_type():
             channel_string: str = request.args.get('channel')
             type_string: str = request.args.get('type')
-            channel, config = self.__core.instrument_controller.services.waveform_generator.get_config(channel_string)
-            config.type = config.match_type_string(type_string)
-            return f"set_type: {channel} {type_string}"
+            if self.__core.instrument_controller.services.waveform_generator is not None:
+                channel, config = self.__core.instrument_controller.services.waveform_generator.get_config(channel_string)
+                config.type = config.match_type_string(type_string)
+                return f"set_type: {channel} {type_string}"
+            else:
+                return f"set_type: None None"
 
         @self.__app.route("/instrument/waveform_generator/config/set_freq", methods=['GET'])
         def instrument_waveform_generator_config_set_freq():
             channel_string: str = request.args.get('channel')
             freq: float = float(request.args.get('freq'))
-            channel, config = self.__core.instrument_controller.services.waveform_generator.get_config(channel_string)
-            config.freq = freq
-            return f"set_freq: {channel} {freq}"
+            if self.__core.instrument_controller.services.waveform_generator is not None:
+                channel, config = self.__core.instrument_controller.services.waveform_generator.get_config(channel_string)
+                config.freq = freq
+                return f"set_freq: {channel} {freq}"
+            else:
+                return f"set_freq: None None"
 
         @self.__app.route("/instrument/waveform_generator/config/get_freq_unit", methods=['GET'])
         def instrument_waveform_generator_config_get_freq_unit():
-            _, config = self.__core.instrument_controller.services.waveform_generator.get_config("1")
-            unit_type_string: str = config.get_freq_unit_type_string()
-            return f"get_freq_unit: {unit_type_string}"
+            if self.__core.instrument_controller.services.waveform_generator is not None:
+                _, config = self.__core.instrument_controller.services.waveform_generator.get_config("1")
+                unit_type_string: str = config.get_freq_unit_type_string()
+                return f"get_freq_unit: {unit_type_string}"
+            else:
+                return f"get_freq_unit: None"
 
         @self.__app.route("/instrument/waveform_generator/config/set_freq_unit", methods=['GET'])
         def instrument_waveform_generator_config_set_freq_unit():
             channel_string: str = request.args.get('channel')
             freq_unit_string: str = request.args.get('freq_unit')
-            channel, config = self.__core.instrument_controller.services.waveform_generator.get_config(channel_string)
-            config.freq_unit = config.match_freq_unit_type_string(freq_unit_string)
-            return f"set_freq_unit: {channel} {freq_unit_string}"
+            if self.__core.instrument_controller.services.waveform_generator is not None:
+                channel, config = self.__core.instrument_controller.services.waveform_generator.get_config(channel_string)
+                config.freq_unit = config.match_freq_unit_type_string(freq_unit_string)
+                return f"set_freq_unit: {channel} {freq_unit_string}"
+            else:
+                return f"set_freq_unit: None None"
 
         @self.__app.route("/instrument/waveform_generator/config/set_high_level", methods=['GET'])
         def instrument_waveform_generator_config_set_high_level():
             channel_string: str = request.args.get('channel')
             high_level: float = float(request.args.get('high_level'))
-            channel, config = self.__core.instrument_controller.services.waveform_generator.get_config(channel_string)
-            config.high_level = high_level
-            return f"set_high_level: {channel} {high_level}"
+            if self.__core.instrument_controller.services.waveform_generator is not None:
+                channel, config = self.__core.instrument_controller.services.waveform_generator.get_config(channel_string)
+                config.high_level = high_level
+                return f"set_high_level: {channel} {high_level}"
+            else:
+                return f"set_high_level: None None"
 
         @self.__app.route("/instrument/waveform_generator/config/set_low_level", methods=['GET'])
         def instrument_waveform_generator_config_set_low_level():
             channel_string: str = request.args.get('channel')
             low_level: float = float(request.args.get('low_level'))
-            channel, config = self.__core.instrument_controller.services.waveform_generator.get_config(channel_string)
-            config.low_level = low_level
-            return f"set_high_level: {channel} {low_level}"
+            if self.__core.instrument_controller.services.waveform_generator is not None:
+                channel, config = self.__core.instrument_controller.services.waveform_generator.get_config(channel_string)
+                config.low_level = low_level
+                return f"set_low_level: {channel} {low_level}"
+            else:
+                return f"set_low_level: None None"
 
         @self.__app.route("/instrument/waveform_generator/config/get_level_unit", methods=['GET'])
         def instrument_waveform_generator_config_get_level_unit():
-            _, config = self.__core.instrument_controller.services.waveform_generator.get_config("1")
-            unit_type_string: str = config.get_freq_unit_type_string()
-            return f"get_freq_unit: {unit_type_string}"
+            if self.__core.instrument_controller.services.waveform_generator is not None:
+                _, config = self.__core.instrument_controller.services.waveform_generator.get_config("1")
+                unit_type_string: str = config.get_freq_unit_type_string()
+                return f"get_freq_unit: {unit_type_string}"
+            else:
+                return f"get_freq_unit: None"
 
         @self.__app.route("/instrument/waveform_generator/config/set_high_level_unit", methods=['GET'])
         def instrument_waveform_generator_config_set_high_level_unit():
             channel_string: str = request.args.get('channel')
             high_level_unit_string: str = request.args.get('high_level_unit')
-            channel, config = self.__core.instrument_controller.services.waveform_generator.get_config(channel_string)
-            config.high_level_unit = config.match_level_unit_type_string(high_level_unit_string)
-            return f"set_high_level_unit: {channel} {high_level_unit_string}"
+            if self.__core.instrument_controller.services.waveform_generator is not None:
+                channel, config = self.__core.instrument_controller.services.waveform_generator.get_config(channel_string)
+                config.high_level_unit = config.match_level_unit_type_string(high_level_unit_string)
+                return f"set_high_level_unit: {channel} {high_level_unit_string}"
+            else:
+                return f"set_high_level_unit: None {high_level_unit_string}"
 
         @self.__app.route("/instrument/waveform_generator/config/set_low_level_unit", methods=['GET'])
         def instrument_waveform_generator_config_set_low_level_unit():
             channel_string: str = request.args.get('channel')
             low_level_unit_string: str = request.args.get('low_level_unit')
-            channel, config = self.__core.instrument_controller.services.waveform_generator.get_config(channel_string)
-            config.low_level_unit = config.match_level_unit_type_string(low_level_unit_string)
-            return f"set_low_level_unit: {channel} {low_level_unit_string}"
+            if self.__core.instrument_controller.services.waveform_generator is not None:
+                channel, config = self.__core.instrument_controller.services.waveform_generator.get_config(channel_string)
+                config.low_level_unit = config.match_level_unit_type_string(low_level_unit_string)
+                return f"set_low_level_unit: {channel} {low_level_unit_string}"
+            else:
+                return f"set_low_level_unit: None {low_level_unit_string}"
+
+        @self.__app.route("/instrument/analog_electronic_load/set_resistance", methods=['GET'])
+        def instrument_analog_electronic_load_set_resistance():
+            if self.__core.instrument_controller.services.analog_electronic_load is not None:
+                resistance: str = request.args.get('set_resistance')
+                self.__core.instrument_controller.services.analog_electronic_load.set_resistance(resistance)
+                return "True"
+            else:
+                return "False"
 
         @self.__app.route("/serial_device/port_list", methods=['GET'])
         def serial_device_port_list():
@@ -343,6 +397,7 @@ class Server:
         @self.__app.route("/serial_device/plc/start", methods=['GET'])
         def serial_device_plc_start():
             if self.__core.serial_controller.services.com_usr_config_success is True:
+                package_config: Optional[str] = request.args.get('package_config')
                 sender = self.__core.serial_controller.services.plc_sender
                 receiver = self.__core.serial_controller.services.plc_receiver
                 sender_port, sender_url = sender.keep_send()
@@ -353,6 +408,21 @@ class Server:
                     receiver2.keep_receive()
                 tester = self.__core.serial_controller.get_tester()
                 tester.start_test_service(sender, receiver, receiver2)
+                if package_config == "单包1kbit":
+                    tester_frame_new: bytes = tester.send_package_1kbit
+                elif package_config == "单包16kbit":
+                    tester_frame_new: bytes = tester.send_package_16kbit
+                else:
+                    tester_frame_new: bytes = package_config.encode()
+                    if len(tester_frame_new) % 3 == 0:
+                        tester_frame_new += b'12\n'
+                    elif len(tester_frame_new) % 3 == 1:  # b'1'
+                        tester_frame_new += b'1\n'
+                    elif len(tester_frame_new) % 3 == 2:  # b'12'
+                        tester_frame_new += b'\n'
+                    else:
+                        tester_frame_new = b'12\n'
+                tester.reset_frame_std_and_restart(tester_frame_new)
             else:
                 sender_port, sender_url, receiver_port, receiver_url = 0, 0, 0, 0
             response_data = {

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません