|
@@ -2,156 +2,188 @@
|
|
|
<el-dialog
|
|
<el-dialog
|
|
|
v-model="visible"
|
|
v-model="visible"
|
|
|
class="ba-operate-dialog"
|
|
class="ba-operate-dialog"
|
|
|
- width="950px"
|
|
|
|
|
|
|
+ width="1200px"
|
|
|
:close-on-click-modal="false"
|
|
:close-on-click-modal="false"
|
|
|
:destroy-on-close="true"
|
|
:destroy-on-close="true"
|
|
|
@close="handleClose"
|
|
@close="handleClose"
|
|
|
>
|
|
>
|
|
|
<template #header>
|
|
<template #header>
|
|
|
<div class="title" v-drag="['.ba-operate-dialog', '.el-dialog__header']" v-zoom="'.ba-operate-dialog'">
|
|
<div class="title" v-drag="['.ba-operate-dialog', '.el-dialog__header']" v-zoom="'.ba-operate-dialog'">
|
|
|
- 套餐详情 --- {{ detailData?.name || '-' }}
|
|
|
|
|
|
|
+ 套餐详情 --- {{ packageInfo?.name || '-' }}
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
- <el-scrollbar class="ba-table-form-scrollbar">
|
|
|
|
|
- <div class="detail-wrapper">
|
|
|
|
|
- <el-descriptions
|
|
|
|
|
- v-if="detailData"
|
|
|
|
|
- :column="3"
|
|
|
|
|
- border
|
|
|
|
|
- class="detail-base-info"
|
|
|
|
|
- size="small"
|
|
|
|
|
- >
|
|
|
|
|
- <el-descriptions-item label="套餐名称">
|
|
|
|
|
- {{ detailData.name }}
|
|
|
|
|
- </el-descriptions-item>
|
|
|
|
|
- <el-descriptions-item label="当前库存状态">
|
|
|
|
|
- <el-tag :type="inventoryStatusTag" effect="plain">
|
|
|
|
|
- {{ inventoryStatusText }}
|
|
|
|
|
- </el-tag>
|
|
|
|
|
- </el-descriptions-item>
|
|
|
|
|
- <el-descriptions-item label="总剩余库存">
|
|
|
|
|
- {{ totalStock }}
|
|
|
|
|
- </el-descriptions-item>
|
|
|
|
|
- <el-descriptions-item label="已卖数量">
|
|
|
|
|
- {{ detailData.sold_quantity }}
|
|
|
|
|
- </el-descriptions-item>
|
|
|
|
|
- <el-descriptions-item label="创建时间">
|
|
|
|
|
- {{ detailData.create_time }}
|
|
|
|
|
- </el-descriptions-item>
|
|
|
|
|
- </el-descriptions>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 规格明细表格 -->
|
|
|
|
|
- <div class="spec-table-title">
|
|
|
|
|
- 套餐规格明细
|
|
|
|
|
- </div>
|
|
|
|
|
- <el-table
|
|
|
|
|
- v-if="specList.length"
|
|
|
|
|
- :data="specList"
|
|
|
|
|
- border
|
|
|
|
|
- size="small"
|
|
|
|
|
- style="width: 100%"
|
|
|
|
|
- >
|
|
|
|
|
- <el-table-column
|
|
|
|
|
- prop="specName"
|
|
|
|
|
- label="规格名称"
|
|
|
|
|
- align="center"
|
|
|
|
|
- min-width="120"
|
|
|
|
|
- />
|
|
|
|
|
- <el-table-column
|
|
|
|
|
- label="库存状态"
|
|
|
|
|
- align="center"
|
|
|
|
|
- min-width="120"
|
|
|
|
|
- >
|
|
|
|
|
- <template #default="{ row }">
|
|
|
|
|
- <el-tag :type="getRowStatusTag(row.stock)" effect="plain">
|
|
|
|
|
- {{ getRowStatusText(row.stock) }}
|
|
|
|
|
- </el-tag>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column
|
|
|
|
|
- label="剩余库存"
|
|
|
|
|
- align="center"
|
|
|
|
|
- min-width="140"
|
|
|
|
|
- >
|
|
|
|
|
- <template #default="{ row, $index }">
|
|
|
|
|
- <el-input-number
|
|
|
|
|
- v-model.number="specList[$index].stock"
|
|
|
|
|
- :min="0"
|
|
|
|
|
- :max="999999"
|
|
|
|
|
- controls-position="right"
|
|
|
|
|
- size="small"
|
|
|
|
|
- />
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column
|
|
|
|
|
- prop="price"
|
|
|
|
|
- label="价格(元)"
|
|
|
|
|
- align="center"
|
|
|
|
|
- min-width="120"
|
|
|
|
|
- />
|
|
|
|
|
- <el-table-column
|
|
|
|
|
- label="套餐类型"
|
|
|
|
|
- align="center"
|
|
|
|
|
- min-width="120"
|
|
|
|
|
- >
|
|
|
|
|
- <template #default="{ row }">
|
|
|
|
|
- {{ packageTypeText(row.packageType) }}
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column
|
|
|
|
|
- label="收费方式"
|
|
|
|
|
- align="center"
|
|
|
|
|
- min-width="140"
|
|
|
|
|
- >
|
|
|
|
|
- <template #default="{ row }">
|
|
|
|
|
- {{ chargeTypeText(row.chargeType) }}
|
|
|
|
|
- <span v-if="showQuantity(row.chargeType) && row.quantity">
|
|
|
|
|
- ({{ row.quantity }}{{ getUnitLabel(row.chargeType) }})
|
|
|
|
|
- </span>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column
|
|
|
|
|
- prop="remark"
|
|
|
|
|
- label="说明"
|
|
|
|
|
- align="center"
|
|
|
|
|
- min-width="200"
|
|
|
|
|
- show-overflow-tooltip
|
|
|
|
|
- />
|
|
|
|
|
- </el-table>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 汇总信息 -->
|
|
|
|
|
- <div class="summary-row">
|
|
|
|
|
- <div class="summary-item">
|
|
|
|
|
- <span class="label">规格数量:</span>
|
|
|
|
|
- <span>{{ specList.length }}</span>
|
|
|
|
|
|
|
+ <el-scrollbar class="ba-table-form-scrollbar" v-loading="loading">
|
|
|
|
|
+ <div class="detail-wrapper" v-if="packageInfo">
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 统计信息卡片 -->
|
|
|
|
|
+ <div class="statistics-card">
|
|
|
|
|
+ <div class="stat-item">
|
|
|
|
|
+ <div class="stat-icon spec-icon">
|
|
|
|
|
+ <el-icon><List /></el-icon>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="stat-content">
|
|
|
|
|
+ <div class="stat-label">规格总数</div>
|
|
|
|
|
+ <div class="stat-value">{{ statistics.spec_count }}</div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="summary-item">
|
|
|
|
|
- <span class="label">总剩余库存:</span>
|
|
|
|
|
- <span class="value-number">{{ totalStock }}</span>
|
|
|
|
|
|
|
+ <div class="stat-item">
|
|
|
|
|
+ <div class="stat-icon stock-icon">
|
|
|
|
|
+ <el-icon><Box /></el-icon>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="stat-content">
|
|
|
|
|
+ <div class="stat-label">总库存</div>
|
|
|
|
|
+ <div class="stat-value">{{ statistics.total_stock }}</div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="summary-item">
|
|
|
|
|
- <span class="label">整体库存状态:</span>
|
|
|
|
|
- <el-tag :type="inventoryStatusTag" effect="plain">
|
|
|
|
|
- {{ inventoryStatusText }}
|
|
|
|
|
- </el-tag>
|
|
|
|
|
|
|
+ <div class="stat-item">
|
|
|
|
|
+ <div class="stat-icon sold-icon">
|
|
|
|
|
+ <el-icon><TrendCharts /></el-icon>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="stat-content">
|
|
|
|
|
+ <div class="stat-label">已售数量</div>
|
|
|
|
|
+ <div class="stat-value">{{ statistics.total_sold }}</div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 规格明细表格 -->
|
|
|
|
|
+ <div class="table-card">
|
|
|
|
|
+ <div class="table-header">
|
|
|
|
|
+ <h3 class="table-title">
|
|
|
|
|
+ <el-icon class="title-icon"><Grid /></el-icon>
|
|
|
|
|
+ 套餐规格明细
|
|
|
|
|
+ <span class="spec-count">(共 {{ specList.length }} 项)</span>
|
|
|
|
|
+ </h3>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-table
|
|
|
|
|
+ v-if="specList.length"
|
|
|
|
|
+ :data="specList"
|
|
|
|
|
+ class="spec-table"
|
|
|
|
|
+ size="default"
|
|
|
|
|
+ stripe
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ type="index"
|
|
|
|
|
+ label="序号"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ width="70"
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ prop="spec_name"
|
|
|
|
|
+ label="规格名称"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ min-width="140"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <span class="spec-name">{{ row.spec_name }}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ prop="price"
|
|
|
|
|
+ label="价格"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ min-width="110"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <span class="price-text">¥{{ row.price }}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ prop="stock"
|
|
|
|
|
+ label="库存"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ min-width="100"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <span class="stock-number">{{ row.stock }}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ label="库存状态"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ min-width="120"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-tag :type="getStockStatusTag(row.stock_status)" effect="dark" size="small">
|
|
|
|
|
+ {{ row.stock_status }}
|
|
|
|
|
+ </el-tag>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ prop="sold_count"
|
|
|
|
|
+ label="已售数量"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ min-width="110"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <span class="sold-number">{{ row.sold_count }}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ prop="type_text"
|
|
|
|
|
+ label="套餐类型"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ min-width="120"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-tag type="primary" effect="plain" size="small">{{ row.type_text }}</el-tag>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ prop="card_type_text"
|
|
|
|
|
+ label="卡类型"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ min-width="110"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-tag type="warning" effect="plain" size="small">{{ row.card_type_text }}</el-tag>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ label="次数/积分"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ min-width="120"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <span v-if="String(row.card_type) === '1'">
|
|
|
|
|
+ <span class="unit-value">{{ row.unit_value || '-' }}</span>
|
|
|
|
|
+ <span class="unit-label">次</span>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <span v-else-if="String(row.card_type) === '2'">
|
|
|
|
|
+ <span class="points-value">{{ row.points_value || '-' }}</span>
|
|
|
|
|
+ <span class="unit-label">分</span>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <span v-else class="no-value">-</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ prop="description"
|
|
|
|
|
+ label="说明描述"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ min-width="150"
|
|
|
|
|
+ show-overflow-tooltip
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <span class="description-cell">{{ row.description || '-' }}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ prop="create_time"
|
|
|
|
|
+ label="创建时间"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ min-width="180"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ <el-empty v-else description="暂无规格数据" :image-size="100" />
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</el-scrollbar>
|
|
</el-scrollbar>
|
|
|
|
|
|
|
|
<template #footer>
|
|
<template #footer>
|
|
|
<div class="dialog-footer">
|
|
<div class="dialog-footer">
|
|
|
<el-button size="large" class="cancel-btn" @click="handleClose">
|
|
<el-button size="large" class="cancel-btn" @click="handleClose">
|
|
|
- 取消
|
|
|
|
|
- </el-button>
|
|
|
|
|
- <el-button
|
|
|
|
|
- size="large"
|
|
|
|
|
- type="primary"
|
|
|
|
|
- class="submit-btn"
|
|
|
|
|
- @click="handleConfirm"
|
|
|
|
|
- >
|
|
|
|
|
- 确认修改库存
|
|
|
|
|
|
|
+ 关闭
|
|
|
</el-button>
|
|
</el-button>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
@@ -159,162 +191,300 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
-import { computed, reactive, ref } from 'vue'
|
|
|
|
|
-
|
|
|
|
|
-interface SpecificationItem {
|
|
|
|
|
- specName: string
|
|
|
|
|
- price: number | undefined
|
|
|
|
|
- stock: number | undefined
|
|
|
|
|
- packageType: string
|
|
|
|
|
- chargeType: string
|
|
|
|
|
- quantity?: number | undefined
|
|
|
|
|
- remark?: string
|
|
|
|
|
-}
|
|
|
|
|
|
|
+import { ref } from 'vue'
|
|
|
|
|
+import { detail } from '/@/api/backend/package/package'
|
|
|
|
|
+import { ElMessage } from 'element-plus'
|
|
|
|
|
+import { Document, List, Box, TrendCharts, Grid } from '@element-plus/icons-vue'
|
|
|
|
|
|
|
|
-interface DetailRow {
|
|
|
|
|
|
|
+// 套餐基本信息接口
|
|
|
|
|
+interface PackageInfo {
|
|
|
id: number
|
|
id: number
|
|
|
name: string
|
|
name: string
|
|
|
- inventory_status: string
|
|
|
|
|
- remaining_stock: number
|
|
|
|
|
- sold_quantity: number
|
|
|
|
|
|
|
+ description: string
|
|
|
|
|
+ sort: number
|
|
|
|
|
+ status: number
|
|
|
create_time: string
|
|
create_time: string
|
|
|
- specificationsList?: SpecificationItem[]
|
|
|
|
|
|
|
+ update_time: string
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const visible = ref(false)
|
|
|
|
|
-const detailData = ref<DetailRow | null>(null)
|
|
|
|
|
-const specList = reactive<SpecificationItem[]>([])
|
|
|
|
|
|
|
+// 规格列表接口
|
|
|
|
|
+interface SpecItem {
|
|
|
|
|
+ id: number
|
|
|
|
|
+ package_id: number
|
|
|
|
|
+ spec_name: string
|
|
|
|
|
+ price: string
|
|
|
|
|
+ stock: number
|
|
|
|
|
+ type: number
|
|
|
|
|
+ card_type: number
|
|
|
|
|
+ unit_value: number
|
|
|
|
|
+ points_value: number
|
|
|
|
|
+ description: string
|
|
|
|
|
+ sold_count: number
|
|
|
|
|
+ sort: number
|
|
|
|
|
+ create_time: string
|
|
|
|
|
+ update_time: string
|
|
|
|
|
+ stock_status: string
|
|
|
|
|
+ type_text: string
|
|
|
|
|
+ card_type_text: string
|
|
|
|
|
+ charge_text: string
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
-const emits = defineEmits<{
|
|
|
|
|
- (e: 'confirm', payload: { id: number; specificationsList: SpecificationItem[]; totalStock: number }): void
|
|
|
|
|
-}>()
|
|
|
|
|
|
|
+// 统计信息接口
|
|
|
|
|
+interface Statistics {
|
|
|
|
|
+ total_stock: number
|
|
|
|
|
+ total_sold: number
|
|
|
|
|
+ spec_count: number
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
-const open = (row: DetailRow) => {
|
|
|
|
|
- detailData.value = { ...row }
|
|
|
|
|
|
|
+// 详情数据接口
|
|
|
|
|
+interface DetailResponse {
|
|
|
|
|
+ package_info: PackageInfo
|
|
|
|
|
+ spec_list: SpecItem[]
|
|
|
|
|
+ statistics: Statistics
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- // 重置规格明细
|
|
|
|
|
- specList.splice(0, specList.length)
|
|
|
|
|
- const list = (row.specificationsList || []) as SpecificationItem[]
|
|
|
|
|
- list.forEach((item) => {
|
|
|
|
|
- specList.push({ ...item })
|
|
|
|
|
- })
|
|
|
|
|
|
|
+const visible = ref(false)
|
|
|
|
|
+const loading = ref(false)
|
|
|
|
|
+const packageInfo = ref<PackageInfo | null>(null)
|
|
|
|
|
+const specList = ref<SpecItem[]>([])
|
|
|
|
|
+const statistics = ref<Statistics>({
|
|
|
|
|
+ total_stock: 0,
|
|
|
|
|
+ total_sold: 0,
|
|
|
|
|
+ spec_count: 0,
|
|
|
|
|
+})
|
|
|
|
|
|
|
|
|
|
+const open = async (id: number) => {
|
|
|
visible.value = true
|
|
visible.value = true
|
|
|
|
|
+ await fetchDetail(id)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 获取详情数据
|
|
|
|
|
+const fetchDetail = async (id: number) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ loading.value = true
|
|
|
|
|
+ const res = await detail({ id })
|
|
|
|
|
+ const data: DetailResponse = res.data
|
|
|
|
|
+
|
|
|
|
|
+ if (data) {
|
|
|
|
|
+ packageInfo.value = data.package_info || null
|
|
|
|
|
+ specList.value = data.spec_list || []
|
|
|
|
|
+ statistics.value = data.statistics || {
|
|
|
|
|
+ total_stock: 0,
|
|
|
|
|
+ total_sold: 0,
|
|
|
|
|
+ spec_count: 0,
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error: any) {
|
|
|
|
|
+ ElMessage.error(error?.msg || '获取详情失败')
|
|
|
|
|
+ handleClose()
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const handleClose = () => {
|
|
const handleClose = () => {
|
|
|
visible.value = false
|
|
visible.value = false
|
|
|
|
|
+ // 重置数据
|
|
|
|
|
+ packageInfo.value = null
|
|
|
|
|
+ specList.value = []
|
|
|
|
|
+ statistics.value = {
|
|
|
|
|
+ total_stock: 0,
|
|
|
|
|
+ total_sold: 0,
|
|
|
|
|
+ spec_count: 0,
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const totalStock = computed(() =>
|
|
|
|
|
- specList.reduce((sum, item) => sum + (Number(item.stock) || 0), 0)
|
|
|
|
|
-)
|
|
|
|
|
-
|
|
|
|
|
-const inventoryStatusText = computed(() => {
|
|
|
|
|
- if (totalStock.value === 0) return '售罄'
|
|
|
|
|
- if (totalStock.value < 10) return '即将售罄'
|
|
|
|
|
- return '充足'
|
|
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
-const inventoryStatusTag = computed(() => {
|
|
|
|
|
- if (totalStock.value === 0) return 'danger'
|
|
|
|
|
- if (totalStock.value < 10) return 'warning'
|
|
|
|
|
|
|
+// 获取库存状态标签类型
|
|
|
|
|
+const getStockStatusTag = (status: string) => {
|
|
|
|
|
+ if (status === '售罄') return 'danger'
|
|
|
|
|
+ if (status === '即将售罄') return 'warning'
|
|
|
return 'success'
|
|
return 'success'
|
|
|
-})
|
|
|
|
|
-
|
|
|
|
|
-const getRowStatusText = (stock?: number) => {
|
|
|
|
|
- const value = Number(stock) || 0
|
|
|
|
|
- if (value === 0) return '售罄'
|
|
|
|
|
- if (value < 10) return '即将售罄'
|
|
|
|
|
- return '充足'
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const getRowStatusTag = (stock?: number) => {
|
|
|
|
|
- const value = Number(stock) || 0
|
|
|
|
|
- if (value === 0) return 'danger'
|
|
|
|
|
- if (value < 10) return 'warning'
|
|
|
|
|
- return 'success'
|
|
|
|
|
-}
|
|
|
|
|
|
|
+defineExpose({ open })
|
|
|
|
|
+</script>
|
|
|
|
|
|
|
|
-const packageTypeText = (type: string) => {
|
|
|
|
|
- if (type === '2') return '团队套餐'
|
|
|
|
|
- return '个人套餐'
|
|
|
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
|
+.detail-wrapper {
|
|
|
|
|
+ padding: 20px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const chargeTypeText = (type: string) => {
|
|
|
|
|
- const map: Record<string, string> = {
|
|
|
|
|
- '1': '按次数',
|
|
|
|
|
- '2': '按积分',
|
|
|
|
|
- '3': '日卡',
|
|
|
|
|
- '4': '周卡',
|
|
|
|
|
- '5': '月卡',
|
|
|
|
|
- '6': '季卡',
|
|
|
|
|
- '7': '年卡',
|
|
|
|
|
|
|
+// 统计信息卡片
|
|
|
|
|
+.statistics-card {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: repeat(3, 1fr);
|
|
|
|
|
+ gap: 16px;
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+
|
|
|
|
|
+ .stat-item {
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 16px;
|
|
|
|
|
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
|
|
|
|
+ transition: all 0.3s ease;
|
|
|
|
|
+ border: 1px solid #f0f0f0;
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ transform: translateY(-2px);
|
|
|
|
|
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .stat-icon {
|
|
|
|
|
+ width: 56px;
|
|
|
|
|
+ height: 56px;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ font-size: 24px;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+
|
|
|
|
|
+ &.spec-icon {
|
|
|
|
|
+ background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &.stock-icon {
|
|
|
|
|
+ background: linear-gradient(135deg, #36a3f7 0%, #409eff 100%);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &.sold-icon {
|
|
|
|
|
+ background: linear-gradient(135deg, #79bbff 0%, #409eff 100%);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .stat-content {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+
|
|
|
|
|
+ .stat-label {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .stat-value {
|
|
|
|
|
+ font-size: 28px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #303133;
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- return map[type] || '-'
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const showQuantity = (chargeType: string) => {
|
|
|
|
|
- return chargeType === '1' || chargeType === '2'
|
|
|
|
|
-}
|
|
|
|
|
|
|
+// 表格卡片
|
|
|
|
|
+.table-card {
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
|
|
|
|
+ border: 1px solid #f0f0f0;
|
|
|
|
|
+
|
|
|
|
|
+ .table-header {
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+ padding-bottom: 12px;
|
|
|
|
|
+ border-bottom: 2px solid #f5f7fa;
|
|
|
|
|
+
|
|
|
|
|
+ .table-title {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #303133;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+
|
|
|
|
|
+ .title-icon {
|
|
|
|
|
+ font-size: 20px;
|
|
|
|
|
+ color: #409eff;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .spec-count {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: 400;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ margin-left: 4px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
-const getUnitLabel = (chargeType: string) => {
|
|
|
|
|
- if (chargeType === '1') return '次'
|
|
|
|
|
- if (chargeType === '2') return '分'
|
|
|
|
|
- return ''
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ .spec-table {
|
|
|
|
|
+ :deep(.el-table__header) {
|
|
|
|
|
+ th {
|
|
|
|
|
+ background: #f8f9fa;
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ border-bottom: 2px solid #ebeef5;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
-const handleConfirm = () => {
|
|
|
|
|
- if (!detailData.value) return
|
|
|
|
|
- emits('confirm', {
|
|
|
|
|
- id: detailData.value.id,
|
|
|
|
|
- specificationsList: specList.map((item) => ({ ...item })),
|
|
|
|
|
- totalStock: totalStock.value,
|
|
|
|
|
- })
|
|
|
|
|
- handleClose()
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ :deep(.el-table__body) {
|
|
|
|
|
+ tr {
|
|
|
|
|
+ transition: background-color 0.2s;
|
|
|
|
|
|
|
|
-defineExpose({ open })
|
|
|
|
|
-</script>
|
|
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ background-color: #f5f7fa;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
-<style scoped lang="scss">
|
|
|
|
|
-.detail-wrapper {
|
|
|
|
|
- padding: 12px 16px 4px;
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ .spec-name {
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #303133;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
-.detail-base-info {
|
|
|
|
|
- margin-bottom: 16px;
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ .price-text {
|
|
|
|
|
+ color: #f56c6c;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ font-size: 15px;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
-.spec-table-title {
|
|
|
|
|
- margin-bottom: 8px;
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
- color: #333;
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ .stock-number {
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #409eff;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
-.summary-row {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- flex-wrap: wrap;
|
|
|
|
|
- justify-content: flex-end;
|
|
|
|
|
- gap: 16px;
|
|
|
|
|
- margin-top: 12px;
|
|
|
|
|
- padding-top: 8px;
|
|
|
|
|
- border-top: 1px dashed #e5e5e5;
|
|
|
|
|
|
|
+ .sold-number {
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ color: #67c23a;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- .summary-item {
|
|
|
|
|
- display: inline-flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- font-size: 13px;
|
|
|
|
|
|
|
+ .charge-text {
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- .label {
|
|
|
|
|
- color: #666;
|
|
|
|
|
- margin-right: 4px;
|
|
|
|
|
|
|
+ .unit-value {
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #409eff;
|
|
|
|
|
+ font-size: 15px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- .value-number {
|
|
|
|
|
|
|
+ .points-value {
|
|
|
font-weight: 600;
|
|
font-weight: 600;
|
|
|
- color: #333;
|
|
|
|
|
|
|
+ color: #e6a23c;
|
|
|
|
|
+ font-size: 15px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .unit-label {
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ margin-left: 4px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .no-value {
|
|
|
|
|
+ color: #c0c4cc;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .description-cell {
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ line-height: 1.5;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -325,6 +495,13 @@ defineExpose({ open })
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
gap: 10px;
|
|
gap: 10px;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+// 响应式设计
|
|
|
|
|
+@media (max-width: 1200px) {
|
|
|
|
|
+ .statistics-card {
|
|
|
|
|
+ grid-template-columns: 1fr;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
</style>
|
|
</style>
|
|
|
|
|
|
|
|
|
|
|