256 lines
6.9 KiB
Vue
256 lines
6.9 KiB
Vue
<template>
|
|
<div v-can="permissionCodes.product.read" class="product-detail">
|
|
<t-loading v-if="isLoading" class="loading" size="32px" />
|
|
<template v-if="product">
|
|
<product-profile :product="product" v-entrance-transition />
|
|
<t-cell-group title="基本信息" theme="card" v-entrance-transition="70">
|
|
<t-cell title="产品分类" :note="product.category?.name || '未分类'" />
|
|
<t-cell title="库存管理" :note="product.stockEnabled ? '启用' : '关闭'" />
|
|
<t-cell title="状态" :note="getProductStatusText(product.status)" />
|
|
<t-cell title="备注" :note="Text.display(product.remark)"></t-cell>
|
|
<t-cell>
|
|
<photo-scroller
|
|
:model-value="attachmentUrlList"
|
|
:photo-items="attachmentItemList"
|
|
title="附件列表"
|
|
:show-add="false"
|
|
:show-remove="false"
|
|
:show-viewer-actions="false"
|
|
/>
|
|
</t-cell>
|
|
</t-cell-group>
|
|
<t-cell-group theme="card" v-entrance-transition="140">
|
|
<t-cell
|
|
v-if="product.stockEnabled && userStore.hasPermissions(permissionCodes.productStock.read)"
|
|
title="库存"
|
|
note="查看库存详情"
|
|
arrow
|
|
@click="viewStock"
|
|
/>
|
|
</t-cell-group>
|
|
<t-cell-group title="业务单位" theme="card" class="units" v-entrance-transition="210">
|
|
<t-cell
|
|
v-for="unit in product.unitList || []"
|
|
:key="unit.id || unit.name"
|
|
>
|
|
<template #title>
|
|
<div class="title">
|
|
<h3 class="name" v-text="unit.name || '未命名单位'" />
|
|
<t-tag
|
|
v-if="!unit.isBase"
|
|
theme="default"
|
|
size="small"
|
|
:content="`${unit.baseQuantityPerUnit} ${getProductBaseUnitName(product)}`"
|
|
/>
|
|
</div>
|
|
</template>
|
|
<template #note>
|
|
<span class="price">
|
|
<span class="qute red text-bold">¥</span>
|
|
<span
|
|
class="value text-bold red"
|
|
v-text="formatProductAmount(unit.salePrice)"
|
|
/>
|
|
</span>
|
|
</template>
|
|
<template #description>
|
|
<div class="tags">
|
|
<t-tag
|
|
v-if="unit.isBase"
|
|
theme="warning"
|
|
variant="light-outline"
|
|
content="基础单位"
|
|
/>
|
|
<t-tag
|
|
v-if="unit.allowSale"
|
|
theme="primary"
|
|
variant="light-outline"
|
|
content="可销售"
|
|
/>
|
|
<t-tag
|
|
v-if="unit.allowPurchase"
|
|
theme="primary"
|
|
variant="light-outline"
|
|
content="可采购"
|
|
/>
|
|
</div>
|
|
</template>
|
|
</t-cell>
|
|
</t-cell-group>
|
|
</template>
|
|
</div>
|
|
<nav-bar-action-menu v-if="actionList.length" :actions="actionList" />
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { DialogPlugin } from "tdesign-mobile-vue";
|
|
import { useUserStore } from "timi-web-api";
|
|
import ProductAPI from "@/api/ProductAPI";
|
|
import NavBarActionMenu from "@/components/NavBarActionMenu.vue";
|
|
import PhotoScroller from "@/components/PhotoScroller.vue";
|
|
import ProductProfile from "@/pages/product/component/ProductProfile.vue";
|
|
import { useNavBarStore } from "@/store/navBarStore";
|
|
import type { NavBarAction } from "@/types/NavBarAction";
|
|
import type { PhotoItem } from "@/types/Photo";
|
|
import type { Product } from "@/types/Product";
|
|
import { getAttachmentReadURL } from "@/utils/attachment";
|
|
import { getErrorMessage } from "@/utils/error";
|
|
import {
|
|
formatProductAmount,
|
|
getProductBaseUnitName,
|
|
getProductStatusText
|
|
} from "@/utils/product";
|
|
import { Message } from "@/utils/message";
|
|
import { permissionCodes } from "@/utils/permission";
|
|
import { Text } from "timi-web";
|
|
|
|
defineOptions({ name: "ProductDetail" });
|
|
|
|
const route = useRoute();
|
|
const router = useRouter();
|
|
const navBarStore = useNavBarStore();
|
|
const userStore = useUserStore();
|
|
const product = ref<Product>();
|
|
const isLoading = ref(false);
|
|
const attachmentItemList = computed<PhotoItem[]>(() => (product.value?.attachmentList || [])
|
|
.filter(item => !!item.id)
|
|
.map(item => ({
|
|
attachment: item,
|
|
attachType: item.attachType || "PHOTO",
|
|
previewUrl: getAttachmentReadURL(item.id || ""),
|
|
url: getAttachmentReadURL(item.id || "", 90)
|
|
})));
|
|
const attachmentUrlList = computed(() => attachmentItemList.value.map(item => item.url));
|
|
const actionList = computed<NavBarAction[]>(() => {
|
|
const list: NavBarAction[] = [];
|
|
if (userStore.hasPermissions(permissionCodes.product.update)) {
|
|
list.push({ key: "edit", text: "编辑产品", icon: "edit-1", theme: "primary", action: edit });
|
|
if (product.value?.status === "ACTIVE") {
|
|
list.push({ key: "disable", text: "停用产品", icon: "close", theme: "danger", action: disable });
|
|
}
|
|
}
|
|
return list;
|
|
});
|
|
|
|
async function load(): Promise<void> {
|
|
const id = String(route.params.id || "");
|
|
if (!id) {
|
|
return;
|
|
}
|
|
isLoading.value = true;
|
|
try {
|
|
product.value = await ProductAPI.detail(id);
|
|
} finally {
|
|
isLoading.value = false;
|
|
}
|
|
}
|
|
|
|
async function edit(): Promise<void> {
|
|
if (product.value?.id) {
|
|
await router.push(`/management/products/${product.value.id}/edit`);
|
|
}
|
|
}
|
|
|
|
async function viewStock(): Promise<void> {
|
|
if (product.value?.id) {
|
|
await router.push({
|
|
name: "ProductStockDetail",
|
|
params: { productId: product.value.id },
|
|
query: { transitionDepth: String(getCurrentRouteDepth() + 1) }
|
|
});
|
|
}
|
|
}
|
|
|
|
function getCurrentRouteDepth(): number {
|
|
const transitionDepth = Array.isArray(route.query.transitionDepth)
|
|
? route.query.transitionDepth[0]
|
|
: route.query.transitionDepth;
|
|
const depth = Number(transitionDepth);
|
|
if (transitionDepth && Number.isInteger(depth) && 0 <= depth && depth <= 20) {
|
|
return depth;
|
|
}
|
|
return Number(route.meta.depth ?? 0);
|
|
}
|
|
|
|
function disable(): void {
|
|
const id = product.value?.id;
|
|
if (!id) {
|
|
return;
|
|
}
|
|
const dialog = DialogPlugin.confirm({
|
|
title: "停用产品",
|
|
content: "停用后不能用于新订单、套餐或库存操作,历史记录仍会保留。确定停用?",
|
|
cancelBtn: "取消",
|
|
confirmBtn: { content: "停用", theme: "danger" },
|
|
onConfirm: async () => {
|
|
try {
|
|
await ProductAPI.disable(id);
|
|
await load();
|
|
Message.success("产品已停用");
|
|
} catch (error) {
|
|
Message.error(getErrorMessage(error, "产品停用失败"));
|
|
} finally {
|
|
dialog.destroy();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
onMounted(async () => {
|
|
navBarStore.clearRight();
|
|
navBarStore.setCustomRightVisible(actionList.value.length > 0);
|
|
try {
|
|
await load();
|
|
navBarStore.setCustomRightVisible(actionList.value.length > 0);
|
|
} catch (error) {
|
|
Message.error(getErrorMessage(error, "产品详情加载失败"));
|
|
}
|
|
});
|
|
|
|
onUnmounted(() => navBarStore.clearRight());
|
|
</script>
|
|
|
|
<style scoped lang="less">
|
|
.product-detail {
|
|
gap: 1rem;
|
|
display: flex;
|
|
padding: calc(var(--app-nav-height) + 1rem) 0 1rem 0;
|
|
flex-direction: column;
|
|
|
|
.units {
|
|
|
|
.title {
|
|
gap: .5rem;
|
|
display: flex;
|
|
align-items: center;
|
|
|
|
.name {
|
|
margin: 0;
|
|
}
|
|
}
|
|
|
|
.price {
|
|
|
|
.value {
|
|
font-size: 1.5rem;
|
|
}
|
|
}
|
|
|
|
.tags {
|
|
gap: .5rem;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
}
|
|
}
|
|
}
|
|
|
|
:global(html.theme-dark) .product-detail .hero {
|
|
background: linear-gradient(135deg, rgba(15, 118, 110, .2), rgba(37, 99, 235, .18));
|
|
|
|
.eyebrow {
|
|
color: #99f6e4;
|
|
}
|
|
|
|
}
|
|
</style>
|