瀏覽代碼

feat(frontend): 日志功能完善,页面顶部工具栏添加一些快捷链接

Signed-off-by: Myon <[email protected]>
Myon 3 年之前
父節點
當前提交
8be43b0555

+ 15 - 15
frontend/package-lock.json

@@ -22,7 +22,7 @@
       },
       "devDependencies": {
         "@babel/eslint-parser": "^7.13.14",
-        "@imyon/eslint-config-preset": "^0.0.3",
+        "@imyon/eslint-config-preset": "^0.0.5",
         "@imyon/eslint-config-prettier": "^0.0.2",
         "@quasar/app": "^3.0.0",
         "dotenv": "^10.0.0",
@@ -1646,16 +1646,16 @@
       }
     },
     "node_modules/@imyon/eslint-config-preset": {
-      "version": "0.0.3",
-      "resolved": "https://registry.npmjs.org/@imyon/eslint-config-preset/-/eslint-config-preset-0.0.3.tgz",
-      "integrity": "sha512-R/VZvZvDRdo9XefWhfyJJ2aY/fBw5f+OgGKAAlG0pa5dJGI17/xMqVMjxHvAKXzfmr/fYgaLAuaux3rbKx3COg==",
+      "version": "0.0.5",
+      "resolved": "https://registry.npmjs.org/@imyon/eslint-config-preset/-/eslint-config-preset-0.0.5.tgz",
+      "integrity": "sha512-vtof3Fxjo49AGC74brO7dwACC9GaEggnw1IpQiVx3L63iMj7SV0+XUvCC3MOHhTRyjUB3Mde21civEuWTu5XJA==",
       "dev": true,
       "dependencies": {
         "@babel/core": "^7.16.12",
         "@babel/eslint-parser": "^7.16.5",
         "@imyon/eslint-config-basic": "^0.0.3",
         "@imyon/eslint-config-typescript": "^0.0.3",
-        "@imyon/eslint-config-vue": "^0.0.3",
+        "@imyon/eslint-config-vue": "^0.0.5",
         "@typescript-eslint/eslint-plugin": "^5.8.1",
         "@typescript-eslint/parser": "^5.8.1",
         "eslint": "^7.14.0",
@@ -1690,9 +1690,9 @@
       }
     },
     "node_modules/@imyon/eslint-config-vue": {
-      "version": "0.0.3",
-      "resolved": "https://registry.npmjs.org/@imyon/eslint-config-vue/-/eslint-config-vue-0.0.3.tgz",
-      "integrity": "sha512-y0NdmC2E3qB4JLdH+iMXFM6ewLX5yZ8C9z9ZjlfddW6X197NZJdvcdpOfj7XSBI/5A2HbcBdXpBhATkNDcM5vA==",
+      "version": "0.0.5",
+      "resolved": "https://registry.npmjs.org/@imyon/eslint-config-vue/-/eslint-config-vue-0.0.5.tgz",
+      "integrity": "sha512-R/fbJR7R/VbQFMEAvWx3+M8g7p1n2HtPKK5a44YzfZjuaqWt1yd7dbT7/VZlANycjES15hqqgJcTvDOIsEnefQ==",
       "dev": true,
       "dependencies": {
         "@babel/core": "^7.16.12",
@@ -14694,16 +14694,16 @@
       }
     },
     "@imyon/eslint-config-preset": {
-      "version": "0.0.3",
-      "resolved": "https://registry.npmjs.org/@imyon/eslint-config-preset/-/eslint-config-preset-0.0.3.tgz",
-      "integrity": "sha512-R/VZvZvDRdo9XefWhfyJJ2aY/fBw5f+OgGKAAlG0pa5dJGI17/xMqVMjxHvAKXzfmr/fYgaLAuaux3rbKx3COg==",
+      "version": "0.0.5",
+      "resolved": "https://registry.npmjs.org/@imyon/eslint-config-preset/-/eslint-config-preset-0.0.5.tgz",
+      "integrity": "sha512-vtof3Fxjo49AGC74brO7dwACC9GaEggnw1IpQiVx3L63iMj7SV0+XUvCC3MOHhTRyjUB3Mde21civEuWTu5XJA==",
       "dev": true,
       "requires": {
         "@babel/core": "^7.16.12",
         "@babel/eslint-parser": "^7.16.5",
         "@imyon/eslint-config-basic": "^0.0.3",
         "@imyon/eslint-config-typescript": "^0.0.3",
-        "@imyon/eslint-config-vue": "^0.0.3",
+        "@imyon/eslint-config-vue": "^0.0.5",
         "@typescript-eslint/eslint-plugin": "^5.8.1",
         "@typescript-eslint/parser": "^5.8.1",
         "eslint": "^7.14.0",
@@ -14738,9 +14738,9 @@
       }
     },
     "@imyon/eslint-config-vue": {
-      "version": "0.0.3",
-      "resolved": "https://registry.npmjs.org/@imyon/eslint-config-vue/-/eslint-config-vue-0.0.3.tgz",
-      "integrity": "sha512-y0NdmC2E3qB4JLdH+iMXFM6ewLX5yZ8C9z9ZjlfddW6X197NZJdvcdpOfj7XSBI/5A2HbcBdXpBhATkNDcM5vA==",
+      "version": "0.0.5",
+      "resolved": "https://registry.npmjs.org/@imyon/eslint-config-vue/-/eslint-config-vue-0.0.5.tgz",
+      "integrity": "sha512-R/fbJR7R/VbQFMEAvWx3+M8g7p1n2HtPKK5a44YzfZjuaqWt1yd7dbT7/VZlANycjES15hqqgJcTvDOIsEnefQ==",
       "dev": true,
       "requires": {
         "@babel/core": "^7.16.12",

+ 1 - 1
frontend/package.json

@@ -33,7 +33,7 @@
   },
   "devDependencies": {
     "@babel/eslint-parser": "^7.13.14",
-    "@imyon/eslint-config-preset": "^0.0.3",
+    "@imyon/eslint-config-preset": "^0.0.5",
     "@imyon/eslint-config-prettier": "^0.0.2",
     "@quasar/app": "^3.0.0",
     "dotenv": "^10.0.0",

+ 47 - 0
frontend/src/components/LogViewer.vue

@@ -0,0 +1,47 @@
+<template>
+  <div class="col bg-grey-2 overflow-auto q-pa-sm" :key="logType + currentItem?.log_lines[0]?.date_time">
+    <q-virtual-scroll
+      ref="logArea"
+      class="full-height"
+      :items="logLines"
+    >
+      <template v-slot="{ item, index }">
+        <div :key="index" style="white-space: nowrap; line-height: 2">
+          {{ getTexLogLine(item) }}
+        </div>
+      </template>
+    </q-virtual-scroll>
+  </div>
+</template>
+
+<script setup>
+// 自动滚动到底部
+import {nextTick, watch} from 'vue';
+import {templateRef} from '@vueuse/core';
+
+const props = defineProps({
+  logLines: {
+    type: Array,
+    default: () => [],
+  }
+});
+
+const logArea = templateRef('logArea');
+
+// eslint-disable-next-line camelcase
+const getTexLogLine = ({ level, date_time, content }) => `[${level}]: ${date_time} - ${content}`;
+
+watch(
+  () => props.logLines.length,
+  () => {
+    const element = logArea.value.$el;
+    // console.log(element.scrollTop, element.clientHeight, element.scrollHeight);
+    // 如果当前正处于底部,则自动滚动
+    if (element.scrollTop + element.clientHeight >= element.scrollHeight - 10) {
+      nextTick(() => {
+        logArea.value.scrollTo(props.logLines.length - 1);
+      });
+    }
+  }
+);
+</script>

+ 0 - 60
frontend/src/components/VersionUpdateBadge.vue

@@ -1,60 +0,0 @@
-<template>
-  <q-badge v-if="latestVersion
-   && systemState.systemInfo
-   &&latestVersion.tag_name !== systemState.systemInfo?.version"
-           class="cursor-pointer"
-           label="new"
-           title="有新的版本更新"
-           @click="visible = true"
-  />
-  <q-dialog v-if="latestVersion" v-model="visible"
-             max-width="400px"
-             max-height="400px">
-    <q-card>
-      <q-card-section>
-        <div class="text-h5">{{latestVersion.tag_name}}更新日志</div>
-      </q-card-section>
-
-      <q-separator/>
-
-      <q-card-section>
-        <markdown :source="latestVersion.body" />
-      </q-card-section>
-
-      <q-separator/>
-
-      <q-card-section align="right">
-        <q-btn
-          color="primary"
-          @click="navigateToReleasePage"
-        >
-          前往更新
-        </q-btn>
-      </q-card-section>
-    </q-card>
-  </q-dialog>
-</template>
-
-<script setup>
-import {onMounted, ref} from 'vue';
-import Markdown from 'components/Markdown';
-import {systemState} from 'src/store/systemState';
-
-const latestVersion = ref(null);
-const visible = ref(false);
-
-
-const getLatestVersion = async () => {
-  const data = await fetch('https://api.github.com/repos/allanpk716/chinesesubfinder/releases/latest').then(
-    (res) => res.json()
-  );
-  latestVersion.value = data;
-};
-
-const navigateToReleasePage = () => {
-  window.open(latestVersion.value.html_url);
-  visible.value = false;
-}
-
-onMounted(getLatestVersion);
-</script>

+ 113 - 0
frontend/src/components/VersionUpdateItem.vue

@@ -0,0 +1,113 @@
+<template>
+  <span v-if="latestVersion?.tag_name
+   && systemState.systemInfo
+   &&latestVersion.tag_name !== systemState.systemInfo?.version"
+        @click="visible = true"
+  >
+    <slot v-if="$slots.default"></slot>
+    <q-badge v-else class="cursor-pointer"
+             label="new"
+             title="有新的版本更新"
+    />
+  </span>
+  <q-dialog v-if="latestVersion" v-model="visible">
+    <q-card class="column" style="width: 600px; min-height: 400px;">
+      <q-card-section>
+        <div class="text-h5">{{latestVersion.tag_name}}</div>
+      </q-card-section>
+
+      <q-tabs
+        v-model="tab"
+        dense
+        class="text-grey"
+        active-color="primary"
+        indicator-color="primary"
+        align="justify"
+        narrow-indicator
+      >
+        <q-tab name="log" label="更新日志" />
+        <q-tab name="update" label="升级方式" />
+      </q-tabs>
+
+      <q-separator/>
+
+      <q-tab-panels class="col" v-model="tab" animated>
+        <q-tab-panel name="log">
+          <markdown :source="latestVersion.body" />
+        </q-tab-panel>
+        <q-tab-panel name="update">
+          <section>
+            <div class="text-h6">Windows</div>
+            <div>
+              下载最新版本替换,
+              <a :href="latestVersion.html_url" target="_blank">
+                下载地址
+              </a>
+            </div>
+          </section>
+
+          <section>
+            <div class="text-h6">Docker</div>
+            <div>
+              参考教程
+              <!-- eslint-disable-next-line max-len -->
+              <a href="https://github.com/allanpk716/ChineseSubFinder/blob/docs/DesignFile/v0.20%E6%95%99%E7%A8%8B/00.Docker%E9%83%A8%E7%BD%B2%E6%95%99%E7%A8%8B.md" target="_blank">
+                Docker部署教程
+              </a>
+            </div>
+            <div class="text-grey">* 新版本发布到Docker发布完成可能需要一小时左右,如果发现Docker拉取的版本没有变化,请耐心等待一段时间</div>
+          </section>
+
+        </q-tab-panel>
+      </q-tab-panels>
+
+      <q-separator/>
+
+      <q-card-actions align="right">
+        <q-btn
+          color="primary"
+          @click="navigateToReleasePage"
+        >
+          前往更新
+        </q-btn>
+      </q-card-actions>
+    </q-card>
+  </q-dialog>
+</template>
+
+<script setup>
+import {onMounted, ref} from 'vue';
+import Markdown from 'components/Markdown';
+import {systemState} from 'src/store/systemState';
+import {LocalStorage} from 'quasar';
+
+const latestVersion = ref(LocalStorage.getItem('latestVersion') ?? null);
+const visible = ref(false);
+const tab = ref('log');
+
+
+const getLatestVersion = async () => {
+  try {
+    const data = await fetch('https://api.github.com/repos/allanpk716/chinesesubfinder/releases/latest').then(
+      (res) => {
+        if (res.ok) {
+          return res.json();
+        }
+        return Promise.reject(res)
+      }
+    );
+    latestVersion.value = data;
+    // 接口请求速率过高有可能403,本地存一份
+    LocalStorage.set('latestVersion', data);
+  } catch (e) {
+    // do nothing
+  }
+};
+
+const navigateToReleasePage = () => {
+  window.open(latestVersion.value.html_url);
+  visible.value = false;
+}
+
+onMounted(getLatestVersion);
+</script>

+ 0 - 0
frontend/src/pages/logs/useRealTimeLog.js → frontend/src/composables/useRealTimeLog.js


+ 7 - 2
frontend/src/composables/useWebSocketApi.js

@@ -31,7 +31,7 @@ class WSManager extends EventEmitter {
     ws.onopen = () => {
       // 连接成功后自动发送验证信息
       this.send('auth', {
-        token: LocalStorage.getItem('token'),
+        token: LocalStorage.getItem('token')?.accessToken,
       });
     };
 
@@ -77,6 +77,11 @@ class WSManager extends EventEmitter {
 
 // 根据BACKEND_URL配置计算ws地址
 export const getWsBaseUrl = () => {
+  try {
+    return process.env.BACKEND_WS_URL;
+  } catch (e) {
+    // do nothing
+  }
   let result = '';
   const backendUrl = process.env.BACKEND_URL;
   if (!backendUrl) {
@@ -91,7 +96,7 @@ export const getWsBaseUrl = () => {
     result = `${protocol}//${window.location.host}${backendUrl}`;
   }
 
-  return 'ws://localhost:8080' || result;
+  return result;
 };
 
 export const wsManager = new WSManager(`${getWsBaseUrl()}/ws`);

+ 18 - 0
frontend/src/layouts/BugReportItem.vue

@@ -0,0 +1,18 @@
+<template>
+  <q-item clickable @click="gotoGithubIssuePage" title="跳转到Github issue页面">
+    <q-item-section>
+      <div class="row items-center">
+        <span><q-icon name="bug_report" size="18px" /></span>
+        <span class="q-ml-xs">问题反馈</span>
+      </div>
+    </q-item-section>
+  </q-item>
+</template>
+
+<script setup>
+const gotoGithubIssuePage = () => {
+  const searchParams = new URLSearchParams();
+  searchParams.append('template', 'ISSUE_TEMPLATE_BUG.md');
+  window.open(`https://github.com/allanpk716/ChineseSubFinder/issues/new?${searchParams.toString()}`, '_blank');
+}
+</script>

+ 35 - 13
frontend/src/layouts/MainLayout.vue

@@ -3,8 +3,25 @@
     <q-header elevated>
       <q-toolbar class="text-white text-primary">
         <q-btn flat dense round color="white" icon="menu" aria-label="Menu" @click="leftDrawerOpen = !leftDrawerOpen" />
-        <div class="text-h5 q-ml-sm">{{$route.meta.title}}</div>
+        <div class="text-h6 q-ml-sm">{{ $route.meta.title }}</div>
         <q-space />
+        <version-update-item>
+          <q-item clickable>
+            <q-item-section class="relative-position q-px-sm">
+              版本升级
+              <div class="absolute-top-right bg-red" style="border-radius: 50%; width: 8px;height: 8px;"></div>
+            </q-item-section>
+          </q-item>
+        </version-update-item>
+        <q-item
+          clickable
+          @click="
+            openPage('https://github.com/allanpk716/ChineseSubFinder/tree/docs/DesignFile/v0.20%E6%95%99%E7%A8%8B')
+          "
+        >
+          <q-item-section> 帮助文档 </q-item-section>
+        </q-item>
+        <BugReportItem />
         <q-btn-dropdown :label="userState.username" icon="account_circle" flat>
           <q-list dense style="min-width: 100px">
             <q-item clickable v-close-popup>
@@ -23,14 +40,14 @@
       show-if-above
       bordered
       dark
-      style="background: #111729;"
+      style="background: #111729"
       content-class="bg-white"
     >
-      <div class="text-h5 q-py-sm q-px-md" style="height: 65px;">
+      <div class="text-h5 q-py-sm q-px-md" style="height: 65px">
         <div>ChineseSubFinder</div>
         <div class="text-body2">
           {{ systemState.systemInfo?.version }}
-          <version-update-badge/>
+<!--          <version-update-item />-->
         </div>
       </div>
       <q-list>
@@ -46,18 +63,19 @@
 
 <script setup>
 import routes from 'src/router/routes';
-import {ref } from 'vue';
-import {useRouter} from 'vue-router';
+import { ref } from 'vue';
+import { useRouter } from 'vue-router';
 import MenuItem from 'layouts/MenuItem';
-import {systemState} from 'src/store/systemState';
-import {userState} from 'src/store/userState';
-import {LocalStorage} from 'quasar';
+import { systemState } from 'src/store/systemState';
+import { userState } from 'src/store/userState';
+import { LocalStorage } from 'quasar';
 import AccessApi from 'src/api/AccessApi';
-import VersionUpdateBadge from 'components/VersionUpdateBadge';
+import BugReportItem from 'layouts/BugReportItem';
+import VersionUpdateItem from 'components/VersionUpdateItem';
 
 const router = useRouter();
 
-const leftDrawerOpen = ref(false)
+const leftDrawerOpen = ref(false);
 const menus = routes.find((e) => e.path === '/').children;
 
 const logout = () => {
@@ -65,6 +83,10 @@ const logout = () => {
   userState.accessToken = undefined;
   LocalStorage.remove('token');
   AccessApi.logout();
-  router.push('/access/login')
-}
+  router.push('/access/login');
+};
+
+const openPage = (url) => {
+  window.open(url, '_blank');
+};
 </script>

+ 55 - 0
frontend/src/pages/jobs/JobProgressCard.vue

@@ -0,0 +1,55 @@
+<template>
+  <q-card flat>
+    <header class="title">{{title}}</header>
+    <div class="row">
+      <div>
+        <q-circular-progress
+          show-value
+          font-size="12px"
+          :value="progress"
+          size="100px"
+          :thickness="0.22"
+          :color="color"
+          track-color="grey-3"
+          class="q-ma-md"
+        >
+          {{ progress }}%
+        </q-circular-progress>
+      </div>
+      <div class="col column justify-center">
+        <div>{{ current }} / {{ total }}</div>
+        <div class="text-grey overflow-hidden ellipsis" style="width: 200px" :title="currentName">
+          {{ currentName }}
+        </div>
+      </div>
+    </div>
+  </q-card>
+</template>
+
+<script setup>
+import {computed} from 'vue';
+
+const props = defineProps({
+  title: String,
+  current: Number,
+  total: Number,
+  currentName: String,
+  color: {
+    type: String,
+    default: 'secondary'
+  },
+})
+
+const progress = computed(() => {
+  const val = props.current / props.total * 100;
+  return val.toFixed(2);
+});
+</script>
+
+<style scoped>
+.title {
+  font-size: 14px;
+  font-weight: bold;
+  padding-left: 20px;
+}
+</style>

+ 11 - 0
frontend/src/pages/jobs/JobRTLogPanel.vue

@@ -0,0 +1,11 @@
+<template>
+  <div class="text-bold">运行日志:</div>
+  <log-viewer class="q-mt-sm" :log-lines="rtLogLines" style="height: 400px;" />
+</template>
+
+<script setup>
+import {useRealTimeLog} from 'src/composables/useRealTimeLog';
+import LogViewer from 'components/LogViewer';
+
+const { logLines: rtLogLines } = useRealTimeLog();
+</script>

+ 43 - 0
frontend/src/pages/jobs/SubJobPabel.vue

@@ -0,0 +1,43 @@
+<template>
+  <div v-if="subJobsDetail">
+    <div class="text-grey row items-center">
+      <div>
+        开始于 {{ subJobsDetail.started_time }}
+      </div>
+
+      <div class="q-my-xs q-ml-md">
+        <q-badge outline v-if="subJobsDetail.status === 'running'" color="positive">运行中</q-badge>
+        <q-badge outline v-else-if="subJobsDetail.status === 'waiting'" color="warning">等待中</q-badge>
+      </div>
+    </div>
+
+    <section class="row q-mt-md">
+      <job-progress-card
+        title="子任务进度"
+        :current="subJobsDetail.working_unit_index"
+        :total="subJobsDetail.unit_count"
+        :current-name="subJobsDetail.working_unit_name"
+      />
+
+      <job-progress-card
+        title="视频进度"
+        :current="subJobsDetail.working_video_index"
+        :total="subJobsDetail.video_count"
+        :current-name="subJobsDetail.working_video_name"
+      />
+
+    </section>
+  </div>
+</template>
+
+<script setup>
+import { useWebSocketApi } from 'src/composables/useWebSocketApi';
+import { ref } from 'vue';
+import JobProgressCard from 'pages/jobs/JobProgressCard';
+
+const subJobsDetail = ref(null);
+
+useWebSocketApi('sub_download_jobs_status', (data) => {
+  subJobsDetail.value = data;
+});
+</script>

+ 12 - 2
frontend/src/pages/jobs/index.vue

@@ -1,7 +1,7 @@
 <template>
   <q-page class="q-pa-md">
-    <q-card v-if="systemState.jobStatus" class="q-pa-md" flat>
-      <header class="column q-gutter-md">
+    <q-card v-if="systemState.jobStatus" flat>
+      <header class="row items-center justify-between q-gutter-md">
         <div>
           当前任务状态:
           <q-badge v-if="isRunning" color="positive">运行中</q-badge>
@@ -19,6 +19,14 @@
         </div>
       </header>
     </q-card>
+
+    <q-separator class="q-my-md"/>
+
+    <sub-job-pabel/>
+
+    <q-separator class="q-my-md"/>
+
+    <job-r-t-log-panel/>
   </q-page>
 </template>
 
@@ -28,6 +36,8 @@ import { useQuasar } from 'quasar';
 import {computed, onMounted, ref} from 'vue';
 import JobApi from 'src/api/JobApi';
 import { SystemMessage } from 'src/utils/Message';
+import SubJobPabel from 'pages/jobs/SubJobPabel';
+import JobRTLogPanel from 'pages/jobs/JobRTLogPanel';
 
 const $q = useQuasar();
 

+ 14 - 37
frontend/src/pages/logs/index.vue

@@ -44,21 +44,11 @@
         </q-list>
       </div>
       <q-separator vertical />
-      <div class="full-height col bg-grey-2 overflow-auto" :key="logType + currentItem?.log_lines[0]?.date_time">
-        <q-virtual-scroll
-          v-model.number="virtualListIndex"
-          ref="logArea"
-          class="full-height q-pa-sm"
-          :items="currentLogLines"
-          :items-size="1000"
-        >
-          <template v-slot="{ item, index }">
-            <div :key="index" style="white-space: nowrap; line-height: 2">
-              {{ getTexLogLine(item) }}
-            </div>
-          </template>
-        </q-virtual-scroll>
-      </div>
+      <log-viewer
+        class="full-height"
+        :log-lines="currentLogLines"
+        :key="logType + currentItem?.log_lines[0]?.date_time"
+      />
     </q-card>
   </fix-height-q-page>
 </template>
@@ -67,13 +57,15 @@
 import { useLogList } from 'pages/logs/useLogList';
 import FixHeightQPage from 'components/FixHeightQPage';
 import { saveText } from 'src/utils/FileDownload';
-import { useRealTimeLog } from 'pages/logs/useRealTimeLog';
-import { computed, nextTick, ref, watch } from 'vue';
-import { templateRef } from '@vueuse/core';
+import { computed, ref } from 'vue';
+import { useRealTimeLog } from 'src/composables/useRealTimeLog';
+import LogViewer from 'components/LogViewer';
+import {getExportSettings, useSettings} from 'pages/settings/useSettings';
 
 const { logList, currentIndex, currentItem } = useLogList();
 const logType = ref('rt'); // rt or history
 
+useSettings();
 const { logLines: rtLogLines } = useRealTimeLog();
 
 const handleHistoryItemClick = (item) => {
@@ -93,26 +85,11 @@ const currentLogLines = computed(() => {
   return lines || [];
 });
 
-const logArea = templateRef('logArea');
-
-// 自动滚动到底部
-watch(
-  () => rtLogLines.value.length,
-  () => {
-    if (logType.value !== 'rt') return;
-    const element = logArea.value.$el;
-    // console.log(element.scrollTop, element.clientHeight, element.scrollHeight);
-    // 如果当前正处于底部,则自动滚动
-    if (element.scrollTop + element.clientHeight >= element.scrollHeight - 10) {
-      nextTick(() => {
-        logArea.value.scrollTo(rtLogLines.value.length - 1);
-      });
-    }
-  }
-);
-
 const downloadLog = (logLines) => {
   const filename = `${logLines[0]?.date_time || 'output'}.log`;
-  saveText(filename, getTextLogContent(logLines));
+  const configString = JSON.stringify(getExportSettings(), null, 2);
+  const logString = getTextLogContent(logLines);
+  const content = `config:\n${configString}\n\nlog:\n${logString}`;
+  saveText(filename, content);
 };
 </script>

+ 3 - 10
frontend/src/pages/settings/exportSettingBtnDialog.vue

@@ -39,28 +39,21 @@
 
 <script setup>
 import { computed, ref } from 'vue';
-import { settingsState } from 'pages/settings/useSettings';
+import {getExportSettings} from 'pages/settings/useSettings';
 import { copyToClipboard } from 'quasar';
 import { SystemMessage } from 'src/utils/Message';
-import {deepCopy} from 'src/utils/CommonUtils';
 import {saveText} from 'src/utils/FileDownload';
 
 const visible = ref(false);
 const hideSensitive = ref(false);
 
 const settingsString = computed(() => {
-  const settings = deepCopy(settingsState.data);
-  delete settings.user_info;
-  if (hideSensitive.value) {
-    delete settings.common_settings.threads;
-    delete settings.emby_settings.api_key;
-    delete settings.emby_settings.address_url;
-  }
+  const settings = getExportSettings(!hideSensitive.value);
   return JSON.stringify(settings, null, 2);
 });
 
 const exportSettings = () => {
-  saveText(settingsString.value, 'ChineseSubFinderSettings.json');
+  saveText('ChineseSubFinderSettings.json', settingsString.value);
 };
 
 const copy = (str) =>

+ 17 - 0
frontend/src/pages/settings/useSettings.js

@@ -30,6 +30,7 @@ const updateFolderMap = () => {
     embySettings.series_paths_mapping || {}
   );
 };
+
 watch(
   () => settingsState.data,
   () => {
@@ -68,3 +69,19 @@ export const submitAll = async () => {
   settingsState.data = { ...settingsState.data, ...deepCopy(formModel) };
   SystemMessage.success('保存成功');
 };
+
+/**
+ * 获取导出的settings
+ * @param includeSensitive
+ * @returns {any}
+ */
+export const getExportSettings = (includeSensitive = false) => {
+  const data = deepCopy(settingsState.data);
+  if (!includeSensitive) {
+    delete data.user_info;
+    delete data.common_settings.threads;
+    delete data.emby_settings.api_key;
+    delete data.emby_settings.address_url;
+  }
+  return data;
+};