FocusCreativity.vue 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986
  1. <template>
  2. <div class="customize-container">
  3. <el-card body-style="padding: 20px 80px 0 80px;">
  4. <div style="font-weight: 700; padding-bottom: 18px">
  5. <span style="color: #306cd7; font-size: 26px">|</span>
  6. <span style="font-size: 18px; padding-left: 5px">创意</span>
  7. </div>
  8. <el-form
  9. ref="ruleFormRef"
  10. :model="ruleForm"
  11. :rules="rules"
  12. label-width="120px"
  13. class="demo-ruleForm"
  14. size="default"
  15. label-position="top"
  16. status-icon>
  17. <el-form-item label="广告名称" prop="name">
  18. <el-input v-model="ruleForm.name" style="width: 50%" />
  19. </el-form-item>
  20. <div style="display: flex; border: 1px solid #dddfe6; padding: 0 0 0 5px; margin-bottom: 20px" v-loading="createLoading">
  21. <div style="width: 50%; padding-left: 5px; border-right: 1px solid #dddfe6">
  22. <el-scrollbar height="700px">
  23. <el-collapse v-model="activeNames" @change="handleChange" style="border-top: none; border-bottom: none">
  24. <el-collapse-item name="1" style="padding-right: 10px">
  25. <template #title> <span style="color: #e47470; margin-right: 4px">*</span>品牌名称和徽标</template>
  26. <el-form-item prop="brandName">
  27. <el-input v-model="ruleForm.brandName" placeholder="请输入品牌名称" style="padding: 0 0 5px 0"></el-input>
  28. </el-form-item>
  29. <el-upload
  30. v-model:file-list="fileList"
  31. :on-change="changeFile"
  32. v-loading="upLoading"
  33. action="#"
  34. accept=".png, .jpg"
  35. :limit="1"
  36. list-type="picture-card"
  37. :auto-upload="false">
  38. <el-icon><Plus /></el-icon>
  39. <template #file="{ file }">
  40. <div>
  41. <img class="el-upload-list__item-thumbnail" :src="file.url" alt="" />
  42. <span class="el-upload-list__item-actions">
  43. <span class="el-upload-list__item-preview" @click="handlePictureCardPreview(file)">
  44. <el-icon><zoom-in /></el-icon>
  45. </span>
  46. <span v-if="!disabled" class="el-upload-list__item-delete" @click="handleRemove(file)">
  47. <el-icon><Delete /></el-icon>
  48. </span>
  49. </span>
  50. </div>
  51. </template>
  52. <template #tip>
  53. <div style="margin-top: 10px">
  54. <div style="display: flex; align-items: center; justify-content: space-between">
  55. <span style="line-height: 17px; font-weight: 600; color: #1e2128">徽标规格</span>
  56. <el-button type="primary" :icon="Picture" @click="openDialog" disabled="true">从素材库中选择</el-button>
  57. </div>
  58. <div class="introduce-item">1、图片大小: 400x400 像素或更大</div>
  59. <div class="introduce-item">2、文件大小: 1MB 或更小</div>
  60. <div class="introduce-item">3、文件格式: PNG 或 JPG</div>
  61. <div class="introduce-item">
  62. 4、内容: 徽标必须填满图片或置于白色或透明背景上详细了解我们的徽标要求
  63. <span style="margin-left: 25px; position: relative">
  64. <el-icon size="14" style="position: absolute; left: -14px; top: 1px"><Link /></el-icon>
  65. <el-link
  66. type="primary"
  67. :underline="false"
  68. href="https://advertising.amazon.com/resources/ad-policy/sponsored-ads-policies#brandlogo"
  69. target="_blank"
  70. >查看要求</el-link
  71. >
  72. </span>
  73. </div>
  74. </div>
  75. </template>
  76. </el-upload>
  77. <!-- 预览弹窗 -->
  78. <el-dialog v-model="dialogVisible">
  79. <img w-full :src="dialogImageUrl" alt="Preview Image" />
  80. </el-dialog>
  81. </el-collapse-item>
  82. <el-collapse-item name="commodity" v-loading="commodityLoading" style="padding-right: 10px">
  83. <template #title>编辑品牌旗舰店页面</template>
  84. <div v-for="(storePage, index) in topStorePages" :key="index" style="margin-bottom: 10px; width: 85%">
  85. <el-card shadow="hover" body-style="padding: 10px;">
  86. <div style="margin-right: 8px; line-height: normal; display: flex; align-items: center">
  87. <el-image class="img-box" :src="storePage.storePageLink" />
  88. <div style="margin-left: 15px">
  89. <span><span style="color: #6d7784">当前名称:</span>{{ storePage.storePageName }}</span>
  90. <div style="margin-bottom: 5px"><span style="color: #6d7784">ASIN: </span>{{ storePage.asin }}</div>
  91. <el-input v-model="storePage.inputName" style="width: 300px" placeholder="修改品牌页面名称"></el-input>
  92. </div>
  93. <div class="card-operation">
  94. <el-button link type="primary" @click="changePicture(storePage.storePageUrl, index)" style="margin-bottom: 10px"
  95. >更换图片</el-button
  96. >
  97. <el-button link type="primary" @click="changePage(storePage.storePageUrl, index)">更换页面</el-button>
  98. </div>
  99. </div>
  100. </el-card>
  101. </div>
  102. </el-collapse-item>
  103. <el-dialog v-model="commodityDialog" title="更换图片" width="50%">
  104. <el-radio-group
  105. v-loading="dialogLoading3"
  106. v-model="selectedCommodity"
  107. style="display: flex; flex-direction: column; align-content: flex-start; align-items: flex-start">
  108. <div v-for="(item, index) in stock" :key="index">
  109. <el-radio :label="item.asin" style="height: 80px; border-bottom: 1px solid #ccc">
  110. <div style="padding: 10px; display: flex; align-items: center">
  111. <div style="margin-right: 8px; line-height: normal">
  112. <el-image class="img-box" :src="item.image_link" />
  113. </div>
  114. <div style="position: relative">
  115. <el-tooltip class="box-item" effect="dark" :content="item.title" placement="top">
  116. <div class="double-line">{{ item.title }}</div>
  117. </el-tooltip>
  118. <span>
  119. <span style="color: #6d7784">ASIN: </span>
  120. <span class="data-color" style="margin-right: 8px">{{ item.asin }}</span>
  121. </span>
  122. </div>
  123. </div>
  124. </el-radio>
  125. </div>
  126. </el-radio-group>
  127. <div style="margin-top: 20px; display: flex; justify-content: center">
  128. <el-button type="primary" :disabled="!selectedCommodity" @click="handleSelectedStore">确定</el-button>
  129. </div>
  130. </el-dialog>
  131. <el-dialog v-model="pageDialog" title="更换页面" width="50%">
  132. <el-radio-group v-loading="pageDialogLoading" v-model="selectedCommodity" @change="handlePageChange" class="radio-group-item">
  133. <div v-for="(item, index) in storePageData.storePageInfo" :key="index" style="width: 100%">
  134. <el-radio
  135. :label="item.storePageId"
  136. class="radio-item"
  137. :disabled="topStorePages.some((storePage) => storePage.storePageName === item.storePageName)">
  138. <div class="radio-item-content">
  139. <div style="position: relative">
  140. <el-tooltip class="box-item" effect="dark" :content="item.storePageName" placement="top">
  141. <div class="double-line">{{ item.storePageName }}</div>
  142. </el-tooltip>
  143. </div>
  144. </div>
  145. </el-radio>
  146. </div>
  147. </el-radio-group>
  148. <!-- <div style="margin-top: 20px; display: flex; justify-content: center">
  149. <el-button type="primary" :disabled="!selectedCommodity">确定</el-button>
  150. </div> -->
  151. </el-dialog>
  152. <el-collapse-item name="4" style="padding-right: 10px">
  153. <template #title> <span style="color: #e47470; margin-right: 4px">*</span>标题</template>
  154. <el-form-item prop="title">
  155. <el-input v-model="ruleForm.title" maxlength="50" placeholder="请输入标题" show-word-limit style="padding: 0 10px 0 0"></el-input>
  156. </el-form-item>
  157. </el-collapse-item>
  158. </el-collapse>
  159. </el-scrollbar>
  160. </div>
  161. <div style="width: 50%; padding: 0 10px; position: relative">
  162. <el-button type="primary" plain @click="submitForm(ruleFormRef)" :disabled="!fileList.length" style="position: absolute; top: 92%; left: 46%"
  163. >保存</el-button
  164. >
  165. </div>
  166. </div>
  167. </el-form>
  168. </el-card>
  169. <el-dialog v-model="centerDialogVisible" title="从素材库中选择" width="65%">
  170. <el-input :prefix-icon="Search"></el-input>
  171. <div class="grid-container">
  172. <div
  173. class="grid-item"
  174. v-for="item in cards"
  175. :key="item.id"
  176. @click="selectCard(item)"
  177. :class="{ selected: isSelected(item.id), hover: hoverId === item.id }"
  178. @mouseover="hoverId = item.id"
  179. @mouseleave="hoverId = null">
  180. <el-card :body-style="{ padding: '0px' }">
  181. <el-image class="image" :src="item.imageUrl" fit="cover" />
  182. <div style="padding: 10px">
  183. <span>
  184. <el-tooltip placement="top" :content="item.title">
  185. {{ item.title }}
  186. </el-tooltip>
  187. </span>
  188. <div class="bottom">
  189. <div class="bottom-item">{{ item.size }}KB</div>
  190. <div class="bottom-item">{{ item.width }} * {{ item.height }}</div>
  191. <div class="bottom-item">徽标</div>
  192. </div>
  193. </div>
  194. </el-card>
  195. </div>
  196. </div>
  197. <template #footer>
  198. <span class="dialog-footer">
  199. <el-button @click="centerDialogVisible = false">取消</el-button>
  200. <el-button type="primary" @click="handleConfirmSelection">确定</el-button>
  201. </span>
  202. </template>
  203. </el-dialog>
  204. <!-- <el-dialog v-model="lifeStyleDialog" title="从素材库中选择" width="65%">
  205. <el-input :prefix-icon="Search"></el-input>
  206. <div class="grid-container">
  207. <div
  208. class="grid-item"
  209. v-for="item in lifeStyleCards"
  210. :key="item.id"
  211. @click="selectCard(item)"
  212. :class="{ selected: isSelected(item.id), hover: hoverId === item.id }"
  213. @mouseover="hoverId = item.id"
  214. @mouseleave="hoverId = null">
  215. <el-card :body-style="{ padding: '0px' }">
  216. <el-image class="image" :src="item.imageUrl" fit="cover" />
  217. <div style="padding: 10px">
  218. <span>
  219. <el-tooltip placement="top" :content="item.title">
  220. {{ item.title }}
  221. </el-tooltip>
  222. </span>
  223. <div class="bottom">
  224. <div class="bottom-item">{{ item.size }}KB</div>
  225. <div class="bottom-item">{{ item.width }} * {{ item.height }}</div>
  226. <div class="bottom-item">徽标</div>
  227. </div>
  228. </div>
  229. </el-card>
  230. </div>
  231. </div>
  232. <template #footer>
  233. <span class="dialog-footer">
  234. <el-button @click="centerDialogVisible = false">取消</el-button>
  235. <el-button type="primary" @click="centerDialogVisible = false">确定</el-button>
  236. </span>
  237. </template>
  238. </el-dialog> -->
  239. </div>
  240. </template>
  241. <script setup lang="ts">
  242. import { reactive, ref, inject, Ref, watch, computed, onMounted, onUnmounted } from 'vue'
  243. import type { FormInstance, FormRules, UploadProps, UploadUserFile } from 'element-plus'
  244. import { ElMessage, ElMessageBox } from 'element-plus'
  245. import { Plus, Picture, Search, Delete, Download, ZoomIn } from '@element-plus/icons-vue'
  246. import type { UploadFile } from 'element-plus'
  247. import emitter from '/@/utils/emitter'
  248. import {
  249. getAssets,
  250. getLifeStyleAssets,
  251. getPageAsins,
  252. getCommodityCard,
  253. getStoreurl,
  254. getDefaultSpotlightAsin,
  255. getSellerInStock,
  256. postStoreSpotlight,
  257. uploadFile,
  258. checkAsset,
  259. } from '../api/index'
  260. import { storeToRefs } from 'pinia'
  261. import { useShopInfo } from '/@/stores/shopInfo'
  262. const shopInfo = useShopInfo()
  263. const { profile } = storeToRefs(shopInfo)
  264. const createLoading = ref(false)
  265. const ruleFormRef = ref<FormInstance>()
  266. interface RuleForm {
  267. name: string
  268. brandName: string
  269. title: string
  270. }
  271. const ruleForm = reactive<RuleForm>({
  272. name: '视频 广告 - 1/15/2024 17:51:10.236',
  273. brandName: '',
  274. title: '',
  275. })
  276. const rules = reactive<FormRules<RuleForm>>({
  277. name: [{ required: true, message: '请输入广告名称', trigger: 'blur' }],
  278. brandName: [{ required: true, message: '请输入品牌名称', trigger: 'blur' }],
  279. title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
  280. })
  281. const submitForm = async (formEl: FormInstance | undefined) => {
  282. if (!formEl) return
  283. await formEl.validate((valid, fields) => {
  284. if (valid) {
  285. console.log('submit!')
  286. createStoreSpotlight()
  287. } else {
  288. console.log('error submit!', fields)
  289. }
  290. })
  291. }
  292. const activeNames = ref(['1'])
  293. const handleChange = (val: string[]) => {
  294. // console.log(val)
  295. if (val.includes('commodity')) {
  296. // getCommodityCardData()
  297. }
  298. }
  299. const imageUrl = ref('')
  300. const handleAvatarSuccess: UploadProps['onSuccess'] = (response, uploadFile) => {
  301. imageUrl.value = URL.createObjectURL(uploadFile.raw!)
  302. console.log('success!')
  303. }
  304. const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
  305. if (rawFile.type !== 'image/jpeg') {
  306. ElMessage.error('Avatar picture must be JPG format!')
  307. return false
  308. } else if (rawFile.size / 1024 / 1024 > 2) {
  309. ElMessage.error('Avatar picture size can not exceed 2MB!')
  310. return false
  311. }
  312. return true
  313. }
  314. // 图片上传相关
  315. const dialogImageUrl = ref('')
  316. const dialogVisible = ref(false)
  317. const disabled = ref(false)
  318. const fileList = ref([])
  319. const selectedId = ref(null)
  320. const hoverId = ref(null)
  321. const selectedCards = ref([])
  322. const selectedImageUrl = ref('')
  323. const centerDialogVisible = ref(false)
  324. const cards = reactive([])
  325. function selectCard(item) {
  326. if (isSelected(item.id)) {
  327. selectedCards.value = selectedCards.value.filter((card) => card.id !== item.id)
  328. } else {
  329. selectedCards.value.push(item)
  330. }
  331. selectedId.value = item.id
  332. }
  333. function handleConfirmSelection() {
  334. if (selectedCards.value.length > 0) {
  335. // 清空 fileList
  336. fileList.value.length = 0
  337. // 假设每次只选择一个图片
  338. selectedImageUrl.value = selectedCards.value[0].imageUrl
  339. // 创建一个新的 UploadFile 对象
  340. const newFile = {
  341. name: selectedCards.value[0].title, // 或者任何你希望用作文件名的字符串
  342. url: selectedImageUrl.value,
  343. // 根据需要添加更多属性
  344. }
  345. // 将新的文件对象添加到 fileList 中
  346. fileList.value.push(newFile)
  347. }
  348. // 清空选中卡片
  349. selectedCards.value = []
  350. // 关闭对话框
  351. centerDialogVisible.value = false
  352. }
  353. function isSelected(id) {
  354. return selectedId.value === id
  355. }
  356. async function getAssetsData() {
  357. const query = {
  358. profile_id: profile.value.profile_id,
  359. assetType: 'IMAGE',
  360. assetSubType: 'LOGO',
  361. }
  362. const response = await getAssets(query)
  363. console.log('🚀 ~ getAssetsData ~ response-->>', response)
  364. cards.splice(0, cards.length)
  365. response.data.forEach((asset) => {
  366. cards.push({
  367. id: asset.assetId,
  368. title: asset.name,
  369. imageUrl: asset.storageLocationUrls.defaultUrl,
  370. width: asset.fileMetadata.width,
  371. height: asset.fileMetadata.height,
  372. size: bytesToKB(asset.fileMetadata.sizeInBytes),
  373. })
  374. })
  375. }
  376. function bytesToKB(bytes) {
  377. return (bytes / 1024).toFixed(2) // 保留两位小数
  378. }
  379. function openDialog() {
  380. centerDialogVisible.value = true
  381. getAssetsData()
  382. }
  383. const lifeStyleDialog = ref(false)
  384. const lifeStyleCards = reactive([])
  385. async function getLifeStyleAssetsData() {
  386. try {
  387. const query = {
  388. profile_id: profile.value.profile_id,
  389. assetType: 'IMAGE',
  390. assetSubType: 'LIFESTYLE_IMAGE',
  391. }
  392. const response = await getLifeStyleAssets(query)
  393. console.log('🚀 ~ getLifeStyleAssetsData ~ response-->>', response)
  394. lifeStyleCards.splice(0, lifeStyleCards.length)
  395. response.data.forEach((asset) => {
  396. lifeStyleCards.push({
  397. id: asset.assetId,
  398. title: asset.name,
  399. imageUrl: asset.storageLocationUrls.defaultUrl,
  400. width: asset.fileMetadata.width,
  401. height: asset.fileMetadata.height,
  402. size: bytesToKB(asset.fileMetadata.sizeInBytes),
  403. })
  404. })
  405. } catch (error) {
  406. console.log('error:', error)
  407. }
  408. }
  409. // function openLifeStyleDialog() {
  410. // lifeStyleDialog.value = true
  411. // getLifeStyleAssetsData()
  412. // }
  413. // 获取商品数据
  414. const topStorePages = ref([])
  415. const commodityLoading = ref(false)
  416. const dialogLoading3 = ref(false)
  417. let subpageslist = []
  418. async function getDefaultCommodityData() {
  419. try {
  420. commodityLoading.value = true
  421. const resp = await getDefaultSpotlightAsin({ profile_id: '3006125408623189' })
  422. if (resp.code === 2000) {
  423. topStorePages.value = resp.data.defaultlist.map((item) => ({
  424. storePageName: item.storePageName,
  425. storePageLink: item.image_link,
  426. storePageUrl: item.storePageUrl,
  427. asin: item.asin,
  428. inputName: item.storePageName, // 新增字段用于存储输入值
  429. }))
  430. subpageslist = topStorePages.value.map((item) => ({
  431. asin: item.asin,
  432. pageTitle: item.inputName,
  433. url: item.storePageUrl,
  434. }))
  435. }
  436. } catch (error) {
  437. console.error('Error in getDefaultCommodityData:', error)
  438. } finally {
  439. commodityLoading.value = false
  440. }
  441. }
  442. const asinList = ref([])
  443. const stock = ref([])
  444. const currentEditingIndex = ref(null)
  445. const selectedCommodityData = ref(null)
  446. async function changePicture(pageUrl, index) {
  447. commodityDialog.value = true
  448. dialogLoading3.value = true
  449. currentEditingIndex.value = index
  450. try {
  451. const query = {
  452. profile_id: profile.value.profile_id,
  453. pageurl: pageUrl,
  454. }
  455. const response = await getPageAsins(query)
  456. asinList.value = response.data.asinList
  457. getStock()
  458. dialogLoading3.value = false
  459. } catch (error) {
  460. console.log('error:', error)
  461. }
  462. }
  463. async function getStock() {
  464. try {
  465. const obj = {
  466. profile_id: profile.value.profile_id,
  467. asinlist: asinList.value,
  468. }
  469. const response = await getSellerInStock(obj)
  470. stock.value = response.data
  471. } catch (error) {
  472. console.error('Error in getStock:', error)
  473. }
  474. }
  475. function handleSelectedStore() {
  476. if (currentEditingIndex.value !== null) {
  477. selectedCommodityData.value = stock.value.find((item) => item.asin === selectedCommodity.value)
  478. if (selectedCommodityData.value) {
  479. // 更新图片链接和ASIN
  480. topStorePages.value[currentEditingIndex.value].storePageLink = selectedCommodityData.value.image_link
  481. topStorePages.value[currentEditingIndex.value].asin = selectedCommodityData.value.asin
  482. // 更新输入框的绑定值以便可以继续编辑名称
  483. topStorePages.value[currentEditingIndex.value].inputName = topStorePages.value[currentEditingIndex.value].inputName
  484. console.log(topStorePages.value)
  485. }
  486. // 重置当前编辑索引和选中的商品
  487. currentEditingIndex.value = null
  488. selectedCommodity.value = null
  489. commodityDialog.value = false
  490. }
  491. }
  492. // 更换页面功能
  493. const pageDialog = ref(false)
  494. const storePageData = ref({ storePageInfo: [] })
  495. const pageDialogLoading = ref(false)
  496. const selectedPageUrl = ref('')
  497. const firstObj: any = ref({})
  498. const pageAsinList = ref([])
  499. const selectedPageName = ref('')
  500. async function changePage(pageUrl, index) {
  501. pageDialog.value = true
  502. pageDialogLoading.value = true
  503. currentEditingIndex.value = index
  504. try {
  505. const response = await getStoreurl({ profile_id: profile.value.profile_id })
  506. storePageData.value = response.data
  507. } catch (error) {
  508. console.log('error:', error)
  509. } finally {
  510. pageDialogLoading.value = false
  511. }
  512. }
  513. async function getAsinList(pageurl) {
  514. try {
  515. const query = {
  516. profile_id: profile.value.profile_id,
  517. pageurl: pageurl,
  518. }
  519. const resp = await getPageAsins(query)
  520. pageAsinList.value = resp.data.asinList
  521. changeCardData()
  522. } catch (error) {
  523. console.log('error:', error)
  524. }
  525. }
  526. async function handlePageChange(selectedPageId) {
  527. const selectedPage = storePageData.value.storePageInfo.find((page) => page.storePageId === selectedPageId)
  528. if (selectedPage) {
  529. selectedPageUrl.value = selectedPage.storePageUrl
  530. selectedPageName.value = selectedPage.storePageName
  531. pageDialog.value = false
  532. await getAsinList(selectedPageUrl.value)
  533. } else {
  534. console.error('Selected page not found in storePageData')
  535. }
  536. }
  537. async function changeCardData() {
  538. try {
  539. const query = {
  540. profile_id: profile.value.profile_id,
  541. asinlist: pageAsinList.value,
  542. }
  543. const response = await getSellerInStock(query)
  544. if (response && response.data.length > 0) {
  545. // 将response的第一个元素赋值给firstObj
  546. firstObj.value = response.data[0]
  547. // 更新图片链接和ASIN
  548. topStorePages.value[currentEditingIndex.value].storePageLink = firstObj.value.image_link
  549. topStorePages.value[currentEditingIndex.value].asin = firstObj.value.asin
  550. topStorePages.value[currentEditingIndex.value].storePageName = selectedPageName.value
  551. topStorePages.value[currentEditingIndex.value].inputName = selectedPageName.value
  552. currentEditingIndex.value = null
  553. } else {
  554. console.error('No data')
  555. }
  556. } catch (error) {
  557. console.error('error:', error)
  558. }
  559. }
  560. // 创建创意
  561. let brandName = ''
  562. let brandEntityId = ''
  563. const respAdGroupId = inject<Ref>('respAdGroupId')
  564. let brandLogoCrop = {}
  565. const upLoading = ref(false)
  566. let respAssetId = ''
  567. function handleRemove(file: UploadFile) {
  568. fileList.value = []
  569. }
  570. function handlePictureCardPreview(file: UploadFile) {
  571. dialogImageUrl.value = file.url!
  572. dialogVisible.value = true
  573. }
  574. function changeFile(file: UploadFile) {
  575. handleUpload(file)
  576. }
  577. async function handleUpload(file: UploadFile) {
  578. const formData = new FormData()
  579. formData.append('file', file.raw)
  580. formData.append('profile_id', profile.value.profile_id)
  581. formData.append('brandEntityId', brandEntityId)
  582. formData.append('assetType', 'IMAGE')
  583. formData.append('assetSubTypeList', JSON.stringify(['LOGO']))
  584. upLoading.value = true
  585. try {
  586. const response = await uploadFile(formData)
  587. const fileName = response.data.file_name
  588. const obj = {
  589. profile_id: profile.value.profile_id,
  590. file_name: fileName,
  591. }
  592. const resp = await checkAsset(obj)
  593. respAssetId = resp.data.assetId
  594. const { width, height } = resp.data.fileMetadata
  595. brandLogoCrop = {
  596. width,
  597. height,
  598. top: 0,
  599. left: 0,
  600. }
  601. if (resp.data.checkresult == 'success') {
  602. ElMessage({ message: '上传成功', type: 'success' })
  603. } else {
  604. ElMessage.error('上传失败')
  605. }
  606. } catch (error) {
  607. console.error('上传失败:', error)
  608. } finally {
  609. upLoading.value = false
  610. }
  611. }
  612. // TODO: url对应的是Home 暂时写死
  613. async function createStoreSpotlight() {
  614. createLoading.value = true
  615. try {
  616. const query = {
  617. profile_id: profile.value.profile_id,
  618. url: 'https://www.amazon.com/stores/page/1D1DD2FD-CF54-4FE5-B1A0-9E01F12F8144',
  619. name: ruleForm.name,
  620. state: 'PAUSED',
  621. adGroupId: respAdGroupId.value,
  622. brandName: brandName,
  623. brandLogoAssetID: respAssetId,
  624. brandLogoCrop: brandLogoCrop,
  625. consentToTranslate: false,
  626. subpageslist: subpageslist,
  627. headline: ruleForm.title,
  628. }
  629. const response = await postStoreSpotlight(query)
  630. if (response.data.creative_state == 'success') {
  631. ElMessage({ message: '创建成功', type: 'success' })
  632. } else {
  633. ElMessage.error('上传失败')
  634. }
  635. } catch (error) {
  636. console.error('error:', error)
  637. } finally {
  638. createLoading.value = false
  639. }
  640. }
  641. watch(topStorePages, () => {
  642. subpageslist = topStorePages.value.map((item) => ({
  643. asin: item.asin,
  644. pageTitle: item.inputName,
  645. url: item.storePageUrl,
  646. }))
  647. console.log('subpageslist', subpageslist)
  648. },{deep: true})
  649. onMounted(() => {
  650. emitter.on('spotlight-shop', (newValue: any) => {
  651. brandName = newValue.brandRegistryName
  652. brandEntityId = newValue.brandEntityId
  653. })
  654. })
  655. // 接收数据端在组件卸载时解绑事件
  656. onUnmounted(() => {
  657. emitter.off('spotlight-shop')
  658. })
  659. const focusShop = inject<Ref>('focusShop')
  660. async function getCommodityCollapseData() {
  661. commodityLoading.value = true
  662. try {
  663. const query = {
  664. profile_id: profile.value.profile_id,
  665. pageurl: focusShop.value,
  666. }
  667. const response = await getPageAsins(query)
  668. asinList.value = response.data.asinList
  669. console.log('asinList', asinList.value)
  670. } catch (error) {
  671. console.log('error:', error)
  672. } finally {
  673. commodityLoading.value = false
  674. }
  675. }
  676. // let lastQueriedAsins = []
  677. const commodityCard = ref([])
  678. // async function getCommodityCardData() {
  679. // try {
  680. // commodityLoading.value = true
  681. // const topAsins = asinList.value.slice(0, 3)
  682. // const newAsins = topAsins.filter((asin) => !lastQueriedAsins.includes(asin))
  683. // if (newAsins.length === 0) {
  684. // commodityLoading.value = false
  685. // return // 如果没有新的 ASIN,直接返回
  686. // }
  687. // lastQueriedAsins = [...topAsins]
  688. // // 清空commodityCard,为新数据做准备
  689. // commodityCard.value = []
  690. // // 对每个新的 ASIN 发送请求
  691. // for (const asin of newAsins) {
  692. // const query = {
  693. // profile_id: profile.value.profile_id,
  694. // asin: asin,
  695. // }
  696. // try {
  697. // const response = await getCommodityCard(query)
  698. // commodityCard.value.push(response.data)
  699. // // console.log('Response for ASIN', asin, ':', response)
  700. // } catch (error) {
  701. // console.log('Error for ASIN', asin, ':', error)
  702. // }
  703. // }
  704. // } catch (error) {
  705. // console.log('Outer error:', error)
  706. // } finally {
  707. // commodityLoading.value = false
  708. // }
  709. // }
  710. // 更改商品功能
  711. const commodityDialog = ref(false)
  712. const selectedCommodity = ref()
  713. const replaceableCommodity = ref([])
  714. function openCommodityDialog(index) {
  715. currentEditingIndex.value = index
  716. commodityDialog.value = true
  717. }
  718. async function getAdditionalCommodityData() {
  719. try {
  720. dialogLoading3.value = true
  721. // 获取除前三个之外的所有 ASIN
  722. const additionalAsins = asinList.value.slice(3)
  723. // 清空 replaceableCommodity,为新数据做准备
  724. replaceableCommodity.value = []
  725. // 对每个额外的 ASIN 发送请求
  726. for (const asin of additionalAsins) {
  727. const query = {
  728. profile_id: profile.value.profile_id,
  729. asin: asin,
  730. }
  731. try {
  732. const response = await getCommodityCard(query)
  733. replaceableCommodity.value.push(response.data)
  734. console.log('🚀 ~ getAdditionalCommodityData ~ replaceableCommodity-->>', replaceableCommodity.value)
  735. // console.log('Response for additional ASIN', asin, ':', response)
  736. } catch (error) {
  737. console.log('Error for additional ASIN', asin, ':', error)
  738. }
  739. }
  740. } catch (error) {
  741. console.log('Outer error:', error)
  742. } finally {
  743. dialogLoading3.value = false
  744. }
  745. }
  746. const flattenedCommodityCard = computed(() => {
  747. return commodityCard.value.flat()
  748. })
  749. const flattenedReplaceableCommodity = computed(() => {
  750. return replaceableCommodity.value.flat()
  751. })
  752. // watch(focusShop.value,
  753. // async () => {
  754. // await getCommodityCollapseData()
  755. // getCommodityCardData()
  756. // getAdditionalCommodityData()
  757. // }
  758. // )
  759. onMounted(async () => {
  760. await getDefaultCommodityData()
  761. // await getCommodityCollapseData()
  762. // getCommodityCardData()
  763. // getAdditionalCommodityData()
  764. })
  765. </script>
  766. <style scoped>
  767. .customize-container {
  768. margin-top: 10px;
  769. }
  770. .upload-button-group {
  771. display: flex;
  772. }
  773. .introduce-item {
  774. line-height: 17px;
  775. font-size: 12px;
  776. color: #88909b;
  777. }
  778. .avatar-uploader .avatar {
  779. width: 178px;
  780. height: 178px;
  781. display: block;
  782. }
  783. ::v-deep(.avatar-uploader .el-upload) {
  784. border: 1px dashed var(--el-border-color);
  785. border-radius: 6px;
  786. cursor: pointer;
  787. position: relative;
  788. overflow: hidden;
  789. transition: var(--el-transition-duration-fast);
  790. }
  791. ::v-deep(.avatar-uploader .el-upload:hover) {
  792. border-color: var(--el-color-primary);
  793. }
  794. ::v-deep(.el-icon.avatar-uploader-icon) {
  795. font-size: 28px;
  796. color: #8c939d;
  797. width: 178px;
  798. height: 178px;
  799. text-align: center;
  800. }
  801. ::v-deep(.avatar-uploader .el-upload.el-upload--text) {
  802. width: 100%;
  803. }
  804. .grid-container {
  805. flex-wrap: wrap;
  806. display: flex;
  807. width: 100%;
  808. justify-content: left;
  809. }
  810. .grid-item {
  811. transition: outline, background-color 0.3s;
  812. box-sizing: border-box;
  813. border: 1px solid #ffffff00;
  814. cursor: pointer;
  815. width: calc(25% - 10px);
  816. margin: 10px 5px;
  817. }
  818. .grid-item span {
  819. display: block; /* 或者 inline-block */
  820. white-space: nowrap; /* 保持文本在一行 */
  821. overflow: hidden; /* 隐藏超出部分 */
  822. text-overflow: ellipsis; /* 超出部分显示省略号 */
  823. max-width: 100%; /* 限制最大宽度 */
  824. font-weight: 600;
  825. line-height: 22px;
  826. }
  827. .grid-item.hover,
  828. .grid-item.selected {
  829. border: 1px solid #306cd8;
  830. border-radius: 4px;
  831. }
  832. .grid-item.selected > :first-child {
  833. background-color: #f5f7fe;
  834. }
  835. .image {
  836. width: 100%;
  837. height: 146.49px;
  838. padding: 10px;
  839. }
  840. .image > :first-child {
  841. border-radius: 10px;
  842. }
  843. .bottom {
  844. display: flex;
  845. justify-content: space-between;
  846. align-items: center;
  847. margin-top: 5px;
  848. }
  849. .bottom-item {
  850. background-color: #f5f7fe;
  851. border-radius: 4px;
  852. padding: 0 3px;
  853. }
  854. .uploaded-image {
  855. width: 100%; /* 或根据需要调整 */
  856. height: auto; /* 保持图片的原始宽高比 */
  857. display: block;
  858. margin-bottom: 10px; /* 或根据需要调整 */
  859. }
  860. .upload-content {
  861. text-align: center;
  862. padding: 20px;
  863. }
  864. .el-carousel__item h3 {
  865. color: #edf5fe;
  866. opacity: 0.75;
  867. line-height: 200px;
  868. margin: 0;
  869. text-align: center;
  870. }
  871. ::v-deep(button.el-carousel__button) {
  872. background-color: #3569d6;
  873. }
  874. .img-box {
  875. min-width: 60px;
  876. height: 60px;
  877. border: 1px solid rgb(194, 199, 207);
  878. border-radius: 4px;
  879. }
  880. .double-line {
  881. color: #1e2128;
  882. font-weight: 500;
  883. overflow: hidden;
  884. display: -webkit-box;
  885. -webkit-box-orient: vertical;
  886. -webkit-line-clamp: 2;
  887. white-space: pre-wrap;
  888. word-break: break-word;
  889. }
  890. .card-operation {
  891. display: flex;
  892. flex-direction: column;
  893. align-items: flex-end;
  894. justify-content: center;
  895. margin-left: 30px;
  896. }
  897. .radio-group-item {
  898. display: flex;
  899. flex-direction: column;
  900. align-content: flex-start;
  901. align-items: flex-start;
  902. }
  903. .radio-item {
  904. height: 80px;
  905. width: 100%;
  906. border-bottom: 1px solid #ccc;
  907. }
  908. .radio-item-content {
  909. padding: 10px;
  910. display: flex;
  911. align-items: center;
  912. }
  913. </style>