add photo

This commit is contained in:
Timi
2026-09-15 12:26:47 +08:00
parent 3b31976e31
commit 8c9842c193
+113 -8
View File
@@ -1,14 +1,40 @@
<template> <template>
<div v-can="permissionCodes.pointRedeemSku.manage" class="redeem-sku-list"> <div v-can="permissionCodes.pointRedeemSku.manage" class="redeem-sku-list">
<div class="filters">
<t-input
v-model.trim="keyword"
class="keyword"
type="search"
borderless
clearable
placeholder="搜索产品名称、编码或 SKU"
/>
</div>
<t-pull-down-refresh v-model="isRefreshing" :refresh-timeout="30000" @refresh="reload" @scrolltolower="load"> <t-pull-down-refresh v-model="isRefreshing" :refresh-timeout="30000" @refresh="reload" @scrolltolower="load">
<t-cell-group v-if="result.list.length" theme="card"> <t-cell-group v-if="result.list.length" theme="card">
<t-cell <t-cell
v-for="(item, index) in result.list" v-for="(item, index) in result.list"
:key="item.id" :key="item.id"
class="item"
arrow arrow
:description="item.unit?.name || '单位未知'"
v-entrance-transition="index * 30" v-entrance-transition="index * 30"
@click="toEdit(item)" @click="toEdit(item)"
> >
<template #left-icon>
<t-image
v-if="getProductPhoto(item)"
:src="getProductPhoto(item)"
:alt="item.product?.name || item.sku?.name || '产品图片'"
fit="cover"
class="avatar"
/>
<div
v-else
class="avatar fallback"
v-text="(item.product?.name || item.sku?.name || '产').slice(0, 1)"
/>
</template>
<template #title> <template #title>
<div class="title"> <div class="title">
<strong v-text="item.product?.name || item.sku?.name || '未命名产品'" /> <strong v-text="item.product?.name || item.sku?.name || '未命名产品'" />
@@ -19,10 +45,8 @@
/> />
</div> </div>
</template> </template>
<template #description> <template #note>
<span <span class="orange" v-text="formatPoint(item.defaultPointCost)" />
v-text="`${item.sku?.code || item.sku?.specSummary || '默认 SKU'} · ${item.unit?.name || '单位未知'} · ${formatPoint(item.defaultPointCost)} / 单位`"
/>
</template> </template>
</t-cell> </t-cell>
</t-cell-group> </t-cell-group>
@@ -37,22 +61,31 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Toolkit } from "timi-web";
import PointAPI from "@/api/PointAPI"; import PointAPI from "@/api/PointAPI";
import { useNavBarStore } from "@/store/navBarStore"; import { useNavBarStore } from "@/store/navBarStore";
import type { PointRedeemSku, PointRedeemSkuStatus } from "@/types/Point"; import type { PointRedeemSku, PointRedeemSkuStatus } from "@/types/Point";
import { Message } from "@/utils/message"; import { Message } from "@/utils/message";
import { getProductPhotoURL } from "@/utils/product";
import { permissionCodes } from "@/utils/permission"; import { permissionCodes } from "@/utils/permission";
defineOptions({ name: "PointRedeemSkuList" }); defineOptions({ name: "PointRedeemSkuList" });
const router = useRouter(); const router = useRouter();
const navBarStore = useNavBarStore(); const navBarStore = useNavBarStore();
const result = reactive({ total: 0, list: [] as PointRedeemSku[] }); const result = reactive({ total: 0, list: [] as PointRedeemSku[] });
const keyword = shallowRef("");
const index = ref(0); const index = ref(0);
const isLoading = ref(false); const isLoading = ref(false);
const isRefreshing = ref(false); const isRefreshing = ref(false);
const isNoMore = ref(false); const isNoMore = ref(false);
const pageSize = 20;
let reloadRequested = false;
async function reload(): Promise<void> { async function reload(): Promise<void> {
if (isLoading.value) {
reloadRequested = true;
return;
}
index.value = 0; index.value = 0;
result.total = 0; result.total = 0;
result.list = []; result.list = [];
@@ -70,23 +103,40 @@ async function load(): Promise<void> {
} }
isLoading.value = true; isLoading.value = true;
try { try {
const page = await PointAPI.redeemSkuList({ index: index.value, size: 20 }); const page = await PointAPI.redeemSkuList({
index: index.value,
size: pageSize,
keyword: keyword.value.trim() || undefined
});
const list = page.list || []; const list = page.list || [];
result.list = result.list.concat(list); result.list = result.list.concat(list);
result.total = page.total || 0; result.total = page.total || 0;
index.value++; index.value++;
isNoMore.value = result.total <= result.list.length || list.length < 20; isNoMore.value = result.total <= result.list.length || list.length < pageSize;
} catch { } catch {
Message.error("兑换配置加载失败"); Message.error("兑换配置加载失败");
} finally { } finally {
isLoading.value = false; isLoading.value = false;
if (reloadRequested) {
reloadRequested = false;
await reload();
}
} }
} }
const reloadByKeyword = Toolkit.debounce(async () => {
await reload();
}, false);
function formatPoint(value?: number): string { function formatPoint(value?: number): string {
return `${(value || 0).toLocaleString("zh-CN")} 分`; return `${(value || 0).toLocaleString("zh-CN")} 分`;
} }
function getProductPhoto(item: PointRedeemSku): string {
const product = item.product || item.sku;
return product ? getProductPhotoURL(product) : "";
}
function statusText(status?: PointRedeemSkuStatus): string { function statusText(status?: PointRedeemSkuStatus): string {
return ({ return ({
DRAFT: "草稿", DRAFT: "草稿",
@@ -111,12 +161,54 @@ onMounted(async () => {
await reload(); await reload();
}); });
onUnmounted(() => navBarStore.clearRight()); watch(keyword, () => {
reloadByKeyword();
});
onUnmounted(() => {
reloadByKeyword.cancel();
navBarStore.clearRight();
});
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
.redeem-sku-list { .redeem-sku-list {
padding: calc(var(--app-nav-height) + 1rem) 0 5rem; gap: 1rem;
display: flex;
padding: var(--app-nav-height) 0 5rem;
flex-direction: column;
.filters {
top: var(--app-nav-height);
left: 0;
right: 0;
z-index: 10;
display: flex;
padding: .75rem 1rem;
position: sticky;
background: #f5f5f8;
box-shadow: var(--td-shadow-1);
flex-direction: column;
.keyword {
--td-input-vertical-padding: .25rem 1rem;
border: 1px solid var(--td-component-border);
background: var(--td-bg-color-container);
border-radius: 1rem;
}
}
.item {
padding: 10px;
border-radius: 1rem;
.avatar {
width: 3.5rem;
height: 3.5rem;
border-radius: .875rem;
}
}
.title { .title {
gap: .5rem; gap: .5rem;
@@ -131,4 +223,17 @@ onUnmounted(() => navBarStore.clearRight());
text-align: center; text-align: center;
} }
} }
:global(html.theme-dark) {
.redeem-sku-list {
.filters {
background: #10161f;
}
.fallback {
color: #ccfbf1;
background: #115e59;
}
}
}
</style> </style>