index.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. <!-- 商品中心 - 商品列表 -->
  2. <template>
  3. <!-- 列表 -->
  4. <ContentWrap style="" class="container">
  5. <div class="search">
  6. <!-- 左侧的 -->
  7. <el-tabs v-model="queryParams.tabType" @tab-click="handleTabClick" class="parent-tabs" v-if="!mobile ">
  8. <el-tab-pane v-for="item in tabsData" :key="item.type" :label="item.name + '(' + item.count + ')'"
  9. :name="item.type" />
  10. </el-tabs>
  11. <div style="text-align: right;" class="search-input">
  12. <el-input v-model="queryParams.name" placeholder="搜索商品名称" @keyup.enter="handleQuery" class="!w-240px">
  13. <template #suffix>
  14. <el-icon class="el-input__icon" @click="handleQuery" style="cursor: pointer;">
  15. <Search />
  16. </el-icon>
  17. </template>
  18. <template #append>
  19. <el-button :icon="Operation" @click="showSearchMore" />
  20. </template>
  21. </el-input>
  22. <el-button v-hasPermi="['product:spu:create']" @click="openForm('create', '', 0)" class="ml-5px" v-if="!mobile && !isDetail && !merchantId">
  23. <Icon class="mr-5px " icon="ep:plus" />
  24. 新增
  25. </el-button>
  26. </div>
  27. </div>
  28. <div class="searchMore" v-show="searchMoreShow">
  29. <el-form ref="queryFormRef" :inline="true" :model="queryParams" class="-mb-15px" label-width="68px">
  30. <el-form-item label="商品分类" prop="categoryId">
  31. <el-cascader v-model="queryParams.categoryId" :options="categoryList" :props="defaultProps" clearable
  32. filterable placeholder="请选择商品分类" />
  33. </el-form-item>
  34. <el-form-item label="商品状态" prop="">
  35. <el-select v-model="queryParams.tabType" placeholder="全部">
  36. <el-option v-for="dict in tabsData" :key="dict.type" :label="dict.name" :value="dict.type" />
  37. </el-select>
  38. </el-form-item>
  39. <el-form-item label="创建时间" prop="createTime">
  40. <el-date-picker style="margin-right: unset;width:auto !important" v-model="queryParams.createTime"
  41. :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" end-placeholder="结束日期"
  42. start-placeholder="开始日期" type="daterange" value-format="YYYY-MM-DD HH:mm:ss" />
  43. </el-form-item>
  44. <el-row>
  45. <el-col>
  46. <el-form-item>
  47. <el-button @click="hideSearchMore">
  48. 收起
  49. </el-button>
  50. <el-button @click="resetQuery">
  51. 重置
  52. </el-button>
  53. <el-button @click="handleQuery" plain type="primary">
  54. <Icon class="mr-5px" icon="ep:search" />
  55. 搜索
  56. </el-button>
  57. </el-form-item>
  58. </el-col>
  59. </el-row>
  60. </el-form>
  61. </div>
  62. <el-row>
  63. <el-col v-for="(o, index) in list" class="card" :key="index" :span="7" :xl="7" :lg="7" :md="7" :sm="24" :xs="24"
  64. @mouseover="handleMouseOver(index)" @mouseout="handleMouseOut(index)">
  65. <el-card @click="openDetail('view', o, ProductSpuStatusEnum.RECYCLE.status, queryParams.tabType)">
  66. <div style="display: flex;justify-content: space-between;align-items: center;margin-bottom: 5px;">
  67. <p style="width: 100%;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;font-size: 16px;color:#000"
  68. :title="o.name">{{ o.name }}
  69. </p>
  70. <div @click.stop="openForm('update', o, ProductSpuStatusEnum.RECYCLE.status, queryParams.tabType)"
  71. v-show="o.showSetting && !mobile && !merchantId" class="setting">
  72. <el-icon size="20" color="rgb(220 223 231)">
  73. <Setting />
  74. </el-icon>
  75. </div>
  76. </div>
  77. <div style="display:flex;align-items: center;">
  78. <div
  79. 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;overflow: hidden;">
  80. <img :src="o.picUrl" style="width: 100%; height: 100%; object-fit: cover; object-position: center;" />
  81. </div>
  82. <div style='line-height: 25px;'>
  83. <p>¥{{ fenToYuan(o.price) }}</p>
  84. <p>销量:{{ o.salesCount }}</p>
  85. <p>库存:{{ o.stock }}</p>
  86. <p>状态:{{ o.status == 1 ? "上架" : "下架" }}</p>
  87. </div>
  88. </div>
  89. </el-card>
  90. </el-col>
  91. </el-row>
  92. <div v-if="list.length == 0">
  93. <p style="text-align: center;margin-top: 100px;">暂无商品</p>
  94. </div>
  95. <!-- 分页 -->
  96. <Pagination v-model:limit="queryParams.pageSize" v-model:page="queryParams.pageNo" :total="total"
  97. @pagination="getList" />
  98. <!-- 表单弹窗:添加/修改 -->
  99. <SpuIndex ref="formRef" @success="getList" />
  100. </ContentWrap>
  101. </template>
  102. <script lang="ts" setup>
  103. import { TabsPaneContext } from 'element-plus'
  104. import { Setting, Search, Operation } from '@element-plus/icons-vue'
  105. import { createImageViewer } from '@/components/ImageViewer'
  106. import { dateFormatter } from '@/utils/formatTime'
  107. import { defaultProps, handleTree, treeToString } from '@/utils/tree'
  108. import { ProductSpuStatusEnum } from '@/utils/constants'
  109. import { fenToYuan } from '@/utils'
  110. import download from '@/utils/download'
  111. import * as ProductSpuApi from '@/api/mall/product/spu'
  112. import * as ProductCategoryApi from '@/api/mall/product/category'
  113. import SpuIndex from "./form/index.vue"
  114. // 从路由中引入store中的app仓库
  115. import { useAppStore } from '@/store/modules/app'
  116. const appStore = useAppStore()
  117. const mobile = computed(() => appStore.getMobile)
  118. // 从商户选项卡中传过来的id 如果有的话即证明是从商户中传过来的
  119. const props = defineProps({
  120. merchantId: {
  121. type: Number,
  122. default: 0
  123. },
  124. isDetail:{
  125. type: Boolean,
  126. default: false
  127. }
  128. });
  129. defineOptions({ name: 'ProductSpu' })
  130. const message = useMessage() // 消息弹窗
  131. const { t } = useI18n() // 国际化
  132. const { push } = useRouter() // 路由跳转
  133. const loading = ref(false) // 列表的加载中
  134. const exportLoading = ref(false) // 导出的加载中
  135. const total = ref(0) // 列表的总页数
  136. const list = ref<ProductSpuApi.Spu[]>([]) // 列表的数据
  137. // tabs 数据
  138. const tabsData = ref([
  139. {
  140. name: '全部',
  141. type: '',
  142. count: 0
  143. },
  144. {
  145. name: '出售中',
  146. type: 0,
  147. count: 0
  148. },
  149. {
  150. name: '仓库中',
  151. type: 1,
  152. count: 0
  153. },
  154. {
  155. name: '已售罄',
  156. type: 2,
  157. count: 0
  158. },
  159. {
  160. name: '警戒库存',
  161. type: 3,
  162. count: 0
  163. },
  164. {
  165. name: '回收站',
  166. type: 4,
  167. count: 0
  168. }
  169. ])
  170. const queryParams = ref({
  171. pageNo: 1,
  172. pageSize: 10,
  173. tabType: '',
  174. name: '',
  175. categoryId: undefined,
  176. createTime: undefined
  177. }) // 查询参数
  178. const queryFormRef = ref() // 搜索的表单Ref
  179. const searchMoreShow = ref(false)
  180. // 鼠标移入显示变动图标
  181. function handleMouseOver(index) {
  182. list.value[index].showSetting = true;
  183. }
  184. // 鼠标移出不显示图标
  185. function handleMouseOut(index) {
  186. list.value[index].showSetting = false;
  187. }
  188. // 打开更多搜索条件
  189. const showSearchMore = () => {
  190. searchMoreShow.value = !searchMoreShow.value
  191. }
  192. const hideSearchMore = () => {
  193. searchMoreShow.value = false
  194. }
  195. /** 查询列表 */
  196. const getList = async () => {
  197. loading.value = true
  198. if (props.merchantId) {
  199. queryParams.value.merchantId = props.merchantId
  200. }
  201. try {
  202. const data = await ProductSpuApi.getSpuPage(queryParams.value)
  203. data.list.forEach(obj => {
  204. obj.showSetting = false;
  205. });
  206. list.value = data.list
  207. // console.log(list.value)
  208. total.value = data.total
  209. } finally {
  210. loading.value = false
  211. searchMoreShow.value = false
  212. getTabsCount()
  213. }
  214. }
  215. /** 切换 Tab */
  216. const handleTabClick = (tab: TabsPaneContext) => {
  217. queryParams.value.tabType = tab.paneName
  218. getList()
  219. }
  220. /** 获得每个 Tab 的数量 */
  221. const getTabsCount = async () => {
  222. const res = await ProductSpuApi.getTabsCount()
  223. tabsData.value[0].count = 0
  224. for (let objName in res) {
  225. if (objName <= 1 || objName === '4') {
  226. tabsData.value[0].count += res[objName]
  227. }
  228. tabsData.value[Number(objName) + 1].count = res[objName]
  229. }
  230. }
  231. /** 添加到仓库 / 回收站的状态 */
  232. const handleStatus02Change = async (row: any, newStatus: number) => {
  233. try {
  234. // 二次确认
  235. const text = newStatus === ProductSpuStatusEnum.RECYCLE.status ? '加入到回收站' : '恢复到仓库'
  236. await message.confirm(`确认要"${row.name}"${text}吗?`)
  237. // 发起修改
  238. await ProductSpuApi.updateStatus({ id: row.id, status: newStatus })
  239. message.success(text + '成功')
  240. // 刷新 tabs 数据
  241. await getTabsCount()
  242. // 刷新列表
  243. await getList()
  244. } catch { }
  245. }
  246. /** 更新上架/下架状态 */
  247. const handleStatusChange = async (row: any) => {
  248. try {
  249. // 二次确认
  250. const text = row.status ? '上架' : '下架'
  251. await message.confirm(`确认要${text}"${row.name}"吗?`)
  252. // 发起修改
  253. await ProductSpuApi.updateStatus({ id: row.id, status: row.status })
  254. message.success(text + '成功')
  255. // 刷新 tabs 数据
  256. await getTabsCount()
  257. // 刷新列表
  258. await getList()
  259. } catch {
  260. // 异常时,需要重置回之前的值
  261. row.status =
  262. row.status === ProductSpuStatusEnum.DISABLE.status
  263. ? ProductSpuStatusEnum.ENABLE.status
  264. : ProductSpuStatusEnum.DISABLE.status
  265. }
  266. }
  267. /** 删除按钮操作 */
  268. const handleDelete = async (id: number) => {
  269. try {
  270. // 删除的二次确认
  271. await message.delConfirm()
  272. // 发起删除
  273. await ProductSpuApi.deleteSpu(id)
  274. message.success(t('common.delSuccess'))
  275. // 刷新tabs数据
  276. await getTabsCount()
  277. // 刷新列表
  278. await getList()
  279. } catch { }
  280. }
  281. /** 商品图预览 */
  282. const imagePreview = (imgUrl: string) => {
  283. createImageViewer({
  284. urlList: [imgUrl]
  285. })
  286. }
  287. /** 搜索按钮操作 */
  288. const handleQuery = () => {
  289. getList()
  290. }
  291. /** 重置按钮操作 */
  292. const resetQuery = () => {
  293. queryParams.value.categoryId = undefined
  294. queryParams.value.createTime = undefined
  295. queryParams.value.name = ""
  296. queryParams.value.tabType = ''
  297. handleQuery()
  298. }
  299. const formRef = ref()
  300. /** 新增或修改 */
  301. const openForm = (type: string, row: any, newStatus: number, tabType: number) => {
  302. formRef.value.open(type, row, newStatus, tabType)
  303. }
  304. /** 查看商品详情 */
  305. const openDetail = (type: string, row: any, newStatus: number) => {
  306. const id = row.id
  307. if (mobile.value) {
  308. push({ name: 'ProductSpuMobileDetail', params: { id } })
  309. } else {
  310. formRef.value.open(type, row, newStatus)
  311. }
  312. }
  313. /** 导出按钮操作 */
  314. const handleExport = async () => {
  315. try {
  316. // 导出的二次确认
  317. await message.exportConfirm()
  318. // 发起导出
  319. exportLoading.value = true
  320. const data = await ProductSpuApi.exportSpu(queryParams)
  321. download.excel(data, '商品列表.xls')
  322. } catch {
  323. } finally {
  324. exportLoading.value = false
  325. }
  326. }
  327. /** 获取分类的节点的完整结构 */
  328. const categoryList = ref() // 分类树
  329. const formatCategoryName = (categoryId: number) => {
  330. return treeToString(categoryList.value, categoryId)
  331. }
  332. /** 激活时 */
  333. onActivated(() => {
  334. getList()
  335. })
  336. /** 初始化 **/
  337. onMounted(async () => {
  338. await getTabsCount()
  339. await getList()
  340. // 获得分类树
  341. const data = await ProductCategoryApi.getCategoryList({})
  342. categoryList.value = handleTree(data, 'id', 'parentId')
  343. })
  344. </script>
  345. <style src="@/styles/SearchIndex.css"  lang="scss" scoped></style>