优化发布图片问题
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user