mirror of
https://github.com/34892002/edgeKey.git
synced 2026-05-06 15:22:43 +08:00
feat: 卡密管理优化
This commit is contained in:
66
components/DataTable.vue
Normal file
66
components/DataTable.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<div class="space-y-3">
|
||||
<div class="overflow-x-auto overflow-y-auto max-h-150">
|
||||
<table class="table table-zebra w-full">
|
||||
<thead class="sticky top-0 z-10 bg-base-200">
|
||||
<tr>
|
||||
<th v-for="col in columns" :key="col.key">{{ col.label }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="!rows.length">
|
||||
<td :colspan="columns.length" class="text-center text-base-content/60">{{ emptyText }}</td>
|
||||
</tr>
|
||||
<tr v-for="(row, i) in rows" :key="i">
|
||||
<td v-for="col in columns" :key="col.key">
|
||||
<slot :name="col.key" :row="row" :value="row[col.key]">{{ row[col.key] ?? '-' }}</slot>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div v-if="total > pageSize" class="flex items-center justify-between">
|
||||
<span class="text-sm text-base-content/60">共 {{ total }} 条,第 {{ page }}/{{ totalPages }} 页</span>
|
||||
<div class="join">
|
||||
<button class="join-item btn btn-sm" :disabled="page <= 1" @click="$emit('update:page', page - 1)">«</button>
|
||||
<button
|
||||
v-for="p in pageNumbers"
|
||||
:key="p"
|
||||
class="join-item btn btn-sm"
|
||||
:class="{ 'btn-active': p === page }"
|
||||
@click="$emit('update:page', p)"
|
||||
>{{ p }}</button>
|
||||
<button class="join-item btn btn-sm" :disabled="page >= totalPages" @click="$emit('update:page', page + 1)">»</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" generic="T extends Record<string, any>">
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
columns: { key: string; label: string }[];
|
||||
rows: T[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize?: number;
|
||||
emptyText?: string;
|
||||
}>(), {
|
||||
pageSize: 20,
|
||||
emptyText: "暂无数据",
|
||||
});
|
||||
|
||||
defineEmits<{ "update:page": [page: number] }>();
|
||||
|
||||
const totalPages = computed(() => Math.max(1, Math.ceil(props.total / props.pageSize)));
|
||||
|
||||
const pageNumbers = computed(() => {
|
||||
const pages: number[] = [];
|
||||
const start = Math.max(1, props.page - 2);
|
||||
const end = Math.min(totalPages.value, start + 4);
|
||||
for (let i = start; i <= end; i++) pages.push(i);
|
||||
return pages;
|
||||
});
|
||||
</script>
|
||||
105
docs/components.md
Normal file
105
docs/components.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# 公共组件文档
|
||||
|
||||
## DataTable
|
||||
|
||||
通用带翻页的表格组件,基于 daisyUI `table` 样式。
|
||||
|
||||
### Props
|
||||
|
||||
| 属性 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `columns` | `{ key: string; label: string }[]` | — | 列定义 |
|
||||
| `rows` | `T[]` | — | 当前页数据 |
|
||||
| `total` | `number` | — | 总条数 |
|
||||
| `page` | `number` | — | 当前页码(从 1 开始) |
|
||||
| `pageSize` | `number` | `20` | 每页条数 |
|
||||
| `emptyText` | `string` | `"暂无数据"` | 空状态文案 |
|
||||
|
||||
### Events
|
||||
|
||||
| 事件 | 参数 | 说明 |
|
||||
|------|------|------|
|
||||
| `update:page` | `page: number` | 用户切换页码时触发 |
|
||||
|
||||
### Slots
|
||||
|
||||
每一列都有一个以 `col.key` 命名的具名插槽,用于自定义单元格渲染。
|
||||
|
||||
插槽 props:
|
||||
|
||||
| 名称 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `row` | `T` | 当前行完整数据 |
|
||||
| `value` | `any` | 当前列的值,等同于 `row[col.key]` |
|
||||
|
||||
不提供插槽时,默认渲染 `value`,值为 `null`/`undefined` 时显示 `-`。
|
||||
|
||||
### 基本用法
|
||||
|
||||
```components/DataTable.vue#L1-5
|
||||
<DataTable
|
||||
:columns="columns"
|
||||
:rows="pageData.items"
|
||||
:total="pageData.total"
|
||||
:page="currentPage"
|
||||
:page-size="20"
|
||||
@update:page="handlePageChange"
|
||||
/>
|
||||
```
|
||||
|
||||
### 自定义列渲染
|
||||
|
||||
```components/DataTable.vue#L1-10
|
||||
<DataTable :columns="columns" :rows="rows" :total="total" :page="page" @update:page="p => page = p">
|
||||
<!-- 自定义状态列 -->
|
||||
<template #status="{ value }">
|
||||
<span class="badge badge-success">{{ value }}</span>
|
||||
</template>
|
||||
<!-- 自定义时间列 -->
|
||||
<template #createdAt="{ value }">
|
||||
{{ new Date(value).toLocaleString() }}
|
||||
</template>
|
||||
</DataTable>
|
||||
```
|
||||
|
||||
### 完整示例
|
||||
|
||||
```pages/admin/cards/+Page.vue#L1-30
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from "vue";
|
||||
import DataTable from "../../../components/DataTable.vue";
|
||||
import { onQueryCards } from "./queryCards.telefunc";
|
||||
|
||||
const PAGE_SIZE = 20;
|
||||
const currentPage = ref(1);
|
||||
const cardPage = ref({ items: [], total: 0 });
|
||||
|
||||
const columns = [
|
||||
{ key: "id", label: "ID" },
|
||||
{ key: "productName", label: "商品" },
|
||||
{ key: "status", label: "状态" },
|
||||
{ key: "createdAt", label: "创建时间" },
|
||||
];
|
||||
|
||||
async function fetchPage(page: number) {
|
||||
cardPage.value = await onQueryCards({ page, pageSize: PAGE_SIZE });
|
||||
currentPage.value = page;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DataTable
|
||||
:columns="columns"
|
||||
:rows="cardPage.items"
|
||||
:total="cardPage.total"
|
||||
:page="currentPage"
|
||||
:page-size="PAGE_SIZE"
|
||||
@update:page="fetchPage"
|
||||
/>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 分页说明
|
||||
|
||||
- 总条数 `total <= pageSize` 时,分页控件自动隐藏
|
||||
- 页码按钮最多显示 5 个,以当前页为中心滑动
|
||||
@@ -63,3 +63,41 @@ export function countCardStats(prisma: PrismaClient) {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function listCardRecordsPaged(
|
||||
prisma: PrismaClient,
|
||||
params: {
|
||||
productId?: number;
|
||||
batchNo?: string;
|
||||
status?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
},
|
||||
) {
|
||||
const where: import("../../generated/prisma/client").Prisma.CardWhereInput = {};
|
||||
if (params.productId) where.productId = params.productId;
|
||||
if (params.batchNo) where.batchNo = { contains: params.batchNo };
|
||||
if (params.status) where.status = params.status as import("../../generated/prisma/client").CardStatus;
|
||||
if (params.startDate || params.endDate) {
|
||||
where.createdAt = {};
|
||||
if (params.startDate) where.createdAt.gte = new Date(params.startDate);
|
||||
if (params.endDate) {
|
||||
const end = new Date(params.endDate);
|
||||
end.setHours(23, 59, 59, 999);
|
||||
where.createdAt.lte = end;
|
||||
}
|
||||
}
|
||||
const skip = (params.page - 1) * params.pageSize;
|
||||
return Promise.all([
|
||||
prisma.card.findMany({ where, include: { product: true }, orderBy: [{ id: "desc" }], skip, take: params.pageSize }),
|
||||
prisma.card.count({ where }),
|
||||
]);
|
||||
}
|
||||
|
||||
export function deleteCardById(prisma: PrismaClient, id: number) {
|
||||
return prisma.card.deleteMany({
|
||||
where: { id, status: "UNUSED" },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { PrismaClient } from "../../generated/prisma/client";
|
||||
import { badRequestError } from "../../lib/app-error";
|
||||
import { getAdminContext, logAdminOperation } from "../auth/service";
|
||||
import { parseCardLines } from "./importer";
|
||||
import { countCardStats, createCardRecord, createManyCards, deleteUnusedCardsByProduct, listCardRecords } from "./repository";
|
||||
import { countCardStats, createCardRecord, createManyCards, deleteCardById, deleteUnusedCardsByProduct, listCardRecords, listCardRecordsPaged } from "./repository";
|
||||
|
||||
function getInventoryContext() {
|
||||
return getContext<{ prisma: PrismaClient }>();
|
||||
@@ -155,3 +155,40 @@ export async function deleteUnusedCards(input: { productId: number }) {
|
||||
count: result.count,
|
||||
};
|
||||
}
|
||||
|
||||
export async function deleteCard(input: { id: number }) {
|
||||
const adminContext = getAdminContext();
|
||||
const { prisma } = adminContext;
|
||||
const adminId = Number(adminContext.session?.user?.id);
|
||||
const result = await deleteCardById(prisma, input.id);
|
||||
if (result.count === 0) throw badRequestError("卡密不存在或已售出,无法删除", "CARD_DELETE_FAILED");
|
||||
await logAdminOperation({ action: "DELETE_CARD", targetType: "Card", targetId: String(input.id), detail: "" }, { prisma, adminId });
|
||||
return { id: input.id };
|
||||
}
|
||||
|
||||
export async function getAdminCardsPaged(params: {
|
||||
productId?: number;
|
||||
batchNo?: string;
|
||||
status?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}) {
|
||||
const { prisma } = getAdminContext();
|
||||
const [cards, total] = await listCardRecordsPaged(prisma, params);
|
||||
return {
|
||||
total,
|
||||
items: cards.map((item) => ({
|
||||
id: item.id,
|
||||
productId: item.productId,
|
||||
productName: item.product.name,
|
||||
status: item.status,
|
||||
batchNo: item.batchNo,
|
||||
orderId: item.orderId,
|
||||
soldAt: item.soldAt ? item.soldAt.toISOString() : null,
|
||||
createdAt: item.createdAt.toISOString(),
|
||||
contentPreview: previewCard(item.content),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,9 +13,7 @@
|
||||
<h1 class="text-xl font-bold">单条新增</h1>
|
||||
<select v-model="singleForm.productId" class="select select-bordered w-full">
|
||||
<option value="">请选择商品</option>
|
||||
<option v-for="product in products" :key="product.id" :value="String(product.id)">
|
||||
{{ product.name }}
|
||||
</option>
|
||||
<option v-for="product in products" :key="product.id" :value="String(product.id)">{{ product.name }}</option>
|
||||
</select>
|
||||
<input v-model="singleForm.batchNo" class="input input-bordered w-full" placeholder="批次号(可选)" />
|
||||
<textarea v-model="singleForm.content" class="textarea textarea-bordered w-full" rows="4" placeholder="输入卡密内容"></textarea>
|
||||
@@ -26,9 +24,7 @@
|
||||
<h2 class="text-xl font-bold">批量导入</h2>
|
||||
<select v-model="importForm.productId" class="select select-bordered w-full">
|
||||
<option value="">请选择商品</option>
|
||||
<option v-for="product in products" :key="product.id" :value="String(product.id)">
|
||||
{{ product.name }}
|
||||
</option>
|
||||
<option v-for="product in products" :key="product.id" :value="String(product.id)">{{ product.name }}</option>
|
||||
</select>
|
||||
<input v-model="importForm.batchNo" class="input input-bordered w-full" placeholder="批次号(可选)" />
|
||||
<textarea v-model="importForm.lines" class="textarea textarea-bordered w-full" rows="8" placeholder="每行一条卡密"></textarea>
|
||||
@@ -45,109 +41,154 @@
|
||||
</section>
|
||||
|
||||
<section class="card bg-base-100 shadow-sm">
|
||||
<div class="card-body">
|
||||
<h2 class="mb-4 text-xl font-bold">库存列表</h2>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-zebra">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>商品</th>
|
||||
<th>卡密预览</th>
|
||||
<th>批次</th>
|
||||
<th>状态</th>
|
||||
<th>订单</th>
|
||||
<th>创建时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="!cardList.length">
|
||||
<td colspan="7" class="text-center text-base-content/60">当前还没有库存卡密。</td>
|
||||
</tr>
|
||||
<tr v-for="card in cardList" :key="card.id">
|
||||
<td>{{ card.id }}</td>
|
||||
<td>{{ card.productName }}</td>
|
||||
<td><code>{{ card.contentPreview }}</code></td>
|
||||
<td>{{ card.batchNo || '-' }}</td>
|
||||
<td>
|
||||
<span class="badge" :class="getStatusBadgeClass(card.status)">
|
||||
{{ getStatusLabel(card.status) }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ card.orderId || '-' }}</td>
|
||||
<td>{{ formatDate(card.createdAt) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="card-body space-y-4">
|
||||
<h2 class="text-xl font-bold">库存列表</h2>
|
||||
|
||||
<!-- 搜索筛选 -->
|
||||
<div class="flex flex-wrap gap-3 items-center">
|
||||
<select v-model="filter.productId" class="select select-sm select-bordered w-46">
|
||||
<option value="">全部商品</option>
|
||||
<option v-for="product in products" :key="product.id" :value="String(product.id)">{{ product.name }}</option>
|
||||
</select>
|
||||
<select v-model="filter.status" class="select select-sm select-bordered w-auto">
|
||||
<option value="">全部状态</option>
|
||||
<option value="UNUSED">未售出</option>
|
||||
<option value="SOLD">已售出</option>
|
||||
<option value="LOCKED">锁定中</option>
|
||||
<option value="INVALID">已失效</option>
|
||||
</select>
|
||||
<input v-model="filter.batchNo" class="input input-sm input-bordered w-52" placeholder="批次号" />
|
||||
<input v-model="filter.startDate" type="date" class="input input-sm input-bordered w-46" />
|
||||
<input v-model="filter.endDate" type="date" class="input input-sm input-bordered w-46" />
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<button class="btn btn-sm btn-primary" @click="handleSearch">搜索</button>
|
||||
<button class="btn btn-sm btn-ghost" @click="handleReset">重置</button>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
:columns="columns"
|
||||
:rows="cardPage.items"
|
||||
:total="cardPage.total"
|
||||
:page="currentPage"
|
||||
:page-size="PAGE_SIZE"
|
||||
@update:page="handlePageChange"
|
||||
>
|
||||
<template #contentPreview="{ value }">
|
||||
<code>{{ value }}</code>
|
||||
</template>
|
||||
<template #status="{ value }">
|
||||
<span class="badge" :class="getStatusBadgeClass(value)">{{ getStatusLabel(value) }}</span>
|
||||
</template>
|
||||
<template #createdAt="{ value }">
|
||||
{{ formatDate(value) }}
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<button
|
||||
v-if="row.status === 'UNUSED'"
|
||||
class="btn btn-xs btn-error btn-outline"
|
||||
@click="handleDeleteCard(row.id)"
|
||||
>删除</button>
|
||||
</template>
|
||||
</DataTable>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { normalizeTelefuncError } from "../../../lib/app-error";
|
||||
import { reactive, ref } from "vue";
|
||||
import { useData } from "vike-vue/useData";
|
||||
import { normalizeTelefuncError } from "../../../lib/app-error";
|
||||
import { onCreateCard } from "./createCard.telefunc";
|
||||
import { onDeleteUnusedCards } from "./deleteUnusedCards.telefunc";
|
||||
import { onImportCards } from "./importCards.telefunc";
|
||||
import { onQueryCards } from "./queryCards.telefunc";
|
||||
import { onDeleteCard } from "./deleteCard.telefunc";
|
||||
import DataTable from "../../../components/DataTable.vue";
|
||||
import type { Data } from "./+data";
|
||||
|
||||
const { cards, products, overview } = useData<Data>();
|
||||
|
||||
const cardList = ref([...cards]);
|
||||
const PAGE_SIZE = 20;
|
||||
const currentPage = ref(1);
|
||||
const cardPage = ref({ items: [...cards], total: cards.length });
|
||||
|
||||
const message = ref("");
|
||||
const errorMessage = ref("");
|
||||
|
||||
const singleForm = reactive({
|
||||
productId: "",
|
||||
content: "",
|
||||
batchNo: "",
|
||||
});
|
||||
const filter = reactive({ productId: "", batchNo: "", status: "", startDate: "", endDate: "" });
|
||||
|
||||
const importForm = reactive({
|
||||
productId: "",
|
||||
lines: "",
|
||||
batchNo: "",
|
||||
});
|
||||
const singleForm = reactive({ productId: "", content: "", batchNo: "" });
|
||||
const importForm = reactive({ productId: "", lines: "", batchNo: "" });
|
||||
|
||||
const columns = [
|
||||
{ key: "id", label: "ID" },
|
||||
{ key: "productName", label: "商品" },
|
||||
{ key: "contentPreview", label: "卡密预览" },
|
||||
{ key: "batchNo", label: "批次" },
|
||||
{ key: "status", label: "状态" },
|
||||
{ key: "orderId", label: "订单" },
|
||||
{ key: "createdAt", label: "创建时间" },
|
||||
{ key: "actions", label: "操作" },
|
||||
];
|
||||
|
||||
function formatDate(value: string) {
|
||||
return new Date(value).toLocaleString();
|
||||
}
|
||||
|
||||
function getStatusLabel(status: string) {
|
||||
return {
|
||||
UNUSED: "未售出",
|
||||
SOLD: "已售出",
|
||||
LOCKED: "锁定中",
|
||||
INVALID: "已失效"
|
||||
}[status] || status;
|
||||
return ({ UNUSED: "未售出", SOLD: "已售出", LOCKED: "锁定中", INVALID: "已失效" } as Record<string, string>)[status] || status;
|
||||
}
|
||||
|
||||
function getStatusBadgeClass(status: string) {
|
||||
return {
|
||||
UNUSED: "badge-success",
|
||||
SOLD: "badge-ghost",
|
||||
LOCKED: "badge-warning",
|
||||
INVALID: "badge-error"
|
||||
}[status] || "badge-ghost";
|
||||
return ({ UNUSED: "badge-success", SOLD: "badge-ghost", LOCKED: "badge-warning", INVALID: "badge-error" } as Record<string, string>)[status] || "badge-ghost";
|
||||
}
|
||||
|
||||
async function fetchPage(page: number) {
|
||||
const result = await onQueryCards({
|
||||
productId: filter.productId ? Number(filter.productId) : undefined,
|
||||
batchNo: filter.batchNo || undefined,
|
||||
status: filter.status || undefined,
|
||||
startDate: filter.startDate || undefined,
|
||||
endDate: filter.endDate || undefined,
|
||||
page,
|
||||
pageSize: PAGE_SIZE,
|
||||
});
|
||||
cardPage.value = result;
|
||||
currentPage.value = page;
|
||||
}
|
||||
|
||||
async function handleSearch() {
|
||||
await fetchPage(1);
|
||||
}
|
||||
|
||||
async function handleReset() {
|
||||
filter.productId = "";
|
||||
filter.batchNo = "";
|
||||
filter.status = "";
|
||||
filter.startDate = "";
|
||||
filter.endDate = "";
|
||||
await fetchPage(1);
|
||||
}
|
||||
|
||||
async function handlePageChange(page: number) {
|
||||
await fetchPage(page);
|
||||
}
|
||||
|
||||
async function handleCreateCard() {
|
||||
message.value = "";
|
||||
errorMessage.value = "";
|
||||
|
||||
try {
|
||||
const result = await onCreateCard({
|
||||
await onCreateCard({
|
||||
productId: Number(singleForm.productId),
|
||||
content: singleForm.content,
|
||||
batchNo: singleForm.batchNo,
|
||||
});
|
||||
cardList.value.unshift(result);
|
||||
singleForm.content = "";
|
||||
singleForm.batchNo = "";
|
||||
message.value = `已新增卡密 #${result.id}`;
|
||||
message.value = "新增成功";
|
||||
await fetchPage(1);
|
||||
} catch (error) {
|
||||
errorMessage.value = normalizeTelefuncError(error, "新增失败");
|
||||
}
|
||||
@@ -156,7 +197,6 @@ async function handleCreateCard() {
|
||||
async function handleImportCards() {
|
||||
message.value = "";
|
||||
errorMessage.value = "";
|
||||
|
||||
try {
|
||||
const result = await onImportCards({
|
||||
productId: Number(importForm.productId),
|
||||
@@ -165,24 +205,35 @@ async function handleImportCards() {
|
||||
});
|
||||
importForm.lines = "";
|
||||
importForm.batchNo = "";
|
||||
message.value = `已导入 ${result.count} 条卡密,请刷新页面查看最新列表。`;
|
||||
message.value = `已导入 ${result.count} 条卡密`;
|
||||
await fetchPage(1);
|
||||
} catch (error) {
|
||||
errorMessage.value = normalizeTelefuncError(error, "导入失败");
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDeleteCard(id: number) {
|
||||
if (!confirm(`确认删除卡密 #${id}?此操作不可撤销。`)) return;
|
||||
message.value = "";
|
||||
errorMessage.value = "";
|
||||
try {
|
||||
await onDeleteCard({ id });
|
||||
message.value = `已删除卡密 #${id}`;
|
||||
await fetchPage(currentPage.value);
|
||||
} catch (error) {
|
||||
errorMessage.value = normalizeTelefuncError(error, "删除失败");
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDeleteUnused() {
|
||||
message.value = "";
|
||||
errorMessage.value = "";
|
||||
|
||||
try {
|
||||
const result = await onDeleteUnusedCards({
|
||||
productId: Number(importForm.productId),
|
||||
});
|
||||
cardList.value = cardList.value.filter((card) => !(card.productId === Number(importForm.productId) && card.status === 'UNUSED'));
|
||||
message.value = `已删除 ${result.count} 条未售卡密。`;
|
||||
const result = await onDeleteUnusedCards({ productId: Number(importForm.productId) });
|
||||
message.value = `已删除 ${result.count} 条未售卡密`;
|
||||
await fetchPage(currentPage.value);
|
||||
} catch (error) {
|
||||
errorMessage.value = normalizeTelefuncError(error, "删除失败");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getAdminProducts } from "../../../modules/catalog/service";
|
||||
import { getAdminCards, getInventoryOverview } from "../../../modules/inventory/service";
|
||||
|
||||
export type Data = ReturnType<typeof data>;
|
||||
export type Data = Awaited<ReturnType<typeof data>>;
|
||||
|
||||
export async function data(pageContext: {
|
||||
prisma: import("../../../generated/prisma/client").PrismaClient;
|
||||
|
||||
7
pages/admin/cards/deleteCard.telefunc.ts
Normal file
7
pages/admin/cards/deleteCard.telefunc.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { assertAdminAccess } from "../../../modules/auth/service";
|
||||
import { deleteCard } from "../../../modules/inventory/service";
|
||||
|
||||
export async function onDeleteCard(input: { id: number }) {
|
||||
assertAdminAccess();
|
||||
return deleteCard(input);
|
||||
}
|
||||
15
pages/admin/cards/queryCards.telefunc.ts
Normal file
15
pages/admin/cards/queryCards.telefunc.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { assertAdminAccess } from "../../../modules/auth/service";
|
||||
import { getAdminCardsPaged } from "../../../modules/inventory/service";
|
||||
|
||||
export async function onQueryCards(params: {
|
||||
productId?: number;
|
||||
batchNo?: string;
|
||||
status?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}) {
|
||||
assertAdminAccess();
|
||||
return getAdminCardsPaged(params);
|
||||
}
|
||||
Reference in New Issue
Block a user