218 lines
6.4 KiB
Vue
218 lines
6.4 KiB
Vue
<template>
|
||
<div v-can="permissionCodes.packageConfig.read" class="package-config-detail">
|
||
<template v-if="config">
|
||
<section class="hero" v-entrance-transition>
|
||
<span class="eyebrow" v-text="'套餐管理'" />
|
||
<div class="title"><h1 v-text="config.name || '未命名套餐'" /><t-tag :content="getPackageConfigStatusText(config.status)" variant="light-outline" :theme="getStatusTheme(config.status)" /></div>
|
||
<p v-text="config.description || '暂无套餐说明'" />
|
||
<div class="hero-meta"><span v-text="`${config.itemList?.length || 0} 项产品权益`" /><strong v-text="formatPackageAmount(config.standardAmount)" /></div>
|
||
</section>
|
||
<t-cell-group title="基本信息" theme="card" v-entrance-transition="70">
|
||
<t-cell title="标准价" :note="formatPackageAmount(config.standardAmount)" />
|
||
<t-cell title="起始日期" :note="config.beginAt ? Time.toDateTime(config.beginAt) : '不限'" />
|
||
<t-cell title="终止日期" :note="config.endAt ? Time.toDateTime(config.endAt) : '不限'" />
|
||
<t-cell title="最后修改人" :note="config.updater?.name || config.updater?.nick || '未知'" />
|
||
</t-cell-group>
|
||
<t-cell-group title="产品权益" theme="card" v-entrance-transition="140">
|
||
<t-cell
|
||
v-for="item in config.itemList || []"
|
||
:key="item.id || `${item.productId}-${item.productUnitId}`"
|
||
:title="item.productNameSnapshot || '未命名产品'"
|
||
:note="`${item.defaultQuantity} ${item.unitSnapshot || ''}${item.remark ? `|${item.remark}` : ''}`"
|
||
/>
|
||
<div v-if="!config.itemList?.length" class="empty" v-text="'暂无产品权益'" />
|
||
</t-cell-group>
|
||
<t-button
|
||
v-if="canPurchase"
|
||
class="cell-button"
|
||
theme="primary"
|
||
content="购买套餐"
|
||
@click="toPurchase"
|
||
/>
|
||
<t-button
|
||
v-if="canDelete"
|
||
class="cell-button"
|
||
theme="danger"
|
||
@click="removeConfig"
|
||
content="删除"
|
||
/>
|
||
</template>
|
||
<t-empty v-else :description="isLoading ? '正在加载套餐配置' : '套餐配置不存在'" />
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { DialogPlugin } from "tdesign-mobile-vue";
|
||
import { Time } from "timi-web";
|
||
import { useUserStore } from "timi-web-api";
|
||
import PackageAPI from "@/api/PackageAPI";
|
||
import { useCurrentStoreStore } from "@/store/currentStoreStore";
|
||
import { useNavBarStore } from "@/store/navBarStore";
|
||
import type { GaoPackageConfig, PackageConfigStatus } from "@/types/Package";
|
||
import { getErrorMessage } from "@/utils/error";
|
||
import { formatPackageAmount, getPackageConfigStatusText } from "@/utils/package";
|
||
import { Message } from "@/utils/message";
|
||
import { gaoRoleCodes, permissionCodes } from "@/utils/permission";
|
||
|
||
defineOptions({ name: "PackageConfigDetail" });
|
||
|
||
const route = useRoute();
|
||
const router = useRouter();
|
||
const navBarStore = useNavBarStore();
|
||
const userStore = useUserStore();
|
||
const currentStoreStore = useCurrentStoreStore();
|
||
const config = ref<GaoPackageConfig>();
|
||
const isLoading = ref(false);
|
||
const canUpdate = computed(() => !!config.value && userStore.hasPermissions(permissionCodes.packageConfig.update));
|
||
const canDelete = computed(() => !!config.value && userStore.hasPermissions(permissionCodes.packageConfig.delete));
|
||
const canPurchase = computed(() =>
|
||
!!config.value
|
||
&& config.value.status === "ACTIVE"
|
||
&& userStore.hasPermissions(permissionCodes.customerPackage.create)
|
||
);
|
||
|
||
function getStatusTheme(status?: PackageConfigStatus): "success" | "warning" | "default" {
|
||
return status === "ACTIVE" ? "success" : status === "INACTIVE" ? "warning" : "default";
|
||
}
|
||
|
||
async function loadData(): Promise<void> {
|
||
const id = String(route.params.id || "");
|
||
if (!id) {
|
||
return;
|
||
}
|
||
isLoading.value = true;
|
||
try {
|
||
if (userStore.hasRoles(gaoRoleCodes.globalManager)) {
|
||
await currentStoreStore.loadStoreList();
|
||
} else {
|
||
await currentStoreStore.loadLoginStore();
|
||
}
|
||
config.value = await PackageAPI.configDetail(id);
|
||
} catch (error) {
|
||
Message.error(getErrorMessage(error, "套餐配置加载失败"));
|
||
} finally {
|
||
isLoading.value = false;
|
||
}
|
||
}
|
||
|
||
async function toEdit(): Promise<void> {
|
||
if (config.value?.id && canUpdate.value) {
|
||
await router.push(`/management/package-configs/${config.value.id}/edit`);
|
||
}
|
||
}
|
||
|
||
async function toPurchase(): Promise<void> {
|
||
if (config.value?.id && canPurchase.value) {
|
||
await router.push(`/management/package-configs/${config.value.id}/purchase`);
|
||
}
|
||
}
|
||
|
||
function removeConfig(): void {
|
||
if (!config.value?.id || !canDelete.value) {
|
||
return;
|
||
}
|
||
const dialog = DialogPlugin.confirm({
|
||
title: "删除套餐配置",
|
||
content: "删除后该配置不再出现在可购买列表中,已购买的客户套餐不受影响。",
|
||
confirmBtn: { content: "删除", theme: "danger" },
|
||
onConfirm: async () => {
|
||
try {
|
||
await PackageAPI.configRemove(config.value!.id!);
|
||
Message.success("套餐配置已删除");
|
||
await router.replace("/management/package-configs");
|
||
} catch (error) {
|
||
Message.error(getErrorMessage(error, "套餐配置删除失败"));
|
||
} finally {
|
||
dialog.destroy();
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
onMounted(async () => {
|
||
await loadData();
|
||
navBarStore.clearRight();
|
||
if (canUpdate.value) {
|
||
navBarStore.setRightText("编辑", "var(--td-brand-color)", toEdit);
|
||
}
|
||
});
|
||
|
||
watch(canUpdate, value => {
|
||
navBarStore.clearRight();
|
||
if (value) {
|
||
navBarStore.setRightText("编辑", "var(--td-brand-color)", toEdit);
|
||
}
|
||
});
|
||
|
||
onUnmounted(() => navBarStore.clearRight());
|
||
</script>
|
||
|
||
<style scoped lang="less">
|
||
.package-config-detail {
|
||
gap: 1rem;
|
||
padding: calc(var(--app-nav-height) + 1rem) 0 2rem;
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
.hero {
|
||
margin: 0 1rem;
|
||
padding: 1rem;
|
||
background: var(--td-bg-color-container);
|
||
box-shadow: var(--td-shadow-1);
|
||
border-radius: 1rem;
|
||
|
||
.title {
|
||
gap: .5rem;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.eyebrow {
|
||
color: var(--td-brand-color);
|
||
display: block;
|
||
font-size: .6875rem;
|
||
font-weight: 700;
|
||
letter-spacing: .08em;
|
||
}
|
||
|
||
h1 {
|
||
margin: 0;
|
||
font-size: 1.25rem;
|
||
line-height: 1.35;
|
||
}
|
||
|
||
p {
|
||
color: var(--td-text-color-secondary);
|
||
margin: .5rem 0 0;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.hero-meta {
|
||
gap: .75rem;
|
||
display: flex;
|
||
margin-top: .75rem;
|
||
color: var(--td-text-color-secondary);
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
strong {
|
||
color: #c2410c;
|
||
font-size: 1.2rem;
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
}
|
||
|
||
:global(html.theme-dark) .package-config-detail .hero {
|
||
.eyebrow {
|
||
color: #99f6e4;
|
||
}
|
||
|
||
strong {
|
||
color: #fdba74;
|
||
}
|
||
}
|
||
</style>
|