|
@@ -3,19 +3,31 @@
|
|
|
<div class="header">
|
|
<div class="header">
|
|
|
<h2>商品管理</h2>
|
|
<h2>商品管理</h2>
|
|
|
<div class="filters">
|
|
<div class="filters">
|
|
|
- <el-select v-model="statusFilter" placeholder="商品状态" @change="loadList" style="width: 140px;">
|
|
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="filters.keyword"
|
|
|
|
|
+ placeholder="搜索商品名称"
|
|
|
|
|
+ prefix-icon="el-icon-search"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ style="width: 180px;"
|
|
|
|
|
+ @clear="loadList"
|
|
|
|
|
+ @keyup.enter.native="loadList"
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-select v-model="filters.status" placeholder="商品状态" @change="loadList" style="width: 130px;" clearable>
|
|
|
<el-option label="全部" value="" />
|
|
<el-option label="全部" value="" />
|
|
|
<el-option label="待审核" value="pending" />
|
|
<el-option label="待审核" value="pending" />
|
|
|
- <el-option label="已上架" value="approved" />
|
|
|
|
|
|
|
+ <el-option label="待上架" value="approved" />
|
|
|
|
|
+ <el-option label="已上架" value="on_shelf" />
|
|
|
|
|
+ <el-option label="已下架" value="off_shelf" />
|
|
|
<el-option label="已拒绝" value="rejected" />
|
|
<el-option label="已拒绝" value="rejected" />
|
|
|
- <el-option label="已下架" value="shelved" />
|
|
|
|
|
</el-select>
|
|
</el-select>
|
|
|
- <el-select v-model="typeFilter" placeholder="商品类型" @change="loadList" style="width: 140px;">
|
|
|
|
|
|
|
+ <el-select v-model="filters.productType" placeholder="商品类型" @change="loadList" style="width: 140px;" clearable>
|
|
|
<el-option label="全部类型" value="" />
|
|
<el-option label="全部类型" value="" />
|
|
|
<el-option label="实物商品" value="physical" />
|
|
<el-option label="实物商品" value="physical" />
|
|
|
<el-option label="虚拟商品" value="virtual" />
|
|
<el-option label="虚拟商品" value="virtual" />
|
|
|
<el-option label="优惠券" value="coupon" />
|
|
<el-option label="优惠券" value="coupon" />
|
|
|
</el-select>
|
|
</el-select>
|
|
|
|
|
+ <el-button type="primary" size="mini" @click="loadList">查询</el-button>
|
|
|
|
|
+ <el-button type="success" size="mini" icon="el-icon-plus" @click="$router.push('/product-edit')">新增商品</el-button>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
@@ -23,8 +35,13 @@
|
|
|
<el-table-column prop="id" label="ID" width="70" />
|
|
<el-table-column prop="id" label="ID" width="70" />
|
|
|
<el-table-column label="商品图片" width="80">
|
|
<el-table-column label="商品图片" width="80">
|
|
|
<template slot-scope="{ row }">
|
|
<template slot-scope="{ row }">
|
|
|
- <img v-if="row.coverImage" :src="row.coverImage" class="product-thumb" @click="previewImage(row.coverImage)" />
|
|
|
|
|
- <span v-else>-</span>
|
|
|
|
|
|
|
+ <img
|
|
|
|
|
+ v-if="row.coverImage"
|
|
|
|
|
+ :src="row.coverImage"
|
|
|
|
|
+ class="product-thumb"
|
|
|
|
|
+ @click="previewImage(row.coverImage)"
|
|
|
|
|
+ />
|
|
|
|
|
+ <span v-else style="color:#999">-</span>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column prop="name" label="商品名称" min-width="150" show-overflow-tooltip />
|
|
<el-table-column prop="name" label="商品名称" min-width="150" show-overflow-tooltip />
|
|
@@ -38,44 +55,52 @@
|
|
|
{{ formatPriceWithSymbol(row.price || 0) }}
|
|
{{ formatPriceWithSymbol(row.price || 0) }}
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <el-table-column prop="stock" label="库存" width="70" />
|
|
|
|
|
|
|
+ <el-table-column prop="stock" label="库存" width="80">
|
|
|
|
|
+ <template slot-scope="{ row }">
|
|
|
|
|
+ <span :class="{ 'stock-warning': row.stock !== null && row.stock < 10 }">
|
|
|
|
|
+ {{ row.stock }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="salesCount" label="销量" width="70" />
|
|
|
<el-table-column label="供应商" width="120" show-overflow-tooltip>
|
|
<el-table-column label="供应商" width="120" show-overflow-tooltip>
|
|
|
<template slot-scope="{ row }">
|
|
<template slot-scope="{ row }">
|
|
|
- {{ row.vendorName || row.brandName || '-' }}
|
|
|
|
|
|
|
+ {{ row.vendorName || '-' }}
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column label="状态" width="90">
|
|
<el-table-column label="状态" width="90">
|
|
|
<template slot-scope="{ row }">
|
|
<template slot-scope="{ row }">
|
|
|
- <el-tag :type="statusType(row.status)">{{ statusLabel(row.status) }}</el-tag>
|
|
|
|
|
|
|
+ <el-tag :type="statusType(row.status)" size="mini">{{ statusLabel(row.status) }}</el-tag>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <el-table-column prop="createTime" label="创建时间" width="160">
|
|
|
|
|
|
|
+ <el-table-column prop="createdAt" label="创建时间" width="160">
|
|
|
<template slot-scope="{ row }">
|
|
<template slot-scope="{ row }">
|
|
|
- {{ formatTime(row.createTime || row.createdAt) }}
|
|
|
|
|
|
|
+ {{ formatTime(row.createdAt) }}
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column label="操作" width="220" fixed="right">
|
|
<el-table-column label="操作" width="220" fixed="right">
|
|
|
<template slot-scope="{ row }">
|
|
<template slot-scope="{ row }">
|
|
|
<el-button v-if="row.status === 'pending'" type="success" size="small" @click="handleReview(row, 'approve')">通过</el-button>
|
|
<el-button v-if="row.status === 'pending'" type="success" size="small" @click="handleReview(row, 'approve')">通过</el-button>
|
|
|
<el-button v-if="row.status === 'pending'" type="danger" size="small" @click="showRejectDialog(row)">拒绝</el-button>
|
|
<el-button v-if="row.status === 'pending'" type="danger" size="small" @click="showRejectDialog(row)">拒绝</el-button>
|
|
|
- <el-button v-if="row.status === 'approved'" type="warning" size="small" @click="handleShelve(row, false)">下架</el-button>
|
|
|
|
|
- <el-button v-if="row.status === 'shelved'" type="primary" size="small" @click="handleShelve(row, true)">上架</el-button>
|
|
|
|
|
- <el-button type="info" size="small" @click="viewDetail(row)">详情</el-button>
|
|
|
|
|
|
|
+ <el-button v-if="row.status === 'on_shelf'" type="warning" size="small" @click="handleShelve(row, false)">下架</el-button>
|
|
|
|
|
+ <el-button v-if="row.status === 'off_shelf'" type="primary" size="small" @click="handleShelve(row, true)">上架</el-button>
|
|
|
|
|
+ <el-button type="info" size="small" @click="$router.push('/product-edit?id=' + row.id)">编辑</el-button>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
</el-table>
|
|
</el-table>
|
|
|
|
|
|
|
|
<el-pagination
|
|
<el-pagination
|
|
|
- @size-change="onPageChange"
|
|
|
|
|
- @current-change="onPageChange"
|
|
|
|
|
- :current-page="page"
|
|
|
|
|
- :page-size="size"
|
|
|
|
|
- :total="total"
|
|
|
|
|
- layout="total, prev, pager, next"
|
|
|
|
|
- style="margin-top: 20px; text-align: right;"
|
|
|
|
|
|
|
+ @size-change="handleSizeChange"
|
|
|
|
|
+ @current-change="handlePageChange"
|
|
|
|
|
+ :current-page="pagination.page"
|
|
|
|
|
+ :page-sizes="[10, 20, 50]"
|
|
|
|
|
+ :page-size="pagination.size"
|
|
|
|
|
+ :total="pagination.total"
|
|
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
|
+ style="margin-top: 16px; text-align: right;"
|
|
|
/>
|
|
/>
|
|
|
|
|
|
|
|
- <el-dialog title="拒绝原因" :visible.sync="rejectDialogVisible" width="400px">
|
|
|
|
|
|
|
+ <el-dialog title="拒绝原因" :visible.sync="rejectDialogVisible" width="420px">
|
|
|
<el-input v-model="rejectReason" type="textarea" :rows="3" placeholder="请输入拒绝原因" />
|
|
<el-input v-model="rejectReason" type="textarea" :rows="3" placeholder="请输入拒绝原因" />
|
|
|
<span slot="footer">
|
|
<span slot="footer">
|
|
|
<el-button @click="rejectDialogVisible = false">取消</el-button>
|
|
<el-button @click="rejectDialogVisible = false">取消</el-button>
|
|
@@ -91,15 +116,15 @@
|
|
|
<el-descriptions-item label="会员价">{{ detail.memberPrice ? formatPriceWithSymbol(detail.memberPrice) : '-' }}</el-descriptions-item>
|
|
<el-descriptions-item label="会员价">{{ detail.memberPrice ? formatPriceWithSymbol(detail.memberPrice) : '-' }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="库存">{{ detail.stock }}</el-descriptions-item>
|
|
<el-descriptions-item label="库存">{{ detail.stock }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="销量">{{ detail.salesCount || 0 }}</el-descriptions-item>
|
|
<el-descriptions-item label="销量">{{ detail.salesCount || 0 }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="供应商">{{ detail.vendorName || detail.brandName || '-' }}</el-descriptions-item>
|
|
|
|
|
- <el-descriptions-item label="供应商手机">{{ detail.vendorPhone || '-' }}</el-descriptions-item>
|
|
|
|
|
|
|
+ <el-descriptions-item label="供应商">{{ detail.vendorName || '-' }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="状态">
|
|
<el-descriptions-item label="状态">
|
|
|
- <el-tag :type="statusType(detail.status)">{{ statusLabel(detail.status) }}</el-tag>
|
|
|
|
|
|
|
+ <el-tag :type="statusType(detail.status)" size="mini">{{ statusLabel(detail.status) }}</el-tag>
|
|
|
</el-descriptions-item>
|
|
</el-descriptions-item>
|
|
|
<el-descriptions-item label="拒绝原因" v-if="detail.rejectReason">{{ detail.rejectReason }}</el-descriptions-item>
|
|
<el-descriptions-item label="拒绝原因" v-if="detail.rejectReason">{{ detail.rejectReason }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="商品描述" :span="2">{{ detail.description || '-' }}</el-descriptions-item>
|
|
<el-descriptions-item label="商品描述" :span="2">{{ detail.description || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="创建时间" :span="2">{{ formatTime(detail.createTime || detail.createdAt) }}</el-descriptions-item>
|
|
|
|
|
|
|
+ <el-descriptions-item label="创建时间" :span="2">{{ formatTime(detail.createdAt) }}</el-descriptions-item>
|
|
|
</el-descriptions>
|
|
</el-descriptions>
|
|
|
|
|
+ <div v-else style="text-align:center;color:#999;">加载中...</div>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
@@ -107,19 +132,22 @@
|
|
|
<script>
|
|
<script>
|
|
|
import { getProductList, reviewProduct, shelveProduct } from '@/api/admin'
|
|
import { getProductList, reviewProduct, shelveProduct } from '@/api/admin'
|
|
|
|
|
|
|
|
-
|
|
|
|
|
export default {
|
|
export default {
|
|
|
|
|
+ name: 'ProductManage',
|
|
|
data() {
|
|
data() {
|
|
|
return {
|
|
return {
|
|
|
-
|
|
|
|
|
list: [],
|
|
list: [],
|
|
|
loading: false,
|
|
loading: false,
|
|
|
- page: 1,
|
|
|
|
|
- size: 20,
|
|
|
|
|
- total: 0,
|
|
|
|
|
- statusFilter: '',
|
|
|
|
|
- typeFilter: '',
|
|
|
|
|
-
|
|
|
|
|
|
|
+ filters: {
|
|
|
|
|
+ keyword: '',
|
|
|
|
|
+ status: '',
|
|
|
|
|
+ productType: ''
|
|
|
|
|
+ },
|
|
|
|
|
+ pagination: {
|
|
|
|
|
+ page: 1,
|
|
|
|
|
+ size: 20,
|
|
|
|
|
+ total: 0
|
|
|
|
|
+ },
|
|
|
rejectDialogVisible: false,
|
|
rejectDialogVisible: false,
|
|
|
rejectTarget: null,
|
|
rejectTarget: null,
|
|
|
rejectReason: '',
|
|
rejectReason: '',
|
|
@@ -134,37 +162,63 @@ export default {
|
|
|
async loadList() {
|
|
async loadList() {
|
|
|
this.loading = true
|
|
this.loading = true
|
|
|
try {
|
|
try {
|
|
|
- var res = await getProductList({ status: this.statusFilter })
|
|
|
|
|
- this.list = res.data || []
|
|
|
|
|
- this.total = (res.data || []).length
|
|
|
|
|
|
|
+ const params = {
|
|
|
|
|
+ page: this.pagination.page,
|
|
|
|
|
+ size: this.pagination.size,
|
|
|
|
|
+ status: this.filters.status || null,
|
|
|
|
|
+ productType: this.filters.productType || null,
|
|
|
|
|
+ keyword: this.filters.keyword || null
|
|
|
|
|
+ }
|
|
|
|
|
+ const res = await getProductList(params)
|
|
|
|
|
+ this.list = res.data.records || []
|
|
|
|
|
+ this.pagination.total = res.data.total || 0
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
this.$message.error('加载失败')
|
|
this.$message.error('加载失败')
|
|
|
} finally {
|
|
} finally {
|
|
|
this.loading = false
|
|
this.loading = false
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
- onPageChange(val) {
|
|
|
|
|
- this.page = val
|
|
|
|
|
|
|
+ handleSizeChange(size) {
|
|
|
|
|
+ this.pagination.size = size
|
|
|
|
|
+ this.pagination.page = 1
|
|
|
|
|
+ this.loadList()
|
|
|
|
|
+ },
|
|
|
|
|
+ handlePageChange(page) {
|
|
|
|
|
+ this.pagination.page = page
|
|
|
this.loadList()
|
|
this.loadList()
|
|
|
},
|
|
},
|
|
|
statusType(status) {
|
|
statusType(status) {
|
|
|
- var s = String(status)
|
|
|
|
|
- var map = { pending: 'warning', '0': 'warning', approved: 'success', '1': 'success', rejected: 'danger', shelved: 'info' }
|
|
|
|
|
- return map[s] || 'info'
|
|
|
|
|
|
|
+ const map = {
|
|
|
|
|
+ pending: 'warning',
|
|
|
|
|
+ approved: 'success',
|
|
|
|
|
+ on_shelf: 'success',
|
|
|
|
|
+ off_shelf: 'info',
|
|
|
|
|
+ rejected: 'danger'
|
|
|
|
|
+ }
|
|
|
|
|
+ return map[status] || 'info'
|
|
|
},
|
|
},
|
|
|
statusLabel(status) {
|
|
statusLabel(status) {
|
|
|
- var s = String(status)
|
|
|
|
|
- var map = { pending: '待审核', '0': '待审核', approved: '已上架', '1': '已上架', rejected: '已拒绝', shelved: '已下架' }
|
|
|
|
|
- return map[s] || status
|
|
|
|
|
|
|
+ const map = {
|
|
|
|
|
+ pending: '待审核',
|
|
|
|
|
+ approved: '待上架',
|
|
|
|
|
+ on_shelf: '已上架',
|
|
|
|
|
+ off_shelf: '已下架',
|
|
|
|
|
+ rejected: '已拒绝'
|
|
|
|
|
+ }
|
|
|
|
|
+ return map[status] || status
|
|
|
},
|
|
},
|
|
|
typeLabel(type) {
|
|
typeLabel(type) {
|
|
|
- var map = { physical: '实物商品', virtual: '虚拟商品', coupon: '优惠券' }
|
|
|
|
|
|
|
+ const map = { physical: '实物商品', virtual: '虚拟商品', coupon: '优惠券' }
|
|
|
return map[type] || type
|
|
return map[type] || type
|
|
|
},
|
|
},
|
|
|
formatTime(time) {
|
|
formatTime(time) {
|
|
|
if (!time) return '-'
|
|
if (!time) return '-'
|
|
|
return new Date(time).toLocaleString()
|
|
return new Date(time).toLocaleString()
|
|
|
},
|
|
},
|
|
|
|
|
+ formatPriceWithSymbol(price) {
|
|
|
|
|
+ if (price === 0) return '免费'
|
|
|
|
|
+ return '¥' + (price / 100).toFixed(2)
|
|
|
|
|
+ },
|
|
|
async handleReview(row, action) {
|
|
async handleReview(row, action) {
|
|
|
try {
|
|
try {
|
|
|
await this.$confirm('确认通过该商品审核?', '提示')
|
|
await this.$confirm('确认通过该商品审核?', '提示')
|
|
@@ -203,10 +257,6 @@ export default {
|
|
|
if (e !== 'cancel') this.$message.error('操作失败')
|
|
if (e !== 'cancel') this.$message.error('操作失败')
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
- viewDetail(row) {
|
|
|
|
|
- this.detail = row
|
|
|
|
|
- this.detailDialogVisible = true
|
|
|
|
|
- },
|
|
|
|
|
previewImage(url) {
|
|
previewImage(url) {
|
|
|
if (!url) return
|
|
if (!url) return
|
|
|
this.$alert('<img src="' + url + '" style="max-width:100%;" />', '商品图片', {
|
|
this.$alert('<img src="' + url + '" style="max-width:100%;" />', '商品图片', {
|
|
@@ -228,13 +278,15 @@ export default {
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
margin-bottom: 20px;
|
|
margin-bottom: 20px;
|
|
|
flex-wrap: wrap;
|
|
flex-wrap: wrap;
|
|
|
|
|
+ gap: 12px;
|
|
|
}
|
|
}
|
|
|
.header h2 {
|
|
.header h2 {
|
|
|
margin: 0;
|
|
margin: 0;
|
|
|
|
|
+ font-size: 18px;
|
|
|
}
|
|
}
|
|
|
.filters {
|
|
.filters {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- gap: 10px;
|
|
|
|
|
|
|
+ gap: 8px;
|
|
|
flex-wrap: wrap;
|
|
flex-wrap: wrap;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
}
|
|
}
|
|
@@ -245,4 +297,8 @@ export default {
|
|
|
border-radius: 4px;
|
|
border-radius: 4px;
|
|
|
cursor: pointer;
|
|
cursor: pointer;
|
|
|
}
|
|
}
|
|
|
-</style>
|
|
|
|
|
|
|
+.stock-warning {
|
|
|
|
|
+ color: #f56c6c;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|