|
@@ -1,449 +1,542 @@
|
|
|
-<!-- 商品中心 - 商品列表 -->
|
|
|
-<template>
|
|
|
- <!-- 搜索工作栏 -->
|
|
|
- <ContentWrap>
|
|
|
- <el-form
|
|
|
- ref="queryFormRef"
|
|
|
- :inline="true"
|
|
|
- :model="queryParams"
|
|
|
- class="-mb-15px"
|
|
|
- label-width="68px"
|
|
|
- >
|
|
|
- <el-form-item label="商品名称" prop="name">
|
|
|
- <el-input
|
|
|
- v-model="queryParams.name"
|
|
|
- class="!w-240px"
|
|
|
- clearable
|
|
|
- placeholder="请输入商品名称"
|
|
|
- @keyup.enter="handleQuery"
|
|
|
- />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="商品分类" prop="categoryId">
|
|
|
- <el-cascader
|
|
|
- v-model="queryParams.categoryId"
|
|
|
- :options="categoryList"
|
|
|
- :props="defaultProps"
|
|
|
- class="w-1/1"
|
|
|
- clearable
|
|
|
- filterable
|
|
|
- placeholder="请选择商品分类"
|
|
|
- />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="创建时间" prop="createTime">
|
|
|
- <el-date-picker
|
|
|
- v-model="queryParams.createTime"
|
|
|
- :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
|
|
- class="!w-240px"
|
|
|
- end-placeholder="结束日期"
|
|
|
- start-placeholder="开始日期"
|
|
|
- type="daterange"
|
|
|
- value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
- />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item>
|
|
|
- <el-button @click="handleQuery">
|
|
|
- <Icon class="mr-5px" icon="ep:search" />
|
|
|
- 搜索
|
|
|
- </el-button>
|
|
|
- <el-button @click="resetQuery">
|
|
|
- <Icon class="mr-5px" icon="ep:refresh" />
|
|
|
- 重置
|
|
|
- </el-button>
|
|
|
- <el-button
|
|
|
- v-hasPermi="['product:spu:create']"
|
|
|
- plain
|
|
|
- type="primary"
|
|
|
- @click="openForm(undefined)"
|
|
|
- >
|
|
|
- <Icon class="mr-5px" icon="ep:plus" />
|
|
|
- 新增
|
|
|
- </el-button>
|
|
|
- <el-button
|
|
|
- v-hasPermi="['product:spu:export']"
|
|
|
- :loading="exportLoading"
|
|
|
- plain
|
|
|
- type="success"
|
|
|
- @click="handleExport"
|
|
|
- >
|
|
|
- <Icon class="mr-5px" icon="ep:download" />
|
|
|
- 导出
|
|
|
- </el-button>
|
|
|
- </el-form-item>
|
|
|
- </el-form>
|
|
|
- </ContentWrap>
|
|
|
-
|
|
|
- <!-- 列表 -->
|
|
|
- <ContentWrap>
|
|
|
- <el-tabs v-model="queryParams.tabType" @tab-click="handleTabClick">
|
|
|
- <el-tab-pane
|
|
|
- v-for="item in tabsData"
|
|
|
- :key="item.type"
|
|
|
- :label="item.name + '(' + item.count + ')'"
|
|
|
- :name="item.type"
|
|
|
- />
|
|
|
- </el-tabs>
|
|
|
- <el-table v-loading="loading" :data="list">
|
|
|
- <el-table-column type="expand">
|
|
|
- <template #default="{ row }">
|
|
|
- <el-form class="spu-table-expand" label-position="left">
|
|
|
- <el-row>
|
|
|
- <el-col :span="24">
|
|
|
- <el-row>
|
|
|
- <el-col :span="8">
|
|
|
- <el-form-item label="商品分类:">
|
|
|
- <span>{{ formatCategoryName(row.categoryId) }}</span>
|
|
|
- </el-form-item>
|
|
|
- </el-col>
|
|
|
- <el-col :span="8">
|
|
|
- <el-form-item label="市场价:">
|
|
|
- <span>{{ fenToYuan(row.marketPrice) }}</span>
|
|
|
- </el-form-item>
|
|
|
- </el-col>
|
|
|
- <el-col :span="8">
|
|
|
- <el-form-item label="成本价:">
|
|
|
- <span>{{ fenToYuan(row.costPrice) }}</span>
|
|
|
- </el-form-item>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
- <el-row>
|
|
|
- <el-col :span="24">
|
|
|
- <el-row>
|
|
|
- <el-col :span="8">
|
|
|
- <el-form-item label="浏览量:">
|
|
|
- <span>{{ row.browseCount }}</span>
|
|
|
- </el-form-item>
|
|
|
- </el-col>
|
|
|
- <el-col :span="8">
|
|
|
- <el-form-item label="虚拟销量:">
|
|
|
- <span>{{ row.virtualSalesCount }}</span>
|
|
|
- </el-form-item>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
- </el-form>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="商品编号" min-width="140" prop="id" />
|
|
|
- <el-table-column label="商品信息" min-width="300">
|
|
|
- <template #default="{ row }">
|
|
|
- <div class="flex">
|
|
|
- <el-image
|
|
|
- fit="cover"
|
|
|
- :src="row.picUrl"
|
|
|
- class="flex-none w-50px h-50px"
|
|
|
- @click="imagePreview(row.picUrl)"
|
|
|
- />
|
|
|
- <div class="ml-4 overflow-hidden">
|
|
|
- <el-tooltip effect="dark" :content="row.name" placement="top">
|
|
|
- <div>
|
|
|
- {{ row.name }}
|
|
|
- </div>
|
|
|
- </el-tooltip>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column align="center" label="价格" min-width="160" prop="price">
|
|
|
- <template #default="{ row }"> ¥ {{ fenToYuan(row.price) }}</template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column align="center" label="销量" min-width="90" prop="salesCount" />
|
|
|
- <el-table-column align="center" label="库存" min-width="90" prop="stock" />
|
|
|
- <el-table-column align="center" label="排序" min-width="70" prop="sort" />
|
|
|
- <el-table-column align="center" label="销售状态" min-width="80">
|
|
|
- <template #default="{ row }">
|
|
|
- <template v-if="row.status >= 0">
|
|
|
- <el-switch
|
|
|
- v-model="row.status"
|
|
|
- :active-value="1"
|
|
|
- :inactive-value="0"
|
|
|
- active-text="上架"
|
|
|
- inactive-text="下架"
|
|
|
- inline-prompt
|
|
|
- @change="handleStatusChange(row)"
|
|
|
- />
|
|
|
- </template>
|
|
|
- <template v-else>
|
|
|
- <el-tag type="info">回收站</el-tag>
|
|
|
- </template>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column
|
|
|
- :formatter="dateFormatter"
|
|
|
- align="center"
|
|
|
- label="创建时间"
|
|
|
- prop="createTime"
|
|
|
- width="180"
|
|
|
- />
|
|
|
- <el-table-column align="center" fixed="right" label="操作" min-width="200">
|
|
|
- <template #default="{ row }">
|
|
|
- <el-button link type="primary" @click="openDetail(row.id)"> 详情 </el-button>
|
|
|
- <el-button
|
|
|
- v-hasPermi="['product:spu:update']"
|
|
|
- link
|
|
|
- type="primary"
|
|
|
- @click="openForm(row.id)"
|
|
|
- >
|
|
|
- 修改
|
|
|
- </el-button>
|
|
|
- <template v-if="queryParams.tabType === 4">
|
|
|
- <el-button
|
|
|
- v-hasPermi="['product:spu:delete']"
|
|
|
- link
|
|
|
- type="danger"
|
|
|
- @click="handleDelete(row.id)"
|
|
|
- >
|
|
|
- 删除
|
|
|
- </el-button>
|
|
|
- <el-button
|
|
|
- v-hasPermi="['product:spu:update']"
|
|
|
- link
|
|
|
- type="primary"
|
|
|
- @click="handleStatus02Change(row, ProductSpuStatusEnum.DISABLE.status)"
|
|
|
- >
|
|
|
- 恢复
|
|
|
- </el-button>
|
|
|
- </template>
|
|
|
- <template v-else>
|
|
|
- <el-button
|
|
|
- v-hasPermi="['product:spu:update']"
|
|
|
- link
|
|
|
- type="danger"
|
|
|
- @click="handleStatus02Change(row, ProductSpuStatusEnum.RECYCLE.status)"
|
|
|
- >
|
|
|
- 回收
|
|
|
- </el-button>
|
|
|
- </template>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- </el-table>
|
|
|
- <!-- 分页 -->
|
|
|
- <Pagination
|
|
|
- v-model:limit="queryParams.pageSize"
|
|
|
- v-model:page="queryParams.pageNo"
|
|
|
- :total="total"
|
|
|
- @pagination="getList"
|
|
|
- />
|
|
|
- </ContentWrap>
|
|
|
-</template>
|
|
|
-<script lang="ts" setup>
|
|
|
-import { TabsPaneContext } from 'element-plus'
|
|
|
-import { createImageViewer } from '@/components/ImageViewer'
|
|
|
-import { dateFormatter } from '@/utils/formatTime'
|
|
|
-import { defaultProps, handleTree, treeToString } from '@/utils/tree'
|
|
|
-import { ProductSpuStatusEnum } from '@/utils/constants'
|
|
|
-import { fenToYuan } from '@/utils'
|
|
|
-import download from '@/utils/download'
|
|
|
-import * as ProductSpuApi from '@/api/mall/product/spu'
|
|
|
-import * as ProductCategoryApi from '@/api/mall/product/category'
|
|
|
-
|
|
|
-defineOptions({ name: 'ProductSpu' })
|
|
|
-
|
|
|
-const message = useMessage() // 消息弹窗
|
|
|
-const { t } = useI18n() // 国际化
|
|
|
-const { push } = useRouter() // 路由跳转
|
|
|
-
|
|
|
-const loading = ref(false) // 列表的加载中
|
|
|
-const exportLoading = ref(false) // 导出的加载中
|
|
|
-const total = ref(0) // 列表的总页数
|
|
|
-const list = ref<ProductSpuApi.Spu[]>([]) // 列表的数据
|
|
|
-// tabs 数据
|
|
|
-const tabsData = ref([
|
|
|
- {
|
|
|
- name: '出售中',
|
|
|
- type: 0,
|
|
|
- count: 0
|
|
|
- },
|
|
|
- {
|
|
|
- name: '仓库中',
|
|
|
- type: 1,
|
|
|
- count: 0
|
|
|
- },
|
|
|
- {
|
|
|
- name: '已售罄',
|
|
|
- type: 2,
|
|
|
- count: 0
|
|
|
- },
|
|
|
- {
|
|
|
- name: '警戒库存',
|
|
|
- type: 3,
|
|
|
- count: 0
|
|
|
- },
|
|
|
- {
|
|
|
- name: '回收站',
|
|
|
- type: 4,
|
|
|
- count: 0
|
|
|
- }
|
|
|
-])
|
|
|
-
|
|
|
-const queryParams = ref({
|
|
|
- pageNo: 1,
|
|
|
- pageSize: 10,
|
|
|
- tabType: 0,
|
|
|
- name: '',
|
|
|
- categoryId: undefined,
|
|
|
- createTime: undefined
|
|
|
-}) // 查询参数
|
|
|
-const queryFormRef = ref() // 搜索的表单Ref
|
|
|
-
|
|
|
-/** 查询列表 */
|
|
|
-const getList = async () => {
|
|
|
- loading.value = true
|
|
|
- try {
|
|
|
- const data = await ProductSpuApi.getSpuPage(queryParams.value)
|
|
|
- list.value = data.list
|
|
|
- total.value = data.total
|
|
|
- } finally {
|
|
|
- loading.value = false
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-/** 切换 Tab */
|
|
|
-const handleTabClick = (tab: TabsPaneContext) => {
|
|
|
- queryParams.value.tabType = tab.paneName as number
|
|
|
- getList()
|
|
|
-}
|
|
|
-
|
|
|
-/** 获得每个 Tab 的数量 */
|
|
|
-const getTabsCount = async () => {
|
|
|
- const res = await ProductSpuApi.getTabsCount()
|
|
|
- for (let objName in res) {
|
|
|
- tabsData.value[Number(objName)].count = res[objName]
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-/** 添加到仓库 / 回收站的状态 */
|
|
|
-const handleStatus02Change = async (row: any, newStatus: number) => {
|
|
|
- try {
|
|
|
- // 二次确认
|
|
|
- const text = newStatus === ProductSpuStatusEnum.RECYCLE.status ? '加入到回收站' : '恢复到仓库'
|
|
|
- await message.confirm(`确认要"${row.name}"${text}吗?`)
|
|
|
- // 发起修改
|
|
|
- await ProductSpuApi.updateStatus({ id: row.id, status: newStatus })
|
|
|
- message.success(text + '成功')
|
|
|
- // 刷新 tabs 数据
|
|
|
- await getTabsCount()
|
|
|
- // 刷新列表
|
|
|
- await getList()
|
|
|
- } catch {}
|
|
|
-}
|
|
|
-
|
|
|
-/** 更新上架/下架状态 */
|
|
|
-const handleStatusChange = async (row: any) => {
|
|
|
- try {
|
|
|
- // 二次确认
|
|
|
- const text = row.status ? '上架' : '下架'
|
|
|
- await message.confirm(`确认要${text}"${row.name}"吗?`)
|
|
|
- // 发起修改
|
|
|
- await ProductSpuApi.updateStatus({ id: row.id, status: row.status })
|
|
|
- message.success(text + '成功')
|
|
|
- // 刷新 tabs 数据
|
|
|
- await getTabsCount()
|
|
|
- // 刷新列表
|
|
|
- await getList()
|
|
|
- } catch {
|
|
|
- // 异常时,需要重置回之前的值
|
|
|
- row.status =
|
|
|
- row.status === ProductSpuStatusEnum.DISABLE.status
|
|
|
- ? ProductSpuStatusEnum.ENABLE.status
|
|
|
- : ProductSpuStatusEnum.DISABLE.status
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-/** 删除按钮操作 */
|
|
|
-const handleDelete = async (id: number) => {
|
|
|
- try {
|
|
|
- // 删除的二次确认
|
|
|
- await message.delConfirm()
|
|
|
- // 发起删除
|
|
|
- await ProductSpuApi.deleteSpu(id)
|
|
|
- message.success(t('common.delSuccess'))
|
|
|
- // 刷新tabs数据
|
|
|
- await getTabsCount()
|
|
|
- // 刷新列表
|
|
|
- await getList()
|
|
|
- } catch {}
|
|
|
-}
|
|
|
-
|
|
|
-/** 商品图预览 */
|
|
|
-const imagePreview = (imgUrl: string) => {
|
|
|
- createImageViewer({
|
|
|
- urlList: [imgUrl]
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-/** 搜索按钮操作 */
|
|
|
-const handleQuery = () => {
|
|
|
- getList()
|
|
|
-}
|
|
|
-
|
|
|
-/** 重置按钮操作 */
|
|
|
-const resetQuery = () => {
|
|
|
- queryFormRef.value.resetFields()
|
|
|
- handleQuery()
|
|
|
-}
|
|
|
-
|
|
|
-/** 新增或修改 */
|
|
|
-const openForm = (id?: number) => {
|
|
|
- // 修改
|
|
|
- if (typeof id === 'number') {
|
|
|
- push({ name: 'ProductSpuEdit', params: { id } })
|
|
|
- return
|
|
|
- }
|
|
|
- // 新增
|
|
|
- push({ name: 'ProductSpuAdd' })
|
|
|
-}
|
|
|
-
|
|
|
-/** 查看商品详情 */
|
|
|
-const openDetail = (id: number) => {
|
|
|
- push({ name: 'ProductSpuDetail', params: { id } })
|
|
|
-}
|
|
|
-
|
|
|
-/** 导出按钮操作 */
|
|
|
-const handleExport = async () => {
|
|
|
- try {
|
|
|
- // 导出的二次确认
|
|
|
- await message.exportConfirm()
|
|
|
- // 发起导出
|
|
|
- exportLoading.value = true
|
|
|
- const data = await ProductSpuApi.exportSpu(queryParams)
|
|
|
- download.excel(data, '商品列表.xls')
|
|
|
- } catch {
|
|
|
- } finally {
|
|
|
- exportLoading.value = false
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-/** 获取分类的节点的完整结构 */
|
|
|
-const categoryList = ref() // 分类树
|
|
|
-const formatCategoryName = (categoryId: number) => {
|
|
|
- return treeToString(categoryList.value, categoryId)
|
|
|
-}
|
|
|
-
|
|
|
-/** 激活时 */
|
|
|
-onActivated(() => {
|
|
|
- getList()
|
|
|
-})
|
|
|
-
|
|
|
-/** 初始化 **/
|
|
|
-onMounted(async () => {
|
|
|
- await getTabsCount()
|
|
|
- await getList()
|
|
|
- // 获得分类树
|
|
|
- const data = await ProductCategoryApi.getCategoryList({})
|
|
|
- categoryList.value = handleTree(data, 'id', 'parentId')
|
|
|
-})
|
|
|
-</script>
|
|
|
-<style lang="scss" scoped>
|
|
|
-.spu-table-expand {
|
|
|
- padding-left: 42px;
|
|
|
-
|
|
|
- :deep(.el-form-item__label) {
|
|
|
- width: 82px;
|
|
|
- font-weight: bold;
|
|
|
- color: #99a9bf;
|
|
|
- }
|
|
|
-}
|
|
|
-</style>
|
|
|
+<!-- 商品中心 - 商品列表 -->
|
|
|
+<template>
|
|
|
+ <!-- 搜索工作栏 -->
|
|
|
+ <!-- <ContentWrap>
|
|
|
+ <el-form ref="queryFormRef" :inline="true" :model="queryParams" class="-mb-15px" label-width="68px">
|
|
|
+ <el-form-item label="商品名称" prop="name">
|
|
|
+ <el-input v-model="queryParams.name" class="!w-240px" clearable placeholder="请输入商品名称"
|
|
|
+ @keyup.enter="handleQuery" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="商品分类" prop="categoryId">
|
|
|
+ <el-cascader v-model="queryParams.categoryId" :options="categoryList" :props="defaultProps"
|
|
|
+ class="w-1/1" clearable filterable placeholder="请选择商品分类" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="创建时间" prop="createTime">
|
|
|
+ <el-date-picker v-model="queryParams.createTime"
|
|
|
+ :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" class="!w-240px"
|
|
|
+ end-placeholder="结束日期" start-placeholder="开始日期" type="daterange"
|
|
|
+ value-format="YYYY-MM-DD HH:mm:ss" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button @click="handleQuery">
|
|
|
+ <Icon class="mr-5px" icon="ep:search" />
|
|
|
+ 搜索
|
|
|
+ </el-button>
|
|
|
+ <el-button @click="resetQuery">
|
|
|
+ <Icon class="mr-5px" icon="ep:refresh" />
|
|
|
+ 重置
|
|
|
+ </el-button>
|
|
|
+ <el-button v-hasPermi="['product:spu:create']" plain type="primary" @click="openForm(undefined)">
|
|
|
+ <Icon class="mr-5px" icon="ep:plus" />
|
|
|
+ 新增
|
|
|
+ </el-button>
|
|
|
+ <el-button v-hasPermi="['product:spu:export']" :loading="exportLoading" plain type="success"
|
|
|
+ @click="handleExport">
|
|
|
+ <Icon class="mr-5px" icon="ep:download" />
|
|
|
+ 导出
|
|
|
+ </el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </ContentWrap> -->
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ <!-- 列表 -->
|
|
|
+ <ContentWrap style="position: relative;min-height:400px">
|
|
|
+ <div style="position: relative;">
|
|
|
+ <div style="text-align: right;background: #fafbfc;padding: 10px;">
|
|
|
+ <el-button v-hasPermi="['product:spu:create']" plain type="primary" @click="openForm(0,'create','')">
|
|
|
+ <Icon class="mr-5px" icon="ep:plus" />
|
|
|
+ 新增
|
|
|
+ </el-button>
|
|
|
+
|
|
|
+ <el-input v-model="queryParams.name" class="!w-240px" placeholder="搜索商品名称" @keyup.enter="handleQuery">
|
|
|
+ <template #suffix>
|
|
|
+ <el-icon class="el-input__icon" @click="handleQuery" style="cursor: pointer;">
|
|
|
+ <Search />
|
|
|
+ </el-icon>
|
|
|
+ </template>
|
|
|
+ <template #append>
|
|
|
+ <el-button :icon="Operation" @click="showSearchMore" />
|
|
|
+ </template>
|
|
|
+
|
|
|
+ </el-input>
|
|
|
+ </div>
|
|
|
+ <div class="searchMore" v-if="searchMoreShow">
|
|
|
+ <el-form ref="queryFormRef" :inline="true" :model="queryParams" class="-mb-15px" label-width="68px">
|
|
|
+ <div>
|
|
|
+ <el-form-item label="商品分类" prop="categoryId">
|
|
|
+ <el-cascader v-model="queryParams.categoryId" :options="categoryList" :props="defaultProps"
|
|
|
+ class="w-1/1" clearable filterable placeholder="请选择商品分类" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="创建时间" prop="createTime">
|
|
|
+ <el-date-picker style="margin-right: unset;" v-model="queryParams.createTime"
|
|
|
+ :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" class="!w-240px"
|
|
|
+ end-placeholder="结束日期" start-placeholder="开始日期" type="daterange"
|
|
|
+ value-format="YYYY-MM-DD HH:mm:ss" />
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <el-button @click="hideSearchMore">
|
|
|
+ 收起
|
|
|
+ </el-button>
|
|
|
+ <el-button @click="resetQuery">
|
|
|
+ 重置
|
|
|
+ </el-button>
|
|
|
+ <el-button @click="handleQuery" plain type="primary">
|
|
|
+ <Icon class="mr-5px" icon="ep:search" />
|
|
|
+ 搜索
|
|
|
+ </el-button>
|
|
|
+ <!-- <el-button v-hasPermi="['product:spu:export']" :loading="exportLoading" plain type="success"
|
|
|
+ @click="handleExport">
|
|
|
+ <Icon class="mr-5px" icon="ep:download" />
|
|
|
+ 导出
|
|
|
+ </el-button> -->
|
|
|
+ </div>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <el-tabs v-model="queryParams.tabType" @tab-click="handleTabClick">
|
|
|
+ <el-tab-pane v-for="item in tabsData" :key="item.type" :label="item.name + '(' + item.count + ')'"
|
|
|
+ :name="item.type" />
|
|
|
+ </el-tabs>
|
|
|
+ <el-row>
|
|
|
+ <el-col v-for="(o, index) in list" class="product-card" :key="index" :span="7"
|
|
|
+ @mouseover="handleMouseOver(index)" @mouseout="handleMouseOut(index)">
|
|
|
+ <el-card @click="openDetail(o.id,'view',o.picUrl)">
|
|
|
+ <div style="display: flex;justify-content: space-between;align-items: center;margin-bottom: 5px;">
|
|
|
+ <p style="width: 100%;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;"
|
|
|
+ :title="o.name">{{o.name}}
|
|
|
+ </p>
|
|
|
+
|
|
|
+
|
|
|
+ <el-button :icon="Setting" plain type="primary" circle
|
|
|
+ @click.stop="openForm(o.id,'update',o.picUrl)" v-show="o.showSetting" class="setting" />
|
|
|
+
|
|
|
+ </div>
|
|
|
+ <div style="display:flex;align-items: center;">
|
|
|
+ <img :src="o.picUrl"
|
|
|
+ style="width: 100px;height: 100px;margin-right:10px;border:1px solid #303133;border-radius: 5px;"
|
|
|
+ @error="handleError" />
|
|
|
+ <div style='line-height: 25px;'>
|
|
|
+ <p>¥{{o.price}}</p>
|
|
|
+ <p>销量:{{o.salesCount}}</p>
|
|
|
+ <p>库存:{{o.stock}}</p>
|
|
|
+ <p>状态:{{o.status == 1?"上架":"下架"}}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <div v-if="list.length == 0">
|
|
|
+ <p style="text-align: center;margin-top: 100px;">暂无商品</p>
|
|
|
+ </div>
|
|
|
+ <!--<el-table v-loading="loading" :data="list">
|
|
|
+ <el-table-column type="expand">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-form class="spu-table-expand" label-position="left">
|
|
|
+ <el-row>
|
|
|
+ <el-col :span="24">
|
|
|
+ <el-row>
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-form-item label="商品分类:">
|
|
|
+ <span>{{ formatCategoryName(row.categoryId) }}</span>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-form-item label="市场价:">
|
|
|
+ <span>{{ fenToYuan(row.marketPrice) }}</span>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-form-item label="成本价:">
|
|
|
+ <span>{{ fenToYuan(row.costPrice) }}</span>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-row>
|
|
|
+ <el-col :span="24">
|
|
|
+ <el-row>
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-form-item label="浏览量:">
|
|
|
+ <span>{{ row.browseCount }}</span>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-form-item label="虚拟销量:">
|
|
|
+ <span>{{ row.virtualSalesCount }}</span>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-form>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="商品编号" min-width="140" prop="id" />
|
|
|
+ <el-table-column label="商品信息" min-width="300">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="flex">
|
|
|
+ <el-image fit="cover" :src="row.picUrl" class="flex-none w-50px h-50px"
|
|
|
+ @click="imagePreview(row.picUrl)" />
|
|
|
+ <div class="ml-4 overflow-hidden">
|
|
|
+ <el-tooltip effect="dark" :content="row.name" placement="top">
|
|
|
+ <div>
|
|
|
+ {{ row.name }}
|
|
|
+ </div>
|
|
|
+ </el-tooltip>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column align="center" label="价格" min-width="160" prop="price">
|
|
|
+ <template #default="{ row }"> ¥ {{ fenToYuan(row.price) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column align="center" label="销量" min-width="90" prop="salesCount" />
|
|
|
+ <el-table-column align="center" label="库存" min-width="90" prop="stock" />
|
|
|
+ <el-table-column align="center" label="排序" min-width="70" prop="sort" />
|
|
|
+ <el-table-column align="center" label="销售状态" min-width="80">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <template v-if="row.status >= 0">
|
|
|
+ <el-switch v-model="row.status" :active-value="1" :inactive-value="0" active-text="上架"
|
|
|
+ inactive-text="下架" inline-prompt @change="handleStatusChange(row)" />
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <el-tag type="info">回收站</el-tag>
|
|
|
+ </template>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column :formatter="dateFormatter" align="center" label="创建时间" prop="createTime" width="180" />
|
|
|
+ <el-table-column align="center" fixed="right" label="操作" min-width="200">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button link type="primary" @click="openDetail(row.id)"> 详情 </el-button>
|
|
|
+ <el-button v-hasPermi="['product:spu:update']" link type="primary" @click="openForm(row.id)">
|
|
|
+ 修改
|
|
|
+ </el-button>
|
|
|
+ <template v-if="queryParams.tabType === 4">
|
|
|
+ <el-button v-hasPermi="['product:spu:delete']" link type="danger" @click="handleDelete(row.id)">
|
|
|
+ 删除
|
|
|
+ </el-button>
|
|
|
+ <el-button v-hasPermi="['product:spu:update']" link type="primary"
|
|
|
+ @click="handleStatus02Change(row, ProductSpuStatusEnum.DISABLE.status)">
|
|
|
+ 恢复
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <el-button v-hasPermi="['product:spu:update']" link type="danger"
|
|
|
+ @click="handleStatus02Change(row, ProductSpuStatusEnum.RECYCLE.status)">
|
|
|
+ 回收
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>-->
|
|
|
+
|
|
|
+ <!-- 分页 -->
|
|
|
+ <Pagination v-model:limit="queryParams.pageSize" v-model:page="queryParams.pageNo" :total="total"
|
|
|
+ @pagination="getList" />
|
|
|
+ <!-- 表单弹窗:添加/修改 -->
|
|
|
+ <SpuIndex ref="formRef" @success="getList" />
|
|
|
+ </ContentWrap>
|
|
|
+</template>
|
|
|
+<script lang="ts" setup>
|
|
|
+ import { TabsPaneContext } from 'element-plus'
|
|
|
+ import { Setting, Search, Operation } from '@element-plus/icons-vue'
|
|
|
+ import { createImageViewer } from '@/components/ImageViewer'
|
|
|
+ import { dateFormatter } from '@/utils/formatTime'
|
|
|
+ import { defaultProps, handleTree, treeToString } from '@/utils/tree'
|
|
|
+ import { ProductSpuStatusEnum } from '@/utils/constants'
|
|
|
+ import { fenToYuan } from '@/utils'
|
|
|
+ import download from '@/utils/download'
|
|
|
+ import * as ProductSpuApi from '@/api/mall/product/spu'
|
|
|
+ import * as ProductCategoryApi from '@/api/mall/product/category'
|
|
|
+ import SpuIndex from "./form/index.vue"
|
|
|
+
|
|
|
+ defineOptions({ name: 'ProductSpu' })
|
|
|
+
|
|
|
+ const message = useMessage() // 消息弹窗
|
|
|
+ const { t } = useI18n() // 国际化
|
|
|
+ const { push } = useRouter() // 路由跳转
|
|
|
+
|
|
|
+ const loading = ref(false) // 列表的加载中
|
|
|
+ const exportLoading = ref(false) // 导出的加载中
|
|
|
+ const total = ref(0) // 列表的总页数
|
|
|
+ const list = ref<ProductSpuApi.Spu[]>([]) // 列表的数据
|
|
|
+ // tabs 数据
|
|
|
+ const tabsData = ref([
|
|
|
+ {
|
|
|
+ name: '出售中',
|
|
|
+ type: 0,
|
|
|
+ count: 0
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '仓库中',
|
|
|
+ type: 1,
|
|
|
+ count: 0
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '已售罄',
|
|
|
+ type: 2,
|
|
|
+ count: 0
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '警戒库存',
|
|
|
+ type: 3,
|
|
|
+ count: 0
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '回收站',
|
|
|
+ type: 4,
|
|
|
+ count: 0
|
|
|
+ }
|
|
|
+ ])
|
|
|
+
|
|
|
+ const queryParams = ref({
|
|
|
+ pageNo: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ tabType: 0,
|
|
|
+ name: '',
|
|
|
+ categoryId: undefined,
|
|
|
+ createTime: undefined
|
|
|
+ }) // 查询参数
|
|
|
+ const queryFormRef = ref() // 搜索的表单Ref
|
|
|
+ const searchMoreShow = ref(false)
|
|
|
+ // 鼠标移入显示变动图标
|
|
|
+ function handleMouseOver(index) {
|
|
|
+ list.value[index].showSetting = true;
|
|
|
+ }
|
|
|
+ // 鼠标移出不显示图标
|
|
|
+ function handleMouseOut(index) {
|
|
|
+ list.value[index].showSetting = false;
|
|
|
+ }
|
|
|
+ // 裂图替换
|
|
|
+ const handleError = (e) => {
|
|
|
+ e.target.src = "https://static.iocoder.cn/mall/a79f5d2ea6bf0c3c11b2127332dfe2df.jpg"
|
|
|
+ }
|
|
|
+ // 打开更多搜索条件
|
|
|
+ const showSearchMore = () => {
|
|
|
+ searchMoreShow.value = !searchMoreShow.value
|
|
|
+ }
|
|
|
+ const hideSearchMore = () => {
|
|
|
+ searchMoreShow.value = false
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 查询列表 */
|
|
|
+ const getList = async () => {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const data = await ProductSpuApi.getSpuPage(queryParams.value)
|
|
|
+ data.list.forEach(obj => {
|
|
|
+ obj.showSetting = false;
|
|
|
+ });
|
|
|
+
|
|
|
+ list.value = data.list
|
|
|
+ // console.log(list.value)
|
|
|
+ total.value = data.total
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ searchMoreShow.value = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 切换 Tab */
|
|
|
+ const handleTabClick = (tab : TabsPaneContext) => {
|
|
|
+ queryParams.value.tabType = tab.paneName as number
|
|
|
+ getList()
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 获得每个 Tab 的数量 */
|
|
|
+ const getTabsCount = async () => {
|
|
|
+ const res = await ProductSpuApi.getTabsCount()
|
|
|
+ for (let objName in res) {
|
|
|
+ tabsData.value[Number(objName)].count = res[objName]
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 添加到仓库 / 回收站的状态 */
|
|
|
+ const handleStatus02Change = async (row : any, newStatus : number) => {
|
|
|
+ try {
|
|
|
+ // 二次确认
|
|
|
+ const text = newStatus === ProductSpuStatusEnum.RECYCLE.status ? '加入到回收站' : '恢复到仓库'
|
|
|
+ await message.confirm(`确认要"${row.name}"${text}吗?`)
|
|
|
+ // 发起修改
|
|
|
+ await ProductSpuApi.updateStatus({ id: row.id, status: newStatus })
|
|
|
+ message.success(text + '成功')
|
|
|
+ // 刷新 tabs 数据
|
|
|
+ await getTabsCount()
|
|
|
+ // 刷新列表
|
|
|
+ await getList()
|
|
|
+ } catch { }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 更新上架/下架状态 */
|
|
|
+ const handleStatusChange = async (row : any) => {
|
|
|
+ try {
|
|
|
+ // 二次确认
|
|
|
+ const text = row.status ? '上架' : '下架'
|
|
|
+ await message.confirm(`确认要${text}"${row.name}"吗?`)
|
|
|
+ // 发起修改
|
|
|
+ await ProductSpuApi.updateStatus({ id: row.id, status: row.status })
|
|
|
+ message.success(text + '成功')
|
|
|
+ // 刷新 tabs 数据
|
|
|
+ await getTabsCount()
|
|
|
+ // 刷新列表
|
|
|
+ await getList()
|
|
|
+ } catch {
|
|
|
+ // 异常时,需要重置回之前的值
|
|
|
+ row.status =
|
|
|
+ row.status === ProductSpuStatusEnum.DISABLE.status
|
|
|
+ ? ProductSpuStatusEnum.ENABLE.status
|
|
|
+ : ProductSpuStatusEnum.DISABLE.status
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 删除按钮操作 */
|
|
|
+ const handleDelete = async (id : number) => {
|
|
|
+ try {
|
|
|
+ // 删除的二次确认
|
|
|
+ await message.delConfirm()
|
|
|
+ // 发起删除
|
|
|
+ await ProductSpuApi.deleteSpu(id)
|
|
|
+ message.success(t('common.delSuccess'))
|
|
|
+ // 刷新tabs数据
|
|
|
+ await getTabsCount()
|
|
|
+ // 刷新列表
|
|
|
+ await getList()
|
|
|
+ } catch { }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 商品图预览 */
|
|
|
+ const imagePreview = (imgUrl : string) => {
|
|
|
+ createImageViewer({
|
|
|
+ urlList: [imgUrl]
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 搜索按钮操作 */
|
|
|
+ const handleQuery = () => {
|
|
|
+ getList()
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 重置按钮操作 */
|
|
|
+ const resetQuery = () => {
|
|
|
+ queryParams.value.name = ""
|
|
|
+ queryFormRef.value.resetFields()
|
|
|
+ handleQuery()
|
|
|
+ }
|
|
|
+
|
|
|
+ const formRef = ref()
|
|
|
+ /** 新增或修改 */
|
|
|
+ const openForm = (id : number, type : string, imageUrl : string) => {
|
|
|
+ formRef.value.open(id, type, imageUrl)
|
|
|
+ // 修改
|
|
|
+ // if (typeof id === 'number') {
|
|
|
+ // push({ name: 'ProductSpuEdit', params: { id } })
|
|
|
+ // return
|
|
|
+ // }
|
|
|
+ // 新增
|
|
|
+ // push({ name: 'ProductSpuAdd' })
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 查看商品详情 */
|
|
|
+ const openDetail = (id : number, type : string, imageUrl : string) => {
|
|
|
+ formRef.value.open(id, type, imageUrl)
|
|
|
+ // push({ name: 'ProductSpuDetail', params: { id } })
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 导出按钮操作 */
|
|
|
+ const handleExport = async () => {
|
|
|
+ try {
|
|
|
+ // 导出的二次确认
|
|
|
+ await message.exportConfirm()
|
|
|
+ // 发起导出
|
|
|
+ exportLoading.value = true
|
|
|
+ const data = await ProductSpuApi.exportSpu(queryParams)
|
|
|
+ download.excel(data, '商品列表.xls')
|
|
|
+ } catch {
|
|
|
+ } finally {
|
|
|
+ exportLoading.value = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 获取分类的节点的完整结构 */
|
|
|
+ const categoryList = ref() // 分类树
|
|
|
+ const formatCategoryName = (categoryId : number) => {
|
|
|
+ return treeToString(categoryList.value, categoryId)
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 激活时 */
|
|
|
+ onActivated(() => {
|
|
|
+ getList()
|
|
|
+ })
|
|
|
+
|
|
|
+ /** 初始化 **/
|
|
|
+ onMounted(async () => {
|
|
|
+ await getTabsCount()
|
|
|
+ await getList()
|
|
|
+ // 获得分类树
|
|
|
+ const data = await ProductCategoryApi.getCategoryList({})
|
|
|
+ categoryList.value = handleTree(data, 'id', 'parentId')
|
|
|
+ })
|
|
|
+</script>
|
|
|
+<style lang="scss" scoped>
|
|
|
+ .searchMore {
|
|
|
+ text-align: right;
|
|
|
+ background: #f3f5f8;
|
|
|
+ padding: 10px;
|
|
|
+ position: absolute;
|
|
|
+ height: 100px;
|
|
|
+ width: calc(100% - 20px);
|
|
|
+ z-index: 111;
|
|
|
+ box-shadow: 0px 9px 6px rgba(0, 0, 0, 0.2);
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-tabs {
|
|
|
+ position: absolute;
|
|
|
+ z-index: 111;
|
|
|
+ top: 25px;
|
|
|
+ left: 40px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .product-card {
|
|
|
+
|
|
|
+ font-size: 14px;
|
|
|
+ margin: 10px;
|
|
|
+
|
|
|
+ .el-card {
|
|
|
+ padding: 15px;
|
|
|
+ cursor: pointer;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ :deep(.el-card__body) {
|
|
|
+ padding: unset;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .setting {
|
|
|
+ top: 8px;
|
|
|
+ right: 8px;
|
|
|
+ position: absolute;
|
|
|
+ }
|
|
|
+
|
|
|
+ p,
|
|
|
+ div {
|
|
|
+ margin: unset;
|
|
|
+ padding: unset;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .spu-table-expand {
|
|
|
+ padding-left: 42px;
|
|
|
+
|
|
|
+ :deep(.el-form-item__label) {
|
|
|
+ width: 82px;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #99a9bf;
|
|
|
+ }
|
|
|
+ }
|
|
|
+</style>
|