add product

This commit is contained in:
Timi
2026-09-03 14:27:40 +08:00
parent 7f669621fb
commit 333999fe21
63 changed files with 5662 additions and 2210 deletions
+133
View File
@@ -0,0 +1,133 @@
<template>
<div v-can="permissionCodes.salesOrder.read" class="sales-order-list">
<div class="filters">
<t-input v-model.trim="keyword" class="keyword" type="search" borderless clearable placeholder="搜索订单号、客户" />
<t-dropdown-menu class="menus">
<t-dropdown-item v-model="orderType" label="订单类型" :options="orderTypeOptions" @change="selectOrderType" />
<t-dropdown-item v-model="status" label="订单状态" :options="statusOptions" @change="selectStatus" />
<t-dropdown-item v-model="paymentStatus" label="收款状态" :options="paymentOptions" @change="selectPaymentStatus" />
</t-dropdown-menu>
</div>
<t-pull-down-refresh v-model="refreshing" :refresh-timeout="30000" @refresh="refresh" @scrolltolower="loadMore">
<div v-if="viewMode" class="view-tip" v-text="viewMode === 'pending-receive' ? '当前显示未收清订单' : '当前显示待处理退款订单'" />
<t-cell-group v-if="displayList.length" theme="card">
<t-cell v-for="(item, index) in displayList" :key="item.id" arrow hover v-entrance-transition="index * 30" @click="toDetail(item)">
<template #title>
<div class="title">
<strong v-text="item.orderNo || item.id || '未命名订单'" />
<t-tag :content="getSalesOrderStatusText(item.status)" variant="light-outline" :theme="getStatusTheme(item.status)" />
</div>
</template>
<template #note><span class="amount" v-text="formatPackageAmount(item.saleAmount)" /></template>
<template #description>
<div class="description">
<span v-text="getSalesOrderTypeText(item.orderType)" />
<span v-text="item.customer?.name || item.customerId || '未知客户'" />
<t-tag :content="getSalesPaymentStatusText(item.paymentStatus)" theme="primary" variant="outline" />
</div>
</template>
</t-cell>
</t-cell-group>
<div v-else class="empty" v-text="loading ? '正在加载销售订单' : '暂无销售订单'" />
<div class="center-tips" v-text="loading ? '正在加载' : noMore ? '已经到底了' : '上拉加载更多'" />
</t-pull-down-refresh>
</div>
<nav-bar-action-menu v-if="canCreate" :actions="actions" />
</template>
<script setup lang="ts">
import { type DropdownValue } from "tdesign-mobile-vue";
import { Toolkit } from "timi-web";
import { useUserStore } from "timi-web-api";
import SalesOrderAPI from "@/api/SalesOrderAPI";
import NavBarActionMenu from "@/components/NavBarActionMenu.vue";
import { useNavBarStore } from "@/store/navBarStore";
import type { SalesOrder, SalesOrderStatus, SalesOrderType, SalesPaymentStatus } from "@/types/SalesOrder";
import { getErrorMessage } from "@/utils/error";
import { formatPackageAmount } from "@/utils/package";
import { getSalesOrderStatusText, getSalesOrderTypeText, getSalesPaymentStatusText, getStatusTheme } from "@/utils/salesOrder";
import { Message } from "@/utils/message";
import { permissionCodes } from "@/utils/permission";
defineOptions({ name: "SalesOrderList" });
const route = useRoute();
const router = useRouter();
const navBarStore = useNavBarStore();
const userStore = useUserStore();
const canCreate = computed(() => userStore.hasPermissions(permissionCodes.salesOrder.create));
const actions = computed(() => canCreate.value ? [{ key: "create", text: "直接产品下单", icon: "add", theme: "primary" as const, action: toCreate }] : []);
const keyword = ref("");
const orderType = ref("ALL");
const viewMode = computed(() => route.query.view === "pending-receive" || route.query.view === "pending-refund" ? String(route.query.view) : "");
const status = ref(viewMode.value === "pending-refund" ? "CANCELLED" : "ALL");
const paymentStatus = ref("ALL");
const list = ref<SalesOrder[]>([]);
const pageIndex = ref(0);
const total = ref(0);
const loading = ref(false);
const refreshing = ref(false);
const noMore = ref(false);
const pageSize = 15;
const orderTypeOptions = [{ label: "全部类型", value: "ALL" }, { label: "套餐订单", value: "PACKAGE" }, { label: "直接产品", value: "DIRECT_PRODUCT" }];
const statusOptions = [{ label: "全部状态", value: "ALL" }, { label: "进行中", value: "ACTIVE" }, { label: "已履约", value: "FULFILLED" }, { label: "已取消", value: "CANCELLED" }];
const paymentOptions = [{ label: "全部收款", value: "ALL" }, { label: "未收款", value: "UNPAID" }, { label: "部分收款", value: "PARTIALLY_PAID" }, { label: "已收款", value: "PAID" }];
const displayList = computed(() => list.value.filter(item => {
if (viewMode.value === "pending-receive") return item.paymentStatus !== "PAID";
if (viewMode.value === "pending-refund") return item.status === "CANCELLED" && item.refundStatus === "PENDING";
return true;
}));
async function fetchList(reset: boolean): Promise<void> {
if (loading.value || (!reset && noMore.value)) return;
if (reset) { pageIndex.value = 0; total.value = 0; noMore.value = false; list.value = []; }
loading.value = true;
try {
const result = await SalesOrderAPI.list({
index: pageIndex.value,
size: pageSize,
keyword: keyword.value || undefined,
orderType: orderType.value === "ALL" ? undefined : orderType.value as SalesOrderType,
status: status.value === "ALL" ? undefined : status.value as SalesOrderStatus,
paymentStatus: paymentStatus.value === "ALL" ? undefined : paymentStatus.value as SalesPaymentStatus
});
const rows = result.list || [];
list.value = reset ? rows : list.value.concat(rows);
total.value = result.total || 0;
pageIndex.value += 1;
noMore.value = rows.length < pageSize || total.value <= list.value.length;
} finally { loading.value = false; }
}
async function refresh(): Promise<void> { try { await fetchList(true); } catch (error) { Message.error(getErrorMessage(error, "销售订单加载失败")); } finally { refreshing.value = false; } }
async function loadMore(): Promise<void> { try { await fetchList(false); } catch (error) { Message.error(getErrorMessage(error, "销售订单加载失败")); } }
async function selectOrderType(value: DropdownValue): Promise<void> { if (!Array.isArray(value)) { orderType.value = String(value); await fetchList(true); } }
async function selectStatus(value: DropdownValue): Promise<void> { if (!Array.isArray(value)) { status.value = String(value); await fetchList(true); } }
async function selectPaymentStatus(value: DropdownValue): Promise<void> { if (!Array.isArray(value)) { paymentStatus.value = String(value); await fetchList(true); } }
async function toDetail(item: SalesOrder): Promise<void> { if (item.id) await router.push(`/management/sales-orders/${item.id}`); }
async function toCreate(): Promise<void> { await router.push("/management/sales-orders/create"); }
const search = Toolkit.debounce(async () => { try { await fetchList(true); } catch (error) { Message.error(getErrorMessage(error, "销售订单加载失败")); } }, false);
watch(keyword, () => search());
watch(viewMode, async value => {
status.value = value === "pending-refund" ? "CANCELLED" : "ALL";
paymentStatus.value = "ALL";
await fetchList(true);
});
onMounted(async () => { navBarStore.clearRight(); navBarStore.setCustomRightVisible(canCreate.value); try { await fetchList(true); } catch (error) { Message.error(getErrorMessage(error, "销售订单加载失败")); } });
onUnmounted(() => { navBarStore.clearRight(); search.cancel(); });
</script>
<style scoped lang="less">
.sales-order-list { padding: var(--app-nav-height) 0 1rem;
.filters { top: var(--app-nav-height); z-index: 10; padding: .75rem 1rem .5rem; position: sticky; background: var(--td-bg-color-page); box-shadow: var(--td-shadow-1);
.keyword { border: 1px solid var(--td-component-border); background: var(--td-bg-color-container); border-radius: 1rem; }
.menus { margin-top: .5rem; border-radius: .875rem;
:deep(.t-dropdown-menu__item) { min-width: 0; }
:deep(.t-dropdown-menu__title) { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
}
}
.title, .description { gap: .5rem; display: flex; align-items: center; }
.description { color: var(--td-text-color-secondary); flex-wrap: wrap; }
.amount { color: var(--td-brand-color); font-weight: 700; }
.view-tip { color: var(--td-text-color-secondary); padding: .75rem 1rem 0; font-size: .8125rem; }
.empty { color: var(--td-text-color-secondary); padding: 3rem 1rem; text-align: center; }
}
</style>