VideoCommodity.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. <template>
  2. <div prop="commodity" style="width: 100%" v-loading="productLoading">
  3. <div style="width: 100%; height: 620px; display: flex; border: 1px solid #e5e7ec; border-radius: 6px">
  4. <div style="width: 50%; border-right: 1px solid #e5e7ec">
  5. <el-tabs v-model="productTabs" class="demo-tabs">
  6. <el-tab-pane label="搜索" name="first">
  7. <div style="margin-bottom: 10px">
  8. <el-input v-model="searchInp" placeholder="Please input" class="input-with-select" @change="inpChange" clearable>
  9. <template #prepend>
  10. <el-select v-model="leftSelect" style="width: 100px" @change="selChange">
  11. <el-option label="名称" value="name" />
  12. <el-option label="ASIN" value="asin" />
  13. <el-option label="SKU" value="sku" />
  14. </el-select>
  15. </template>
  16. <template #append>
  17. <el-select v-model="rightSelect" style="width: 100px">
  18. <el-option label="最新优先" value="latest" />
  19. <el-option label="最早优先" value="earliest" />
  20. <el-option label="优选广告" value="optimal" />
  21. </el-select>
  22. </template>
  23. </el-input>
  24. </div>
  25. <el-table
  26. height="490"
  27. style="width: 100%"
  28. v-loading="loading"
  29. :data="productTableData"
  30. :header-cell-style="headerCellStyle"
  31. @selection-change="handleSelectionChange">
  32. <el-table-column type="selection" width="50" />
  33. <el-table-column prop="asin" label="商品">
  34. <template #default="scope">
  35. <div style="display: flex; align-items: center">
  36. <div style="margin-right: 8px; line-height: normal">
  37. <el-image class="img-box" :src="scope.row.image_link" />
  38. </div>
  39. <div>
  40. <el-tooltip class="box-item" effect="dark" :content="scope.row.title" placement="top">
  41. <div class="single-line">{{ scope.row.title ? scope.row.title : '--' }}</div>
  42. </el-tooltip>
  43. <div class="data-color">
  44. <span style="font-weight: 500; color: rgb(30, 33, 41)">${{ scope.row.price ? scope.row.price : '--' }}</span>
  45. <span style="margin: 0 5px; color: #cacdd4">|</span>
  46. <span style="color: #6d7784">{{ scope.row.quantity }}</span>
  47. </div>
  48. <span>
  49. ASIN: <span class="data-color" style="margin-right: 8px">{{ scope.row.asin ? scope.row.asin : '--' }}</span>
  50. </span>
  51. <span>
  52. SKU: <span class="data-color">{{ scope.row.sku ? scope.row.sku : '--' }}</span>
  53. </span>
  54. </div>
  55. </div>
  56. </template>
  57. </el-table-column>
  58. <el-table-column prop="name" label="Name" width="120" align="right">
  59. <template #header>
  60. <el-button type="primary" size="normal" link :disabled="addedTableData.length >= 1" @click="handleGoodsAdd">添加已选中</el-button>
  61. </template>
  62. <template #default="scope">
  63. <el-button type="primary" size="small" :disabled="addedTableData.length >= 1" @click="addSingleGoods(scope)" text>添加</el-button>
  64. </template>
  65. </el-table-column>
  66. </el-table>
  67. <el-pagination
  68. @current-change="handleCurrentChange"
  69. @size-change="handleSizeChange"
  70. :current-page="currentPage"
  71. :page-size="pageSize"
  72. :total="totalItems"
  73. layout="prev, pager, next" />
  74. </el-tab-pane>
  75. <el-tab-pane label="输入" name="second">
  76. <el-input
  77. style="padding: 10px"
  78. v-model="productTextarea"
  79. :rows="20"
  80. type="textarea"
  81. placeholder="请输入ASIN,多个ASIN使用逗号、空格或换行符分隔。(未完成)"
  82. maxlength="11000" />
  83. <div style="display: flex; flex-direction: row-reverse; margin-top: 10px">
  84. <el-button v-for="button in buttons" :key="button.text" :type="button.type" link @click="addGods">{{ button.text }}</el-button>
  85. </div>
  86. </el-tab-pane>
  87. </el-tabs>
  88. </div>
  89. <div style="width: 50%">
  90. <el-card class="box-card" shadow="never" style="border: 0">
  91. <template #header>
  92. <div class="card-header">
  93. <span style="font-weight: 550; font-size: 15px; color: #1f2128">已添加: {{ addedTableData.length }}</span>
  94. <el-text type="warning" truncated>最多添加一个产品</el-text>
  95. <el-button class="button" type="danger" text bg @click="delAllGoods">全部删除</el-button>
  96. </div>
  97. </template>
  98. <div class="card-body"></div>
  99. </el-card>
  100. <div style="padding: 0 10px 0 10px; margin-top: -12px">
  101. <el-table
  102. :data="addedTableData"
  103. height="475"
  104. style="width: 100%"
  105. :header-cell-style="headerCellStyle"
  106. @selection-change="handleAddedGoodsChange">
  107. <el-table-column type="selection" width="50" />
  108. <el-table-column prop="asin" label="ASIN">
  109. <template #default="scope">
  110. <div style="display: flex; align-items: center">
  111. <div style="margin-right: 8px; line-height: normal">
  112. <el-image class="img-box" :src="scope.row.image_link" />
  113. </div>
  114. <div>
  115. <el-tooltip class="box-item" effect="dark" :content="scope.row.title" placement="top">
  116. <div class="single-line">{{ scope.row.title ? scope.row.title : '--' }}</div>
  117. </el-tooltip>
  118. <div class="data-color">
  119. <span style="font-weight: 500; color: rgb(30, 33, 41)">${{ scope.row.price ? scope.row.price : '--' }}</span>
  120. <span style="margin: 0 5px; color: #cacdd4">|</span>
  121. <span style="color: #6d7784">{{ scope.row.quantity }}</span>
  122. </div>
  123. <span
  124. >ASIN:
  125. <span class="data-color" style="margin-right: 8px">{{ scope.row.asin ? scope.row.asin : '--' }}</span>
  126. </span>
  127. <span
  128. >SKU:
  129. <span class="data-color">{{ scope.row.sku ? scope.row.sku : '--' }}</span>
  130. </span>
  131. </div>
  132. </div>
  133. </template>
  134. </el-table-column>
  135. <el-table-column prop="name" label="Name" width="120" align="right">
  136. <template #header>
  137. <el-button type="danger" size="normal" link @click="delSelectedGoods">删除已选中</el-button>
  138. </template>
  139. <template #default="scope">
  140. <el-button type="primary" size="small" @click="delSingleGoods(scope)" text>删除</el-button>
  141. </template>
  142. </el-table-column>
  143. </el-table>
  144. </div>
  145. <!-- <div style="display: flex; justify-content: space-around; padding-top: 5px">
  146. <el-button type="primary" plain :disabled="productSave" @click="submitProductForm">保存</el-button>
  147. </div> -->
  148. </div>
  149. </div>
  150. </div>
  151. </template>
  152. <script setup lang="ts">
  153. import { inject, onMounted, ref, watch, provide, onUnmounted } from 'vue'
  154. import type { Ref } from 'vue'
  155. import type { TabsPaneContext } from 'element-plus'
  156. import { ElMessage } from 'element-plus'
  157. import { storeToRefs } from 'pinia'
  158. import { useShopInfo } from '/@/stores/shopInfo'
  159. import { request } from '/@/utils/service'
  160. const shopInfo = useShopInfo()
  161. const { profile } = storeToRefs(shopInfo)
  162. const productTextarea = ref('')
  163. const productLoading = ref(false)
  164. let addedAdsTableItems = ref([])
  165. const currentPage = ref() // 当前页
  166. const pageSize = ref(20) // 每页显示条目数
  167. const totalItems = ref() // 数据总量
  168. const productTableData = ref([]) // 左侧表格数据
  169. const loading = ref(false)
  170. let addedTableData = ref([])
  171. let selections = []
  172. let addedSels = []
  173. const searchInp = ref('')
  174. const leftSelect = ref('name')
  175. let productSave = ref(true)
  176. const buttons = [{ type: 'primary', text: '添加' }] as const
  177. const productTabs = ref('first')
  178. const rightSelect = ref('latest')
  179. const respCampaignId = inject<Ref>('respCampaignId')
  180. const respAdGroupId = inject<Ref>('respAdGroupId')
  181. function setTableData(asin = '', sku = '') {
  182. return request({
  183. url: '/api/sellers/listings/our/',
  184. method: 'GET',
  185. params: {
  186. page: currentPage.value,
  187. limit: pageSize.value,
  188. profile_id: profile.value.profile_id,
  189. asin,
  190. sku,
  191. },
  192. })
  193. .then((resp) => {
  194. productTableData.value = resp.data
  195. totalItems.value = resp.total
  196. currentPage.value = resp.page
  197. loading.value = false
  198. })
  199. .catch((error) => {
  200. console.error('Error fetching data:', error)
  201. loading.value = false
  202. })
  203. }
  204. function addSingleGoods(scope) {
  205. // console.log('scope', scope.row)
  206. const isAlreadyAdded = addedTableData.value.some((item) => item.sku === scope.row.sku)
  207. if (!isAlreadyAdded) {
  208. addedTableData.value.push(scope.row)
  209. } else {
  210. console.log('Item is already added.')
  211. }
  212. }
  213. function addGods() {
  214. const inputData = productTextarea.value
  215. const asins = inputData.split(/[\n,]+/)
  216. asins.forEach((asin) => {
  217. if (asin.trim()) {
  218. setTableData(asin.trim())
  219. .then((response) => {
  220. console.log(`Data for ASIN ${asin}:`, response) // 更新这里来正确地访问数据
  221. })
  222. .catch((error) => {
  223. console.error(`Error fetching data for ASIN ${asin}:`, error)
  224. })
  225. }
  226. })
  227. }
  228. function delSingleGoods(scope) {
  229. const index = addedTableData.value.findIndex((item) => item.sku === scope.row.sku)
  230. if (index !== -1) {
  231. addedTableData.value.splice(index, 1)
  232. console.log('Item removed successfully.')
  233. } else {
  234. console.log('Item not found.')
  235. }
  236. }
  237. function delAllGoods() {
  238. addedTableData.value = []
  239. // addedTableData.value.splice(0, addedTableData.value.length)
  240. }
  241. // 删除第二个table中已经选中的项
  242. function delSelectedGoods() {
  243. addedTableData.value = addedTableData.value.filter((item) => !addedSels.includes(item))
  244. addedSels = []
  245. }
  246. function inpChange(e) {
  247. const value = e
  248. if (leftSelect.value === 'asin') {
  249. loading.value = true
  250. setTableData(value)
  251. } else if (leftSelect.value === 'sku') {
  252. loading.value = true
  253. setTableData('', value)
  254. }
  255. }
  256. function selChange(e) {
  257. console.log('e', e)
  258. const value = e
  259. if (leftSelect.value === 'asin' && searchInp.value) {
  260. loading.value = true
  261. setTableData(value)
  262. } else if (leftSelect.value === 'sku' && searchInp.value) {
  263. loading.value = true
  264. setTableData('', value)
  265. }
  266. }
  267. // 点击表格选项触发事件
  268. function handleSelectionChange(selection) {
  269. selections = selection
  270. }
  271. // 获取addedTable中已选中的项
  272. function handleAddedGoodsChange(selection) {
  273. addedSels = selection
  274. }
  275. // 添加已选中的项
  276. function handleGoodsAdd() {
  277. // 过滤掉已经存在于addedData.value中的项
  278. const newSelections = selections.filter(
  279. (sel) => !addedTableData.value.some((added) => added.sku === sel.sku) // 使用sku作为唯一标识
  280. )
  281. // 如果有新的不重复项,加入到addedData.value中
  282. if (newSelections.length > 0) {
  283. addedTableData.value.push(...newSelections)
  284. }
  285. }
  286. // 点击Tab
  287. const handleGoodsTabs = (tab: TabsPaneContext, event: Event) => {
  288. console.log(tab, event)
  289. }
  290. function isItemInList(item, list) {
  291. return list.some((listItem) => listItem.sku === item.sku && listItem.asin === item.asin)
  292. }
  293. // 监听商品右侧表格已添加的数据并转化数据格式
  294. watch(
  295. addedTableData,
  296. (newValue, oldValue) => {
  297. newValue.forEach((item) => {
  298. if (!isItemInList(item, addedAdsTableItems.value)) {
  299. addedAdsTableItems.value.push({ sku: item.sku, asin: item.asin })
  300. }
  301. })
  302. if (addedTableData.value.length !== 0) {
  303. productSave.value = false
  304. } else {
  305. productSave.value = true
  306. }
  307. },
  308. { deep: true }
  309. )
  310. async function createAds() {
  311. try {
  312. const requestData = {
  313. profile_id: profile.value.profile_id,
  314. campaignId: respCampaignId.value,
  315. adGroupId: respAdGroupId.value,
  316. asinsku: addedAdsTableItems.value,
  317. state: 'PAUSED',
  318. }
  319. const filteredRequestData = Object.fromEntries(Object.entries(requestData).filter(([_, v]) => v != null))
  320. const resp = await request({
  321. url: '/api/ad_manage/spads/create/',
  322. method: 'POST',
  323. data: filteredRequestData,
  324. })
  325. console.log('🚀 ~ createAds ~ resp-->>', resp)
  326. productLoading.value = false
  327. if (resp.data.success.length > 0) {
  328. productSave.value = false
  329. addedTableData.value = []
  330. ElMessage({
  331. message: '商品创建成功',
  332. type: 'success',
  333. })
  334. } else {
  335. ElMessage.error('商品创建失败!')
  336. }
  337. } catch (error) {
  338. console.error('请求失败:', error)
  339. }
  340. }
  341. function submitProductForm() {
  342. productLoading.value = true
  343. createAds()
  344. }
  345. // 处理分页器当前页变化
  346. function handleCurrentChange(newPage) {
  347. currentPage.value = newPage
  348. loading.value = true
  349. setTableData()
  350. }
  351. // 处理分页器每页显示条目数变化
  352. function handleSizeChange(newSize) {
  353. pageSize.value = newSize
  354. currentPage.value = 1 // 重置到第一页
  355. }
  356. const headerCellStyle = (args) => {
  357. if (args.rowIndex === 0) {
  358. return {
  359. backgroundColor: 'rgba(245, 245, 245, 0.9)',
  360. }
  361. }
  362. }
  363. const emit = defineEmits(['update-added-data']);
  364. // 当数据发生变化时,触发事件
  365. watch(addedTableData, (newValue) => {
  366. emit('update-added-data', newValue);
  367. }, {deep: true});
  368. onMounted(() => {
  369. setTableData()
  370. })
  371. </script>
  372. <style lang="scss" scoped>
  373. ::v-deep(.el-form--default.el-form--label-top .el-form-item .el-form-item__label) {
  374. font-weight: 500;
  375. }
  376. .demo-tabs > .el-tabs__content {
  377. padding: 52px;
  378. color: #6b778c;
  379. font-size: 32px;
  380. font-weight: 600;
  381. }
  382. /* 广告组商品Tab栏 */
  383. ::v-deep(.el-tabs__nav-scroll) {
  384. overflow: hidden;
  385. margin-left: 20px;
  386. }
  387. ::v-deep(.el-table__inner-wrapper::before) {
  388. background-color: white;
  389. }
  390. // 表格内容边距
  391. div {
  392. & #pane-first,
  393. & #pane-second {
  394. margin: 10px;
  395. }
  396. }
  397. .card-header {
  398. display: flex;
  399. justify-content: space-between;
  400. align-items: center;
  401. }
  402. .box-card {
  403. width: 100%;
  404. margin-right: 10px;
  405. }
  406. .single-line {
  407. color: rgb(30, 33, 41);
  408. overflow: hidden;
  409. display: -webkit-box;
  410. -webkit-box-orient: vertical;
  411. -webkit-line-clamp: 1;
  412. white-space: pre-wrap;
  413. word-break: break-word;
  414. }
  415. .data-color {
  416. color: rgb(30, 33, 41);
  417. }
  418. .img-box {
  419. width: 60px;
  420. height: 60px;
  421. margin-top: 5px;
  422. border: 1px solid rgb(194, 199, 207);
  423. border-radius: 4px;
  424. }
  425. /* 商品定向Tab栏 */
  426. ::v-deep(.goods-orientation-tabs #tab-1) {
  427. border-right: 0;
  428. }
  429. </style>