116 lines
4.3 KiB
Vue
116 lines
4.3 KiB
Vue
<template>
|
||
<section v-if="!isDetail" class="m-task-mall" :style="{ backgroundImage: `url(${mobileAssets.mallBackground})` }">
|
||
<PageHeader title="手工商城" />
|
||
|
||
<img class="m-mall-banner" :src="mobileAssets.mallBanner" alt="精选任务,轻松赚收益" />
|
||
|
||
<AsyncState
|
||
:loading="loading"
|
||
:error="error"
|
||
:empty="!products.length"
|
||
empty-text="暂无手工商品"
|
||
@retry="loadList"
|
||
>
|
||
<div class="m-mall-list">
|
||
<button v-for="item in products" :key="item.id" class="m-mall-task" type="button" @click="openSupport">
|
||
<span class="m-mall-cover">
|
||
<img v-if="safeImage(item.cover_url)" :src="safeImage(item.cover_url)" :alt="item.name" />
|
||
<n-icon v-else :component="ImageOutline" />
|
||
</span>
|
||
<strong>{{ item.name }}</strong>
|
||
<small>已领取:{{ item.claimed_count || 0 }}</small>
|
||
</button>
|
||
</div>
|
||
</AsyncState>
|
||
</section>
|
||
|
||
<section v-else class="m-page m-page--white m-mall-detail-page">
|
||
<PageHeader title="商品详情" />
|
||
<AsyncState
|
||
:loading="loading"
|
||
:error="error"
|
||
:empty="!product"
|
||
empty-text="商品不存在"
|
||
@retry="loadDetail"
|
||
>
|
||
<article v-if="product" class="m-product-detail">
|
||
<div class="m-product-hero">
|
||
<img v-if="safeImage(product.cover_url)" :src="safeImage(product.cover_url)" :alt="product.name" />
|
||
<n-icon v-else :component="ImageOutline" />
|
||
</div>
|
||
<section>
|
||
<h1>{{ product.name }}</h1>
|
||
<div class="m-price"><strong>{{ money(product.price_amount, product.currency) }}</strong><span>已领取 {{ product.claimed_count || 0 }} 份</span></div>
|
||
</section>
|
||
<section class="m-product-intro"><h2>项目介绍</h2><p>{{ product.detail_text || product.summary || '暂无详细介绍' }}</p></section>
|
||
<section class="m-process">
|
||
<h2>加工说明</h2>
|
||
<div>
|
||
<span><img :src="mobileAssets.processSimple" alt="" />操作简单</span>
|
||
<span><img :src="mobileAssets.processMaterial" alt="" />材料配发</span>
|
||
<span><img :src="mobileAssets.processTime" alt="" />时间自由</span>
|
||
<span><img :src="mobileAssets.processReturn" alt="" />完成回收</span>
|
||
</div>
|
||
</section>
|
||
<div class="m-buy-bar"><span>合计 <b>{{ money(product.price_amount, product.currency) }}</b></span><router-link to="/app/claim">立即领取</router-link></div>
|
||
</article>
|
||
</AsyncState>
|
||
</section>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||
import { useRoute, useRouter } from 'vue-router';
|
||
import { ImageOutline } from '@vicons/ionicons5';
|
||
import PageHeader from '../components/PageHeader.vue';
|
||
import AsyncState from '../components/AsyncState.vue';
|
||
import { errorText, money, request, safeImage } from '../api';
|
||
import { mobileAssets } from '../assets';
|
||
|
||
const route = useRoute();
|
||
const router = useRouter();
|
||
const isDetail = computed(() => Boolean(route.params.id));
|
||
const loading = ref(true);
|
||
const error = ref('');
|
||
const products = ref<any[]>([]);
|
||
const product = ref<any>();
|
||
let listController: AbortController | undefined;
|
||
function openSupport() {
|
||
router.push({ name: 'MobileSupport' });
|
||
}
|
||
async function loadList() {
|
||
listController?.abort();
|
||
const controller = new AbortController();
|
||
listController = controller;
|
||
loading.value = true;
|
||
error.value = '';
|
||
try {
|
||
const data = await request<any>({ url: '/v1/manual-products', params: { page: 1, page_size: 100 }, signal: controller.signal });
|
||
products.value = data.items || [];
|
||
} catch (err) {
|
||
if (!controller.signal.aborted) error.value = errorText(err);
|
||
} finally {
|
||
if (listController === controller) loading.value = false;
|
||
}
|
||
}
|
||
async function loadDetail() {
|
||
loading.value = true;
|
||
error.value = '';
|
||
try {
|
||
product.value = await request({ url: `/v1/manual-products/${route.params.id}` });
|
||
} catch (err) {
|
||
error.value = errorText(err);
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
function load() {
|
||
if (isDetail.value) void loadDetail();
|
||
else void loadList();
|
||
}
|
||
|
||
onMounted(load);
|
||
onBeforeUnmount(() => listController?.abort());
|
||
watch(() => route.params.id, load);
|
||
</script>
|