From a1d7e81859f554f3a53680cc35f0f49bf1f77098 Mon Sep 17 00:00:00 2001
From: wwf <1971391498@qq.com>
Date: 星期四, 14 五月 2026 14:37:02 +0800
Subject: [PATCH] 导入项目

---
 src/views/mall/promotion/components/SpuSelect.vue |  324 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 324 insertions(+), 0 deletions(-)

diff --git a/src/views/mall/promotion/components/SpuSelect.vue b/src/views/mall/promotion/components/SpuSelect.vue
new file mode 100644
index 0000000..648a863
--- /dev/null
+++ b/src/views/mall/promotion/components/SpuSelect.vue
@@ -0,0 +1,324 @@
+<template>
+  <Dialog v-model="dialogVisible" :appendToBody="true" :title="dialogTitle" width="70%">
+    <ContentWrap>
+      <el-row :gutter="20" class="mb-10px">
+        <el-col :span="6">
+          <el-input
+            v-model="queryParams.name"
+            class="!w-240px"
+            clearable
+            placeholder="璇疯緭鍏ュ晢鍝佸悕绉�"
+            @keyup.enter="handleQuery"
+          />
+        </el-col>
+        <el-col :span="6">
+          <el-tree-select
+            v-model="queryParams.categoryId"
+            :data="categoryList"
+            :props="defaultProps"
+            check-strictly
+            class="w-1/1"
+            node-key="id"
+            placeholder="璇烽�夋嫨鍟嗗搧鍒嗙被"
+          />
+        </el-col>
+        <el-col :span="6">
+          <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-col>
+        <el-col :span="6">
+          <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-col>
+      </el-row>
+      <el-table
+        ref="spuListRef"
+        v-loading="loading"
+        :data="list"
+        :expand-row-keys="expandRowKeys"
+        row-key="id"
+        @expand-change="expandChange"
+        @selection-change="selectSpu"
+      >
+        <el-table-column v-if="isSelectSku" type="expand" width="30">
+          <template #default>
+            <SkuList
+              v-if="isExpand"
+              ref="skuListRef"
+              :isComponent="true"
+              :isDetail="true"
+              :prop-form-data="spuData"
+              :property-list="propertyList"
+              @selection-change="selectSku"
+            />
+          </template>
+        </el-table-column>
+        <el-table-column type="selection" width="55" />
+        <el-table-column key="id" align="center" label="鍟嗗搧缂栧彿" prop="id" />
+        <el-table-column label="鍟嗗搧鍥�" min-width="80">
+          <template #default="{ row }">
+            <el-image :src="row.picUrl" class="h-30px w-30px" @click="imagePreview(row.picUrl)" />
+          </template>
+        </el-table-column>
+        <el-table-column
+          :show-overflow-tooltip="true"
+          label="鍟嗗搧鍚嶇О"
+          min-width="300"
+          prop="name"
+        />
+        <el-table-column align="center" label="鍟嗗搧鍞环" min-width="90" prop="price">
+          <template #default="{ row }">
+            {{ formatToFraction(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
+          :formatter="dateFormatter"
+          align="center"
+          label="鍒涘缓鏃堕棿"
+          prop="createTime"
+          width="180"
+        />
+      </el-table>
+      <!-- 鍒嗛〉 -->
+      <Pagination
+        v-model:limit="queryParams.pageSize"
+        v-model:page="queryParams.pageNo"
+        :total="total"
+        @pagination="getList"
+      />
+    </ContentWrap>
+    <template #footer>
+      <el-button type="primary" @click="confirm">纭� 瀹�</el-button>
+      <el-button @click="dialogVisible = false">鍙� 娑�</el-button>
+    </template>
+  </Dialog>
+</template>
+
+<script lang="ts" setup>
+import { getPropertyList, PropertyAndValues, SkuList } from '@/views/mall/product/spu/components'
+import { ElTable } from 'element-plus'
+import { dateFormatter } from '@/utils/formatTime'
+import { createImageViewer } from '@/components/ImageViewer'
+import { floatToFixed2, formatToFraction } from '@/utils'
+import { defaultProps, handleTree } from '@/utils/tree'
+
+import * as ProductCategoryApi from '@/api/mall/product/category'
+import * as ProductSpuApi from '@/api/mall/product/spu'
+import { propTypes } from '@/utils/propTypes'
+
+defineOptions({ name: 'PromotionSpuSelect' })
+
+const props = defineProps({
+  // 榛樿涓嶉渶瑕侊紙涓嶉渶瑕佺殑鎯呭喌涓嬪彧杩斿洖 spu锛岄渶瑕佺殑鎯呭喌涓嬭繑鍥� 閫変腑鐨� spu 鍜� sku 鍒楄〃锛�
+  // 鍏跺畠娲诲姩闇�瑕侀�夋嫨鍟嗗搧鍜屽晢鍝佸睘鎬у鍏ユ缁勪欢鍗冲彲锛岄渶娣诲姞缁勪欢灞炴�� :isSelectSku='true'
+  isSelectSku: propTypes.bool.def(false), // 鏄惁闇�瑕侀�夋嫨 sku 灞炴��
+  radio: propTypes.bool.def(false) // 鏄惁鍗曢�� sku
+})
+
+const message = useMessage() // 娑堟伅寮圭獥
+const total = ref(0) // 鍒楄〃鐨勬�婚〉鏁�
+const list = ref<any[]>([]) // 鍒楄〃鐨勬暟鎹�
+const loading = ref(false) // 鍒楄〃鐨勫姞杞戒腑
+const dialogVisible = ref(false) // 寮圭獥鐨勬槸鍚﹀睍绀�
+const dialogTitle = ref('') // 寮圭獥鐨勬爣棰�
+const queryParams = ref({
+  pageNo: 1,
+  pageSize: 10,
+  tabType: 0, // 榛樿鑾峰彇涓婃灦鐨勫晢鍝�
+  name: '',
+  categoryId: null,
+  createTime: []
+}) // 鏌ヨ鍙傛暟
+const propertyList = ref<PropertyAndValues[]>([]) // 鍟嗗搧灞炴�у垪琛�
+const spuListRef = ref<InstanceType<typeof ElTable>>()
+const skuListRef = ref<InstanceType<typeof SkuList>>() // 鍟嗗搧灞炴�ч�夋嫨 Ref
+const spuData = ref<ProductSpuApi.Spu>() // 鍟嗗搧璇︽儏
+const isExpand = ref(false) // 鎺у埗 SKU 鍒楄〃鏄剧ず
+const expandRowKeys = ref<number[]>() // 鎺у埗灞曞紑琛岄渶瑕佽缃� row-key 灞炴�ф墠鑳戒娇鐢紝璇ュ睘鎬т负灞曞紑琛岀殑 keys 鏁扮粍銆�
+
+//============ 鍟嗗搧閫夋嫨鐩稿叧 ============
+const selectedSpuId = ref<number>(0) // 閫変腑鐨勫晢鍝� spuId
+const selectedSkuIds = ref<number[]>([]) // 閫変腑鐨勫晢鍝� skuIds
+const selectSku = (val: ProductSpuApi.Sku[]) => {
+  const skuTable = skuListRef.value?.getSkuTableRef()
+  if (selectedSpuId.value === 0) {
+    message.warning('璇峰厛閫夋嫨鍟嗗搧鍐嶉�夋嫨鐩稿簲鐨勮鏍硷紒锛侊紒')
+    skuTable?.clearSelection()
+    return
+  }
+  if (val.length === 0) {
+    selectedSkuIds.value = []
+    return
+  }
+  if (props.radio) {
+    // 鍙�夋嫨涓�涓�
+    selectedSkuIds.value = [val.map((sku) => sku.id!)[0]]
+    // 濡傛灉澶т簬1涓�
+    if (val.length > 1) {
+      // 娓呯┖閫夋嫨
+      skuTable?.clearSelection()
+      // 鍙樻洿涓烘渶鍚庝竴娆¢�夋嫨鐨�
+      skuTable?.toggleRowSelection(val.pop(), true)
+      return
+    }
+  } else {
+    selectedSkuIds.value = val.map((sku) => sku.id!)
+  }
+}
+const selectSpu = (val: ProductSpuApi.Spu[]) => {
+  if (val.length === 0) {
+    selectedSpuId.value = 0
+    return
+  }
+  // 鍙�夋嫨涓�涓�
+  selectedSpuId.value = val.map((spu) => spu.id!)[0]
+  // 鍒囨崲閫夋嫨 spu 濡傛灉鏈夐�夋嫨鐨� sku 鍒欐竻绌�,纭繚閫夋嫨鐨� sku 鏄搴旂殑 spu 涓嬮潰鐨�
+  if (selectedSkuIds.value.length > 0) {
+    selectedSkuIds.value = []
+  }
+  // 濡傛灉澶т簬1涓�
+  if (val.length > 1) {
+    // 娓呯┖閫夋嫨
+    spuListRef.value?.clearSelection()
+    // 鍙樻洿涓烘渶鍚庝竴娆¢�夋嫨鐨�
+    spuListRef.value?.toggleRowSelection(val.pop(), true)
+    return
+  }
+  expandChange(val[0], val)
+}
+
+// 璁$畻鍟嗗搧灞炴��
+const expandChange = async (row: ProductSpuApi.Spu, expandedRows?: ProductSpuApi.Spu[]) => {
+  // 鍒ゆ柇闇�瑕佸睍寮�鐨� spuId === 閫夋嫨鐨� spuId銆傚鏋滈�夋嫨浜� A 灏卞睍寮� A 鐨� skuList銆傚鏋滈�夋嫨浜� A 鎵嬪姩灞曞紑 B 鍒欓樆鏂�
+  // 鐩殑闃叉璇�� sku
+  if (selectedSpuId.value !== 0) {
+    if (row.id !== selectedSpuId.value) {
+      message.warning('浣犲凡閫夋嫨鍟嗗搧璇峰厛鍙栨秷')
+      expandRowKeys.value = [selectedSpuId.value]
+      return
+    }
+    // 濡傛灉宸插睍寮� skuList 鍒欓�夋嫨姝ゅ搴旂殑 spu 涓嶉渶瑕侀噸鏂拌幏鍙栨覆鏌� skuList
+    if (isExpand.value && spuData.value?.id === row.id) {
+      return
+    }
+  }
+  spuData.value = {}
+  propertyList.value = []
+  isExpand.value = false
+  if (expandedRows?.length === 0) {
+    // 濡傛灉灞曞紑涓暟涓� 0
+    expandRowKeys.value = []
+    return
+  }
+  // 鑾峰彇 SPU 璇︽儏
+  const res = (await ProductSpuApi.getSpu(row.id as number)) as ProductSpuApi.Spu
+  res.skus?.forEach((item) => {
+    item.price = floatToFixed2(item.price)
+    item.marketPrice = floatToFixed2(item.marketPrice)
+    item.costPrice = floatToFixed2(item.costPrice)
+    item.firstBrokeragePrice = floatToFixed2(item.firstBrokeragePrice)
+    item.secondBrokeragePrice = floatToFixed2(item.secondBrokeragePrice)
+  })
+  propertyList.value = getPropertyList(res)
+  spuData.value = res
+  isExpand.value = true
+  expandRowKeys.value = [row.id!]
+}
+
+// 纭閫夋嫨鏃剁殑瑙﹀彂浜嬩欢
+const emits = defineEmits<{
+  (e: 'confirm', spuId: number, skuIds?: number[]): void
+}>()
+/**
+ * 纭閫夋嫨杩斿洖閫変腑鐨� spu 鍜� sku (濡傛灉闇�瑕侀�夋嫨sku鐨勮瘽)
+ */
+const confirm = () => {
+  if (selectedSpuId.value === 0) {
+    message.warning('娌℃湁閫夋嫨浠讳綍鍟嗗搧')
+    return
+  }
+  if (props.isSelectSku && selectedSkuIds.value.length === 0) {
+    message.warning('娌℃湁閫夋嫨浠讳綍鍟嗗搧灞炴��')
+    return
+  }
+  // 杩斿洖鍚勮嚜 id 鍒楄〃
+  props.isSelectSku
+    ? emits('confirm', selectedSpuId.value, selectedSkuIds.value)
+    : emits('confirm', selectedSpuId.value)
+  // 鍏抽棴寮圭獥
+  dialogVisible.value = false
+  selectedSpuId.value = 0
+  selectedSkuIds.value = []
+}
+
+/** 鎵撳紑寮圭獥 */
+const open = () => {
+  dialogTitle.value = '鍟嗗搧閫夋嫨'
+  dialogVisible.value = true
+}
+defineExpose({ open }) // 鎻愪緵 open 鏂规硶锛岀敤浜庢墦寮�寮圭獥
+
+/** 鏌ヨ鍒楄〃 */
+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
+  }
+}
+
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+  getList()
+}
+
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+const resetQuery = () => {
+  queryParams.value = {
+    pageNo: 1,
+    pageSize: 10,
+    tabType: 0, // 榛樿鑾峰彇涓婃灦鐨勫晢鍝�
+    name: '',
+    categoryId: null,
+    createTime: []
+  }
+  getList()
+}
+
+/** 鍟嗗搧鍥鹃瑙� */
+const imagePreview = (imgUrl: string) => {
+  createImageViewer({
+    zIndex: 99999999,
+    urlList: [imgUrl]
+  })
+}
+
+const categoryList = ref() // 鍒嗙被鏍�
+
+/** 鍒濆鍖� **/
+onMounted(async () => {
+  await getList()
+  // 鑾峰緱鍒嗙被鏍�
+  const data = await ProductCategoryApi.getCategoryList({})
+  categoryList.value = handleTree(data, 'id', 'parentId')
+})
+</script>

--
Gitblit v1.8.0