|  | @@ -1,449 +1,535 @@
 | 
	
		
			
				|  |  | -<!-- 商品中心 - 商品列表  -->
 | 
	
		
			
				|  |  | -<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 style="" class="spu-div">
 | 
	
		
			
				|  |  | +		<div style="position: relative;">
 | 
	
		
			
				|  |  | +			<div style="text-align: right;background: #fafbfc;padding: 10px;">
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				<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>
 | 
	
		
			
				|  |  | +				 
 | 
	
		
			
				|  |  | +				<el-button v-hasPermi="['product:spu:create']" @click="openForm('create','',0)">
 | 
	
		
			
				|  |  | +					<Icon class="mr-5px" icon="ep:plus" />
 | 
	
		
			
				|  |  | +					新增
 | 
	
		
			
				|  |  | +				</el-button>
 | 
	
		
			
				|  |  | +			</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" class="parent-tabs">
 | 
	
		
			
				|  |  | +			<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('view',o,ProductSpuStatusEnum.RECYCLE.status,queryParams.tabType)">
 | 
	
		
			
				|  |  | +					<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;font-size: 16px;color:#000"
 | 
	
		
			
				|  |  | +							:title="o.name">{{o.name}}
 | 
	
		
			
				|  |  | +						</p>
 | 
	
		
			
				|  |  | +						<div @click.stop="openForm('update',o,ProductSpuStatusEnum.RECYCLE.status,queryParams.tabType)"
 | 
	
		
			
				|  |  | +							v-show="o.showSetting" class="setting">
 | 
	
		
			
				|  |  | +							<el-icon size="20" color="rgb(220 223 231)">
 | 
	
		
			
				|  |  | +								<Setting />
 | 
	
		
			
				|  |  | +							</el-icon>
 | 
	
		
			
				|  |  | +						</div>
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					</div>
 | 
	
		
			
				|  |  | +					<div style="display:flex;align-items: center;">
 | 
	
		
			
				|  |  | +						<div style="width: 178px;height: 100px;margin-right:10px;border:1px solid rgb(220 223 231);border-radius: 5px;display: flex;align-items: center;justify-content: center;"
 | 
	
		
			
				|  |  | +							@error="handleError">
 | 
	
		
			
				|  |  | +							<img :src="o.picUrl" style="width: auto;height: auto;max-width: 100%;max-height: 100%;" />
 | 
	
		
			
				|  |  | +						</div>
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +						<div style='line-height: 25px;'>
 | 
	
		
			
				|  |  | +							<p>¥{{ fenToYuan(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
 | 
	
		
			
				|  |  | +			getTabsCount()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	/** 切换 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.categoryId = undefined
 | 
	
		
			
				|  |  | +		queryParams.value.createTime = undefined
 | 
	
		
			
				|  |  | +		queryParams.value.name = ""
 | 
	
		
			
				|  |  | +		// queryFormRef.value.resetFields()
 | 
	
		
			
				|  |  | +		// console.log(queryParams.value)
 | 
	
		
			
				|  |  | +		// console.log(queryFormRef.value)
 | 
	
		
			
				|  |  | +		handleQuery()
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	const formRef = ref()
 | 
	
		
			
				|  |  | +	/** 新增或修改 */
 | 
	
		
			
				|  |  | +	const openForm = (type : string, row : any, newStatus : number, tabType : number) => {
 | 
	
		
			
				|  |  | +		formRef.value.open(type, row, newStatus, tabType)
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	/** 查看商品详情 */
 | 
	
		
			
				|  |  | +	const openDetail = (type : string, row : any, newStatus : number) => {
 | 
	
		
			
				|  |  | +		formRef.value.open(type, row, newStatus)
 | 
	
		
			
				|  |  | +		// 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-div {
 | 
	
		
			
				|  |  | +		position: relative;
 | 
	
		
			
				|  |  | +		min-height: 400px;
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	::v-deep .parent-tabs .el-tabs__nav-wrap::after {
 | 
	
		
			
				|  |  | +		position: static !important;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	::v-deep .parent-tabs .el-tabs__active-bar {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		background-color: unset;
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	.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 {
 | 
	
		
			
				|  |  | +			width: 30px;
 | 
	
		
			
				|  |  | +			top: 0px;
 | 
	
		
			
				|  |  | +			right: 0px;
 | 
	
		
			
				|  |  | +			position: absolute;
 | 
	
		
			
				|  |  | +			height: 30px;
 | 
	
		
			
				|  |  | +			line-height: 30px;
 | 
	
		
			
				|  |  | +			text-align: center;
 | 
	
		
			
				|  |  | +			display: flex;
 | 
	
		
			
				|  |  | +			align-items: center;
 | 
	
		
			
				|  |  | +			justify-content: center;
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		.setting:hover {
 | 
	
		
			
				|  |  | +			background: #666
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		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>
 |