Skip to content

Commit a27068a

Browse files
committed
feat: 优化前端手动备份; 预览窗口可随时切换 "在网页中预览"
1 parent 4f6a6ea commit a27068a

8 files changed

Lines changed: 167 additions & 68 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sub-store-front-end",
3-
"version": "2.29.1",
3+
"version": "2.29.2",
44
"private": true,
55
"packageManager": "pnpm@11.0.9",
66
"scripts": {

pnpm-lock.yaml

Lines changed: 38 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/api/settings/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ export function useSettingsApi() {
2727
}
2828
});
2929
},
30+
downloadBackup: (): AxiosPromise<Blob> => {
31+
return request({
32+
url: '/api/storage',
33+
method: 'get',
34+
responseType: 'blob',
35+
});
36+
},
3037
restoreSettings: (data: StoragePostData): AxiosPromise<MyAxiosRes> => {
3138
return request({
3239
url: '/api/storage',

src/components/PreviewPanel.vue

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@
1919
>
2020
<label for="prettyYaml">{{ prettyYamlLabel }}</label>
2121
</div>
22+
<div class="preview-option-item">
23+
<input
24+
type="checkbox"
25+
id="displayPreviewInWebPage"
26+
name="displayPreviewInWebPage"
27+
:checked="appearanceSetting.displayPreviewInWebPage"
28+
@change="setDisplayPreviewInWebPage"
29+
>
30+
<label for="displayPreviewInWebPage">{{ displayPreviewInWebPageLabel }}</label>
31+
</div>
2232
</div>
2333
<ul class="preview-list">
2434
<li v-for="platform in platformList" :key="platform.name">
@@ -104,6 +114,7 @@
104114
tipsOkText,
105115
includeUnsupportedProxyLabel,
106116
prettyYamlLabel,
117+
displayPreviewInWebPageLabel,
107118
} = defineProps<{
108119
name: string;
109120
displayName?: string;
@@ -113,6 +124,7 @@
113124
desc: string;
114125
includeUnsupportedProxyLabel: string;
115126
prettyYamlLabel: string;
127+
displayPreviewInWebPageLabel: string;
116128
url?: string;
117129
tipsTitle?: string;
118130
tipsContent?: string;
@@ -158,6 +170,20 @@
158170
watch(prettyYaml, (value) => {
159171
setLocalStorageBoolean(PREVIEW_PRETTY_YAML_KEY, value);
160172
});
173+
const setDisplayPreviewInWebPage = async (event: Event) => {
174+
const input = event.target as HTMLInputElement;
175+
try {
176+
await changeAppearanceSetting({
177+
appearanceSetting: {
178+
displayPreviewInWebPage: input.checked,
179+
},
180+
});
181+
} catch (e) {
182+
console.error(e);
183+
} finally {
184+
input.checked = appearanceSetting.value.displayPreviewInWebPage;
185+
}
186+
};
161187
const buildUrlWithQuery = (url: string, query: Record<string, string | boolean>): string => {
162188
if (!url) {
163189
return '';

src/components/ShareListItem.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,7 @@ const onClickPreviews = () => {
406406
notify: t("subPage.copyNotify.succeedWithShare"),
407407
includeUnsupportedProxyLabel: t("subPage.panel.options.includeUnsupportedProxy"),
408408
prettyYamlLabel: t("subPage.panel.options.prettyYaml"),
409+
displayPreviewInWebPageLabel: t("moreSettingPage.displayPreviewInWebPage"),
409410
tipsTitle: t("subPage.panel.tips.title"),
410411
tipsContent: `${t("subPage.panel.tips.content")}\n${t(
411412
"syncPage.addArtForm.includeUnsupportedProxy.tips.content",

src/components/SubListItem.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,7 @@ const openPreviewPanel = () => {
868868
notify: t(`subPage.copyNotify.${shareBtnVisible.value ? "succeedWithShare" : "succeed"}`),
869869
includeUnsupportedProxyLabel: t("subPage.panel.options.includeUnsupportedProxy"),
870870
prettyYamlLabel: t("subPage.panel.options.prettyYaml"),
871+
displayPreviewInWebPageLabel: t("moreSettingPage.displayPreviewInWebPage"),
871872
tipsTitle: t(`subPage.panel.tips.title`),
872873
tipsContent: `${t("subPage.panel.tips.content")}\n${t(
873874
"syncPage.addArtForm.includeUnsupportedProxy.tips.content",

src/store/settings.ts

Lines changed: 46 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -530,51 +530,54 @@ export const useSettingsStore = defineStore("settingsStore", {
530530
async changeAppearanceSetting(data: SettingsPostData) {
531531
Toast.loading("保存外观设置中...", { cover: true, id: "theme__loading" });
532532
const { showNotify } = useAppNotifyStore();
533-
const forceEditorGroupingMode = Boolean(
534-
data.appearanceSetting
535-
&& !this.hasRemoteEditorGroupingMode
536-
&& this.hasCachedEditorGroupingMode
537-
&& isEditorGroupingMode(data.appearanceSetting.editorGroupingMode)
538-
);
539-
const requestData = data.appearanceSetting
540-
? {
541-
...data,
542-
appearanceSetting: createAppearanceSettingPatch(
543-
data.appearanceSetting,
544-
this.appearanceSetting,
545-
{ forceEditorGroupingMode },
546-
),
547-
}
548-
: data;
549-
const res = await settingsApi.setSettings(requestData);
550-
if (res?.data?.status === "success" && res?.data?.data) {
551-
const hasAppearanceSettingPatch = hasRemoteAppearanceSetting(requestData.appearanceSetting);
552-
const responseHasAppearanceSetting = hasRemoteAppearanceSetting(res.data.data.appearanceSetting);
553-
const responseHasEditorGroupingMode = hasRemoteEditorGroupingMode(res.data.data.appearanceSetting);
554-
const requestHasEditorGroupingMode = isEditorGroupingMode(requestData.appearanceSetting?.editorGroupingMode);
555-
this.hasRemoteAppearanceSetting = this.hasRemoteAppearanceSetting
556-
|| responseHasAppearanceSetting;
557-
this.hasRemoteEditorGroupingMode = responseHasEditorGroupingMode;
558-
this.applyAppearanceSetting(
559-
hasAppearanceSettingPatch
560-
? {
561-
...this.appearanceSetting,
562-
...requestData.appearanceSetting,
563-
}
564-
: res.data.data.appearanceSetting,
565-
{
566-
cacheEditorGroupingMode: responseHasEditorGroupingMode
567-
|| requestHasEditorGroupingMode
568-
|| this.hasCachedEditorGroupingMode,
569-
},
533+
try {
534+
const forceEditorGroupingMode = Boolean(
535+
data.appearanceSetting
536+
&& !this.hasRemoteEditorGroupingMode
537+
&& this.hasCachedEditorGroupingMode
538+
&& isEditorGroupingMode(data.appearanceSetting.editorGroupingMode)
570539
);
571-
} else {
572-
showNotify({
573-
title: `保存外观设置失败`,
574-
type: "danger",
575-
});
540+
const requestData = data.appearanceSetting
541+
? {
542+
...data,
543+
appearanceSetting: createAppearanceSettingPatch(
544+
data.appearanceSetting,
545+
this.appearanceSetting,
546+
{ forceEditorGroupingMode },
547+
),
548+
}
549+
: data;
550+
const res = await settingsApi.setSettings(requestData);
551+
if (res?.data?.status === "success" && res?.data?.data) {
552+
const hasAppearanceSettingPatch = hasRemoteAppearanceSetting(requestData.appearanceSetting);
553+
const responseHasAppearanceSetting = hasRemoteAppearanceSetting(res.data.data.appearanceSetting);
554+
const responseHasEditorGroupingMode = hasRemoteEditorGroupingMode(res.data.data.appearanceSetting);
555+
const requestHasEditorGroupingMode = isEditorGroupingMode(requestData.appearanceSetting?.editorGroupingMode);
556+
this.hasRemoteAppearanceSetting = this.hasRemoteAppearanceSetting
557+
|| responseHasAppearanceSetting;
558+
this.hasRemoteEditorGroupingMode = responseHasEditorGroupingMode;
559+
this.applyAppearanceSetting(
560+
hasAppearanceSettingPatch
561+
? {
562+
...this.appearanceSetting,
563+
...requestData.appearanceSetting,
564+
}
565+
: res.data.data.appearanceSetting,
566+
{
567+
cacheEditorGroupingMode: responseHasEditorGroupingMode
568+
|| requestHasEditorGroupingMode
569+
|| this.hasCachedEditorGroupingMode,
570+
},
571+
);
572+
} else {
573+
showNotify({
574+
title: `保存外观设置失败`,
575+
type: "danger",
576+
});
577+
}
578+
} finally {
579+
Toast.hide("theme__loading");
576580
}
577-
Toast.hide("theme__loading");
578581
},
579582
},
580583
});

src/views/My.vue

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -78,18 +78,20 @@
7878
/>
7979
{{ $t(`myPage.storage.manual.restore`) }}
8080
</nut-button>
81-
<a :href="host + '/api/storage'" target="_blank">
82-
<nut-button
83-
class="download-btn"
84-
type="primary"
85-
size="small"
86-
>
87-
<font-awesome-icon
88-
icon="fa-solid fa-cloud-arrow-down"
89-
/>
90-
{{ $t(`myPage.storage.manual.backup`) }}
91-
</nut-button>
92-
</a>
81+
<nut-button
82+
class="download-btn"
83+
type="primary"
84+
size="small"
85+
:disabled="backupIsLoading"
86+
:loading="backupIsLoading"
87+
@click="downloadBackup"
88+
>
89+
<font-awesome-icon
90+
v-if="!backupIsLoading"
91+
icon="fa-solid fa-cloud-arrow-down"
92+
/>
93+
{{ $t(`myPage.storage.manual.backup`) }}
94+
</nut-button>
9395
</div>
9496
<div v-else class="actions">
9597
<nut-button
@@ -1126,6 +1128,7 @@ const setDisplayInfo = () => {
11261128
const downloadIsLoading = ref(false);
11271129
const uploadIsLoading = ref(false);
11281130
const restoreIsLoading = ref(false);
1131+
const backupIsLoading = ref(false);
11291132
const syncIsDisabled = computed(() => {
11301133
return (
11311134
uploadIsLoading.value ||
@@ -1142,6 +1145,38 @@ const desText = computed(() => {
11421145
return [t(`myPage.placeholder.uploadTime`), butifyDate(syncTime.value)];
11431146
}
11441147
});
1148+
const downloadBackup = async () => {
1149+
backupIsLoading.value = true;
1150+
try {
1151+
const res = await useSettingsApi().downloadBackup();
1152+
if (!res || res.status < 200 || res.status >= 300) throw new Error('backup failed');
1153+
1154+
const disposition = res.headers?.['content-disposition'] || '';
1155+
const filename = decodeURIComponent(
1156+
disposition.match(/filename="?([^";]+)"?/i)?.[1] || 'sub-store_data.json',
1157+
);
1158+
const url = URL.createObjectURL(new Blob([res.data], { type: 'application/octet-stream' }));
1159+
const link = document.createElement('a');
1160+
link.href = url;
1161+
link.download = filename;
1162+
document.body.appendChild(link);
1163+
link.click();
1164+
link.remove();
1165+
setTimeout(() => URL.revokeObjectURL(url), 1000);
1166+
showNotify({
1167+
type: "success",
1168+
title: t(`myPage.notify.download.succeed`),
1169+
});
1170+
} catch (e) {
1171+
showNotify({
1172+
type: "danger",
1173+
title: t(`myPage.notify.download.failed`),
1174+
});
1175+
console.error(e);
1176+
} finally {
1177+
backupIsLoading.value = false;
1178+
}
1179+
};
11451180
const fileChange = async (event) => {
11461181
const file = event.target.files[0];
11471182
if(!file) return

0 commit comments

Comments
 (0)