2025-07-02 11:05:23 +08:00
|
|
|
|
<template>
|
|
|
|
|
<div class="store-management-container">
|
2025-07-15 19:17:53 +08:00
|
|
|
|
<el-card class="full-height-card">
|
2025-07-02 11:05:23 +08:00
|
|
|
|
<template #header>
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
<span>店铺管理</span>
|
|
|
|
|
<div class="header-actions">
|
|
|
|
|
<el-button type="primary" @click="showCreateDialog">
|
|
|
|
|
<el-icon><Plus /></el-icon>
|
|
|
|
|
新增店铺
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button @click="refreshStores">
|
|
|
|
|
<el-icon><Refresh /></el-icon>
|
|
|
|
|
刷新
|
|
|
|
|
</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<!-- 搜索和过滤 -->
|
2025-07-15 19:17:53 +08:00
|
|
|
|
<div class="table-container" ref="tableContainerRef">
|
|
|
|
|
<div class="filter-section" ref="filterSectionRef">
|
|
|
|
|
<el-row :gutter="20" align="middle">
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="searchQuery"
|
|
|
|
|
placeholder="搜索店铺名称或ID"
|
|
|
|
|
clearable
|
|
|
|
|
@input="handleSearch"
|
|
|
|
|
>
|
|
|
|
|
<template #prefix>
|
|
|
|
|
<el-icon><Search /></el-icon>
|
|
|
|
|
</template>
|
|
|
|
|
</el-input>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="4">
|
|
|
|
|
<el-select v-model="statusFilter" placeholder="状态筛选" clearable @change="handleFilter" style="width: 100%;">
|
|
|
|
|
<el-option label="全部状态" value="" />
|
|
|
|
|
<el-option label="营业中" value="active" />
|
|
|
|
|
<el-option label="暂停营业" value="inactive" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="4">
|
|
|
|
|
<el-select v-model="typeFilter" placeholder="类型筛选" clearable @change="handleFilter" style="width: 100%;">
|
|
|
|
|
<el-option label="全部类型" value="" />
|
|
|
|
|
<el-option label="旗舰店" value="旗舰店" />
|
|
|
|
|
<el-option label="标准店" value="标准店" />
|
|
|
|
|
<el-option label="便民店" value="便民店" />
|
|
|
|
|
<el-option label="社区店" value="社区店" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
</div>
|
2025-07-02 11:05:23 +08:00
|
|
|
|
|
2025-07-15 19:17:53 +08:00
|
|
|
|
<!-- 店铺列表 -->
|
|
|
|
|
<el-table
|
|
|
|
|
:data="pagedStores"
|
|
|
|
|
v-loading="loading"
|
|
|
|
|
stripe
|
|
|
|
|
@selection-change="handleSelectionChange"
|
|
|
|
|
class="store-table"
|
|
|
|
|
:height="tableHeight"
|
|
|
|
|
>
|
2025-07-02 11:05:23 +08:00
|
|
|
|
<el-table-column type="selection" width="55" />
|
2025-07-15 19:17:53 +08:00
|
|
|
|
<el-table-column prop="store_id" label="店铺ID" width="100" align="center" />
|
|
|
|
|
<el-table-column prop="store_name" label="店铺名称" width="250" align="center" show-overflow-tooltip />
|
|
|
|
|
<el-table-column prop="location" label="位置" width="250" align="center" show-overflow-tooltip/>
|
|
|
|
|
<el-table-column prop="type" label="类型" width="120" align="center">
|
2025-07-02 11:05:23 +08:00
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-tag :type="getStoreTypeTag(row.type)">
|
|
|
|
|
{{ row.type }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
2025-07-15 19:17:53 +08:00
|
|
|
|
<el-table-column prop="size" label="面积(㎡)" width="150" align="center"/>
|
|
|
|
|
<el-table-column prop="opening_date" label="开业日期" width="150" align="center"/>
|
|
|
|
|
<el-table-column prop="status" label="状态" width="150" align="center">
|
2025-07-02 11:05:23 +08:00
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-tag :type="row.status === 'active' ? 'success' : 'danger'">
|
|
|
|
|
{{ row.status === 'active' ? '营业中' : '暂停营业' }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
2025-07-15 19:17:53 +08:00
|
|
|
|
<el-table-column label="操作" width="200" fixed="right" align="center">
|
2025-07-02 11:05:23 +08:00
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-button link type="primary" @click="viewStoreDetails(row)">
|
|
|
|
|
详情
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button link type="primary" @click="editStore(row)">
|
|
|
|
|
编辑
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button link type="primary" @click="viewStoreProducts(row)">
|
|
|
|
|
产品
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button link type="danger" @click="deleteStore(row)">
|
|
|
|
|
删除
|
|
|
|
|
</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
|
|
<!-- 分页 -->
|
|
|
|
|
<el-pagination
|
|
|
|
|
v-if="total > pageSize"
|
2025-07-15 19:17:53 +08:00
|
|
|
|
layout="total, prev, pager, next, jumper"
|
2025-07-02 11:05:23 +08:00
|
|
|
|
:total="total"
|
|
|
|
|
:page-size="pageSize"
|
2025-07-15 19:17:53 +08:00
|
|
|
|
:current-page="currentPage"
|
2025-07-02 11:05:23 +08:00
|
|
|
|
@current-change="handlePageChange"
|
|
|
|
|
@size-change="handleSizeChange"
|
|
|
|
|
class="pagination"
|
2025-07-15 19:17:53 +08:00
|
|
|
|
ref="paginationRef"
|
2025-07-02 11:05:23 +08:00
|
|
|
|
/>
|
2025-07-15 19:17:53 +08:00
|
|
|
|
</div>
|
2025-07-02 11:05:23 +08:00
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
<!-- 新增/编辑店铺对话框 -->
|
|
|
|
|
<el-dialog
|
|
|
|
|
v-model="dialogVisible"
|
|
|
|
|
:title="isEditing ? '编辑店铺' : '新增店铺'"
|
|
|
|
|
width="600px"
|
|
|
|
|
@close="resetForm"
|
2025-07-15 19:17:53 +08:00
|
|
|
|
class="form-dialog"
|
2025-07-02 11:05:23 +08:00
|
|
|
|
>
|
|
|
|
|
<el-form
|
|
|
|
|
ref="formRef"
|
|
|
|
|
:model="form"
|
|
|
|
|
:rules="rules"
|
|
|
|
|
label-width="100px"
|
|
|
|
|
>
|
|
|
|
|
<el-form-item label="店铺ID" prop="store_id">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="form.store_id"
|
|
|
|
|
:disabled="isEditing"
|
|
|
|
|
placeholder="请输入店铺ID,如S001"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="店铺名称" prop="store_name">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="form.store_name"
|
|
|
|
|
placeholder="请输入店铺名称"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="位置" prop="location">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="form.location"
|
|
|
|
|
placeholder="请输入店铺地址"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="店铺类型" prop="type">
|
|
|
|
|
<el-select v-model="form.type" placeholder="请选择店铺类型" style="width: 100%">
|
|
|
|
|
<el-option label="旗舰店" value="旗舰店" />
|
|
|
|
|
<el-option label="标准店" value="标准店" />
|
|
|
|
|
<el-option label="便民店" value="便民店" />
|
|
|
|
|
<el-option label="社区店" value="社区店" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="面积(㎡)" prop="size">
|
|
|
|
|
<el-input-number
|
|
|
|
|
v-model="form.size"
|
|
|
|
|
:min="1"
|
|
|
|
|
:max="10000"
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="开业日期" prop="opening_date">
|
|
|
|
|
<el-date-picker
|
|
|
|
|
v-model="form.opening_date"
|
|
|
|
|
type="date"
|
|
|
|
|
placeholder="选择开业日期"
|
|
|
|
|
format="YYYY-MM-DD"
|
|
|
|
|
value-format="YYYY-MM-DD"
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="状态" prop="status">
|
|
|
|
|
<el-radio-group v-model="form.status">
|
|
|
|
|
<el-radio label="active">营业中</el-radio>
|
|
|
|
|
<el-radio label="inactive">暂停营业</el-radio>
|
|
|
|
|
</el-radio-group>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
<el-button @click="dialogVisible = false">取消</el-button>
|
|
|
|
|
<el-button type="primary" @click="submitForm" :loading="submitting">
|
|
|
|
|
{{ isEditing ? '更新' : '创建' }}
|
|
|
|
|
</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
<!-- 店铺详情对话框 -->
|
|
|
|
|
<el-dialog
|
|
|
|
|
v-model="detailDialogVisible"
|
|
|
|
|
title="店铺详情"
|
|
|
|
|
width="800px"
|
|
|
|
|
>
|
|
|
|
|
<div v-if="selectedStore" class="store-detail">
|
|
|
|
|
<el-descriptions :column="2" border>
|
|
|
|
|
<el-descriptions-item label="店铺ID">{{ selectedStore.store_id }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="店铺名称">{{ selectedStore.store_name }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="位置">{{ selectedStore.location }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="类型">
|
|
|
|
|
<el-tag :type="getStoreTypeTag(selectedStore.type)">
|
|
|
|
|
{{ selectedStore.type }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="面积">{{ selectedStore.size }} ㎡</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="开业日期">{{ selectedStore.opening_date }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="状态">
|
|
|
|
|
<el-tag :type="selectedStore.status === 'active' ? 'success' : 'danger'">
|
|
|
|
|
{{ selectedStore.status === 'active' ? '营业中' : '暂停营业' }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
</el-descriptions>
|
|
|
|
|
|
|
|
|
|
<!-- 店铺统计信息 -->
|
|
|
|
|
<div class="store-stats" v-if="storeStats">
|
|
|
|
|
<h4>店铺统计</h4>
|
|
|
|
|
<el-row :gutter="20">
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
<el-statistic title="销售产品种类" :value="storeStats.product_count || 0" />
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
<el-statistic title="总销售额" :value="storeStats.total_sales || 0" :precision="2" prefix="¥" />
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
<el-statistic title="总销量" :value="storeStats.total_quantity || 0" />
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
<el-statistic title="销售记录数" :value="storeStats.record_count || 0" />
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
2025-07-15 19:17:53 +08:00
|
|
|
|
|
2025-07-02 11:05:23 +08:00
|
|
|
|
<!-- 店铺产品对话框 -->
|
|
|
|
|
<el-dialog
|
|
|
|
|
v-model="productsDialogVisible"
|
|
|
|
|
title="店铺产品列表"
|
|
|
|
|
width="1000px"
|
|
|
|
|
>
|
|
|
|
|
<div v-if="storeProducts.length > 0">
|
|
|
|
|
<el-table :data="storeProducts" stripe>
|
|
|
|
|
<el-table-column prop="product_id" label="产品ID" width="100" />
|
|
|
|
|
<el-table-column prop="product_name" label="产品名称" width="200" />
|
|
|
|
|
<el-table-column prop="category" label="分类" width="120" />
|
|
|
|
|
<el-table-column prop="total_sales" label="总销量" width="100" align="right" />
|
|
|
|
|
<el-table-column prop="avg_price" label="平均价格" width="100" align="right">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
¥{{ row.avg_price?.toFixed(2) }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column prop="last_sale_date" label="最后销售日期" width="120" />
|
|
|
|
|
</el-table>
|
|
|
|
|
</div>
|
|
|
|
|
<el-empty v-else description="该店铺暂无产品销售记录" />
|
|
|
|
|
</el-dialog>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2025-07-15 19:17:53 +08:00
|
|
|
|
import { ref, onMounted, onUnmounted, computed, nextTick } from 'vue'
|
2025-07-02 11:05:23 +08:00
|
|
|
|
import axios from 'axios'
|
|
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
|
import { Plus, Refresh, Search } from '@element-plus/icons-vue'
|
|
|
|
|
|
|
|
|
|
// 响应式数据
|
|
|
|
|
const stores = ref([])
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
const selectedStores = ref([])
|
|
|
|
|
|
|
|
|
|
// 搜索和过滤
|
|
|
|
|
const searchQuery = ref('')
|
|
|
|
|
const statusFilter = ref('')
|
|
|
|
|
const typeFilter = ref('')
|
|
|
|
|
|
|
|
|
|
// 分页
|
|
|
|
|
const currentPage = ref(1)
|
2025-07-15 19:17:53 +08:00
|
|
|
|
const pageSize = ref(12)
|
2025-07-02 11:05:23 +08:00
|
|
|
|
const total = ref(0)
|
|
|
|
|
|
2025-07-15 19:17:53 +08:00
|
|
|
|
// 布局和高度
|
|
|
|
|
const tableContainerRef = ref(null);
|
|
|
|
|
const filterSectionRef = ref(null);
|
|
|
|
|
const paginationRef = ref(null);
|
|
|
|
|
const tableHeight = ref(400); // 默认高度
|
|
|
|
|
|
2025-07-02 11:05:23 +08:00
|
|
|
|
// 对话框
|
|
|
|
|
const dialogVisible = ref(false)
|
|
|
|
|
const detailDialogVisible = ref(false)
|
|
|
|
|
const productsDialogVisible = ref(false)
|
|
|
|
|
const isEditing = ref(false)
|
|
|
|
|
const submitting = ref(false)
|
|
|
|
|
|
|
|
|
|
// 表单
|
|
|
|
|
const formRef = ref()
|
|
|
|
|
const form = ref({
|
|
|
|
|
store_id: '',
|
|
|
|
|
store_name: '',
|
|
|
|
|
location: '',
|
|
|
|
|
type: '',
|
|
|
|
|
size: null,
|
|
|
|
|
opening_date: '',
|
|
|
|
|
status: 'active'
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 详情数据
|
|
|
|
|
const selectedStore = ref(null)
|
|
|
|
|
const storeStats = ref(null)
|
|
|
|
|
const storeProducts = ref([])
|
|
|
|
|
|
|
|
|
|
// 表单验证规则
|
|
|
|
|
const rules = {
|
|
|
|
|
store_id: [
|
|
|
|
|
{ required: true, message: '请输入店铺ID', trigger: 'blur' },
|
|
|
|
|
{ pattern: /^[A-Z]\d{3}$/, message: '店铺ID格式应为S001', trigger: 'blur' }
|
|
|
|
|
],
|
|
|
|
|
store_name: [
|
|
|
|
|
{ required: true, message: '请输入店铺名称', trigger: 'blur' },
|
|
|
|
|
{ min: 2, max: 50, message: '店铺名称长度在2到50个字符', trigger: 'blur' }
|
|
|
|
|
],
|
|
|
|
|
location: [
|
|
|
|
|
{ required: true, message: '请输入店铺位置', trigger: 'blur' }
|
|
|
|
|
],
|
|
|
|
|
type: [
|
|
|
|
|
{ required: true, message: '请选择店铺类型', trigger: 'change' }
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 计算属性
|
|
|
|
|
const filteredStores = computed(() => {
|
2025-07-15 19:17:53 +08:00
|
|
|
|
let result = stores.value;
|
2025-07-02 11:05:23 +08:00
|
|
|
|
|
|
|
|
|
if (searchQuery.value) {
|
2025-07-15 19:17:53 +08:00
|
|
|
|
const query = searchQuery.value.toLowerCase();
|
|
|
|
|
result = result.filter(
|
|
|
|
|
(store) =>
|
|
|
|
|
store.store_name.toLowerCase().includes(query) ||
|
|
|
|
|
store.store_id.toLowerCase().includes(query)
|
|
|
|
|
);
|
2025-07-02 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (statusFilter.value) {
|
2025-07-15 19:17:53 +08:00
|
|
|
|
result = result.filter((store) => store.status === statusFilter.value);
|
2025-07-02 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (typeFilter.value) {
|
2025-07-15 19:17:53 +08:00
|
|
|
|
result = result.filter((store) => store.type === typeFilter.value);
|
2025-07-02 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-07-15 19:17:53 +08:00
|
|
|
|
return result;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const pagedStores = computed(() => {
|
|
|
|
|
const start = (currentPage.value - 1) * pageSize.value;
|
|
|
|
|
const end = start + pageSize.value;
|
|
|
|
|
total.value = filteredStores.value.length;
|
|
|
|
|
return filteredStores.value.slice(start, end);
|
|
|
|
|
});
|
2025-07-02 11:05:23 +08:00
|
|
|
|
|
|
|
|
|
// 方法
|
|
|
|
|
const fetchStores = async () => {
|
|
|
|
|
try {
|
|
|
|
|
loading.value = true
|
|
|
|
|
const response = await axios.get('/api/stores')
|
|
|
|
|
if (response.data.status === 'success') {
|
|
|
|
|
stores.value = response.data.data
|
|
|
|
|
} else {
|
|
|
|
|
ElMessage.error('获取店铺列表失败')
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
ElMessage.error('请求失败')
|
|
|
|
|
console.error(error)
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const refreshStores = () => {
|
|
|
|
|
fetchStores()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleSearch = () => {
|
|
|
|
|
currentPage.value = 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleFilter = () => {
|
|
|
|
|
currentPage.value = 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handlePageChange = (page) => {
|
|
|
|
|
currentPage.value = page
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleSizeChange = (size) => {
|
|
|
|
|
pageSize.value = size
|
|
|
|
|
currentPage.value = 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleSelectionChange = (selection) => {
|
|
|
|
|
selectedStores.value = selection
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getStoreTypeTag = (type) => {
|
|
|
|
|
const typeMap = {
|
|
|
|
|
'旗舰店': 'primary',
|
|
|
|
|
'标准店': 'success',
|
|
|
|
|
'便民店': 'info',
|
|
|
|
|
'社区店': 'warning'
|
|
|
|
|
}
|
|
|
|
|
return typeMap[type] || 'info'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const showCreateDialog = () => {
|
|
|
|
|
isEditing.value = false
|
|
|
|
|
dialogVisible.value = true
|
|
|
|
|
resetForm()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const editStore = (store) => {
|
|
|
|
|
isEditing.value = true
|
|
|
|
|
form.value = { ...store }
|
|
|
|
|
dialogVisible.value = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const resetForm = () => {
|
|
|
|
|
if (formRef.value) {
|
|
|
|
|
formRef.value.resetFields()
|
|
|
|
|
}
|
|
|
|
|
form.value = {
|
|
|
|
|
store_id: '',
|
|
|
|
|
store_name: '',
|
|
|
|
|
location: '',
|
|
|
|
|
type: '',
|
|
|
|
|
size: null,
|
|
|
|
|
opening_date: '',
|
|
|
|
|
status: 'active'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const submitForm = async () => {
|
|
|
|
|
if (!formRef.value) return
|
|
|
|
|
|
|
|
|
|
await formRef.value.validate(async (valid) => {
|
|
|
|
|
if (valid) {
|
|
|
|
|
try {
|
|
|
|
|
submitting.value = true
|
|
|
|
|
const url = isEditing.value ? `/api/stores/${form.value.store_id}` : '/api/stores'
|
|
|
|
|
const method = isEditing.value ? 'put' : 'post'
|
|
|
|
|
|
|
|
|
|
const response = await axios[method](url, form.value)
|
|
|
|
|
|
|
|
|
|
if (response.data.status === 'success') {
|
|
|
|
|
ElMessage.success(isEditing.value ? '店铺更新成功' : '店铺创建成功')
|
|
|
|
|
dialogVisible.value = false
|
|
|
|
|
await fetchStores()
|
|
|
|
|
} else {
|
|
|
|
|
ElMessage.error(response.data.message || '操作失败')
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
ElMessage.error('请求失败')
|
|
|
|
|
console.error(error)
|
|
|
|
|
} finally {
|
|
|
|
|
submitting.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const deleteStore = async (store) => {
|
|
|
|
|
try {
|
|
|
|
|
await ElMessageBox.confirm(
|
|
|
|
|
`确定要删除店铺 "${store.store_name}" 吗?此操作不可恢复。`,
|
|
|
|
|
'确认删除',
|
|
|
|
|
{
|
|
|
|
|
confirmButtonText: '删除',
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
type: 'warning'
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const response = await axios.delete(`/api/stores/${store.store_id}`)
|
|
|
|
|
if (response.data.status === 'success') {
|
|
|
|
|
ElMessage.success('店铺删除成功')
|
|
|
|
|
await fetchStores()
|
|
|
|
|
} else {
|
|
|
|
|
ElMessage.error(response.data.message || '删除失败')
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (error !== 'cancel') {
|
|
|
|
|
ElMessage.error('删除请求失败')
|
|
|
|
|
console.error(error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const viewStoreDetails = async (store) => {
|
|
|
|
|
selectedStore.value = store
|
|
|
|
|
detailDialogVisible.value = true
|
|
|
|
|
|
|
|
|
|
// 获取店铺统计信息
|
|
|
|
|
try {
|
|
|
|
|
const response = await axios.get(`/api/stores/${store.store_id}/statistics`)
|
|
|
|
|
if (response.data.status === 'success') {
|
|
|
|
|
storeStats.value = response.data.data
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取店铺统计失败:', error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const viewStoreProducts = async (store) => {
|
|
|
|
|
selectedStore.value = store
|
|
|
|
|
productsDialogVisible.value = true
|
|
|
|
|
|
|
|
|
|
// 获取店铺产品列表
|
|
|
|
|
try {
|
|
|
|
|
const response = await axios.get(`/api/stores/${store.store_id}/products`)
|
|
|
|
|
if (response.data.status === 'success') {
|
|
|
|
|
storeProducts.value = response.data.data
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取店铺产品失败:', error)
|
|
|
|
|
storeProducts.value = []
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 生命周期
|
2025-07-15 19:17:53 +08:00
|
|
|
|
const updateTableHeight = () => {
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
if (tableContainerRef.value) {
|
|
|
|
|
const containerHeight = tableContainerRef.value.clientHeight;
|
|
|
|
|
const filterHeight = filterSectionRef.value?.offsetHeight || 0;
|
|
|
|
|
const paginationHeight = paginationRef.value?.$el.offsetHeight || 0;
|
|
|
|
|
|
|
|
|
|
// 减去筛选区、分页区以及一些间距
|
|
|
|
|
const calculatedHeight = containerHeight - filterHeight - paginationHeight - 20;
|
|
|
|
|
tableHeight.value = calculatedHeight > 200 ? calculatedHeight : 200; // 最小高度
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2025-07-02 11:05:23 +08:00
|
|
|
|
onMounted(() => {
|
2025-07-15 19:17:53 +08:00
|
|
|
|
fetchStores();
|
|
|
|
|
updateTableHeight();
|
|
|
|
|
window.addEventListener('resize', updateTableHeight);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
window.removeEventListener('resize', updateTableHeight);
|
|
|
|
|
});
|
2025-07-02 11:05:23 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.store-management-container {
|
2025-07-15 19:17:53 +08:00
|
|
|
|
height: 97%;
|
|
|
|
|
padding: 6px 10px 15px 15px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.full-height-card {
|
|
|
|
|
height: 100%;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.el-card__body) {
|
|
|
|
|
flex-grow: 1;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
2025-07-02 11:05:23 +08:00
|
|
|
|
padding: 20px;
|
2025-07-15 19:17:53 +08:00
|
|
|
|
overflow: hidden;
|
2025-07-02 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.card-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.header-actions {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-15 19:17:53 +08:00
|
|
|
|
.table-container {
|
|
|
|
|
flex-grow: 1;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
overflow: hidden; /* 确保容器本身不滚动 */
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-02 11:05:23 +08:00
|
|
|
|
.filter-section {
|
2025-07-15 19:17:53 +08:00
|
|
|
|
padding-bottom: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.store-table {
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.store-table .el-table__cell) {
|
|
|
|
|
padding: 12px 2px;
|
2025-07-02 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pagination {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
2025-07-15 19:17:53 +08:00
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 14px 0;
|
2025-07-02 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.store-detail {
|
2025-07-15 19:17:53 +08:00
|
|
|
|
padding: 5px 0;
|
2025-07-02 11:05:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.store-stats {
|
|
|
|
|
margin-top: 30px;
|
|
|
|
|
padding-top: 20px;
|
|
|
|
|
border-top: 1px solid #ebeef5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.store-stats h4 {
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
color: #303133;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
.store-management-container {
|
|
|
|
|
padding: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.filter-section .el-col {
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.header-actions {
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 5px;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-15 19:17:53 +08:00
|
|
|
|
|
|
|
|
|
.form-dialog :deep(.el-dialog) {
|
|
|
|
|
background: transparent;
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
}
|
|
|
|
|
</style>
|