Files
gao-customer-h5/src/pages/package/PackageOrderConfigList.vue
T
2026-09-18 18:25:31 +08:00

300 lines
7.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div v-can="permissionCodes.packageConfig.read" class="package-order-config-list">
<div class="filters">
<t-input
v-model.trim="keyword"
class="keyword"
borderless
clearable
placeholder="搜索套餐名称或说明"
/>
<t-dropdown-menu class="status">
<t-dropdown-item
v-model="status"
label="状态"
:options="statusOptions"
@change="selectStatus"
/>
</t-dropdown-menu>
</div>
<t-pull-down-refresh
v-model="refreshing"
:refresh-timeout="30000"
@refresh="reload"
@scrolltolower="loadMore"
>
<t-cell-group
v-if="configList.length"
class="list fix-last-cell-line empty-cell-title-margin"
theme="card"
>
<t-cell
v-for="(item, index) in configList"
:key="item.id"
class="item"
arrow
v-entrance-transition="index * 30"
@click="selectConfig(item)"
>
<template #title>
<div class="title">
<strong class="name clip-text" v-text="item.name || '未命名套餐'" />
<t-tag :content="getPackageConfigStatusText(item.status)" variant="light-outline" :theme="getStatusTheme(item.status)" />
</div>
</template>
<template #description>
<div class="description">
<div class="products">
<t-tag
v-for="productItem in item.itemList || []"
:key="productItem.id || `${productItem.skuId}-${productItem.skuUnitId}`"
:content="getProductText(productItem)"
theme="primary"
variant="light"
/>
<t-tag v-if="!item.itemList?.length" content="暂无产品权益" />
</div>
<div class="price">
<span class="label gray">标准价:</span>
<span class="value red" v-text="formatPackageAmount(item.standardAmount)" />
</div>
</div>
</template>
</t-cell>
</t-cell-group>
<div v-else class="center-tips" v-text="loading ? '正在加载套餐' : '暂无套餐'" />
<div class="center-tips" v-text="loadStateText" />
</t-pull-down-refresh>
</div>
</template>
<script setup lang="ts">
import { type DropdownValue } from "tdesign-mobile-vue";
import { Toolkit } from "timi-web";
import PackageAPI from "@/api/PackageAPI";
import { useNavBarStore } from "@/store/navBarStore";
import type { GaoPackageConfig, PackageConfigItem, PackageConfigStatus } from "@/types/Package";
import { getErrorMessage } from "@/utils/error";
import { Message } from "@/utils/message";
import { formatPackageAmount, getPackageConfigStatusText, getPackageQuantityText } from "@/utils/package";
import { permissionCodes } from "@/utils/permission";
import { getProductSkuSnapshotText } from "@/utils/product";
defineOptions({ name: "PackageOrderConfigList" });
const route = useRoute();
const router = useRouter();
const navBarStore = useNavBarStore();
const keyword = ref("");
const status = ref("ALL");
const configList = ref<GaoPackageConfig[]>([]);
const pageIndex = ref(0);
const total = ref(0);
const loading = ref(false);
const refreshing = ref(false);
const noMore = ref(false);
const pageSize = 15;
const statusOptions = [
{ label: "全部状态", value: "ALL" },
{ label: "草稿", value: "DRAFT" },
{ label: "启用", value: "ACTIVE" },
{ label: "停用", value: "INACTIVE" }
];
const loadStateText = computed(() => loading.value ? "正在加载" : noMore.value ? "已经到底了" : "上拉加载更多");
async function loadMore(): Promise<void> {
if (loading.value || noMore.value) {
return;
}
loading.value = true;
try {
const result = await PackageAPI.configList({
index: pageIndex.value,
size: pageSize,
keyword: keyword.value || undefined,
status: status.value === "ALL" ? undefined : status.value as PackageConfigStatus
});
const rows = result.list || [];
configList.value = configList.value.concat(rows);
total.value = result.total || 0;
pageIndex.value += 1;
noMore.value = rows.length < pageSize || total.value <= configList.value.length;
} finally {
loading.value = false;
}
}
async function reload(): Promise<void> {
if (loading.value) {
return;
}
pageIndex.value = 0;
configList.value = [];
total.value = 0;
noMore.value = false;
try {
await loadMore();
} catch (error) {
Message.error(getErrorMessage(error, "套餐加载失败"));
} finally {
refreshing.value = false;
}
}
async function selectStatus(value: DropdownValue): Promise<void> {
if (Array.isArray(value)) {
return;
}
status.value = String(value);
await reload();
}
const reloadByKeyword = Toolkit.debounce(async () => {
await reload();
}, false);
watch(keyword, () => {
reloadByKeyword();
});
async function selectConfig(config: GaoPackageConfig): Promise<void> {
if (!config.id) {
return;
}
await router.push({
name: "SalesOrderList",
query: {
orderType: "PACKAGE",
packageConfigId: config.id,
packageConfigName: config.name || "未命名套餐",
transitionDepth: String(Number(route.meta.depth ?? 0) + 1)
}
});
}
function getStatusTheme(statusValue?: PackageConfigStatus): "success" | "warning" | "default" {
return statusValue === "ACTIVE" ? "success" : statusValue === "INACTIVE" ? "warning" : "default";
}
function getProductText(item: PackageConfigItem): string {
const quantity = getPackageQuantityText(item.defaultQuantity);
const skuText = getProductSkuSnapshotText(item.productNameSnapshot, item.skuNameSnapshot, item.specSummarySnapshot);
return `${item.productNameSnapshot || "未命名产品"}${skuText ? ` · ${skuText}` : ""} × ${quantity}${item.unitSnapshot ? ` ${item.unitSnapshot}` : ""}`;
}
onMounted(async () => {
navBarStore.clearRight();
try {
await reload();
} catch (error) {
Message.error(getErrorMessage(error, "套餐加载失败"));
}
});
onUnmounted(() => {
navBarStore.clearRight();
reloadByKeyword.cancel();
});
</script>
<style scoped lang="less">
.package-order-config-list {
padding: var(--app-nav-height) 0 1rem;
.filters {
top: var(--app-nav-height);
left: 0;
right: 0;
z-index: 10;
gap: .5rem;
display: flex;
padding: .75rem 1rem;
position: sticky;
background: var(--td-bg-color-page);
box-shadow: var(--td-shadow-1);
.keyword {
--td-input-vertical-padding: .25rem 1rem;
flex: 1;
border: 1px solid var(--td-component-border);
z-index: 1;
background: var(--td-bg-color-container);
border-radius: 1rem;
}
.status {
--td-spacer: 4px;
--td-spacer-1: 6px;
border: 1px solid rgba(148, 163, 184, .18);
margin-left: -2rem;
padding-left: 2rem;
border-radius: 1rem;
&:after {
display: none;
}
}
}
.list {
gap: 1rem;
display: flex;
padding-top: 1rem;
flex-direction: column;
.item {
padding: 10px;
min-width: 0;
:deep(.t-cell__title) {
min-width: 0;
}
.title {
gap: .5rem;
flex: 1;
display: flex;
min-width: 0;
align-items: center;
justify-content: space-between;
}
.description {
display: flex;
min-width: 0;
flex-direction: column;
.products {
gap: .35rem;
display: flex;
margin-top: .5rem;
overflow-x: auto;
white-space: nowrap;
scrollbar-width: none;
:deep(.t-tag) {
flex: 0 0 auto;
}
&::-webkit-scrollbar {
display: none;
}
}
.price {
margin-top: .5rem;
text-align: right;
.value {
font-size: 1.1rem;
}
}
}
}
}
}
</style>