优化历史包袱,修复发布页面默认0 恢哈伏笔
This commit is contained in:
@@ -17,6 +17,7 @@ type ListingDTO struct {
|
|||||||
HafCoinAmount int64 `json:"haf_coin_amount"`
|
HafCoinAmount int64 `json:"haf_coin_amount"`
|
||||||
AssetSummary map[string]any `json:"asset_summary,omitempty"`
|
AssetSummary map[string]any `json:"asset_summary,omitempty"`
|
||||||
ScreenshotURLS []string `json:"screenshot_urls"`
|
ScreenshotURLS []string `json:"screenshot_urls"`
|
||||||
|
CoverURL string `json:"cover_url"`
|
||||||
PriceHourly float64 `json:"price_hourly"`
|
PriceHourly float64 `json:"price_hourly"`
|
||||||
PriceDaily float64 `json:"price_daily"`
|
PriceDaily float64 `json:"price_daily"`
|
||||||
PriceWeekly float64 `json:"price_weekly"`
|
PriceWeekly float64 `json:"price_weekly"`
|
||||||
|
|||||||
@@ -448,6 +448,8 @@ func rowsToDTO(rows []listingRow) []ListingDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (row listingRow) toDTO() ListingDTO {
|
func (row listingRow) toDTO() ListingDTO {
|
||||||
|
assetSummary := decodeAssetSummary(row.AssetSummary)
|
||||||
|
screenshotURLS := cleanScreenshotURLs(decodeScreenshots(row.ScreenshotURLS))
|
||||||
return ListingDTO{
|
return ListingDTO{
|
||||||
ID: row.ID,
|
ID: row.ID,
|
||||||
AccountID: row.AccountID,
|
AccountID: row.AccountID,
|
||||||
@@ -461,8 +463,9 @@ func (row listingRow) toDTO() ListingDTO {
|
|||||||
LoginPlatform: row.LoginPlatform,
|
LoginPlatform: row.LoginPlatform,
|
||||||
RankLevel: row.RankLevel,
|
RankLevel: row.RankLevel,
|
||||||
HafCoinAmount: row.HafCoinAmount,
|
HafCoinAmount: row.HafCoinAmount,
|
||||||
AssetSummary: decodeAssetSummary(row.AssetSummary),
|
AssetSummary: assetSummary,
|
||||||
ScreenshotURLS: decodeScreenshots(row.ScreenshotURLS),
|
ScreenshotURLS: screenshotURLS,
|
||||||
|
CoverURL: firstScreenshotURL(screenshotURLS),
|
||||||
PriceHourly: row.PriceHourly,
|
PriceHourly: row.PriceHourly,
|
||||||
PriceDaily: row.PriceDaily,
|
PriceDaily: row.PriceDaily,
|
||||||
PriceWeekly: row.PriceWeekly,
|
PriceWeekly: row.PriceWeekly,
|
||||||
@@ -479,6 +482,8 @@ func (row listingRow) toDTO() ListingDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func toDTO(account model.GameAccount, listing model.RentalListing) *ListingDTO {
|
func toDTO(account model.GameAccount, listing model.RentalListing) *ListingDTO {
|
||||||
|
assetSummary := decodeAssetSummary(account.AssetSummary)
|
||||||
|
screenshotURLS := cleanScreenshotURLs(decodeScreenshots(account.ScreenshotURLS))
|
||||||
return &ListingDTO{
|
return &ListingDTO{
|
||||||
ID: listing.ID,
|
ID: listing.ID,
|
||||||
AccountID: account.ID,
|
AccountID: account.ID,
|
||||||
@@ -490,8 +495,9 @@ func toDTO(account model.GameAccount, listing model.RentalListing) *ListingDTO {
|
|||||||
LoginPlatform: account.LoginPlatform,
|
LoginPlatform: account.LoginPlatform,
|
||||||
RankLevel: account.RankLevel,
|
RankLevel: account.RankLevel,
|
||||||
HafCoinAmount: account.HafCoinAmount,
|
HafCoinAmount: account.HafCoinAmount,
|
||||||
AssetSummary: decodeAssetSummary(account.AssetSummary),
|
AssetSummary: assetSummary,
|
||||||
ScreenshotURLS: decodeScreenshots(account.ScreenshotURLS),
|
ScreenshotURLS: screenshotURLS,
|
||||||
|
CoverURL: firstScreenshotURL(screenshotURLS),
|
||||||
PriceHourly: listing.PriceHourly,
|
PriceHourly: listing.PriceHourly,
|
||||||
PriceDaily: listing.PriceDaily,
|
PriceDaily: listing.PriceDaily,
|
||||||
PriceWeekly: listing.PriceWeekly,
|
PriceWeekly: listing.PriceWeekly,
|
||||||
@@ -508,21 +514,9 @@ func toDTO(account model.GameAccount, listing model.RentalListing) *ListingDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func marshalScreenshots(urls []string) (datatypes.JSON, error) {
|
func marshalScreenshots(urls []string) (datatypes.JSON, error) {
|
||||||
cleaned := make([]string, 0, len(urls))
|
cleaned := cleanScreenshotURLs(urls)
|
||||||
seen := make(map[string]struct{}, len(urls))
|
if len(cleaned) > 12 {
|
||||||
for _, url := range urls {
|
cleaned = cleaned[:12]
|
||||||
url = strings.TrimSpace(url)
|
|
||||||
if url == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := seen[url]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[url] = struct{}{}
|
|
||||||
cleaned = append(cleaned, url)
|
|
||||||
if len(cleaned) >= 12 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
raw, err := json.Marshal(cleaned)
|
raw, err := json.Marshal(cleaned)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -564,6 +558,30 @@ func decodeAssetSummary(raw datatypes.JSON) map[string]any {
|
|||||||
return summary
|
return summary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cleanScreenshotURLs(urls []string) []string {
|
||||||
|
cleaned := make([]string, 0, len(urls))
|
||||||
|
seen := make(map[string]struct{}, len(urls))
|
||||||
|
for _, url := range urls {
|
||||||
|
url = strings.TrimSpace(url)
|
||||||
|
if url == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := seen[url]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[url] = struct{}{}
|
||||||
|
cleaned = append(cleaned, url)
|
||||||
|
}
|
||||||
|
return cleaned
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstScreenshotURL(urls []string) string {
|
||||||
|
if len(urls) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return urls[0]
|
||||||
|
}
|
||||||
|
|
||||||
func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizType string, bizID uint64, meta AuditMeta, detail map[string]any) error {
|
func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizType string, bizID uint64, meta AuditMeta, detail map[string]any) error {
|
||||||
raw, err := json.Marshal(detail)
|
raw, err := json.Marshal(detail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package listing
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -151,5 +152,17 @@ func validateRequest(req CreateRequest) error {
|
|||||||
if req.HafCoinAmount < 0 {
|
if req.HafCoinAmount < 0 {
|
||||||
return ErrInvalidInput
|
return ErrInvalidInput
|
||||||
}
|
}
|
||||||
|
if !hasScreenshotURL(req.ScreenshotURLS) {
|
||||||
|
return ErrInvalidInput
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hasScreenshotURL(urls []string) bool {
|
||||||
|
for _, url := range urls {
|
||||||
|
if strings.TrimSpace(url) != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -127,16 +127,7 @@ export function mergeListingPublishOptions(options?: Partial<ListingPublishOptio
|
|||||||
login_method_options: options?.login_method_options?.length ? options.login_method_options : defaultListingPublishOptions.login_method_options,
|
login_method_options: options?.login_method_options?.length ? options.login_method_options : defaultListingPublishOptions.login_method_options,
|
||||||
region_options: options?.region_options?.length ? options.region_options : defaultListingPublishOptions.region_options,
|
region_options: options?.region_options?.length ? options.region_options : defaultListingPublishOptions.region_options,
|
||||||
skin_groups: options?.skin_groups?.length ? options.skin_groups : defaultListingPublishOptions.skin_groups,
|
skin_groups: options?.skin_groups?.length ? options.skin_groups : defaultListingPublishOptions.skin_groups,
|
||||||
quantity_items: options?.quantity_items?.length
|
quantity_items: options?.quantity_items?.length ? options.quantity_items as PublishQuantityItem[] : defaultListingPublishOptions.quantity_items,
|
||||||
? normalizeQuantityItems(options.quantity_items as PublishQuantityItem[])
|
|
||||||
: defaultListingPublishOptions.quantity_items,
|
|
||||||
screenshot_slots: options?.screenshot_slots?.length ? options.screenshot_slots as PublishScreenshotSlot[] : defaultListingPublishOptions.screenshot_slots,
|
screenshot_slots: options?.screenshot_slots?.length ? options.screenshot_slots as PublishScreenshotSlot[] : defaultListingPublishOptions.screenshot_slots,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeQuantityItems(items: PublishQuantityItem[]): PublishQuantityItem[] {
|
|
||||||
return items.map((item) => ({
|
|
||||||
...item,
|
|
||||||
key: String(item.key) === 'armor6Ammo' ? 'level6Ammo' : item.key,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export interface Listing {
|
|||||||
haf_coin_amount: number
|
haf_coin_amount: number
|
||||||
asset_summary?: Record<string, unknown>
|
asset_summary?: Record<string, unknown>
|
||||||
screenshot_urls: string[]
|
screenshot_urls: string[]
|
||||||
|
cover_url: string
|
||||||
price_hourly: number
|
price_hourly: number
|
||||||
price_daily: number
|
price_daily: number
|
||||||
price_weekly: number
|
price_weekly: number
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import {
|
|||||||
import MobileHomeFilterSheet, {
|
import MobileHomeFilterSheet, {
|
||||||
type FilterSection,
|
type FilterSection,
|
||||||
} from "./MobileHomeFilterSheet.vue";
|
} from "./MobileHomeFilterSheet.vue";
|
||||||
import { buildFallbackListings } from "./mobileHomeFallback";
|
|
||||||
import {
|
import {
|
||||||
assetRegions,
|
assetRegions,
|
||||||
formatHafCoinM,
|
formatHafCoinM,
|
||||||
@@ -32,8 +31,6 @@ import {
|
|||||||
getServerRegion,
|
getServerRegion,
|
||||||
getSkinGroup,
|
getSkinGroup,
|
||||||
hasGiftResources,
|
hasGiftResources,
|
||||||
normalizeLoginMethod,
|
|
||||||
normalizeServerRegion,
|
|
||||||
readAssetNumber,
|
readAssetNumber,
|
||||||
readAssetString,
|
readAssetString,
|
||||||
} from "./listingDisplay";
|
} from "./listingDisplay";
|
||||||
@@ -77,12 +74,6 @@ const rangePresets: Record<string, Array<{ label: string; min: string; max: stri
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const fallbackListings = computed(() => buildFallbackListings(publishOptions.value));
|
|
||||||
|
|
||||||
const sourceListings = computed(() =>
|
|
||||||
loadFailed.value ? fallbackListings.value : listings.value
|
|
||||||
);
|
|
||||||
|
|
||||||
const activeSortLabel = computed(
|
const activeSortLabel = computed(
|
||||||
() =>
|
() =>
|
||||||
sortOptions.find((option) => option.key === activeSort.value)?.label ||
|
sortOptions.find((option) => option.key === activeSort.value)?.label ||
|
||||||
@@ -92,7 +83,7 @@ const activeSortLabel = computed(
|
|||||||
const serverFilterOptions = computed(() =>
|
const serverFilterOptions = computed(() =>
|
||||||
uniqueOptions(
|
uniqueOptions(
|
||||||
publishOptions.value.server_options
|
publishOptions.value.server_options
|
||||||
.map((item) => normalizeServerRegion(item))
|
.map((item) => item.trim())
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -100,7 +91,7 @@ const serverFilterOptions = computed(() =>
|
|||||||
const loginMethodFilterOptions = computed(() =>
|
const loginMethodFilterOptions = computed(() =>
|
||||||
uniqueOptions(
|
uniqueOptions(
|
||||||
publishOptions.value.login_method_options
|
publishOptions.value.login_method_options
|
||||||
.map((item) => normalizeLoginMethod(item))
|
.map((item) => item.trim())
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -145,7 +136,7 @@ const activeFilterCount = computed(() => {
|
|||||||
|
|
||||||
const displayListings = computed(() => {
|
const displayListings = computed(() => {
|
||||||
const keyword = searchValue.value.trim().toLowerCase();
|
const keyword = searchValue.value.trim().toLowerCase();
|
||||||
const filtered = sourceListings.value.filter((item) => {
|
const filtered = listings.value.filter((item) => {
|
||||||
if (!matchesFilters(item)) return false;
|
if (!matchesFilters(item)) return false;
|
||||||
if (!keyword) return true;
|
if (!keyword) return true;
|
||||||
return searchText(item).includes(keyword);
|
return searchText(item).includes(keyword);
|
||||||
@@ -432,7 +423,7 @@ function uniqueOptions(values: string[]) {
|
|||||||
left-icon="info-o"
|
left-icon="info-o"
|
||||||
color="#6b7a90"
|
color="#6b7a90"
|
||||||
background="transparent"
|
background="transparent"
|
||||||
text="接口暂不可用,当前展示移动端示例数据。"
|
text="接口暂不可用,请稍后刷新。"
|
||||||
/>
|
/>
|
||||||
<van-empty
|
<van-empty
|
||||||
v-else-if="displayListings.length === 0"
|
v-else-if="displayListings.length === 0"
|
||||||
@@ -454,8 +445,8 @@ function uniqueOptions(values: string[]) {
|
|||||||
>
|
>
|
||||||
<div class="card-cover">
|
<div class="card-cover">
|
||||||
<img
|
<img
|
||||||
v-if="item.screenshot_urls?.[0]"
|
v-if="item.cover_url"
|
||||||
:src="item.screenshot_urls[0]"
|
:src="item.cover_url"
|
||||||
:alt="getListingTitle(item)"
|
:alt="getListingTitle(item)"
|
||||||
/>
|
/>
|
||||||
<span v-else>图</span>
|
<span v-else>图</span>
|
||||||
|
|||||||
@@ -4,15 +4,14 @@ import { useRoute, useRouter } from "vue-router";
|
|||||||
import { showToast, showDialog } from "vant";
|
import { showToast, showDialog } from "vant";
|
||||||
|
|
||||||
import { fetchListing, type Listing } from "@/api/listings";
|
import { fetchListing, type Listing } from "@/api/listings";
|
||||||
import { defaultListingPublishOptions } from "@/api/listingOptions";
|
|
||||||
import { createOrder } from "@/api/orders";
|
import { createOrder } from "@/api/orders";
|
||||||
import { useSessionStore } from "@/stores/session";
|
import { useSessionStore } from "@/stores/session";
|
||||||
import { buildFallbackListings } from "./mobileHomeFallback";
|
|
||||||
import {
|
import {
|
||||||
assetRegions,
|
assetRegions,
|
||||||
formatHafCoinM,
|
formatHafCoinM,
|
||||||
formatRatio,
|
formatRatio,
|
||||||
getCoinWan,
|
getCoinWan,
|
||||||
|
getDailyLoss,
|
||||||
getListingChips,
|
getListingChips,
|
||||||
getListingDisplayPrice,
|
getListingDisplayPrice,
|
||||||
getListingResources,
|
getListingResources,
|
||||||
@@ -20,7 +19,6 @@ import {
|
|||||||
getListingTitle,
|
getListingTitle,
|
||||||
getLoginMethod,
|
getLoginMethod,
|
||||||
getRentDays,
|
getRentDays,
|
||||||
getScreenshotMap,
|
|
||||||
getServerRegion,
|
getServerRegion,
|
||||||
readAssetNumber,
|
readAssetNumber,
|
||||||
readAssetString,
|
readAssetString,
|
||||||
@@ -40,15 +38,7 @@ onMounted(async () => {
|
|||||||
listing.value = await fetchListing(String(route.params.id));
|
listing.value = await fetchListing(String(route.params.id));
|
||||||
rentHours.value = Math.max(listing.value.min_rent_hours || 1, 1);
|
rentHours.value = Math.max(listing.value.min_rent_hours || 1, 1);
|
||||||
} catch {
|
} catch {
|
||||||
const fallback = buildFallbackListings(defaultListingPublishOptions).find(
|
showToast({ message: "加载失败", icon: "warning-o" });
|
||||||
(item) => String(item.id) === String(route.params.id)
|
|
||||||
);
|
|
||||||
if (fallback) {
|
|
||||||
listing.value = fallback;
|
|
||||||
rentHours.value = Math.max(fallback.min_rent_hours || 1, 1);
|
|
||||||
} else {
|
|
||||||
showToast({ message: "加载失败", icon: "warning-o" });
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
@@ -77,20 +67,9 @@ const detailMetrics = computed(() => {
|
|||||||
|
|
||||||
const detailScreenshots = computed(() => {
|
const detailScreenshots = computed(() => {
|
||||||
if (!listing.value) return [];
|
if (!listing.value) return [];
|
||||||
const screenshotMap = getScreenshotMap(listing.value);
|
const labels = ["纯币截图", "游戏ID截图", "总资产截图", "腾讯安全中心截图", "皮肤截图"];
|
||||||
const labels: Record<string, string> = {
|
|
||||||
coin: "纯币截图",
|
|
||||||
gameId: "游戏ID",
|
|
||||||
totalAsset: "总资产",
|
|
||||||
tencentSecurity: "腾讯安全中心",
|
|
||||||
skin: "皮肤截图",
|
|
||||||
};
|
|
||||||
const mapped = Object.entries(screenshotMap)
|
|
||||||
.filter(([, url]) => typeof url === "string" && url)
|
|
||||||
.map(([key, url]) => ({ label: labels[key] || "账号截图", url: String(url) }));
|
|
||||||
if (mapped.length) return mapped;
|
|
||||||
return (listing.value.screenshot_urls || []).map((url, index) => ({
|
return (listing.value.screenshot_urls || []).map((url, index) => ({
|
||||||
label: `账号截图${index + 1}`,
|
label: labels[index] || `账号截图${index + 1}`,
|
||||||
url,
|
url,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
@@ -239,12 +218,12 @@ function isNavActive(path: string) {
|
|||||||
listing.rank_level
|
listing.rank_level
|
||||||
}}</van-tag>
|
}}</van-tag>
|
||||||
<van-tag
|
<van-tag
|
||||||
v-if="readAssetString(listing, 'daily_loss')"
|
v-if="getDailyLoss(listing)"
|
||||||
color="#fff7ed"
|
color="#fff7ed"
|
||||||
text-color="#ea580c"
|
text-color="#ea580c"
|
||||||
size="medium"
|
size="medium"
|
||||||
>
|
>
|
||||||
损耗 {{ readAssetString(listing, "daily_loss") }}
|
损耗 {{ getDailyLoss(listing) }}
|
||||||
</van-tag>
|
</van-tag>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="detail-title">{{ getListingTitle(listing) }}</h2>
|
<h2 class="detail-title">{{ getListingTitle(listing) }}</h2>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ function isNavActive(path: string) {
|
|||||||
const form = reactive({
|
const form = reactive({
|
||||||
server_region: "",
|
server_region: "",
|
||||||
face_owner: "",
|
face_owner: "",
|
||||||
haf_coin_amount: 0,
|
haf_coin_amount: "" as number | "",
|
||||||
rank_level: "",
|
rank_level: "",
|
||||||
secret_kd: "",
|
secret_kd: "",
|
||||||
fire_level: "" as number | "",
|
fire_level: "" as number | "",
|
||||||
@@ -43,9 +43,7 @@ const form = reactive({
|
|||||||
deposit_amount: "" as number | "",
|
deposit_amount: "" as number | "",
|
||||||
ratio: "" as number | "",
|
ratio: "" as number | "",
|
||||||
final_price: 0,
|
final_price: 0,
|
||||||
settlement_amount: 0,
|
|
||||||
rent_days: 0,
|
rent_days: 0,
|
||||||
daily_loss: "",
|
|
||||||
remark: "",
|
remark: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -94,6 +92,9 @@ const screenshotSlots = computed(() => publishOptions.value.screenshot_slots);
|
|||||||
const screenshotUrls = computed(() =>
|
const screenshotUrls = computed(() =>
|
||||||
screenshotSlots.value.map((item) => screenshotFiles[item.key]).filter(Boolean)
|
screenshotSlots.value.map((item) => screenshotFiles[item.key]).filter(Boolean)
|
||||||
);
|
);
|
||||||
|
const dailyLossText = computed(() =>
|
||||||
|
form.rent_days ? calculateDailyLoss(Number(form.haf_coin_amount || 0)) : ""
|
||||||
|
);
|
||||||
|
|
||||||
onMounted(loadPublishOptions);
|
onMounted(loadPublishOptions);
|
||||||
|
|
||||||
@@ -174,9 +175,7 @@ function calculatePrice() {
|
|||||||
const finalPrice = roundMoney(coinWan / ratio);
|
const finalPrice = roundMoney(coinWan / ratio);
|
||||||
const rentDays = coinWan >= 30000 ? 7 : coinWan >= 10000 ? 3 : 1;
|
const rentDays = coinWan >= 30000 ? 7 : coinWan >= 10000 ? 3 : 1;
|
||||||
form.final_price = finalPrice;
|
form.final_price = finalPrice;
|
||||||
form.settlement_amount = finalPrice;
|
|
||||||
form.rent_days = rentDays;
|
form.rent_days = rentDays;
|
||||||
form.daily_loss = coinWan >= 30000 ? "30M" : coinWan >= 10000 ? "20M" : "10M";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
@@ -232,7 +231,7 @@ function validateForm() {
|
|||||||
if (form.deposit_amount === "" || Number(form.deposit_amount) < 0) {
|
if (form.deposit_amount === "" || Number(form.deposit_amount) < 0) {
|
||||||
return "请填写押金";
|
return "请填写押金";
|
||||||
}
|
}
|
||||||
if (!form.final_price || !form.settlement_amount || !form.rent_days) {
|
if (!form.final_price || !form.rent_days) {
|
||||||
return "请先点击计算价格";
|
return "请先点击计算价格";
|
||||||
}
|
}
|
||||||
for (const item of screenshotSlots.value) {
|
for (const item of screenshotSlots.value) {
|
||||||
@@ -246,7 +245,6 @@ function validateForm() {
|
|||||||
function buildAssetSummary() {
|
function buildAssetSummary() {
|
||||||
return {
|
return {
|
||||||
face_owner: form.face_owner,
|
face_owner: form.face_owner,
|
||||||
haf_coin_wan: Number(form.haf_coin_amount),
|
|
||||||
secret_kd: form.secret_kd,
|
secret_kd: form.secret_kd,
|
||||||
fire_level: Number(form.fire_level),
|
fire_level: Number(form.fire_level),
|
||||||
season_insurance: form.season_insurance,
|
season_insurance: form.season_insurance,
|
||||||
@@ -258,7 +256,6 @@ function buildAssetSummary() {
|
|||||||
quantity: Number(quantityValues[item.key] || 0),
|
quantity: Number(quantityValues[item.key] || 0),
|
||||||
mode: quantityModes[item.key],
|
mode: quantityModes[item.key],
|
||||||
})),
|
})),
|
||||||
skins: selectedSkins.value,
|
|
||||||
skin_groups: skinGroups.value.reduce<Record<string, string[]>>((groups, group) => {
|
skin_groups: skinGroups.value.reduce<Record<string, string[]>>((groups, group) => {
|
||||||
groups[group.key] = group.options.filter((skin) =>
|
groups[group.key] = group.options.filter((skin) =>
|
||||||
selectedSkins.value.includes(skin)
|
selectedSkins.value.includes(skin)
|
||||||
@@ -269,16 +266,8 @@ function buildAssetSummary() {
|
|||||||
start: form.online_start,
|
start: form.online_start,
|
||||||
end: form.online_end,
|
end: form.online_end,
|
||||||
},
|
},
|
||||||
login_method: form.login_method,
|
|
||||||
ban_record: form.ban_record,
|
ban_record: form.ban_record,
|
||||||
common_regions: form.common_regions,
|
common_regions: form.common_regions,
|
||||||
screenshots: { ...screenshotFiles },
|
|
||||||
deposit_amount: Number(form.deposit_amount),
|
|
||||||
ratio: Number(form.ratio),
|
|
||||||
price: Number(form.final_price),
|
|
||||||
settlement_amount: Number(form.settlement_amount),
|
|
||||||
rent_days: Number(form.rent_days),
|
|
||||||
daily_loss: form.daily_loss,
|
|
||||||
remark: form.remark,
|
remark: form.remark,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -287,6 +276,12 @@ function roundMoney(value: number) {
|
|||||||
return Math.round(value * 100) / 100;
|
return Math.round(value * 100) / 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function calculateDailyLoss(coinWan: number) {
|
||||||
|
if (coinWan >= 30000) return "30M";
|
||||||
|
if (coinWan >= 10000) return "20M";
|
||||||
|
return "10M";
|
||||||
|
}
|
||||||
|
|
||||||
function readError(error: unknown, fallback: string) {
|
function readError(error: unknown, fallback: string) {
|
||||||
if (typeof error === "object" && error && "response" in error) {
|
if (typeof error === "object" && error && "response" in error) {
|
||||||
const response = (error as { response?: { data?: { message?: string } } })
|
const response = (error as { response?: { data?: { message?: string } } })
|
||||||
@@ -642,7 +637,7 @@ function readError(error: unknown, fallback: string) {
|
|||||||
class="publish-field result-field"
|
class="publish-field result-field"
|
||||||
/>
|
/>
|
||||||
<van-field
|
<van-field
|
||||||
:model-value="form.settlement_amount ? `¥${form.settlement_amount}` : ''"
|
:model-value="form.final_price ? `¥${form.final_price}` : ''"
|
||||||
label="结算金额"
|
label="结算金额"
|
||||||
readonly
|
readonly
|
||||||
required
|
required
|
||||||
@@ -657,7 +652,7 @@ function readError(error: unknown, fallback: string) {
|
|||||||
class="publish-field result-field"
|
class="publish-field result-field"
|
||||||
/>
|
/>
|
||||||
<van-field
|
<van-field
|
||||||
v-model="form.daily_loss"
|
:model-value="dailyLossText"
|
||||||
label="每日损耗"
|
label="每日损耗"
|
||||||
readonly
|
readonly
|
||||||
placeholder="自动计算 10M/20M/30M"
|
placeholder="自动计算 10M/20M/30M"
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ export interface ListingDisplayResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getCoinWan(item: Listing) {
|
export function getCoinWan(item: Listing) {
|
||||||
const assetCoin = readAssetNumber(item, "haf_coin_wan");
|
|
||||||
if (assetCoin > 0) return assetCoin;
|
|
||||||
return Math.round(Number(item.haf_coin_amount || 0) / 10000);
|
return Math.round(Number(item.haf_coin_amount || 0) / 10000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,8 +27,6 @@ export function formatHafCoinM(amountWan: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getListingDisplayPrice(item: Listing) {
|
export function getListingDisplayPrice(item: Listing) {
|
||||||
const assetPrice = readAssetNumber(item, "price");
|
|
||||||
if (assetPrice > 0) return assetPrice;
|
|
||||||
const rentDays = getRentDays(item);
|
const rentDays = getRentDays(item);
|
||||||
if (rentDays > 0 && item.price_daily > 0) {
|
if (rentDays > 0 && item.price_daily > 0) {
|
||||||
return roundMoney(item.price_daily * rentDays);
|
return roundMoney(item.price_daily * rentDays);
|
||||||
@@ -39,15 +35,11 @@ export function getListingDisplayPrice(item: Listing) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getRentDays(item: Listing) {
|
export function getRentDays(item: Listing) {
|
||||||
const assetDays = readAssetNumber(item, "rent_days");
|
|
||||||
if (assetDays > 0) return assetDays;
|
|
||||||
if (item.max_rent_hours >= 24) return Math.max(Math.round(item.max_rent_hours / 24), 1);
|
if (item.max_rent_hours >= 24) return Math.max(Math.round(item.max_rent_hours / 24), 1);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRatioValue(item: Listing) {
|
export function getRatioValue(item: Listing) {
|
||||||
const ratio = readAssetNumber(item, "ratio");
|
|
||||||
if (ratio > 0) return ratio;
|
|
||||||
const price = getListingDisplayPrice(item);
|
const price = getListingDisplayPrice(item);
|
||||||
if (price <= 0) return 0;
|
if (price <= 0) return 0;
|
||||||
return getCoinWan(item) / price;
|
return getCoinWan(item) / price;
|
||||||
@@ -65,11 +57,11 @@ export function getValuePerYuanText(item: Listing) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getLoginMethod(item: Listing) {
|
export function getLoginMethod(item: Listing) {
|
||||||
return normalizeLoginMethod(readAssetString(item, "login_method") || item.login_platform);
|
return item.login_platform.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getServerRegion(item: Listing) {
|
export function getServerRegion(item: Listing) {
|
||||||
return normalizeServerRegion(item.server_region);
|
return item.server_region.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getListingTitle(item: Listing) {
|
export function getListingTitle(item: Listing) {
|
||||||
@@ -134,8 +126,7 @@ export function getListingResources(item: Listing): ListingDisplayResource[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getResourceQuantity(item: Listing, resourceKey: string) {
|
export function getResourceQuantity(item: Listing, resourceKey: string) {
|
||||||
const keys = getResourceKeyAliases(resourceKey);
|
return getListingResources(item).find((resource) => resource.key === resourceKey)?.quantity || 0;
|
||||||
return getListingResources(item).find((resource) => keys.includes(resource.key))?.quantity || 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasGiftResources(item: Listing) {
|
export function hasGiftResources(item: Listing) {
|
||||||
@@ -158,13 +149,10 @@ export function getSkinGroup(item: Listing, groupKey: string) {
|
|||||||
|
|
||||||
export function getSkinNames(item: Listing) {
|
export function getSkinNames(item: Listing) {
|
||||||
const skinGroups = item.asset_summary?.skin_groups;
|
const skinGroups = item.asset_summary?.skin_groups;
|
||||||
if (typeof skinGroups === "object" && skinGroups !== null) {
|
if (typeof skinGroups !== "object" || skinGroups === null) return [];
|
||||||
return Object.values(skinGroups as Record<string, unknown>)
|
return Object.values(skinGroups as Record<string, unknown>)
|
||||||
.flatMap((group) => (Array.isArray(group) ? group : []))
|
.flatMap((group) => (Array.isArray(group) ? group : []))
|
||||||
.filter((skin): skin is string => typeof skin === "string");
|
.filter((skin): skin is string => typeof skin === "string");
|
||||||
}
|
|
||||||
const skins = item.asset_summary?.skins;
|
|
||||||
return Array.isArray(skins) ? skins.filter((skin): skin is string => typeof skin === "string") : [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function assetRegions(item: Listing) {
|
export function assetRegions(item: Listing) {
|
||||||
@@ -174,13 +162,6 @@ export function assetRegions(item: Listing) {
|
|||||||
: [];
|
: [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getScreenshotMap(item: Listing) {
|
|
||||||
const screenshots = item.asset_summary?.screenshots;
|
|
||||||
return typeof screenshots === "object" && screenshots !== null
|
|
||||||
? (screenshots as Record<string, unknown>)
|
|
||||||
: {};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getOnlineTimeText(item: Listing) {
|
export function getOnlineTimeText(item: Listing) {
|
||||||
const onlineTime = item.asset_summary?.online_time;
|
const onlineTime = item.asset_summary?.online_time;
|
||||||
if (typeof onlineTime !== "object" || onlineTime === null) return "";
|
if (typeof onlineTime !== "object" || onlineTime === null) return "";
|
||||||
@@ -190,6 +171,13 @@ export function getOnlineTimeText(item: Listing) {
|
|||||||
return `${start.replace(":00", "")}-${end.replace(":00", "")}点`;
|
return `${start.replace(":00", "")}-${end.replace(":00", "")}点`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getDailyLoss(item: Listing) {
|
||||||
|
const coinWan = getCoinWan(item);
|
||||||
|
if (coinWan >= 30000) return "30M";
|
||||||
|
if (coinWan >= 10000) return "20M";
|
||||||
|
return "10M";
|
||||||
|
}
|
||||||
|
|
||||||
export function readAssetString(item: Listing, key: string) {
|
export function readAssetString(item: Listing, key: string) {
|
||||||
const value = item.asset_summary?.[key];
|
const value = item.asset_summary?.[key];
|
||||||
return typeof value === "string" ? value : "";
|
return typeof value === "string" ? value : "";
|
||||||
@@ -208,23 +196,6 @@ function readUnknownNumber(value: unknown) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeServerRegion(value: string) {
|
|
||||||
const text = value.trim();
|
|
||||||
const upper = text.toUpperCase();
|
|
||||||
if (upper.includes("STEAM")) return "Steam";
|
|
||||||
if (upper.includes("QQ")) return "QQ";
|
|
||||||
if (text.includes("微信")) return "微信";
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function normalizeLoginMethod(value: string) {
|
|
||||||
const text = value.trim();
|
|
||||||
if (!text) return "";
|
|
||||||
if (text.includes("扫码")) return "扫码";
|
|
||||||
if (text.includes("账密") || text.includes("账号密码")) return "账号密码";
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatInsuranceSlotText(value: string) {
|
function formatInsuranceSlotText(value: string) {
|
||||||
const parts = value.split("*").map((item) => Number(item));
|
const parts = value.split("*").map((item) => Number(item));
|
||||||
const rows = parts[0] || 0;
|
const rows = parts[0] || 0;
|
||||||
@@ -245,12 +216,6 @@ function formatResourceShort(item: Listing, key: string, label: string) {
|
|||||||
return quantity > 0 ? `${quantity}${label}` : "";
|
return quantity > 0 ? `${quantity}${label}` : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function getResourceKeyAliases(key: string) {
|
|
||||||
if (key === "level6Ammo") return ["level6Ammo", "armor6Ammo"];
|
|
||||||
if (key === "armor6Ammo") return ["armor6Ammo", "level6Ammo"];
|
|
||||||
return [key];
|
|
||||||
}
|
|
||||||
|
|
||||||
function roundMoney(value: number) {
|
function roundMoney(value: number) {
|
||||||
return Math.round(value * 100) / 100;
|
return Math.round(value * 100) / 100;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,177 +0,0 @@
|
|||||||
import {
|
|
||||||
defaultListingPublishOptions,
|
|
||||||
type ListingPublishOptions,
|
|
||||||
} from "@/api/listingOptions";
|
|
||||||
import type { Listing } from "@/api/listings";
|
|
||||||
|
|
||||||
const fallbackRegionOptions = ["广东", "上海", "江苏", "四川", "北京", "浙江"];
|
|
||||||
|
|
||||||
const coinWanOptions = [
|
|
||||||
1800, 3200, 5800, 9600, 12000, 18000, 26000, 36000, 52000, 68000,
|
|
||||||
];
|
|
||||||
const depositOptions = [60, 80, 100, 120, 160, 200];
|
|
||||||
const secretKdOptions = [1.2, 1.6, 2.1, 2.8, 3.4, 4.2];
|
|
||||||
const onlineWindows = [
|
|
||||||
["09:00", "23:30"],
|
|
||||||
["10:30", "01:00"],
|
|
||||||
["12:00", "24:00"],
|
|
||||||
["18:00", "02:00"],
|
|
||||||
] as const;
|
|
||||||
const resourceQuantityPresets: Record<string, number[]> = {
|
|
||||||
awmAmmo: [0, 20, 48, 80, 120, 180],
|
|
||||||
helmet6: [0, 2, 4, 6, 10, 16],
|
|
||||||
armor6: [0, 1, 3, 5, 8, 12],
|
|
||||||
kit5: [0, 3, 6, 10, 15, 24],
|
|
||||||
level6Ammo: [0, 120, 240, 360, 520, 800],
|
|
||||||
gridCard9: [0, 1, 2, 4, 6, 10],
|
|
||||||
};
|
|
||||||
|
|
||||||
function pick<T>(items: readonly T[], index: number, fallback: T): T {
|
|
||||||
if (!items.length) return fallback;
|
|
||||||
return items[index % items.length] ?? fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
function pickMany(items: string[], start: number, count: number) {
|
|
||||||
if (!items.length) return [];
|
|
||||||
const fallback = items[0] || "";
|
|
||||||
return Array.from({ length: Math.min(count, items.length) }, (_, idx) =>
|
|
||||||
pick(items, start + idx, fallback)
|
|
||||||
).filter(Boolean);
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildResources(options: ListingPublishOptions, seed: number) {
|
|
||||||
return options.quantity_items.map((item, index) => ({
|
|
||||||
key: item.key,
|
|
||||||
label: item.label,
|
|
||||||
quantity: pick(
|
|
||||||
resourceQuantityPresets[item.key] || [0, 1, 3, 6],
|
|
||||||
seed + index,
|
|
||||||
0
|
|
||||||
),
|
|
||||||
mode: "收费",
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildSkinGroups(options: ListingPublishOptions, seed: number) {
|
|
||||||
return options.skin_groups.reduce<Record<string, string[]>>((groups, group, index) => {
|
|
||||||
const count = group.key === "melee" ? 2 : index % 2 === 0 ? 3 : 2;
|
|
||||||
groups[group.key] = pickMany(group.options, seed + index * 2, count);
|
|
||||||
return groups;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildScreenshotMap(options: ListingPublishOptions, seed: number) {
|
|
||||||
return options.screenshot_slots.reduce<Record<string, string>>((screenshots, slot, index) => {
|
|
||||||
screenshots[slot.key] = `https://picsum.photos/seed/hfb-${slot.key}-${seed + index}/960/540`;
|
|
||||||
return screenshots;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
function flattenSkinGroups(groups: Record<string, string[]>) {
|
|
||||||
return Object.values(groups).flat();
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateRentDays(coinWan: number) {
|
|
||||||
return coinWan >= 30000 ? 7 : coinWan >= 10000 ? 3 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateDailyLoss(coinWan: number) {
|
|
||||||
return coinWan >= 30000 ? "30M" : coinWan >= 10000 ? "20M" : "10M";
|
|
||||||
}
|
|
||||||
|
|
||||||
function roundMoney(value: number) {
|
|
||||||
return Math.round(value * 100) / 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatHafCoinM(coinWan: number) {
|
|
||||||
const coinM = Math.round((coinWan / 100) * 10) / 10;
|
|
||||||
return `${Number.isInteger(coinM) ? coinM : coinM.toFixed(1)}M`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildFallbackListings(
|
|
||||||
options: ListingPublishOptions = defaultListingPublishOptions
|
|
||||||
): Listing[] {
|
|
||||||
return Array.from({ length: 10 }, (_, index) => {
|
|
||||||
const seed = index + 1;
|
|
||||||
const server = pick(options.server_options, index, "QQ");
|
|
||||||
const loginMethod = pick(options.login_method_options, index + 1, "账号密码");
|
|
||||||
const rank = pick(options.rank_options, index + 3, "钻石");
|
|
||||||
const insurance = pick(options.insurance_options, index + 1, "2*2");
|
|
||||||
const stamina = pick(options.level_options, index + 4, "5级");
|
|
||||||
const load = pick(options.level_options, index + 5, "5级");
|
|
||||||
const faceOwner = pick(options.face_options, index, "是");
|
|
||||||
const coinWan = pick(coinWanOptions, index, 12000);
|
|
||||||
const ratio = 5 + (index % 4);
|
|
||||||
const rentDays = calculateRentDays(coinWan);
|
|
||||||
const finalPrice = roundMoney(coinWan / ratio);
|
|
||||||
const settlementAmount = finalPrice;
|
|
||||||
const dailyPrice = roundMoney(finalPrice / rentDays);
|
|
||||||
const hourlyPrice = Math.max(roundMoney(dailyPrice / 24), 0.01);
|
|
||||||
const deposit = pick(depositOptions, index, 100);
|
|
||||||
const skinGroups = buildSkinGroups(options, seed);
|
|
||||||
const screenshots = buildScreenshotMap(options, seed);
|
|
||||||
const commonRegions = pickMany(
|
|
||||||
options.region_options.length ? options.region_options : fallbackRegionOptions,
|
|
||||||
index * 2,
|
|
||||||
2
|
|
||||||
);
|
|
||||||
const [onlineStart, onlineEnd] = pick(onlineWindows, index, onlineWindows[0]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: 9100 + seed,
|
|
||||||
account_id: 0,
|
|
||||||
owner_id: 0,
|
|
||||||
title: `${server} ${rank} ${formatHafCoinM(coinWan)}哈夫币`,
|
|
||||||
description: `${loginMethod}交接,${insurance}保险,体力${stamina}、负重${load},示例数据按发布表单随机生成。`,
|
|
||||||
game_name: "三角洲行动",
|
|
||||||
server_region: server,
|
|
||||||
login_platform: loginMethod,
|
|
||||||
rank_level: rank,
|
|
||||||
haf_coin_amount: coinWan * 10000,
|
|
||||||
asset_summary: {
|
|
||||||
face_owner: faceOwner,
|
|
||||||
haf_coin_wan: coinWan,
|
|
||||||
total_asset_wan: Math.round(coinWan * (1.08 + (index % 4) * 0.04)),
|
|
||||||
secret_kd: pick(secretKdOptions, index, 1.6),
|
|
||||||
fire_level: 38 + seed * 3,
|
|
||||||
season_insurance: insurance,
|
|
||||||
stamina_level: stamina,
|
|
||||||
load_level: load,
|
|
||||||
daily_loss: calculateDailyLoss(coinWan),
|
|
||||||
ratio,
|
|
||||||
login_method: loginMethod,
|
|
||||||
common_regions: commonRegions,
|
|
||||||
resources: buildResources(options, seed),
|
|
||||||
skins: flattenSkinGroups(skinGroups),
|
|
||||||
skin_groups: skinGroups,
|
|
||||||
online_time: {
|
|
||||||
start: onlineStart,
|
|
||||||
end: onlineEnd,
|
|
||||||
},
|
|
||||||
ban_record: index % 4 === 0 ? "无封禁记录,近期登录稳定" : "",
|
|
||||||
screenshots,
|
|
||||||
deposit_amount: deposit,
|
|
||||||
price: finalPrice,
|
|
||||||
settlement_amount: settlementAmount,
|
|
||||||
rent_days: rentDays,
|
|
||||||
remark:
|
|
||||||
index % 3 === 0
|
|
||||||
? "部分仓库材料请勿使用,下单后会在群聊再次提醒。"
|
|
||||||
: "",
|
|
||||||
},
|
|
||||||
screenshot_urls: Object.values(screenshots),
|
|
||||||
price_hourly: hourlyPrice,
|
|
||||||
price_daily: dailyPrice,
|
|
||||||
price_weekly: Math.round(dailyPrice * 7 * 10) / 10,
|
|
||||||
deposit_amount: deposit,
|
|
||||||
min_rent_hours: coinWan >= 10000 ? 24 : 8,
|
|
||||||
max_rent_hours: coinWan >= 30000 ? 168 : 72,
|
|
||||||
status: "published",
|
|
||||||
review_status: "approved",
|
|
||||||
review_reason: "",
|
|
||||||
published_at: new Date(Date.now() - seed * 18 * 60 * 1000).toISOString(),
|
|
||||||
created_at: new Date(Date.now() - seed * 20 * 60 * 1000).toISOString(),
|
|
||||||
updated_at: new Date(Date.now() - seed * 10 * 60 * 1000).toISOString(),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -20,7 +20,6 @@ import {
|
|||||||
type ListingPublishOptions,
|
type ListingPublishOptions,
|
||||||
} from "@/api/listingOptions";
|
} from "@/api/listingOptions";
|
||||||
import { fetchListings, type Listing } from "@/api/listings";
|
import { fetchListings, type Listing } from "@/api/listings";
|
||||||
import { buildFallbackListings } from "@/views/mobile/mobileHomeFallback";
|
|
||||||
import {
|
import {
|
||||||
formatHafCoinM,
|
formatHafCoinM,
|
||||||
getCoinM,
|
getCoinM,
|
||||||
@@ -32,12 +31,9 @@ import {
|
|||||||
getLoginMethod,
|
getLoginMethod,
|
||||||
getServerRegion,
|
getServerRegion,
|
||||||
hasGiftResources,
|
hasGiftResources,
|
||||||
normalizeLoginMethod,
|
|
||||||
normalizeServerRegion,
|
|
||||||
} from "@/views/mobile/listingDisplay";
|
} from "@/views/mobile/listingDisplay";
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const loadFailed = ref(false);
|
|
||||||
const listings = ref<Listing[]>([]);
|
const listings = ref<Listing[]>([]);
|
||||||
const announcements = ref<string[]>(defaultHomeAnnouncements);
|
const announcements = ref<string[]>(defaultHomeAnnouncements);
|
||||||
const banners = ref<HomeBannerSlide[]>(defaultHomeBanners);
|
const banners = ref<HomeBannerSlide[]>(defaultHomeBanners);
|
||||||
@@ -52,29 +48,24 @@ const filters = reactive({
|
|||||||
});
|
});
|
||||||
const sortBy = ref("recommended");
|
const sortBy = ref("recommended");
|
||||||
|
|
||||||
const fallbackListings = computed(() => buildFallbackListings(publishOptions.value));
|
|
||||||
const sourceListings = computed(() =>
|
|
||||||
loadFailed.value ? fallbackListings.value : listings.value
|
|
||||||
);
|
|
||||||
|
|
||||||
const serverOptions = computed(() =>
|
const serverOptions = computed(() =>
|
||||||
uniqueOptions(
|
uniqueOptions(
|
||||||
publishOptions.value.server_options
|
publishOptions.value.server_options
|
||||||
.map((item) => normalizeServerRegion(item))
|
.map((item) => item.trim())
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const loginMethodOptions = computed(() =>
|
const loginMethodOptions = computed(() =>
|
||||||
uniqueOptions(
|
uniqueOptions(
|
||||||
publishOptions.value.login_method_options
|
publishOptions.value.login_method_options
|
||||||
.map((item) => normalizeLoginMethod(item))
|
.map((item) => item.trim())
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const visibleListings = computed(() => {
|
const visibleListings = computed(() => {
|
||||||
const keyword = filters.keyword.trim().toLowerCase();
|
const keyword = filters.keyword.trim().toLowerCase();
|
||||||
const filtered = sourceListings.value.filter((item) => {
|
const filtered = listings.value.filter((item) => {
|
||||||
const text = [
|
const text = [
|
||||||
item.title,
|
item.title,
|
||||||
item.description,
|
item.description,
|
||||||
@@ -114,12 +105,12 @@ const statCards = computed(() => [
|
|||||||
{ label: "可租账号", value: `${visibleListings.value.length}`, hint: "当前筛选结果" },
|
{ label: "可租账号", value: `${visibleListings.value.length}`, hint: "当前筛选结果" },
|
||||||
{
|
{
|
||||||
label: "高哈夫币",
|
label: "高哈夫币",
|
||||||
value: `${sourceListings.value.filter((item) => getCoinM(item) >= 100).length}`,
|
value: `${listings.value.filter((item) => getCoinM(item) >= 100).length}`,
|
||||||
hint: "100M 以上",
|
hint: "100M 以上",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "账密登录",
|
label: "账密登录",
|
||||||
value: `${sourceListings.value.filter((item) => getLoginMethod(item) === "账号密码").length}`,
|
value: `${listings.value.filter((item) => getLoginMethod(item) === "账号密码").length}`,
|
||||||
hint: "交接更快",
|
hint: "交接更快",
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
@@ -128,7 +119,6 @@ onMounted(loadHome);
|
|||||||
|
|
||||||
async function loadHome() {
|
async function loadHome() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
loadFailed.value = false;
|
|
||||||
try {
|
try {
|
||||||
const [nextListings, config] = await Promise.all([
|
const [nextListings, config] = await Promise.all([
|
||||||
fetchListings(),
|
fetchListings(),
|
||||||
@@ -139,7 +129,6 @@ async function loadHome() {
|
|||||||
banners.value = config.banners;
|
banners.value = config.banners;
|
||||||
publishOptions.value = config.publish_options;
|
publishOptions.value = config.publish_options;
|
||||||
} catch {
|
} catch {
|
||||||
loadFailed.value = true;
|
|
||||||
announcements.value = defaultHomeAnnouncements;
|
announcements.value = defaultHomeAnnouncements;
|
||||||
banners.value = defaultHomeBanners;
|
banners.value = defaultHomeBanners;
|
||||||
publishOptions.value = defaultListingPublishOptions;
|
publishOptions.value = defaultListingPublishOptions;
|
||||||
@@ -328,8 +317,8 @@ function uniqueOptions(values: string[]) {
|
|||||||
>
|
>
|
||||||
<div class="desktop-cover">
|
<div class="desktop-cover">
|
||||||
<img
|
<img
|
||||||
v-if="item.screenshot_urls?.[0]"
|
v-if="item.cover_url"
|
||||||
:src="item.screenshot_urls[0]"
|
:src="item.cover_url"
|
||||||
:alt="getListingTitle(item)"
|
:alt="getListingTitle(item)"
|
||||||
/>
|
/>
|
||||||
<span v-else>HFB</span>
|
<span v-else>HFB</span>
|
||||||
|
|||||||
@@ -179,8 +179,8 @@ function uniqueOptions(values: string[]) {
|
|||||||
>
|
>
|
||||||
<div class="resource-cover">
|
<div class="resource-cover">
|
||||||
<img
|
<img
|
||||||
v-if="item.screenshot_urls?.[0]"
|
v-if="item.cover_url"
|
||||||
:src="item.screenshot_urls[0]"
|
:src="item.cover_url"
|
||||||
:alt="item.title"
|
:alt="item.title"
|
||||||
/>
|
/>
|
||||||
<span v-else>HFB</span>
|
<span v-else>HFB</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user