SpuSelect.vue 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. <template>
  2. <Dialog v-model="dialogVisible" :appendToBody="true" :title="dialogTitle" width="70%">
  3. <ContentWrap>
  4. <el-row :gutter="20" class="mb-10px">
  5. <el-col :span="6">
  6. <el-input
  7. v-model="queryParams.name"
  8. class="!w-240px"
  9. clearable
  10. placeholder="请输入商品名称"
  11. @keyup.enter="handleQuery"
  12. />
  13. </el-col>
  14. <el-col :span="6">
  15. <el-tree-select
  16. v-model="queryParams.categoryId"
  17. :data="categoryList"
  18. :props="defaultProps"
  19. check-strictly
  20. class="w-1/1"
  21. node-key="id"
  22. placeholder="请选择商品分类"
  23. />
  24. </el-col>
  25. <el-col :span="6">
  26. <el-date-picker
  27. v-model="queryParams.createTime"
  28. :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
  29. class="!w-240px"
  30. end-placeholder="结束日期"
  31. start-placeholder="开始日期"
  32. type="daterange"
  33. value-format="YYYY-MM-DD HH:mm:ss"
  34. />
  35. </el-col>
  36. <el-col :span="6">
  37. <el-button @click="handleQuery">
  38. <Icon class="mr-5px" icon="ep:search" />
  39. 搜索
  40. </el-button>
  41. <el-button @click="resetQuery">
  42. <Icon class="mr-5px" icon="ep:refresh" />
  43. 重置
  44. </el-button>
  45. </el-col>
  46. </el-row>
  47. <el-table
  48. ref="spuListRef"
  49. v-loading="loading"
  50. :data="list"
  51. :expand-row-keys="expandRowKeys"
  52. row-key="id"
  53. @expand-change="expandChange"
  54. @selection-change="selectSpu"
  55. >
  56. <el-table-column v-if="isSelectSku" type="expand" width="30">
  57. <template #default>
  58. <SkuList
  59. v-if="isExpand"
  60. ref="skuListRef"
  61. :isComponent="true"
  62. :isDetail="true"
  63. :prop-form-data="spuData"
  64. :property-list="propertyList"
  65. @selection-change="selectSku"
  66. />
  67. </template>
  68. </el-table-column>
  69. <el-table-column type="selection" width="55" />
  70. <el-table-column key="id" align="center" label="商品编号" prop="id" />
  71. <el-table-column label="商品图" min-width="80">
  72. <template #default="{ row }">
  73. <el-image :src="row.picUrl" class="w-30px h-30px" @click="imagePreview(row.picUrl)" />
  74. </template>
  75. </el-table-column>
  76. <el-table-column
  77. :show-overflow-tooltip="true"
  78. label="商品名称"
  79. min-width="300"
  80. prop="name"
  81. />
  82. <el-table-column align="center" label="商品售价" min-width="90" prop="price">
  83. <template #default="{ row }">
  84. {{ formatToFraction(row.price) }}
  85. </template>
  86. </el-table-column>
  87. <el-table-column align="center" label="销量" min-width="90" prop="salesCount" />
  88. <el-table-column align="center" label="库存" min-width="90" prop="stock" />
  89. <el-table-column align="center" label="排序" min-width="70" prop="sort" />
  90. <el-table-column
  91. :formatter="dateFormatter"
  92. align="center"
  93. label="创建时间"
  94. prop="createTime"
  95. width="180"
  96. />
  97. </el-table>
  98. <!-- 分页 -->
  99. <Pagination
  100. v-model:limit="queryParams.pageSize"
  101. v-model:page="queryParams.pageNo"
  102. :total="total"
  103. @pagination="getList"
  104. />
  105. </ContentWrap>
  106. <template #footer>
  107. <el-button type="primary" @click="confirm">确 定</el-button>
  108. <el-button @click="dialogVisible = false">取 消</el-button>
  109. </template>
  110. </Dialog>
  111. </template>
  112. <script lang="ts" setup>
  113. import { getPropertyList, Properties, SkuList } from '@/views/mall/product/spu/components'
  114. import { ElTable } from 'element-plus'
  115. import { dateFormatter } from '@/utils/formatTime'
  116. import { createImageViewer } from '@/components/ImageViewer'
  117. import { formatToFraction } from '@/utils'
  118. import { defaultProps, handleTree } from '@/utils/tree'
  119. import * as ProductCategoryApi from '@/api/mall/product/category'
  120. import * as ProductSpuApi from '@/api/mall/product/spu'
  121. import { propTypes } from '@/utils/propTypes'
  122. defineOptions({ name: 'PromotionSpuSelect' })
  123. const props = defineProps({
  124. // 默认不需要(不需要的情况下只返回 spu,需要的情况下返回 选中的 spu 和 sku 列表)
  125. // 其它活动需要选择商品和商品属性导入此组件即可,需添加组件属性 :isSelectSku='true'
  126. isSelectSku: propTypes.bool.def(false) // 是否需要选择 sku 属性
  127. })
  128. const message = useMessage() // 消息弹窗
  129. const total = ref(0) // 列表的总页数
  130. const list = ref<any[]>([]) // 列表的数据
  131. const loading = ref(false) // 列表的加载中
  132. const dialogVisible = ref(false) // 弹窗的是否展示
  133. const dialogTitle = ref('') // 弹窗的标题
  134. const queryParams = ref({
  135. pageNo: 1,
  136. pageSize: 10,
  137. tabType: 0, // 默认获取上架的商品
  138. name: '',
  139. categoryId: null,
  140. createTime: []
  141. }) // 查询参数
  142. const propertyList = ref<Properties[]>([]) // 商品属性列表
  143. const spuListRef = ref<InstanceType<typeof ElTable>>()
  144. const skuListRef = ref() // 商品属性选择 Ref
  145. const spuData = ref<ProductSpuApi.Spu>() // 商品详情
  146. const isExpand = ref(false) // 控制 SKU 列表显示
  147. const expandRowKeys = ref<number[]>() // 控制展开行需要设置 row-key 属性才能使用,该属性为展开行的 keys 数组。
  148. //============ 商品选择相关 ============
  149. const selectedSpuId = ref<number>(0) // 选中的商品 spuId
  150. const selectedSkuIds = ref<number[]>([]) // 选中的商品 skuIds
  151. const selectSku = (val: ProductSpuApi.Sku[]) => {
  152. if (selectedSpuId.value === 0) {
  153. message.warning('请先选择商品再选择相应的规格!!!')
  154. skuListRef.value.clearSelection()
  155. return
  156. }
  157. selectedSkuIds.value = val.map((sku) => sku.id!)
  158. }
  159. const selectSpu = (val: ProductSpuApi.Spu[]) => {
  160. if (val.length === 0) {
  161. selectedSpuId.value = 0
  162. return
  163. }
  164. // 只选择一个
  165. selectedSpuId.value = val.map((spu) => spu.id!)[0]
  166. // 切换选择 spu 如果有选择的 sku 则清空,确保选择的 sku 是对应的 spu 下面的
  167. if (selectedSkuIds.value.length > 0) {
  168. selectedSkuIds.value = []
  169. }
  170. // 如果大于1个
  171. if (val.length > 1) {
  172. // 清空选择
  173. spuListRef.value.clearSelection()
  174. // 变更为最后一次选择的
  175. spuListRef.value.toggleRowSelection(val.pop(), true)
  176. return
  177. }
  178. expandChange(val[0], val)
  179. }
  180. // 计算商品属性
  181. const expandChange = async (row: ProductSpuApi.Spu, expandedRows?: ProductSpuApi.Spu[]) => {
  182. // 判断需要展开的 spuId === 选择的 spuId。如果选择了 A 就展开 A 的 skuList。如果选择了 A 手动展开 B 则阻断
  183. // 目的防止误选 sku
  184. if (selectedSpuId.value !== 0) {
  185. if (row.id !== selectedSpuId.value) {
  186. message.warning('你已选择商品请先取消')
  187. expandRowKeys.value = [selectedSpuId.value]
  188. return
  189. }
  190. // 如果以展开 skuList 则选择此对应的 spu 不需要重新获取渲染 skuList
  191. if (isExpand.value && spuData.value?.id === row.id) {
  192. return
  193. }
  194. }
  195. spuData.value = {}
  196. propertyList.value = []
  197. isExpand.value = false
  198. if (expandedRows?.length === 0) {
  199. // 如果展开个数为 0
  200. expandRowKeys.value = []
  201. return
  202. }
  203. // 获取 SPU 详情
  204. const res = (await ProductSpuApi.getSpu(row.id as number)) as ProductSpuApi.Spu
  205. propertyList.value = getPropertyList(res)
  206. spuData.value = res
  207. isExpand.value = true
  208. expandRowKeys.value = [row.id!]
  209. }
  210. // 确认选择时的触发事件
  211. const emits = defineEmits<{
  212. (e: 'confirm', spuId: number, skuIds?: number[]): void
  213. }>()
  214. /**
  215. * 确认选择返回选中的 spu 和 sku (如果需要选择sku的话)
  216. */
  217. const confirm = () => {
  218. if (selectedSpuId.value === 0) {
  219. message.warning('没有选择任何商品')
  220. return
  221. }
  222. if (props.isSelectSku && selectedSkuIds.value.length === 0) {
  223. message.warning('没有选择任何商品属性')
  224. return
  225. }
  226. // 返回各自 id 列表
  227. props.isSelectSku
  228. ? emits('confirm', selectedSpuId.value, selectedSkuIds.value)
  229. : emits('confirm', selectedSpuId.value)
  230. // 关闭弹窗
  231. dialogVisible.value = false
  232. selectedSpuId.value = 0
  233. selectedSkuIds.value = []
  234. }
  235. /** 打开弹窗 */
  236. const open = () => {
  237. dialogTitle.value = '商品选择'
  238. dialogVisible.value = true
  239. }
  240. defineExpose({ open }) // 提供 open 方法,用于打开弹窗
  241. /** 查询列表 */
  242. const getList = async () => {
  243. loading.value = true
  244. try {
  245. const data = await ProductSpuApi.getSpuPage(queryParams.value)
  246. list.value = data.list
  247. total.value = data.total
  248. } finally {
  249. loading.value = false
  250. }
  251. }
  252. /** 搜索按钮操作 */
  253. const handleQuery = () => {
  254. getList()
  255. }
  256. /** 重置按钮操作 */
  257. const resetQuery = () => {
  258. queryParams.value = {
  259. pageNo: 1,
  260. pageSize: 10,
  261. tabType: 0, // 默认获取上架的商品
  262. name: '',
  263. categoryId: null,
  264. createTime: []
  265. }
  266. getList()
  267. }
  268. /** 商品图预览 */
  269. const imagePreview = (imgUrl: string) => {
  270. createImageViewer({
  271. zIndex: 99999999,
  272. urlList: [imgUrl]
  273. })
  274. }
  275. const categoryList = ref() // 分类树
  276. /** 初始化 **/
  277. onMounted(async () => {
  278. await getList()
  279. // 获得分类树
  280. const data = await ProductCategoryApi.getCategoryList({})
  281. categoryList.value = handleTree(data, 'id', 'parentId')
  282. })
  283. </script>