SpuAndSkuSelectForm.vue 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  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. @change="nodeClick"
  24. />
  25. </el-col>
  26. <el-col :span="6">
  27. <el-date-picker
  28. v-model="queryParams.createTime"
  29. :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
  30. class="!w-240px"
  31. end-placeholder="结束日期"
  32. start-placeholder="开始日期"
  33. type="daterange"
  34. value-format="YYYY-MM-DD HH:mm:ss"
  35. />
  36. </el-col>
  37. <el-col :span="6">
  38. <el-button @click="handleQuery">
  39. <Icon class="mr-5px" icon="ep:search" />
  40. 搜索
  41. </el-button>
  42. <el-button @click="resetQuery">
  43. <Icon class="mr-5px" icon="ep:refresh" />
  44. 重置
  45. </el-button>
  46. </el-col>
  47. </el-row>
  48. <el-table
  49. ref="spuListRef"
  50. v-loading="loading"
  51. :data="list"
  52. :expand-row-keys="expandRowKeys"
  53. row-key="id"
  54. @expand-change="expandChange"
  55. @selection-change="selectSpu"
  56. >
  57. <el-table-column v-if="isSelectSku" type="expand" width="30">
  58. <template #default>
  59. <SkuList
  60. v-if="isExpand"
  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" name="SeckillActivitySpuAndSkuSelect" 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 { checkSelectedNode, 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. const props = defineProps({
  123. // 默认不需要(不需要的情况下只返回 spu,需要的情况下返回 选中的 spu 和 sku 列表)
  124. // 其它活动需要选择商品和商品属性导入此组件即可,需添加组件属性 :isSelectSku='true'
  125. isSelectSku: propTypes.bool.def(false) // 是否需要选择 sku 属性
  126. })
  127. const message = useMessage() // 消息弹窗
  128. const total = ref(0) // 列表的总页数
  129. const list = ref<any[]>([]) // 列表的数据
  130. const loading = ref(false) // 列表的加载中
  131. const dialogVisible = ref(false) // 弹窗的是否展示
  132. const dialogTitle = ref('') // 弹窗的标题
  133. const queryParams = ref({
  134. pageNo: 1,
  135. pageSize: 10,
  136. tabType: 0, // 默认获取上架的商品
  137. name: '',
  138. categoryId: null,
  139. createTime: []
  140. }) // 查询参数
  141. const propertyList = ref<Properties[]>([]) // 商品属性列表
  142. const spuListRef = ref<InstanceType<typeof ElTable>>()
  143. const spuData = ref<ProductSpuApi.Spu | {}>() // 商品详情
  144. const isExpand = ref(false) // 控制 SKU 列表显示
  145. const expandRowKeys = ref<number[]>() // 控制展开行需要设置 row-key 属性才能使用,该属性为展开行的 keys 数组。
  146. // 计算商品属性
  147. const expandChange = async (row: ProductSpuApi.Spu, expandedRows: ProductSpuApi.Spu[]) => {
  148. spuData.value = {}
  149. propertyList.value = []
  150. isExpand.value = false
  151. // 如果展开个数为 0
  152. if (expandedRows.length === 0) {
  153. expandRowKeys.value = []
  154. return
  155. }
  156. // 获取 SPU 详情
  157. const res = (await ProductSpuApi.getSpu(row.id as number)) as ProductSpuApi.SpuRespVO
  158. propertyList.value = getPropertyList(res)
  159. spuData.value = res
  160. isExpand.value = true
  161. expandRowKeys.value = [row.id!]
  162. }
  163. //============ 商品选择相关 ============
  164. const selectedSpu = ref<ProductSpuApi.Spu>() // 选中的商品 spu 只能选择一个
  165. const selectedSku = ref<ProductSpuApi.Sku[]>() // 选中的商品 sku
  166. const selectSku = (val: ProductSpuApi.Sku[]) => {
  167. selectedSku.value = val
  168. }
  169. const selectSpu = (val: ProductSpuApi.Spu[]) => {
  170. // 只选择一个
  171. selectedSpu.value = val[0]
  172. // 如果大于1个
  173. if (val.length > 1) {
  174. // 清空选择
  175. spuListRef.value.clearSelection()
  176. // 变更为最后一次选择的
  177. spuListRef.value.toggleRowSelection(val.pop(), true)
  178. }
  179. }
  180. // 确认选择时的触发事件
  181. const emits = defineEmits<{
  182. (e: 'confirm', value: ProductSpuApi.Spu, value1?: ProductSpuApi.Sku[]): void
  183. }>()
  184. /**
  185. * 确认选择返回选中的 spu 和 sku (如果需要选择sku的话)
  186. */
  187. const confirm = () => {
  188. if (typeof selectedSpu.value === 'undefined') {
  189. message.warning('没有选择任何商品')
  190. return
  191. }
  192. if (
  193. (props.isSelectSku && typeof selectedSku.value === 'undefined') ||
  194. selectedSku.value?.length === 0
  195. ) {
  196. message.warning('没有选择任何商品属性')
  197. return
  198. }
  199. // TODO 返回选择 sku 没测试过,后续测试完善
  200. props.isSelectSku
  201. ? emits('confirm', selectedSpu.value!, selectedSku.value!)
  202. : emits('confirm', selectedSpu.value!)
  203. // 关闭弹窗
  204. dialogVisible.value = false
  205. }
  206. // TODO @puhui999:直接叫商品选择;不用外部传入标题;
  207. /** 打开弹窗 TODO 没做国际化 */
  208. const open = (title: string) => {
  209. dialogTitle.value = title
  210. dialogVisible.value = true
  211. }
  212. defineExpose({ open }) // 提供 open 方法,用于打开弹窗
  213. /** 查询列表 */
  214. const getList = async () => {
  215. loading.value = true
  216. try {
  217. const data = await ProductSpuApi.getSpuPage(queryParams.value)
  218. list.value = data.list
  219. total.value = data.total
  220. } finally {
  221. loading.value = false
  222. }
  223. }
  224. /** 搜索按钮操作 */
  225. const handleQuery = () => {
  226. getList()
  227. }
  228. /** 重置按钮操作 */
  229. const resetQuery = () => {
  230. queryParams.value = {
  231. pageNo: 1,
  232. pageSize: 10,
  233. tabType: 0, // 默认获取上架的商品
  234. name: '',
  235. categoryId: null,
  236. createTime: []
  237. }
  238. getList()
  239. }
  240. /** 商品图预览 */
  241. const imagePreview = (imgUrl: string) => {
  242. createImageViewer({
  243. zIndex: 99999999,
  244. urlList: [imgUrl]
  245. })
  246. }
  247. const categoryList = ref() // 分类树
  248. // TODO @puhui999:商品搜索的时候,可以通过一级搜二级;所以这个校验可以去掉哈;也就是说,只允许挂在二级,但是一级可搜索到
  249. /**
  250. * 校验所选是否为二级及以下节点
  251. */
  252. const nodeClick = () => {
  253. if (!checkSelectedNode(categoryList.value, queryParams.value.categoryId)) {
  254. queryParams.value.categoryId = null
  255. message.warning('必须选择二级及以下节点!!')
  256. }
  257. }
  258. /** 初始化 **/
  259. onMounted(async () => {
  260. await getList()
  261. // 获得分类树
  262. const data = await ProductCategoryApi.getCategoryList({})
  263. categoryList.value = handleTree(data, 'id', 'parentId')
  264. })
  265. </script>