发布功能可以 存草靠
This commit is contained in:
@@ -431,15 +431,32 @@
|
|||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.publish-actions {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 92px minmax(0, 1fr);
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset-btn,
|
||||||
.submit-btn {
|
.submit-btn {
|
||||||
height: 44px;
|
height: 44px;
|
||||||
margin-top: 8px;
|
width: 100%;
|
||||||
border: none !important;
|
border: none !important;
|
||||||
background: #ff6a00 !important;
|
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.reset-btn {
|
||||||
|
border: 1px solid #d7dce3 !important;
|
||||||
|
background: #fff !important;
|
||||||
|
color: #4b5563 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
background: #ff6a00 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.bottom-nav {
|
.bottom-nav {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, reactive, ref } from "vue";
|
import { computed, onMounted, reactive, ref, watch } from "vue";
|
||||||
import { useRouter, useRoute } from "vue-router";
|
import { useRouter, useRoute } from "vue-router";
|
||||||
import { showToast } from "vant";
|
import { showDialog, showToast } from "vant";
|
||||||
|
|
||||||
import { uploadFile } from "@/api/files";
|
import { uploadFile } from "@/api/files";
|
||||||
import {
|
import {
|
||||||
@@ -14,10 +14,42 @@ import {
|
|||||||
} from "@/api/listingOptions";
|
} from "@/api/listingOptions";
|
||||||
import { createListing } from "@/api/listings";
|
import { createListing } from "@/api/listings";
|
||||||
|
|
||||||
|
type PublishForm = {
|
||||||
|
server_region: string;
|
||||||
|
face_owner: string;
|
||||||
|
haf_coin_amount: number | "";
|
||||||
|
rank_level: string;
|
||||||
|
secret_kd: string;
|
||||||
|
fire_level: number | "";
|
||||||
|
season_insurance: string;
|
||||||
|
stamina_level: string;
|
||||||
|
load_level: string;
|
||||||
|
login_method: string;
|
||||||
|
online_start: string;
|
||||||
|
online_end: string;
|
||||||
|
ban_record: string;
|
||||||
|
common_regions: string[];
|
||||||
|
deposit_amount: number | "";
|
||||||
|
ratio: number | "";
|
||||||
|
final_price: number;
|
||||||
|
rent_days: number;
|
||||||
|
remark: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface PublishDraft {
|
||||||
|
form: PublishForm;
|
||||||
|
quantityValues: Record<QuantityKey, number>;
|
||||||
|
quantityModes: Record<QuantityKey, ChargeMode>;
|
||||||
|
screenshotFiles: Record<ScreenshotKey, string>;
|
||||||
|
selectedSkins: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const draftKey = "hfb.mobile.publish.draft";
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const uploading = ref(false);
|
const uploading = ref(false);
|
||||||
|
const suppressDraftSave = ref(false);
|
||||||
const publishOptions = ref<ListingPublishOptions>(defaultListingPublishOptions);
|
const publishOptions = ref<ListingPublishOptions>(defaultListingPublishOptions);
|
||||||
|
|
||||||
function isNavActive(path: string) {
|
function isNavActive(path: string) {
|
||||||
@@ -25,13 +57,14 @@ function isNavActive(path: string) {
|
|||||||
return route.path.startsWith(path);
|
return route.path.startsWith(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
const form = reactive({
|
function defaultForm(): PublishForm {
|
||||||
|
return {
|
||||||
server_region: "",
|
server_region: "",
|
||||||
face_owner: "",
|
face_owner: "",
|
||||||
haf_coin_amount: "" as number | "",
|
haf_coin_amount: "",
|
||||||
rank_level: "",
|
rank_level: "",
|
||||||
secret_kd: "",
|
secret_kd: "",
|
||||||
fire_level: "" as number | "",
|
fire_level: "",
|
||||||
season_insurance: "",
|
season_insurance: "",
|
||||||
stamina_level: "",
|
stamina_level: "",
|
||||||
load_level: "",
|
load_level: "",
|
||||||
@@ -39,39 +72,54 @@ const form = reactive({
|
|||||||
online_start: "",
|
online_start: "",
|
||||||
online_end: "",
|
online_end: "",
|
||||||
ban_record: "",
|
ban_record: "",
|
||||||
common_regions: [] as string[],
|
common_regions: [],
|
||||||
deposit_amount: "" as number | "",
|
deposit_amount: "",
|
||||||
ratio: "" as number | "",
|
ratio: "",
|
||||||
final_price: 0,
|
final_price: 0,
|
||||||
rent_days: 0,
|
rent_days: 0,
|
||||||
remark: "",
|
remark: "",
|
||||||
});
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const quantityValues = reactive<Record<QuantityKey, number>>({
|
function defaultQuantityValues(): Record<QuantityKey, number> {
|
||||||
|
return {
|
||||||
awmAmmo: 0,
|
awmAmmo: 0,
|
||||||
helmet6: 0,
|
helmet6: 0,
|
||||||
armor6: 0,
|
armor6: 0,
|
||||||
kit5: 0,
|
kit5: 0,
|
||||||
level6Ammo: 0,
|
level6Ammo: 0,
|
||||||
gridCard9: 0,
|
gridCard9: 0,
|
||||||
});
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const quantityModes = reactive<Record<QuantityKey, ChargeMode>>({
|
function defaultQuantityModes(): Record<QuantityKey, ChargeMode> {
|
||||||
|
return {
|
||||||
awmAmmo: "收费",
|
awmAmmo: "收费",
|
||||||
helmet6: "收费",
|
helmet6: "收费",
|
||||||
armor6: "收费",
|
armor6: "收费",
|
||||||
kit5: "收费",
|
kit5: "收费",
|
||||||
level6Ammo: "收费",
|
level6Ammo: "收费",
|
||||||
gridCard9: "收费",
|
gridCard9: "收费",
|
||||||
});
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const screenshotFiles = reactive<Record<ScreenshotKey, string>>({
|
function defaultScreenshotFiles(): Record<ScreenshotKey, string> {
|
||||||
|
return {
|
||||||
coin: "",
|
coin: "",
|
||||||
gameId: "",
|
gameId: "",
|
||||||
totalAsset: "",
|
totalAsset: "",
|
||||||
tencentSecurity: "",
|
tencentSecurity: "",
|
||||||
skin: "",
|
skin: "",
|
||||||
});
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = reactive<PublishForm>(defaultForm());
|
||||||
|
|
||||||
|
const quantityValues = reactive<Record<QuantityKey, number>>(defaultQuantityValues());
|
||||||
|
|
||||||
|
const quantityModes = reactive<Record<QuantityKey, ChargeMode>>(defaultQuantityModes());
|
||||||
|
|
||||||
|
const screenshotFiles = reactive<Record<ScreenshotKey, string>>(defaultScreenshotFiles());
|
||||||
|
|
||||||
const selectedSkins = ref<string[]>([]);
|
const selectedSkins = ref<string[]>([]);
|
||||||
const fileInput = ref<HTMLInputElement | null>(null);
|
const fileInput = ref<HTMLInputElement | null>(null);
|
||||||
@@ -96,7 +144,18 @@ const dailyLossText = computed(() =>
|
|||||||
form.rent_days ? calculateDailyLoss(Number(form.haf_coin_amount || 0)) : ""
|
form.rent_days ? calculateDailyLoss(Number(form.haf_coin_amount || 0)) : ""
|
||||||
);
|
);
|
||||||
|
|
||||||
onMounted(loadPublishOptions);
|
onMounted(() => {
|
||||||
|
restoreDraft();
|
||||||
|
loadPublishOptions();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
[form, quantityValues, quantityModes, screenshotFiles, selectedSkins],
|
||||||
|
() => {
|
||||||
|
saveDraft();
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
async function loadPublishOptions() {
|
async function loadPublishOptions() {
|
||||||
try {
|
try {
|
||||||
@@ -106,6 +165,74 @@ async function loadPublishOptions() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildDraft(): PublishDraft {
|
||||||
|
return {
|
||||||
|
form: {
|
||||||
|
...form,
|
||||||
|
common_regions: [...form.common_regions],
|
||||||
|
},
|
||||||
|
quantityValues: { ...quantityValues },
|
||||||
|
quantityModes: { ...quantityModes },
|
||||||
|
screenshotFiles: { ...screenshotFiles },
|
||||||
|
selectedSkins: [...selectedSkins.value],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveDraft() {
|
||||||
|
if (suppressDraftSave.value) return;
|
||||||
|
localStorage.setItem(draftKey, JSON.stringify(buildDraft()));
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreDraft() {
|
||||||
|
const raw = localStorage.getItem(draftKey);
|
||||||
|
if (!raw) return;
|
||||||
|
try {
|
||||||
|
const draft = JSON.parse(raw) as Partial<PublishDraft>;
|
||||||
|
Object.assign(form, defaultForm(), draft.form || {});
|
||||||
|
form.common_regions = Array.isArray(draft.form?.common_regions)
|
||||||
|
? [...draft.form.common_regions]
|
||||||
|
: [];
|
||||||
|
Object.assign(quantityValues, defaultQuantityValues(), draft.quantityValues || {});
|
||||||
|
Object.assign(quantityModes, defaultQuantityModes(), draft.quantityModes || {});
|
||||||
|
Object.assign(screenshotFiles, defaultScreenshotFiles(), draft.screenshotFiles || {});
|
||||||
|
selectedSkins.value = Array.isArray(draft.selectedSkins)
|
||||||
|
? draft.selectedSkins.filter((skin): skin is string => typeof skin === "string")
|
||||||
|
: [];
|
||||||
|
} catch {
|
||||||
|
localStorage.removeItem(draftKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetDraftState() {
|
||||||
|
Object.assign(form, defaultForm());
|
||||||
|
Object.assign(quantityValues, defaultQuantityValues());
|
||||||
|
Object.assign(quantityModes, defaultQuantityModes());
|
||||||
|
Object.assign(screenshotFiles, defaultScreenshotFiles());
|
||||||
|
selectedSkins.value = [];
|
||||||
|
activeUploadKey.value = "coin";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleResetDraft() {
|
||||||
|
try {
|
||||||
|
await showDialog({
|
||||||
|
title: "重置发布内容",
|
||||||
|
message: "将清空当前填写内容和本地草稿。",
|
||||||
|
confirmButtonText: "重置",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
showCancelButton: true,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
suppressDraftSave.value = true;
|
||||||
|
resetDraftState();
|
||||||
|
localStorage.removeItem(draftKey);
|
||||||
|
showToast({ message: "已重置", icon: "passed" });
|
||||||
|
window.setTimeout(() => {
|
||||||
|
suppressDraftSave.value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function selectRadio<T extends string>(value: T, setter: (value: T) => void) {
|
function selectRadio<T extends string>(value: T, setter: (value: T) => void) {
|
||||||
setter(value);
|
setter(value);
|
||||||
}
|
}
|
||||||
@@ -158,7 +285,7 @@ function handleFireLevelInput(value: string | number) {
|
|||||||
form.fire_level = "";
|
form.fire_level = "";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
form.fire_level = Math.max(38, Math.trunc(level));
|
form.fire_level = Math.trunc(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculatePrice() {
|
function calculatePrice() {
|
||||||
@@ -207,6 +334,7 @@ async function handleSubmit() {
|
|||||||
min_rent_hours: 24,
|
min_rent_hours: 24,
|
||||||
max_rent_hours: rentDays * 24,
|
max_rent_hours: rentDays * 24,
|
||||||
});
|
});
|
||||||
|
localStorage.removeItem(draftKey);
|
||||||
showToast({ message: "发布成功", icon: "passed" });
|
showToast({ message: "发布成功", icon: "passed" });
|
||||||
await router.push("/m/profile");
|
await router.push("/m/profile");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -671,9 +799,17 @@ function readError(error: unknown, fallback: string) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="publish-actions">
|
||||||
|
<van-button
|
||||||
|
round
|
||||||
|
class="reset-btn"
|
||||||
|
:disabled="loading"
|
||||||
|
@click="handleResetDraft"
|
||||||
|
>
|
||||||
|
重置
|
||||||
|
</van-button>
|
||||||
<van-button
|
<van-button
|
||||||
type="primary"
|
type="primary"
|
||||||
block
|
|
||||||
round
|
round
|
||||||
class="submit-btn"
|
class="submit-btn"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@@ -682,6 +818,7 @@ function readError(error: unknown, fallback: string) {
|
|||||||
>
|
>
|
||||||
保存发布
|
保存发布
|
||||||
</van-button>
|
</van-button>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<nav class="bottom-nav">
|
<nav class="bottom-nav">
|
||||||
|
|||||||
Reference in New Issue
Block a user