优化发布图片问题

This commit is contained in:
yml2213
2026-05-23 12:19:24 +08:00
parent 367f26ffd1
commit d90f31360f
6 changed files with 122 additions and 25 deletions
+8
View File
@@ -41,3 +41,11 @@ export async function fetchAdminFileBlob(key: string) {
})
return data
}
export async function fetchFileBlobByURL(fileURL: string) {
const apiPath = fileURL.startsWith('/api/') ? fileURL.slice(4) : fileURL
const { data } = await apiClient.get<Blob>(apiPath, {
responseType: 'blob',
})
return data
}
@@ -81,6 +81,8 @@ const detailSkinGroups = computed(() => {
const titles: Record<string, string> = {
melee: "近战皮肤",
operator: "干员皮肤",
operatorGold: "干员金皮",
operatorRed: "干员红皮",
weapon: "武器皮肤",
};
return Object.entries(groups as Record<string, unknown>)
@@ -1,9 +1,9 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from "vue";
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from "vue";
import { useRouter, useRoute } from "vue-router";
import { showDialog, showToast } from "vant";
import { uploadFile } from "@/api/files";
import { fetchFileBlobByURL, uploadFile } from "@/api/files";
import {
emptyListingPublishOptions,
fetchListingPublishOptions,
@@ -94,6 +94,7 @@ const quantityValues = reactive<Record<QuantityKey, number>>(defaultQuantityValu
const quantityModes = reactive<Record<QuantityKey, ChargeMode>>(defaultQuantityModes());
const screenshotFiles = reactive<Record<ScreenshotKey, string>>(defaultScreenshotFiles());
const screenshotPreviews = reactive<Record<ScreenshotKey, string>>({});
const selectedSkins = ref<string[]>([]);
const fileInput = ref<HTMLInputElement | null>(null);
@@ -118,8 +119,11 @@ const screenshotUrls = computed(() =>
);
const coinWanAmount = computed(() => Number(form.haf_coin_amount || 0));
const calculatedRatio = computed(() => calculatePublishRatio());
const calculatedConsumablePrice = computed(() => calculateConsumablePrice());
const calculatedFinalPrice = computed(() =>
calculatedRatio.value > 0 ? roundMoney(coinWanAmount.value / calculatedRatio.value) : 0
calculatedRatio.value > 0
? roundMoney(coinWanAmount.value / calculatedRatio.value + calculatedConsumablePrice.value)
: 0
);
const calculatedRentDays = computed(() =>
coinWanAmount.value > 0 ? calculateRentDays(coinWanAmount.value) : 0
@@ -130,6 +134,10 @@ onMounted(() => {
loadPublishOptions();
});
onBeforeUnmount(() => {
revokeAllScreenshotPreviews();
});
watch(
[form, quantityValues, quantityModes, screenshotFiles, selectedSkins],
() => {
@@ -176,6 +184,7 @@ function restoreDraft() {
selectedSkins.value = Array.isArray(draft.selectedSkins)
? draft.selectedSkins.filter((skin): skin is string => typeof skin === "string")
: [];
hydrateScreenshotPreviews();
} catch {
localStorage.removeItem(draftKey);
}
@@ -207,6 +216,7 @@ function resetDraftState() {
Object.assign(quantityValues, defaultQuantityValues());
Object.assign(quantityModes, defaultQuantityModes());
Object.assign(screenshotFiles, defaultScreenshotFiles());
revokeAllScreenshotPreviews();
selectedSkins.value = [];
activeUploadKey.value = "coin";
}
@@ -257,12 +267,16 @@ async function handleScreenshotUpload(event: Event) {
const input = event.target as HTMLInputElement;
const file = input.files?.[0];
if (!file) return;
const key = activeUploadKey.value;
const previewURL = URL.createObjectURL(file);
setScreenshotPreview(key, previewURL);
uploading.value = true;
try {
const uploaded = await uploadFile(file, "listing");
screenshotFiles[activeUploadKey.value] = uploaded.url;
screenshotFiles[key] = uploaded.url;
showToast({ message: "截图已上传", icon: "passed" });
} catch (error) {
revokeScreenshotPreview(key);
showToast({ message: readError(error, "截图上传失败"), icon: "cross" });
} finally {
uploading.value = false;
@@ -272,6 +286,44 @@ async function handleScreenshotUpload(event: Event) {
function removeScreenshot(key: ScreenshotKey) {
screenshotFiles[key] = "";
revokeScreenshotPreview(key);
}
function getScreenshotPreviewURL(key: ScreenshotKey) {
return screenshotPreviews[key] || screenshotFiles[key] || "";
}
function setScreenshotPreview(key: ScreenshotKey, previewURL: string) {
revokeScreenshotPreview(key);
screenshotPreviews[key] = previewURL;
}
function revokeScreenshotPreview(key: ScreenshotKey) {
const previewURL = screenshotPreviews[key];
if (previewURL?.startsWith("blob:")) {
URL.revokeObjectURL(previewURL);
}
delete screenshotPreviews[key];
}
function revokeAllScreenshotPreviews() {
for (const key of Object.keys(screenshotPreviews)) {
revokeScreenshotPreview(key);
}
}
async function hydrateScreenshotPreviews() {
for (const [key, fileURL] of Object.entries(screenshotFiles)) {
if (!fileURL || screenshotPreviews[key] || !fileURL.startsWith("/api/files/object")) {
continue;
}
try {
const blob = await fetchFileBlobByURL(fileURL);
setScreenshotPreview(key, URL.createObjectURL(blob));
} catch {
// 草稿预览失败不影响已上传文件地址,提交时仍会带上原 URL。
}
}
}
function handleFireLevelInput(value: string | number) {
@@ -386,6 +438,28 @@ function roundMoney(value: number) {
return Math.round(value * 100) / 100;
}
function calculateConsumablePrice() {
const total = quantityItems.value.reduce((sum, item) => {
const quantity = Number(quantityValues[item.key] || 0);
const mode = quantityModes[item.key] || "收费";
if (quantity <= 0 || mode !== "收费") return sum;
return sum + quantity * readUnitPrice(item.price);
}, 0);
return roundMoney(total);
}
function readUnitPrice(priceText: string) {
const normalized = priceText.replace(//g, ",").trim();
const fractionMatch = normalized.match(/(\d+(?:\.\d+)?)\s*元\s*\/\s*(\d+(?:\.\d+)?)/);
if (fractionMatch) {
const amount = Number(fractionMatch[1]);
const count = Number(fractionMatch[2]);
return count > 0 ? amount / count : 0;
}
const singleMatch = normalized.match(/(\d+(?:\.\d+)?)\s*元/);
return singleMatch ? Number(singleMatch[1]) : 0;
}
function calculatePublishRatio() {
if (
coinWanAmount.value <= 0 ||
@@ -420,7 +494,7 @@ function getInsuranceSlots(insurance: string) {
function getConfigHitCount() {
const checks = [
hasSelectedSkinGroup("operator"),
hasSelectedSkinGroup("operatorRed"),
hasSelectedSkinGroup("melee"),
isMaxLevel(form.stamina_level),
isMaxLevel(form.load_level),
@@ -756,7 +830,7 @@ function readError(error: unknown, fallback: string) {
<small>{{ slot.hint }}</small>
</div>
<div v-if="screenshotFiles[slot.key]" class="upload-preview">
<img :src="screenshotFiles[slot.key]" :alt="slot.label" />
<img :src="getScreenshotPreviewURL(slot.key)" :alt="slot.label" />
<button type="button" @click="removeScreenshot(slot.key)">移除</button>
</div>
<button