优化后端可以编辑皮肤等等

This commit is contained in:
yml
2026-05-22 23:34:18 +08:00
parent 52ac6b7827
commit b68411166d
11 changed files with 1286 additions and 694 deletions
@@ -1,34 +1,24 @@
<script setup lang="ts">
import { computed, reactive, ref } from "vue";
import { computed, onMounted, reactive, ref } from "vue";
import { useRouter, useRoute } from "vue-router";
import { showToast } from "vant";
import { uploadFile } from "@/api/files";
import {
defaultListingPublishOptions,
fetchListingPublishOptions,
type ChargeMode,
type ListingPublishOptions,
type QuantityKey,
type ScreenshotKey,
} from "@/api/listingOptions";
import { createListing } from "@/api/listings";
type ChargeMode = "赠送" | "收费";
type QuantityKey =
| "awmAmmo"
| "helmet6"
| "armor6"
| "kit5"
| "armor6Ammo"
| "gridCard9";
type ScreenshotKey =
| "coin"
| "gameId"
| "totalAsset"
| "tencentSecurity"
| "skin";
type SkinCategoryKey = "melee" | "operator" | "weapon";
const router = useRouter();
const route = useRoute();
const loading = ref(false);
const uploading = ref(false);
const publishOptions = ref<ListingPublishOptions>(defaultListingPublishOptions);
function isNavActive(path: string) {
if (path === "/m") return route.path === "/m";
@@ -89,166 +79,31 @@ const selectedSkins = ref<string[]>([]);
const fileInput = ref<HTMLInputElement | null>(null);
const activeUploadKey = ref<ScreenshotKey>("coin");
const serverOptions = ["QQ", "微信", "Steam"];
const faceOptions = ["是", "否"];
const rankOptions = [
"青铜",
"白银",
"黄金",
"铂金",
"钻石",
"黑鹰",
"三角洲巅峰",
];
const insuranceOptions = ["2*1", "2*2", "2*3", "3*3"];
const levelOptions = ["1级", "2级", "3级", "4级", "5级", "6级", "7级"];
const loginMethodOptions = ["扫码", "账号密码"];
const regionOptions = [
"北京",
"天津",
"河北",
"山西",
"内蒙古",
"辽宁",
"吉林",
"黑龙江",
"上海",
"江苏",
"浙江",
"安徽",
"福建",
"江西",
"山东",
"河南",
"湖北",
"湖南",
"广东",
"广西",
"海南",
"重庆",
"四川",
"贵州",
"云南",
"陕西",
"甘肃",
"青海",
"宁夏",
"新疆",
"西藏",
"香港",
"澳门",
"台湾",
];
const skinGroups: Array<{
key: SkinCategoryKey;
title: string;
options: string[];
}> = [
{
key: "melee",
title: "近战皮肤",
options: [
"坠星者",
"暗星",
"龙牙",
"信条",
"怜悯",
"赤枭",
"影锋",
"黑海",
"北极星",
"电锯惊魂",
"处刑者",
],
},
{
key: "operator",
title: "干员皮肤",
options: [
"威龙-凌霄戍卫",
"红狼-蚀金玫瑰",
"露娜-黑天际线",
"蛊-能天使午夜邮差",
"骇爪-维什戴尔",
"骇爪-水墨云图",
"威龙-吴彦祖",
"威龙-铁面判官",
"威龙-壮志凌云",
"威龙-蛟龙特战队",
"蜂医-送葬人",
"蜂医-危险物质",
"蜂医-黑鹰坠落",
"牧羊人-街头之星",
"牧羊人-黑鹰坠落",
"乌鲁鲁-黑鹰坠落",
"红狼-黑鹰坠落",
"无名-夜鹰",
"深蓝-不破誓约",
"红狼-电锯惊魂",
"露娜-古墓丽影劳拉",
"赛伊德",
],
},
{
key: "weapon",
title: "武器皮肤",
options: [
"M7棱镜攻势S2",
"腾龙气象感应",
"MP7电玩高手S2",
"M250电玩高手S2",
"AS Val悬赏令",
"K416命运",
"M4A1棱镜攻势",
"SCAR-H电玩高手",
"QBZ95王牌之剑",
"AUG气象感应",
"Vector美杜莎",
"KC17-造物纪元",
],
},
];
const quantityItems: Array<{
key: QuantityKey;
label: string;
price: string;
placeholder?: string;
}> = [
{ key: "awmAmmo", label: "AWM子弹", price: "0.8元/发" },
{ key: "helmet6", label: "6头数量", price: "2元/个" },
{ key: "armor6", label: "6甲数量", price: "3元/个" },
{ key: "kit5", label: "5级全装套数量", price: "5元/个" },
{ key: "armor6Ammo", label: "6甲子弹", price: "0.26元/发" },
{
key: "gridCard9",
label: "9格体验卡",
price: "5元/张",
placeholder: "如果是赛季9格,此处填0即可",
},
];
const screenshotSlots: Array<{
key: ScreenshotKey;
label: string;
required: boolean;
hint: string;
}> = [
{ key: "coin", label: "纯币截图", required: true, hint: "请上传纯币截图(必传)" },
{ key: "gameId", label: "游戏ID截图", required: true, hint: "请上传游戏ID截图(必传)" },
{ key: "totalAsset", label: "总资产截图", required: true, hint: "请上传总资产截图(必传)" },
{
key: "tencentSecurity",
label: "腾讯安全中心截图",
required: true,
hint: "请上传腾讯安全中心截图(必传)",
},
{ key: "skin", label: "皮肤截图", required: false, hint: "请上传皮肤截图(选传)" },
];
const screenshotUrls = computed(() =>
screenshotSlots.map((item) => screenshotFiles[item.key]).filter(Boolean)
const serverOptions = computed(() => publishOptions.value.server_options);
const faceOptions = computed(() => publishOptions.value.face_options);
const rankOptions = computed(() => publishOptions.value.rank_options);
const insuranceOptions = computed(() => publishOptions.value.insurance_options);
const levelOptions = computed(() => publishOptions.value.level_options);
const loginMethodOptions = computed(
() => publishOptions.value.login_method_options
);
const regionOptions = computed(() => publishOptions.value.region_options);
const skinGroups = computed(() => publishOptions.value.skin_groups);
const quantityItems = computed(() => publishOptions.value.quantity_items);
const screenshotSlots = computed(() => publishOptions.value.screenshot_slots);
const screenshotUrls = computed(() =>
screenshotSlots.value.map((item) => screenshotFiles[item.key]).filter(Boolean)
);
onMounted(loadPublishOptions);
async function loadPublishOptions() {
try {
publishOptions.value = await fetchListingPublishOptions();
} catch {
publishOptions.value = defaultListingPublishOptions;
}
}
function selectRadio<T extends string>(value: T, setter: (value: T) => void) {
setter(value);
@@ -380,7 +235,7 @@ function validateForm() {
if (!form.final_price || !form.settlement_amount || !form.rent_days) {
return "请先点击计算价格";
}
for (const item of screenshotSlots) {
for (const item of screenshotSlots.value) {
if (item.required && !screenshotFiles[item.key]) {
return `请上传${item.label}`;
}
@@ -397,14 +252,14 @@ function buildAssetSummary() {
season_insurance: form.season_insurance,
stamina_level: form.stamina_level,
load_level: form.load_level,
resources: quantityItems.map((item) => ({
resources: quantityItems.value.map((item) => ({
key: item.key,
label: item.label,
quantity: Number(quantityValues[item.key] || 0),
mode: quantityModes[item.key],
})),
skins: selectedSkins.value,
skin_groups: skinGroups.reduce<Record<string, string[]>>((groups, group) => {
skin_groups: skinGroups.value.reduce<Record<string, string[]>>((groups, group) => {
groups[group.key] = group.options.filter((skin) =>
selectedSkins.value.includes(skin)
);
@@ -875,509 +730,4 @@ function readError(error: unknown, fallback: string) {
</main>
</template>
<style scoped>
.mobile-publish {
min-height: 100dvh;
background: #f4f6f8;
padding-bottom: 56px;
}
.page-header {
position: sticky;
top: 0;
z-index: 10;
display: flex;
align-items: center;
height: 48px;
padding: 0 12px;
background: #fff;
border-bottom: 1px solid #eceff3;
}
.page-header h1 {
flex: 1;
margin: 0;
font-size: 17px;
font-weight: 700;
text-align: center;
}
.back-btn {
display: grid;
width: 36px;
height: 36px;
place-items: center;
border: none;
background: none;
color: #25282d;
cursor: pointer;
}
.header-spacer {
width: 36px;
}
.form-body {
padding: 12px 14px 24px;
}
.form-section {
padding: 14px;
margin-bottom: 12px;
background: #fff;
border: 1px solid #edf0f4;
border-radius: 8px;
}
.section-title {
margin: 0 0 10px;
font-size: 14px;
font-weight: 700;
color: #171a1f;
}
.field-hint {
margin: -2px 0 8px;
color: #858c96;
font-size: 11px;
line-height: 1.5;
}
.publish-field {
margin-bottom: 8px;
border-radius: 8px;
background: #f8fafc;
overflow: hidden;
}
.publish-field :deep(.van-field__label) {
width: 88px;
color: #30343a;
font-size: 13px;
font-weight: 600;
}
.publish-field :deep(.van-field__control) {
color: #20242a;
font-size: 14px;
}
.radio-group,
.multi-chip-group {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.radio-group.small {
gap: 6px;
}
.radio-btn {
min-height: 28px;
padding: 5px 12px;
border: 1px solid #d7dce3;
border-radius: 14px;
background: #fff;
color: #5c6470;
font-size: 12px;
font-weight: 600;
line-height: 1;
}
.radio-btn.active {
border-color: #1477ff;
background: #1477ff;
color: #fff;
}
.time-row,
.result-grid {
display: grid;
grid-template-columns: 1fr;
gap: 8px;
}
.level-panel {
display: grid;
gap: 8px;
}
.level-row {
display: grid;
grid-template-columns: 58px minmax(0, 1fr);
gap: 8px;
align-items: center;
padding: 10px;
border-radius: 8px;
background: #f8fafc;
}
.level-label {
color: #30343a;
font-size: 13px;
font-weight: 700;
white-space: nowrap;
}
.level-label span {
margin-right: 2px;
color: #ee0a24;
}
.level-options {
display: grid;
grid-template-columns: repeat(7, minmax(0, 1fr));
gap: 6px;
}
.level-btn {
min-width: 0;
height: 30px;
padding: 0;
border: 1px solid #d7dce3;
border-radius: 15px;
background: #fff;
color: #5c6470;
font-size: 12px;
font-weight: 700;
line-height: 1;
}
.level-btn.active {
border-color: #1477ff;
background: #1477ff;
color: #fff;
}
.quantity-row {
display: grid;
grid-template-columns: minmax(0, 1fr) 92px;
gap: 8px;
align-items: stretch;
margin-bottom: 8px;
}
.quantity-card {
display: grid;
grid-template-columns: minmax(0, 1fr) 72px;
gap: 10px;
align-items: center;
min-height: 72px;
padding: 10px 12px;
border-radius: 8px;
background: #f8fafc;
}
.quantity-meta {
display: grid;
min-width: 0;
gap: 5px;
}
.quantity-meta strong {
overflow: hidden;
color: #30343a;
font-size: 13px;
font-weight: 700;
line-height: 1.25;
text-overflow: ellipsis;
white-space: nowrap;
}
.quantity-meta span {
margin-right: 2px;
color: #ee0a24;
}
.quantity-meta small {
color: #858c96;
font-size: 11px;
font-weight: 600;
}
.quantity-input {
width: 100%;
min-width: 0;
height: 36px;
padding: 0 8px;
border: 1px solid #e0e5ec;
border-radius: 8px;
background: #fff;
color: #20242a;
font-size: 16px;
font-weight: 700;
text-align: center;
outline: none;
}
.quantity-input:focus {
border-color: #1477ff;
}
.mode-toggle {
display: grid;
grid-template-columns: 1fr;
gap: 6px;
min-height: 72px;
}
.mode-toggle button {
border: 1px solid #d7dce3;
border-radius: 8px;
background: #fff;
color: #606873;
font-size: 12px;
font-weight: 700;
}
.mode-toggle button.active {
border-color: #ff6a00;
background: #fff3ea;
color: #e65f00;
}
.skin-groups {
display: grid;
gap: 10px;
margin-bottom: 8px;
}
.skin-group {
padding: 10px;
border-radius: 8px;
background: #f8fafc;
}
.skin-group-title {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
color: #30343a;
font-size: 13px;
font-weight: 700;
}
.skin-group-title small {
color: #858c96;
font-size: 11px;
font-weight: 600;
}
.region-panel {
display: grid;
gap: 8px;
padding: 10px;
border-radius: 8px;
background: #f8fafc;
}
.region-title {
display: flex;
align-items: center;
justify-content: space-between;
color: #30343a;
font-size: 13px;
font-weight: 700;
}
.region-title small {
color: #858c96;
font-size: 11px;
font-weight: 600;
}
.region-grid {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 6px;
}
.region-btn {
min-width: 0;
height: 28px;
padding: 0 2px;
border: 1px solid #d7dce3;
border-radius: 14px;
background: #fff;
color: #5c6470;
font-size: 11px;
font-weight: 700;
}
.region-btn.active {
border-color: #1477ff;
background: #1477ff;
color: #fff;
}
.upload-list {
display: grid;
gap: 10px;
}
.upload-line {
display: grid;
grid-template-columns: minmax(0, 1fr) 86px;
gap: 10px;
align-items: center;
min-height: 76px;
padding: 10px;
border: 1px solid #edf0f4;
border-radius: 8px;
background: #f8fafc;
}
.upload-meta {
display: grid;
gap: 4px;
}
.upload-meta strong {
color: #242830;
font-size: 13px;
}
.upload-meta span {
color: #f04438;
}
.upload-meta small {
color: #858c96;
font-size: 11px;
line-height: 1.4;
}
.upload-add,
.upload-preview {
width: 76px;
height: 76px;
border-radius: 8px;
}
.upload-add {
display: grid;
place-items: center;
border: 1px dashed #c7cdd6;
background: #fff;
color: #7b8490;
font-size: 11px;
}
.upload-add:disabled {
opacity: 0.5;
}
.upload-preview {
position: relative;
overflow: hidden;
background: #e8ecf1;
}
.upload-preview img {
width: 100%;
height: 100%;
object-fit: cover;
}
.upload-preview button {
position: absolute;
right: 0;
bottom: 0;
height: 24px;
padding: 0 8px;
border: none;
border-radius: 8px 0 0;
background: rgba(0, 0, 0, 0.62);
color: #fff;
font-size: 11px;
}
.calc-btn {
height: 40px;
margin-bottom: 8px;
border: none;
border-radius: 8px;
background: #1477ff;
color: #fff;
font-weight: 700;
}
.result-field :deep(.van-field__control) {
color: #ff6a00;
font-weight: 800;
}
.submit-btn {
height: 44px;
margin-top: 8px;
border: none !important;
background: #ff6a00 !important;
font-size: 15px;
font-weight: 700;
}
.bottom-nav {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 100;
display: flex;
height: 50px;
background: #fff;
border-top: 1px solid #eee;
}
.nav-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2px;
color: #999;
font-size: 10px;
text-decoration: none;
}
.nav-item.active {
color: #1477ff;
}
.nav-item span {
font-weight: 600;
}
.publish-pill {
display: grid;
width: 36px;
height: 26px;
place-items: center;
border-radius: 13px;
background: #ff6a00;
color: #fff;
font-size: 18px;
font-weight: 900;
}
.nav-publish span,
.nav-publish.active span {
color: #ff6a00;
}
@media (min-width: 430px) {
.time-row,
.result-grid {
grid-template-columns: 1fr 1fr;
}
}
@media (max-width: 370px) {
.level-options,
.region-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
}
</style>
<style scoped src="./MobileSellerListingCreateView.css"></style>