index.vue 99 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588
  1. <template>
  2. <div>
  3. <el-card>
  4. <div style="padding-left: 5px">
  5. <!-- 广告活动 -->
  6. <el-form
  7. :label-position="labelPosition"
  8. ref="campaignFormRef"
  9. :model="campaignRuleForm"
  10. :rules="campaignRules"
  11. label-width="120px"
  12. class="demo-ruleForm"
  13. :size="formSize"
  14. status-icon>
  15. <el-card shadow="never" body-style="padding-bottom: 0 !important;" v-loading="campaignLoading">
  16. <div style="font-size: 24px; font-weight: bold">广告活动</div>
  17. <hr />
  18. <br />
  19. <el-form-item label="广告活动名称" prop="name" style="width: 350px">
  20. <el-input v-model="campaignRuleForm.name" placeholder="请输入广告活动名称" />
  21. </el-form-item>
  22. <el-form-item label="广告组合" prop="adMix">
  23. <el-select v-model="campaignRuleForm.adMix" clearable placeholder="请选择">
  24. <el-option v-for="item in adMixOptions" :key="item.portfolioId" :label="item.name" :value="item.portfolioId" />
  25. </el-select>
  26. </el-form-item>
  27. <el-form-item prop="startDate" label="开始日期" style="width: 350px">
  28. <el-date-picker
  29. v-model="campaignRuleForm.startDate"
  30. type="date"
  31. format="YYYY-MM-DD"
  32. value-format="YYYY-MM-DD"
  33. label="开始日期"
  34. placeholder="开始日期"
  35. style="width: 100%" />
  36. </el-form-item>
  37. <el-form-item prop="date2" label="结束日期" style="width: 350px">
  38. <el-date-picker
  39. v-model="campaignRuleForm.date2"
  40. type="date"
  41. format="YYYY-MM-DD"
  42. value-format="YYYY-MM-DD"
  43. label="结束日期"
  44. placeholder="结束日期"
  45. style="width: 100%" />
  46. </el-form-item>
  47. <el-form-item prop="budget" label="每日预算" style="width: 300px">
  48. <el-input
  49. v-model="campaignRuleForm.budget"
  50. maxlength="7"
  51. oninput="value=value.indexOf('.') > -1?value.slice(0, value.indexOf('.') + 3):value">
  52. <template #prepend>$</template>
  53. </el-input>
  54. </el-form-item>
  55. <el-form-item label="投放类型" prop="type" class="column-item">
  56. <el-radio-group v-model="campaignRuleForm.type" @click="changeType">
  57. <div>
  58. <el-radio label="AUTO">自动</el-radio>
  59. <div class="radio-description">定向与您推广商品相似的关键词和商品</div>
  60. </div>
  61. <div>
  62. <el-radio label="MANUAL">手动</el-radio>
  63. <div class="radio-description">选择关键词或商品以定向购物者搜索并设置自定义出价</div>
  64. </div>
  65. </el-radio-group>
  66. </el-form-item>
  67. <el-form-item label="竞价策略" prop="bidStrategy" class="column-item column-margin-bottom">
  68. <el-radio-group v-model="campaignRuleForm.bidStrategy">
  69. <div>
  70. <el-radio label="LEGACY_FOR_SALES" border>
  71. 动态竞价-仅降低
  72. <div class="radio-description-2">当您的广告不太可能带来销售时,我们将实时降低您的竞价</div>
  73. </el-radio>
  74. </div>
  75. <div>
  76. <el-radio label="AUTO_FOR_SALES" border>
  77. 动态竞价-提高和降低
  78. <div class="radio-description-2">
  79. 当您的广告很有可能带来销售时,我们将实时提高您的竞价(最高可达 100%),并在您的广告不太可能带来销售时降低您的竞价
  80. </div>
  81. </el-radio>
  82. </div>
  83. <el-radio label="MANUAL" border>
  84. 固定竞价
  85. <div class="radio-description-2">我们将使用您的确切竞价和您设置的任何手动调整,而不会根据售出可能性对您的竞价进行更改</div>
  86. </el-radio>
  87. </el-radio-group>
  88. </el-form-item>
  89. <el-form-item label="按展示位置调整出价" prop="placeBid">
  90. <p style="color: #8e9196">除了出价策略外,您还可以将出价提高多达900%</p>
  91. <div class="gap-items">
  92. <div class="gap-item">搜索结果顶部(首页)</div>
  93. <el-input v-model="campaignRuleForm.placeBid" class="gap-item">
  94. <template #append>%</template>
  95. </el-input>
  96. </div>
  97. <div class="gap-items">
  98. <div class="gap-item">商品首页</div>
  99. <el-input v-model="campaignRuleForm.firstPage" class="gap-item">
  100. <template #append>%</template>
  101. </el-input>
  102. </div>
  103. <div class="gap-items" style="margin-bottom: 0">
  104. <div class="gap-item">搜索结果的其余位置</div>
  105. <el-input v-model="campaignRuleForm.other" class="gap-item">
  106. <template #append>%</template>
  107. </el-input>
  108. </div>
  109. </el-form-item>
  110. <el-form-item style="margin-left: 48%">
  111. <el-button type="primary" plain @click="submitCampaignForm(campaignFormRef)">保存</el-button>
  112. </el-form-item>
  113. </el-card>
  114. <br />
  115. <!-- 广告组 -->
  116. <el-card shadow="never" body-style="padding-bottom: 0 !important;" v-loading="adGroupLoading">
  117. <div style="font-size: 20px; font-weight: bold">广告组</div>
  118. <hr />
  119. <br />
  120. <el-form ref="adGroupRuleFormRef" :model="adGroupRuleForm" :rules="adGroupRules">
  121. <el-form-item required label="广告组名称" prop="adGroupName" style="width: 350px; margin-top: 20px">
  122. <el-input v-model="adGroupRuleForm.adGroupName" placeholder="请输入广告组名称"/>
  123. </el-form-item>
  124. <el-form-item required label="默认竞价" prop="defaultBidInp">
  125. <el-input v-model="adGroupRuleForm.defaultBidInp" minlength="3" maxlength="4" style="width: 200px">
  126. <template #prepend>$</template>
  127. </el-input>
  128. </el-form-item>
  129. <el-form-item style="margin-left: 48%">
  130. <el-button type="primary" plain @click="submitGroupsForm(adGroupRuleFormRef)" :disabled="adGroupSave">保存</el-button>
  131. </el-form-item>
  132. </el-form>
  133. </el-card>
  134. <!-- 商品表格 -->
  135. <div style="margin-top: 20px; font-size: 24px; font-weight: bold">商品</div>
  136. <hr />
  137. <br />
  138. <el-form-item prop="commodity" style="width: 100%" v-loading="commodityLoading">
  139. <div style="width: 100%; height: 620px; display: flex; border: 1px solid #e5e7ec; border-radius: 6px">
  140. <div style="width: 50%; border-right: 1px solid #e5e7ec">
  141. <el-tabs v-model="activeName" class="demo-tabs">
  142. <el-tab-pane label="搜索" name="first">
  143. <div style="margin-bottom: 10px">
  144. <el-input v-model="searchInp" placeholder="Please input" class="input-with-select" @change="inpChange" clearable>
  145. <template #prepend>
  146. <el-select v-model="select" style="width: 100px" @change="selChange">
  147. <el-option label="名称" value="name" />
  148. <el-option label="ASIN" value="asin" />
  149. <el-option label="SKU" value="sku" />
  150. </el-select>
  151. </template>
  152. <template #append>
  153. <el-select v-model="select2" style="width: 100px">
  154. <el-option label="最新优先" value="latest" />
  155. <el-option label="最早优先" value="earliest" />
  156. <el-option label="优选广告" value="optimal" />
  157. </el-select>
  158. </template>
  159. </el-input>
  160. </div>
  161. <el-table
  162. height="490"
  163. style="width: 100%"
  164. v-loading="loading"
  165. :data="fullTableData"
  166. :header-cell-style="headerCellStyle"
  167. @selection-change="handleSelectionChange">
  168. <el-table-column type="selection" width="50" />
  169. <el-table-column prop="asin" label="商品">
  170. <template #default="scope">
  171. <div style="display: flex; align-items: center">
  172. <div style="margin-right: 8px; line-height: normal">
  173. <el-image class="img-box" :src="scope.row.image_link" />
  174. </div>
  175. <div>
  176. <el-tooltip class="box-item" effect="dark" :content="scope.row.title" placement="top">
  177. <div class="single-line">{{ scope.row.title ? scope.row.title : '--' }}</div>
  178. </el-tooltip>
  179. <div class="data-color">
  180. <span style="font-weight: 500; color: rgb(30, 33, 41)">${{ scope.row.price ? scope.row.price : '--' }}</span>
  181. <span style="margin: 0 5px; color: #cacdd4">|</span>
  182. <span style="color: #6d7784">{{ scope.row.quantity }}</span>
  183. </div>
  184. <span>
  185. ASIN: <span class="data-color" style="margin-right: 8px">{{ scope.row.asin ? scope.row.asin : '--' }}</span>
  186. </span>
  187. <span>
  188. SKU: <span class="data-color">{{ scope.row.sku ? scope.row.sku : '--' }}</span>
  189. </span>
  190. </div>
  191. </div>
  192. </template>
  193. </el-table-column>
  194. <el-table-column prop="name" label="Name" width="120" align="right">
  195. <template #header>
  196. <el-button type="primary" size="normal" link @click="handleGoodsAdd">添加已选中</el-button>
  197. </template>
  198. <template #default="scope">
  199. <el-button type="primary" size="small" @click="addSingleGoods(scope)" text>添加</el-button>
  200. </template>
  201. </el-table-column>
  202. </el-table>
  203. <el-pagination
  204. @current-change="handleCurrentChange"
  205. @size-change="handleSizeChange"
  206. :current-page="currentPage"
  207. :page-size="pageSize"
  208. :total="totalItems"
  209. layout="prev, pager, next" />
  210. </el-tab-pane>
  211. <el-tab-pane label="输入" name="second">
  212. <el-input
  213. style="padding: 10px"
  214. v-model="goodsTextarea"
  215. :rows="20"
  216. type="textarea"
  217. placeholder="请输入ASIN,多个ASIN使用逗号、空格或换行符分隔。(未完成)"
  218. maxlength="11000" />
  219. <div style="display: flex; flex-direction: row-reverse; margin-top: 10px">
  220. <el-button v-for="button in buttons" :key="button.text" :type="button.type" link @click="addGods">{{ button.text }}</el-button>
  221. </div>
  222. </el-tab-pane>
  223. </el-tabs>
  224. </div>
  225. <div style="width: 50%">
  226. <el-card class="box-card" shadow="never" style="border: 0">
  227. <template #header>
  228. <div class="card-header">
  229. <span style="font-weight: 550; font-size: 15px; color: #1f2128">已添加: {{ adsTableData.length }}</span>
  230. <el-button class="button" text bg @click="delAllGoods">全部删除</el-button>
  231. </div>
  232. </template>
  233. <div class="card-body"></div>
  234. </el-card>
  235. <div style="padding: 0 10px 0 10px; margin-top: -12px">
  236. <el-table
  237. :data="adsTableData"
  238. height="475"
  239. style="width: 100%"
  240. :header-cell-style="headerCellStyle"
  241. @selection-change="handleAddedGoodsChange">
  242. <el-table-column type="selection" width="50" />
  243. <el-table-column prop="asin" label="ASIN">
  244. <template #default="scope">
  245. <div style="display: flex; align-items: center">
  246. <div style="margin-right: 8px; line-height: normal">
  247. <el-image class="img-box" :src="scope.row.image_link" />
  248. </div>
  249. <div>
  250. <el-tooltip class="box-item" effect="dark" :content="scope.row.title" placement="top">
  251. <div class="single-line">{{ scope.row.title ? scope.row.title : '--' }}</div>
  252. </el-tooltip>
  253. <div class="data-color">
  254. <span style="font-weight: 500; color: rgb(30, 33, 41)">${{ scope.row.price ? scope.row.price : '--' }}</span>
  255. <span style="margin: 0 5px; color: #cacdd4">|</span>
  256. <span style="color: #6d7784">{{ scope.row.quantity }}</span>
  257. </div>
  258. <span
  259. >ASIN:
  260. <span class="data-color" style="margin-right: 8px">{{ scope.row.asin ? scope.row.asin : '--' }}</span>
  261. </span>
  262. <span
  263. >SKU:
  264. <span class="data-color">{{ scope.row.sku ? scope.row.sku : '--' }}</span>
  265. </span>
  266. </div>
  267. </div>
  268. </template>
  269. </el-table-column>
  270. <el-table-column prop="name" label="Name" width="120" align="right">
  271. <template #header>
  272. <el-button type="primary" size="normal" link @click="delSelectedGoods">删除已选中</el-button>
  273. </template>
  274. <template #default="scope">
  275. <el-button type="primary" size="small" @click="delSingleGoods(scope)" text>删除</el-button>
  276. </template>
  277. </el-table-column>
  278. </el-table>
  279. </div>
  280. <div style="display: flex; justify-content: space-around; padding-top: 10px">
  281. <el-button type="primary" plain :disabled="adsSave" @click="submitAdsForm">保存</el-button>
  282. </div>
  283. </div>
  284. </div>
  285. </el-form-item>
  286. <!-- 按目标组设置出价 -->
  287. <el-form
  288. :label-position="labelPosition"
  289. ref="targetGroupFormRef"
  290. :model="targetGroupRuleForm"
  291. :rules="targetGroupRules"
  292. label-width="120px"
  293. class="demo-ruleForm"
  294. :size="formSize"
  295. status-icon>
  296. <div class="column-item" v-if="campaignRuleForm.type == 'AUTO'">
  297. <div style="margin-top: 20px; font-size: 24px; font-weight: bold">按目标组设置出价</div>
  298. <hr />
  299. <br />
  300. <el-card shadow="never" v-if="campaignRuleForm.type == 'AUTO'" class="box-card" v-loading="targetGroupLoading">
  301. <div>
  302. <div style="color: #8e9095">
  303. <span>目标群体</span>
  304. <span class="suggested-bid-item">建议竞价</span>
  305. <span>竞价</span>
  306. </div>
  307. <div style="display: flex">
  308. <el-switch v-model="targetGroupRuleForm.closeMatch" size="small" active-text="紧密匹配" @change="closeMatchChange" />
  309. <span class="suggested-bid-item">--</span>
  310. <el-form-item prop="closeMatchInp">
  311. <el-input
  312. :disabled="!targetGroupRuleForm.closeMatch"
  313. v-model="targetGroupRuleForm.closeMatchInp"
  314. minlength="3"
  315. maxlength="4"
  316. class="bid-input">
  317. <template #prepend>$</template>
  318. </el-input>
  319. </el-form-item>
  320. </div>
  321. <div>
  322. <div style="display: flex">
  323. <el-switch v-model="targetGroupRuleForm.broadMatch" size="small" active-text="广泛匹配" @change="broadMatchChange" />
  324. <span class="suggested-bid-item">--</span>
  325. <el-form-item prop="broadMatchInp">
  326. <el-input
  327. :disabled="!targetGroupRuleForm.broadMatch"
  328. v-model="targetGroupRuleForm.broadMatchInp"
  329. minlength="3"
  330. maxlength="4"
  331. class="bid-input">
  332. <template #prepend>$</template>
  333. </el-input>
  334. </el-form-item>
  335. </div>
  336. </div>
  337. <div>
  338. <div style="display: flex">
  339. <el-switch v-model="targetGroupRuleForm.similarProducts" size="small" active-text="同类商品" @change="similarProductsChange" />
  340. <span class="suggested-bid-item">--</span>
  341. <el-form-item prop="similarProductsInp">
  342. <el-input
  343. :disabled="!targetGroupRuleForm.similarProducts"
  344. v-model="targetGroupRuleForm.similarProductsInp"
  345. minlength="3"
  346. maxlength="4"
  347. class="bid-input">
  348. <template #prepend>$</template>
  349. </el-input>
  350. </el-form-item>
  351. </div>
  352. </div>
  353. <div>
  354. <div style="display: flex">
  355. <el-switch v-model="targetGroupRuleForm.relatedProducts" size="small" active-text="关联商品" @change="relatedProductsChange" />
  356. <span class="suggested-bid-item">--</span>
  357. <el-form-item prop="relatedProductsInp">
  358. <el-input
  359. :disabled="!targetGroupRuleForm.relatedProducts"
  360. v-model="targetGroupRuleForm.relatedProductsInp"
  361. minlength="3"
  362. maxlength="4"
  363. class="bid-input">
  364. <template #prepend>$</template>
  365. </el-input>
  366. </el-form-item>
  367. </div>
  368. </div>
  369. </div>
  370. <div style="display: flex; justify-content: space-around">
  371. <el-button type="primary" plain :disabled="targetGroupBidSave" @click="submitTargetGroupForm">保存</el-button>
  372. </div>
  373. </el-card>
  374. </div>
  375. </el-form>
  376. <!-- 投放类型 -->
  377. <div class="column-item" v-if="campaignRuleForm.type == 'MANUAL'">
  378. <p style="color: #606266; font-weight: 450"><span style="color: #e47470">*</span> 投放类型</p>
  379. <el-radio-group v-model="ruleForm.targetType" @change="changeTargetType">
  380. <div style="display: flex">
  381. <el-radio label="keyWords" style="align-items: flex-start; margin-top: 5px">
  382. <div style="margin-top: -4px">关键词投放</div>
  383. <div>选择关键词以帮助您的商品出现在购物者搜索中</div>
  384. </el-radio>
  385. </div>
  386. <div>
  387. <el-radio label="Goods" style="align-items: flex-start; margin-top: 20px">
  388. <div style="margin-top: -4px">商品投放</div>
  389. <div>选择特定的商品, 分类, 品牌或者其他商品功能来定向您的广告</div>
  390. </el-radio>
  391. </div>
  392. </el-radio-group>
  393. </div>
  394. <!-- 关键词定向 -->
  395. <div
  396. style="font-size: 20px; font-weight: bold; margin-top: 30px"
  397. v-if="ruleForm.targetType == 'keyWords' && campaignRuleForm.type == 'MANUAL'">
  398. 关键词定向
  399. </div>
  400. <hr v-if="ruleForm.targetType == 'keyWords' && campaignRuleForm.type == 'MANUAL'" />
  401. <el-form-item style="width: 100%; margin-top: 20px" v-if="ruleForm.targetType == 'keyWords' && campaignRuleForm.type == 'MANUAL'">
  402. <div style="width: 100%; height: 600px; display: flex; border: 1px solid #e5e7eb; border-radius: 6px" v-loading="keywordsLoading">
  403. <div style="width: 50%; border-right: 1px solid #e5e7eb">
  404. <el-tabs v-model="keyWordsTabs" class="demo-tabs" @tab-click="handleGoodsTabs">
  405. <div style="margin: 8px">
  406. <p style="margin-left: 8px; margin-bottom: -8px; font-weight: 500; color: #616266">竞价:</p>
  407. <div style="display: flex; align-items: center">
  408. <el-select v-model="bidType" class="m-2" placeholder="Select" style="width: 450px">
  409. <el-option v-for="item in bidTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
  410. </el-select>
  411. <el-input v-model="bidInput" :disabled="!(bidType == 'customBid')" placeholder="Please input">
  412. <template #prepend>$</template>
  413. </el-input>
  414. </div>
  415. <div style="display: flex">
  416. <span style="margin: 0 10px 0 8px; font-weight: 500; color: #616266">匹配类型: </span>
  417. <el-checkbox v-model="broadType" label="广泛" />
  418. <el-checkbox v-model="phraseType" label="词组" />
  419. <el-checkbox v-model="exactType" label="精确" />
  420. </div>
  421. </div>
  422. <el-tab-pane label="建议" name="first">
  423. <el-table
  424. height="425"
  425. style="width: 100%; padding-left: 5px"
  426. v-loading="loading"
  427. :data="keyWordsTableData"
  428. :header-cell-style="headerCellStyle"
  429. :header-row-style="changeKeyWordsTableHeader">
  430. <el-table-column prop="asin" label="关键词"> </el-table-column>
  431. <el-table-column prop="name" label="匹配类型"> </el-table-column>
  432. <el-table-column prop="name" label="建议出价" width="120"> </el-table-column>
  433. </el-table>
  434. </el-tab-pane>
  435. <el-tab-pane label="输入" name="second">
  436. <el-input v-model="keyWordsTextarea" :rows="10" type="textarea" style="padding-left: 5px" />
  437. <div style="display: flex; flex-direction: row-reverse; margin-top: 10px">
  438. <el-button type="primary" text bg @click="addKeyWords">添加</el-button>
  439. </div>
  440. </el-tab-pane>
  441. </el-tabs>
  442. </div>
  443. <div style="width: 50%">
  444. <el-card class="box-card" shadow="never" style="border: none">
  445. <template #header>
  446. <div class="card-header">
  447. <span style="font-weight: 550; font-size: 15px; color: #1f2128">已添加: {{ addedKeyWordsTableData.length }}</span>
  448. <span style="color: #529b2e">成功: {{ successCount }}</span>
  449. <span style="color: #c45656">失败: {{ errorCount }}</span>
  450. <el-button class="button" type="danger" text bg @click="delAllKeyWords">全部删除</el-button>
  451. </div>
  452. </template>
  453. <div class="card-body" body-style="padding-bottom: -20px;">
  454. <el-table
  455. :data="addedKeyWordsTableData"
  456. style="width: 100%; height: 450px"
  457. :header-row-style="changeKeyWordsTableHeader"
  458. :header-cell-style="headerCellStyle">
  459. <el-table-column prop="keyword" label="关键词" width="auto" />
  460. <el-table-column prop="matchType" label="匹配类型" />
  461. <el-table-column prop="bid" label="出价">
  462. <template #default="scope">
  463. <el-input v-model="scope.row.bid" placeholder="Please input bid" />
  464. </template>
  465. </el-table-column>
  466. <el-table-column prop="suggestBid" label="建议出价" />
  467. <el-table-column prop="operate" label="操作" width="60" align="right">
  468. <template #default="scope">
  469. <el-button type="danger" size="small" link @click="delSingleKeyWord(scope)">删除</el-button>
  470. </template>
  471. </el-table-column>
  472. </el-table>
  473. </div>
  474. </el-card>
  475. <div style="display: flex; justify-content: space-around; padding-top: 0px">
  476. <el-button type="primary" plain @click="keyWordsSave" :disabled="!addedKeyWordsTableData.length">保存</el-button>
  477. </div>
  478. </div>
  479. </div>
  480. </el-form-item>
  481. <div
  482. style="font-size: 20px; font-weight: bold; margin-top: 30px"
  483. v-if="campaignRuleForm.type === 'AUTO' || (ruleForm.targetType === 'keyWords' && campaignRuleForm.type === 'MANUAL')">
  484. 否定词
  485. </div>
  486. <hr v-if="campaignRuleForm.type === 'AUTO' || (ruleForm.targetType === 'keyWords' && campaignRuleForm.type === 'MANUAL')" />
  487. <!-- 否定词表格 -->
  488. <el-form-item
  489. style="width: 100%; margin-top: 20px"
  490. v-if="campaignRuleForm.type === 'AUTO' || (ruleForm.targetType === 'keyWords' && campaignRuleForm.type === 'MANUAL')">
  491. <div style="width: 100%; height: 520px; display: flex; border: 1px solid #e5e7ec; border-radius: 6px" v-loading="negativeWordsLoading">
  492. <div style="width: 50%; border-right: 1px solid #e5e7ec">
  493. <div style="margin: 10px 0">
  494. <span style="margin-left: 25px; color: #e47470">*</span>
  495. <span style="color: #666666; margin-right: 10px">匹配类型: </span>
  496. <el-checkbox v-model="NEGATIVE_PHRASE" label="词组否定" />
  497. <el-checkbox v-model="NEGATIVE_EXACT" label="精确否定" />
  498. </div>
  499. <el-input
  500. v-model="negativeWordsTextarea"
  501. :rows="17"
  502. type="textarea"
  503. placeholder="请输入关键词,多个关键词使用逗号或者换行符分隔。(最多添加1000个关键词)"
  504. maxlength="11000"
  505. style="padding: 0 20px" />
  506. <div style="display: flex; flex-direction: row-reverse; margin-top: 10px">
  507. <el-button style="margin-right: 18px" type="primary" text bg @click="addNegative">添加</el-button>
  508. </div>
  509. </div>
  510. <div style="width: 50%">
  511. <el-card class="box-card" shadow="never" style="border: none">
  512. <template #header>
  513. <div class="card-header">
  514. <span style="font-weight: 550; font-size: 15px; color: #1f2128">已添加: {{ addedData.length }}</span>
  515. <el-button class="button" text bg @click="delAllNegative">全部删除</el-button>
  516. </div>
  517. </template>
  518. <div class="card-body">
  519. <el-table :data="tableData" style="width: 100%; height: 370px; padding-bottom: 0" :header-row-style="changeNegTableHeader">
  520. <el-table-column prop="negativeWords" label="否定词" width="auto" />
  521. <el-table-column prop="operate" label="操作" width="60" align="right">
  522. <template #default="scope">
  523. <el-button type="primary" size="small" @click="delSingleNegative(scope)" text>删除</el-button>
  524. </template>
  525. </el-table-column>
  526. </el-table>
  527. </div>
  528. </el-card>
  529. <div style="display: flex; justify-content: space-around">
  530. <el-button type="primary" plain @click="negativeWordsSave" :disabled="!negativeList.length">保存</el-button>
  531. </div>
  532. </div>
  533. </div>
  534. </el-form-item>
  535. <!-- 商品定向 -->
  536. <div style="font-size: 20px; font-weight: bold; margin-top: 30px" v-if="ruleForm.targetType == 'Goods'">商品定向</div>
  537. <hr v-if="ruleForm.targetType == 'Goods'" />
  538. <el-form-item style="width: 100%; margin-top: 20px" v-if="ruleForm.targetType == 'Goods'">
  539. <div
  540. style="width: 100%; height: 600px; display: flex; border: 1px solid #e5e7eb; border-radius: 6px"
  541. v-loading="productOrientationLoading">
  542. <div style="width: 50%; border-right: 1px solid #e5e7eb">
  543. <el-tabs
  544. type="border-card"
  545. stretch
  546. class="goods-orientation-tabs"
  547. style="border: 0; border-right: 0; border-bottom-left-radius: 6px; border-top-left-radius: 5px; overflow: hidden">
  548. <el-tab-pane label="品类" style="border-top-left-radius: 6px">
  549. <div style="display: flex; align-items: center">
  550. <span style="width: 40px">竞价:</span>
  551. <el-select v-model="categoryBiddingType" @change="singleGoodsBidSelectChanged" class="m-2" placeholder="Select">
  552. <el-option v-for="item in categoryBiddingTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
  553. </el-select>
  554. <el-input v-model="singleGoodsBidInput" :disabled="categoryBiddingType === 'defaultBid'" style="width: 200px">
  555. <template #prepend>$</template>
  556. </el-input>
  557. </div>
  558. <el-tabs v-model="categoryTabs" class="category-tabs">
  559. <el-tab-pane label="建议" name="first">
  560. <el-table :data="proposalTableData" style="width: 100%" height="422">
  561. <el-table-column prop="proposal" label="建议" width="520">
  562. <template #header> 0建议 </template>
  563. </el-table-column>
  564. <el-table-column prop="address" label="Address">
  565. <template #header>
  566. <el-button type="primary" size="normal" link @click="handleGoodsAdd">全部添加</el-button>
  567. </template>
  568. <template #default="scope">
  569. <el-button type="primary" size="small" @click="addSingleGoods(scope)" text>添加</el-button>
  570. </template>
  571. </el-table-column>
  572. </el-table>
  573. </el-tab-pane>
  574. <el-tab-pane label="搜索" name="second">
  575. <el-input placeholder="请输入关键词过滤" />
  576. <el-scrollbar height="390px">
  577. <el-tree :data="searchClassifyTableData" :props="defaultProps" :expand-on-click-node="false">
  578. <template #default="{ node, data }">
  579. <span class="custom-tree-node">
  580. <span style="width: 75%">{{ node.label }}</span>
  581. <span style="color: rgb(50, 108, 216)" v-if="data.ta == true">
  582. <a @click="refine(data)"> 细化 </a>
  583. <a style="margin-left: 8px" @click="orientate(node, data)"> 定向 </a>
  584. </span>
  585. </span>
  586. </template>
  587. </el-tree>
  588. </el-scrollbar>
  589. <el-dialog v-model="visible" :title="`细化分类: ${dialogTitle}`" @close="dialogClose" destroy-on-close>
  590. <div style="display: flex; justify-content: space-between">
  591. <span>根据特定品牌、价格范围、星级和Prime配送资格,细化分类</span>
  592. <span>
  593. <el-checkbox v-model="dialogForm.isCount" label="显示商品数量" @change="isCountChanged" />
  594. </span>
  595. </div>
  596. <el-form :model="dialogForm" :rules="dialogRules" ref="dialogFormRef" style="margin-top: 20px">
  597. <el-form-item style="padding-left: 140px">
  598. <span style="margin-right: 10px; color: #616266; font-weight: 500">品牌</span>
  599. <el-select
  600. v-model="dialogForm.dialogselectValue"
  601. @change="dialogSelectChange"
  602. multiple
  603. placeholder="请选择"
  604. :loading="dialogSelectLoading">
  605. <el-option v-for="item in dialogForm.dialogOptions" :key="item.value" :label="item.label" :value="item.value" />
  606. </el-select>
  607. </el-form-item>
  608. <el-form-item prop="prices" style="padding-left: 112px; margin-top: 10px">
  609. <span style="margin-right: 10px; color: #616266; font-weight: 500">价格范围</span>
  610. <el-input-number v-model="dialogForm.prices.lowest" :min="1" :controls="false" placeholder="无最低商品价格" />
  611. --
  612. <el-input-number v-model="dialogForm.prices.highest" :min="1" :controls="false" placeholder="无最高商品价格" />
  613. </el-form-item>
  614. <el-form-item prop="starRating" style="padding-left: 85px; margin-top: 10px">
  615. <span style="margin-right: 15px; color: #616266; font-weight: 500">查看星级评定</span>
  616. <el-slider v-model="dialogForm.starRating" range show-stops :max="5" :marks="marks" style="width: 70%" />
  617. </el-form-item>
  618. <el-form-item prop="delivery" style="padding-left: 140px; margin-top: 30px">
  619. <span style="margin-right: 10px; color: #616266; font-weight: 500">配送</span>
  620. <el-radio-group v-model="dialogForm.delivery">
  621. <el-radio label="all" style="font-weight: 400">所有</el-radio>
  622. <el-radio label="eligible" style="font-weight: 400">具备Prime资格</el-radio>
  623. <el-radio label="diseligible" style="font-weight: 400">不具备Prime资格</el-radio>
  624. </el-radio-group>
  625. </el-form-item>
  626. </el-form>
  627. <template #footer>
  628. <div style="display: flex; justify-content: space-between">
  629. <span v-loading="countLoadig"
  630. >定位到的商品数量:
  631. <span v-if="dialogForm.isCount == true">{{ commodityCount[0]?.min }} - {{ commodityCount[0]?.max }}</span></span
  632. >
  633. <span class="dialog-footer">
  634. <el-button @click="visible = false">取消</el-button>
  635. <el-button type="primary" @click="dialogFormSubmit">确定</el-button>
  636. </span>
  637. </div>
  638. </template>
  639. </el-dialog>
  640. </el-tab-pane>
  641. </el-tabs>
  642. </el-tab-pane>
  643. <el-tab-pane label="单个商品">
  644. <div style="display: flex; align-items: center">
  645. <span style="width: 40px">竞价:</span>
  646. <el-select class="m-2" v-model="singleGoodsBidSelect" @change="singleGoodsBidSelectChanged">
  647. <el-option v-for="item in singleGoodsBidTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
  648. </el-select>
  649. <el-input v-model="singleGoodsBidInput" :disabled="singleGoodsBidSelect == 'defaultBid'" style="width: 200px">
  650. <template #prepend>$</template>
  651. </el-input>
  652. <div style="margin-left: 20px">
  653. <span style="margin-right: 10px">类型:</span>
  654. <el-checkbox v-model="expand" label="扩展" />
  655. <el-checkbox v-model="accurate" label="精准" />
  656. </div>
  657. </div>
  658. <el-tabs v-model="singleGoodsTabs" class="category-tabs">
  659. <el-tab-pane label="建议" name="first">
  660. <el-table :data="proposalTableData" style="width: 100%" height="342">
  661. <el-table-column prop="proposal" label="商品" width="520" />
  662. <el-table-column prop="address" label="类型" />
  663. <el-table-column prop="operational" label="操作" />
  664. </el-table>
  665. </el-tab-pane>
  666. <el-tab-pane label="搜索" name="second">
  667. <el-input v-model="singleGoodsSearchInp" @change="singleGoodsSearchChaneged" placeholder="按ASIN搜索"></el-input>
  668. <el-table :data="searchTableData" style="width: 100%" height="309">
  669. <el-table-column prop="asin" label="商品" width="520">
  670. <template #default="{ row }">
  671. <div style="display: flex; align-items: center">
  672. <img :src="row.image_link" style="width: 40px; height: 40px; margin-right: 10px" />
  673. <span>{{ row.title }}</span>
  674. </div>
  675. </template>
  676. </el-table-column>
  677. <el-table-column prop="productTypes" label="类型">
  678. <template #default="scope">
  679. <div v-if="expand">扩展</div>
  680. <div v-if="accurate">精准</div>
  681. </template>
  682. </el-table-column>
  683. <el-table-column prop="operational" label="操作">
  684. <template #default="scope">
  685. <el-button class="button" text @click="addSingleSearch(scope)">添加</el-button>
  686. </template>
  687. </el-table-column>
  688. </el-table>
  689. </el-tab-pane>
  690. <!-- TODO: 商品定向TextArea -->
  691. <el-tab-pane label="输入" name="third">待完成</el-tab-pane>
  692. </el-tabs>
  693. </el-tab-pane>
  694. </el-tabs>
  695. </div>
  696. <div style="width: 50%">
  697. <el-card class="box-card" shadow="never" style="border: none">
  698. <template #header>
  699. <div class="card-header">
  700. <span style="font-weight: 550; font-size: 15px; color: #1f2128">已添加: {{ productOrientationTableData.length }}</span>
  701. <el-button class="button" text bg @click="delAllCna">全部删除</el-button>
  702. </div>
  703. </template>
  704. <div class="card-body">
  705. <el-table
  706. height="460"
  707. :data="productOrientationTableData"
  708. style="width: 100%"
  709. :header-row-style="changeKeyWordsTableHeader"
  710. :header-cell-style="headerCellStyle">
  711. <el-table-column prop="cna" label="分类 & 商品" width="300">
  712. <template #default="scope">
  713. <div v-if="scope.row.cna || scope.row.classification">
  714. 分类: <span style="color: #000000">{{ scope.row.cna ? scope.row.cna : scope.row.classification }}</span>
  715. </div>
  716. <div v-if="scope.row.asin">
  717. {{ scope.row.asin ? scope.row.asin : '--' }}
  718. </div>
  719. <div v-if="scope.row.brand">
  720. 品牌: <span style="color: #000000">{{ scope.row.brand }}</span>
  721. </div>
  722. <div v-if="scope.row.low_price || scope.row.high_price">
  723. 品牌价格:
  724. <span style="color: #000000">
  725. {{ scope.row.low_price ? '$' + scope.row.low_price : '--' }} -
  726. {{ scope.row.high_price ? '$' + scope.row.high_price : '--' }}
  727. </span>
  728. </div>
  729. <div v-if="scope.row.low_rating || scope.row.high_rating">
  730. 评分: <span style="color: #000000">{{ scope.row.low_rating }} - {{ scope.row.high_rating }}</span>
  731. </div>
  732. <div v-if="scope.row.deliveryText">
  733. 配送: <span style="color: #000000">{{ scope.row.deliveryText }}</span>
  734. </div>
  735. </template>
  736. </el-table-column>
  737. <el-table-column prop="type" label="类型">
  738. <template #default="scope">
  739. {{ scope.row.productTypeText ? scope.row.productTypeText : '--' }}
  740. </template>
  741. </el-table-column>
  742. <el-table-column prop="bid" label="竞价">
  743. <template #default="scope">
  744. <el-input-number v-model="scope.row.bid" :min="0.02" :max="1000000" :controls="false" size="small" />
  745. </template>
  746. </el-table-column>
  747. <el-table-column prop="operate" label="操作" width="60" align="right">
  748. <template #default="scope">
  749. <el-button text size="small" @click="delCna(scope.$index)">删除</el-button>
  750. </template>
  751. </el-table-column>
  752. </el-table>
  753. </div>
  754. </el-card>
  755. <div style="display: flex; justify-content: space-around; margin-top: -8px">
  756. <el-button type="primary" plain @click="productTagetSave">保存</el-button>
  757. </div>
  758. </div>
  759. </div>
  760. </el-form-item>
  761. <div
  762. style="font-size: 20px; font-weight: bold; margin-top: 30px"
  763. v-if="campaignRuleForm.type == 'AUTO' || (ruleForm.targetType == 'Goods' && campaignRuleForm.type === 'MANUAL')">
  764. 否定商品
  765. </div>
  766. <hr v-if="campaignRuleForm.type == 'AUTO' || (ruleForm.targetType == 'Goods' && campaignRuleForm.type === 'MANUAL')" />
  767. <el-form-item
  768. prop="matchType"
  769. style="width: 100%; margin-top: 20px"
  770. v-if="campaignRuleForm.type == 'AUTO' || (ruleForm.targetType == 'Goods' && campaignRuleForm.type === 'MANUAL')">
  771. <div style="width: 100%; height: 600px; display: flex; border: 1px solid #e5e7ec; border-radius: 6px" v-loading="negativeGoodsLoading">
  772. <div style="width: 50%; border-right: 1px solid #e5e7ec">
  773. <el-tabs v-model="negativeTabs" class="demo-tabs" @tab-click="handleNegGoodsTabs">
  774. <el-tab-pane label="搜索" name="first">
  775. <div style="margin-bottom: 10px">
  776. <el-input placeholder="按ASIN搜索" v-model="negativeInput" @change="searchNegativeGoods" clearable />
  777. </div>
  778. <el-table
  779. height="495"
  780. style="width: 100%"
  781. v-loading="loading"
  782. :data="negativeTableData"
  783. :header-cell-style="headerCellStyle"
  784. :show-header="false">
  785. <el-table-column prop="asin" label="商品">
  786. <template #default="scope">
  787. <div style="display: flex; align-items: center">
  788. <div style="margin-right: 8px; line-height: normal">
  789. <el-image class="img-box" :src="scope.row.image_link" />
  790. </div>
  791. <div>
  792. <el-tooltip class="box-item" effect="dark" :content="scope.row.title" placement="top">
  793. <div class="single-line">{{ scope.row.title ? scope.row.title : '--' }}</div>
  794. </el-tooltip>
  795. <span>
  796. ASIN: <span class="data-color" style="margin-right: 8px">{{ scope.row.asin ? scope.row.asin : '--' }}</span>
  797. </span>
  798. </div>
  799. </div>
  800. </template>
  801. </el-table-column>
  802. <el-table-column prop="name" label="Name" width="120" align="right">
  803. <template #header> </template>
  804. <template #default="scope">
  805. <el-button type="primary" size="small" @click="addSingleNegativeGoods(scope)" text>添加</el-button>
  806. </template>
  807. </el-table-column>
  808. </el-table>
  809. </el-tab-pane>
  810. <el-tab-pane label="输入" name="second">
  811. <el-input
  812. v-model="ruleForm.negativeGoodsTextarea"
  813. :rows="17"
  814. type="textarea"
  815. placeholder="未完成"
  816. maxlength="11000"
  817. style="padding: 10px 10px" />
  818. <div style="display: flex; flex-direction: row-reverse; margin-top: 10px">
  819. <el-button style="margin-right: 10px" type="primary" text bg @click="addNegativeGoods">添加</el-button>
  820. </div>
  821. </el-tab-pane>
  822. </el-tabs>
  823. </div>
  824. <div style="width: 50%">
  825. <el-card class="box-card" shadow="never" style="border: none">
  826. <template #header>
  827. <div class="card-header">
  828. <span style="font-weight: 550; font-size: 15px; color: #1f2128">已添加: {{ addedNegetiveTableData.length }}</span>
  829. <el-button class="button" text bg @click="delAllNegativeGoods">全部删除</el-button>
  830. </div>
  831. </template>
  832. <div class="card-body"></div>
  833. </el-card>
  834. <div style="padding: 0 10px 0 10px; margin-top: -30px">
  835. <el-table
  836. :data="addedNegetiveTableData"
  837. height="473"
  838. style="width: 100%"
  839. :header-cell-style="headerCellStyle"
  840. @selection-change="handleAddedNegGoods">
  841. <el-table-column prop="asin" label="商品">
  842. <template #default="scope">
  843. <div style="display: flex; align-items: center">
  844. <div style="margin-right: 8px; line-height: normal">
  845. <el-image class="img-box" :src="scope.row.image_link" />
  846. </div>
  847. <div>
  848. <el-tooltip class="box-item" effect="dark" :content="scope.row.title" placement="top">
  849. <div class="single-line">{{ scope.row.title ? scope.row.title : '--' }}</div>
  850. </el-tooltip>
  851. <span
  852. >ASIN:
  853. <span class="data-color" style="margin-right: 8px">{{ scope.row.asin ? scope.row.asin : '--' }}</span>
  854. </span>
  855. </div>
  856. </div>
  857. </template>
  858. </el-table-column>
  859. <el-table-column label="操作" width="120" align="right">
  860. <template #default="scope">
  861. <el-button type="primary" size="small" @click="delSingleNegativeGoods(scope)" text>删除</el-button>
  862. </template>
  863. </el-table-column>
  864. </el-table>
  865. </div>
  866. <div style="display: flex; justify-content: space-around; padding-top: 10px">
  867. <el-button plain type="primary" @click="negativeGoodsSave" :disabled="!addedNegetiveTableData.length">保存</el-button>
  868. </div>
  869. </div>
  870. </div>
  871. </el-form-item>
  872. <!-- <br />
  873. <el-form-item>
  874. <el-button size="large" @click="resetForm(ruleFormRef)">取消</el-button>
  875. <el-button size="large" type="primary" plain @click="submitForm(ruleFormRef)">保存</el-button>
  876. </el-form-item> -->
  877. </el-form>
  878. </div>
  879. </el-card>
  880. </div>
  881. </template>
  882. <script lang="ts" setup>
  883. import { onMounted, reactive, ref, computed, watch } from 'vue'
  884. import type { CSSProperties } from 'vue'
  885. import { useRoute } from 'vue-router'
  886. import type { FormInstance, FormRules, TabsPaneContext } from 'element-plus'
  887. import { ElMessage } from 'element-plus'
  888. import { useShopInfo } from '/@/stores/shopInfo'
  889. import { usePublicData } from '/@/stores/publicData'
  890. import { storeToRefs } from 'pinia'
  891. import { useRouter } from 'vue-router'
  892. import { request } from '/@/utils/service'
  893. const negativeTableData = ref([])
  894. const addedNegetiveTableData = ref([])
  895. const router = useRouter()
  896. const route = useRoute()
  897. const shopInfo = useShopInfo()
  898. const { profile } = storeToRefs(shopInfo)
  899. const loading = ref(true)
  900. const fullTableData = ref([]) // 表格数据
  901. let addedData = ref([]) // 已添加的商品数据
  902. let adsTableData = ref([])
  903. let selections = [] // 添加选中的项
  904. let addedSels = [] // 删除选中的项
  905. const currentPage = ref() // 当前页
  906. const pageSize = ref(20) // 每页显示条目数
  907. const totalItems = ref() // 数据总量
  908. const showCard = ref(false)
  909. const activeName = ref('first')
  910. const negativeTabs = ref('first')
  911. const keyWordsTabs = ref('first')
  912. const productOrientationTabs = ref('first')
  913. const categoryTabs = ref('first')
  914. const singleGoodsTabs = ref('first')
  915. const searchInp = ref('')
  916. const select = ref('name')
  917. const select2 = ref('latest')
  918. const buttons = [{ type: 'primary', text: '添加' }] as const
  919. const negativeInput = ref('')
  920. // 表单相关数据
  921. const campaignLoading = ref(false)
  922. const formSize = ref('default')
  923. const labelPosition = ref('top')
  924. // ------------------------------------------广告活动------------------------------------------
  925. let respCampaignId = ref('')
  926. let adGroupSave = ref(true)
  927. const campaignFormRef = ref<FormInstance>()
  928. interface CampaignForm {
  929. name: string
  930. adMix: string
  931. count: string
  932. startDate: string
  933. date2: string
  934. budget: string
  935. delivery: boolean
  936. type: string
  937. bidStrategy: string
  938. placeBid: string
  939. firstPage: string
  940. other: string
  941. }
  942. const campaignRuleForm = reactive<CampaignForm>({
  943. name: '',
  944. adMix: '',
  945. count: '',
  946. startDate: '',
  947. date2: '',
  948. budget: '',
  949. delivery: false,
  950. type: 'AUTO',
  951. bidStrategy: 'LEGACY_FOR_SALES',
  952. placeBid: '',
  953. firstPage: '',
  954. other: '',
  955. })
  956. const campaignRules = computed(() => ({
  957. name: [{ required: true, message: 'Please input Activity name', trigger: 'blur' }],
  958. adMix: [{ required: false, message: 'Please select Activity zone', trigger: 'change' }],
  959. count: [{ required: true, message: 'Please select Activity count', trigger: 'change' }],
  960. startDate: [{ required: true, type: 'date', message: 'Please pick a date', trigger: 'change' }],
  961. date2: [{ required: false, type: 'date', message: 'Please pick a time', trigger: 'change' }],
  962. budget: [
  963. { required: true, message: '请输入预算', trigger: 'blur' },
  964. { pattern: /^(?:[1-9]\d{0,5}|1000000)(?:\.\d{1,2})?$/, message: '预算必须是1到1000000之间的数字,小数点后最多两位', trigger: 'blur' },
  965. ],
  966. type: [{ required: false, trigger: 'change' }],
  967. bidStrategy: [{ required: true, message: 'Please select activity resource', trigger: 'change' }],
  968. placeBid: [{ required: false, pattern: /^[0-9]{1,3}$/, message: '必须是0~900之间的整数百分比', trigger: 'change' }],
  969. firstPage: [{ required: false, pattern: /^[0-9]{1,3}$/, message: '必须是0~900之间的整数百分比', trigger: 'change' }],
  970. other: [{ required: false, pattern: /^[0-9]{1,3}$/, message: '必须是0~900之间的整数百分比', trigger: 'change' }],
  971. }))
  972. async function createCampaigns() {
  973. const previousRespCampaignId = respCampaignId.value
  974. try {
  975. // 必需字段列表
  976. const requiredFields = [
  977. { key: 'profile_id', value: profile.value.profile_id },
  978. { key: 'name', value: campaignRuleForm.name },
  979. { key: 'startDate', value: campaignRuleForm.startDate },
  980. { key: 'targetingType', value: campaignRuleForm.type },
  981. { key: 'strategy', value: campaignRuleForm.bidStrategy },
  982. { key: 'budget', value: campaignRuleForm.budget },
  983. ]
  984. // 检查每个必需字段
  985. requiredFields.forEach((field) => {
  986. if (!field.value) {
  987. throw new Error(`缺少必需的字段: ${field.key}`)
  988. }
  989. })
  990. // 构建请求数据
  991. const requestData = {
  992. profile_id: profile.value.profile_id,
  993. name: campaignRuleForm.name,
  994. startDate: campaignRuleForm.startDate,
  995. budget: campaignRuleForm.budget,
  996. targetingType: campaignRuleForm.type,
  997. strategy: campaignRuleForm.bidStrategy,
  998. state: 'PAUSED',
  999. // 可选字段
  1000. endDate: campaignRuleForm.date2,
  1001. t_percentage: campaignRuleForm.placeBid,
  1002. p_percentage: campaignRuleForm.firstPage,
  1003. r_percentage: campaignRuleForm.other,
  1004. portfolioId: campaignRuleForm.adMix,
  1005. }
  1006. // 过滤掉 undefined 或空的可选字段
  1007. const filteredRequestData = Object.fromEntries(Object.entries(requestData).filter(([_, v]) => v != null))
  1008. const resp = await request({
  1009. url: '/api/ad_manage/spcampaigns/create/',
  1010. method: 'POST',
  1011. data: filteredRequestData,
  1012. })
  1013. if (resp.data && resp.data.campaignId) {
  1014. // 确保 resp.data 和 resp.data.campaignId 存在
  1015. respCampaignId.value = resp.data.campaignId // 更新 respCampaignId
  1016. adGroupSave.value = false
  1017. campaignLoading.value = false
  1018. ElMessage({
  1019. message: '广告活动创建成功',
  1020. type: 'success',
  1021. })
  1022. } else {
  1023. campaignLoading.value = false
  1024. ElMessage.error('广告活动创建失败!')
  1025. // 如果创建失败,恢复到之前的 respCampaignId
  1026. respCampaignId.value = previousRespCampaignId
  1027. }
  1028. } catch (error) {
  1029. console.error('请求失败:', error)
  1030. // 如果请求失败,也恢复到之前的 respCampaignId
  1031. respCampaignId.value = previousRespCampaignId
  1032. }
  1033. }
  1034. const adMixOptions = ref([])
  1035. async function getAdMix() {
  1036. try {
  1037. const resp = await request({
  1038. url: '/api/ad_manage/portfolios/select_list',
  1039. method: 'GET',
  1040. })
  1041. adMixOptions.value = resp.data
  1042. } catch (error) {
  1043. console.log('error:', error)
  1044. }
  1045. }
  1046. async function submitCampaignForm(formEl: FormInstance | undefined) {
  1047. if (!formEl) return
  1048. await formEl.validate((valid, fields) => {
  1049. if (valid) {
  1050. console.log('submit!')
  1051. campaignLoading.value = true
  1052. createCampaigns()
  1053. } else {
  1054. console.log('error submit!', fields)
  1055. }
  1056. })
  1057. }
  1058. async function submitForm(formEl: FormInstance | undefined) {
  1059. if (!formEl) return
  1060. await formEl.validate((valid, fields) => {
  1061. if (valid) {
  1062. console.log('submit!')
  1063. createCampaigns()
  1064. } else {
  1065. console.log('error submit!', fields)
  1066. }
  1067. })
  1068. }
  1069. function resetForm(formEl: FormInstance | undefined) {
  1070. if (!formEl) return
  1071. formEl.resetFields()
  1072. }
  1073. // ------------------------------------------------------广告组------------------------------------------------------
  1074. const adGroupRuleFormRef = ref<FormInstance>()
  1075. interface AdGroupForm {
  1076. adGroupName: string
  1077. defaultBidInp: string
  1078. }
  1079. const adGroupRuleForm = reactive<AdGroupForm>({
  1080. adGroupName: '',
  1081. defaultBidInp: '',
  1082. })
  1083. const adGroupRules = computed(() => ({
  1084. adGroupName: [{ required: true, message: '请输入广告组名称', trigger: 'blur' }],
  1085. defaultBidInp: [
  1086. { required: true, message: '请输入默认出价', trigger: 'blur' },
  1087. { validator: validateDefaultBidInp, trigger: 'blur' },
  1088. ],
  1089. }))
  1090. function validateDefaultBidInp(rule, value, callback) {
  1091. // 通用校验方法
  1092. if (value === '') {
  1093. callback(new Error('请输入默认出价'))
  1094. } else if (!/^(0\.0[2-9]|0\.[1-9]\d?|[1-9]\d{0,2}(\.\d{1,2})?|1000(\.0{1,2})?)$/.test(value)) {
  1095. callback(new Error('最小不低于0.02,最大不超过1000'))
  1096. } else {
  1097. const numericValue = parseFloat(value)
  1098. const numericBudget = parseFloat(campaignRuleForm.budget)
  1099. if (numericValue > numericBudget) {
  1100. callback(new Error('默认出价不能大于当前预算'))
  1101. } else {
  1102. callback()
  1103. }
  1104. }
  1105. }
  1106. let respAdGroupId = ref('')
  1107. let adsSave = ref(true)
  1108. async function createGroups() {
  1109. try {
  1110. // 构建请求数据
  1111. const requestData = {
  1112. profile_id: profile.value.profile_id,
  1113. campaignId: respCampaignId.value,
  1114. name: adGroupRuleForm.adGroupName,
  1115. defaultBid: adGroupRuleForm.defaultBidInp,
  1116. state: 'PAUSED',
  1117. }
  1118. // 过滤掉 undefined 或空的可选字段
  1119. const filteredRequestData = Object.fromEntries(Object.entries(requestData).filter(([_, v]) => v != null))
  1120. const resp = await request({
  1121. url: '/api/ad_manage/spgroups/create/',
  1122. method: 'POST',
  1123. data: filteredRequestData,
  1124. })
  1125. respAdGroupId.value = resp.data.adGroupId
  1126. console.log('🚀 ~ createGroups ~ resp-->>', resp)
  1127. adGroupLoading.value = false
  1128. if (respAdGroupId.value) {
  1129. ElMessage({
  1130. message: '广告组创建成功',
  1131. type: 'success',
  1132. })
  1133. } else {
  1134. ElMessage.error('广告组创建失败!')
  1135. }
  1136. } catch (error) {
  1137. console.error('请求失败:', error)
  1138. }
  1139. }
  1140. async function submitGroupsForm(formEl: FormInstance | undefined) {
  1141. if (!formEl) return
  1142. await formEl.validate((valid, fields) => {
  1143. if (valid) {
  1144. adGroupLoading.value = true
  1145. console.log('submit!')
  1146. createGroups()
  1147. } else {
  1148. console.log('error submit!', fields)
  1149. }
  1150. })
  1151. }
  1152. // ------------------------------------------商品------------------------------------------
  1153. const goodsTextarea = ref('')
  1154. const adGroupLoading = ref(false)
  1155. const commodityLoading = ref(false)
  1156. let addedAdsTableItems = ref([])
  1157. function setTableData(asin = '', sku = '') {
  1158. return request({
  1159. url: '/api/sellers/listings/our/',
  1160. method: 'GET',
  1161. params: {
  1162. page: currentPage.value,
  1163. limit: pageSize.value,
  1164. profile_id: profile.value.profile_id,
  1165. asin,
  1166. sku,
  1167. },
  1168. })
  1169. .then((resp) => {
  1170. fullTableData.value = resp.data
  1171. totalItems.value = resp.total
  1172. currentPage.value = resp.page
  1173. loading.value = false
  1174. })
  1175. .catch((error) => {
  1176. console.error('Error fetching data:', error)
  1177. loading.value = false
  1178. })
  1179. }
  1180. function addSingleGoods(scope) {
  1181. // console.log('scope', scope.row)
  1182. const isAlreadyAdded = adsTableData.value.some((item) => item.sku === scope.row.sku)
  1183. if (!isAlreadyAdded) {
  1184. adsTableData.value.push(scope.row)
  1185. } else {
  1186. console.log('Item is already added.')
  1187. }
  1188. }
  1189. function addGods() {
  1190. const inputData = goodsTextarea.value
  1191. const asins = inputData.split(/[\n,]+/)
  1192. asins.forEach((asin) => {
  1193. if (asin.trim()) {
  1194. setTableData(asin.trim())
  1195. .then((response) => {
  1196. console.log(`Data for ASIN ${asin}:`, response) // 更新这里来正确地访问数据
  1197. })
  1198. .catch((error) => {
  1199. console.error(`Error fetching data for ASIN ${asin}:`, error)
  1200. })
  1201. }
  1202. })
  1203. }
  1204. function delSingleGoods(scope) {
  1205. const index = adsTableData.value.findIndex((item) => item.sku === scope.row.sku)
  1206. if (index !== -1) {
  1207. adsTableData.value.splice(index, 1)
  1208. console.log('Item removed successfully.')
  1209. } else {
  1210. console.log('Item not found.')
  1211. }
  1212. }
  1213. function delAllGoods() {
  1214. adsTableData.value = []
  1215. // adsTableData.value.splice(0, adsTableData.value.length)
  1216. }
  1217. // 删除第二个table中已经选中的项
  1218. function delSelectedGoods() {
  1219. adsTableData.value = adsTableData.value.filter((item) => !addedSels.includes(item))
  1220. addedSels = []
  1221. }
  1222. function inpChange(e) {
  1223. const value = e
  1224. if (select.value === 'asin') {
  1225. loading.value = true
  1226. setTableData(value)
  1227. } else if (select.value === 'sku') {
  1228. loading.value = true
  1229. setTableData('', value)
  1230. }
  1231. }
  1232. function selChange(e) {
  1233. console.log('e', e)
  1234. const value = e
  1235. if (select.value === 'asin' && searchInp.value) {
  1236. loading.value = true
  1237. setTableData(value)
  1238. } else if (select.value === 'sku' && searchInp.value) {
  1239. loading.value = true
  1240. setTableData('', value)
  1241. }
  1242. }
  1243. // 点击表格选项触发事件
  1244. function handleSelectionChange(selection) {
  1245. selections = selection
  1246. }
  1247. // 获取addedTable中已选中的项
  1248. function handleAddedGoodsChange(selection) {
  1249. addedSels = selection
  1250. }
  1251. // 添加已选中的项
  1252. function handleGoodsAdd() {
  1253. // 过滤掉已经存在于addedData.value中的项
  1254. const newSelections = selections.filter(
  1255. (sel) => !adsTableData.value.some((added) => added.sku === sel.sku) // 使用sku作为唯一标识
  1256. )
  1257. // 如果有新的不重复项,加入到addedData.value中
  1258. if (newSelections.length > 0) {
  1259. adsTableData.value.push(...newSelections)
  1260. }
  1261. }
  1262. // 点击Tab
  1263. const handleGoodsTabs = (tab: TabsPaneContext, event: Event) => {
  1264. // console.log(tab, event)
  1265. }
  1266. function isItemInList(item, list) {
  1267. return list.some((listItem) => listItem.sku === item.sku && listItem.asin === item.asin)
  1268. }
  1269. // 监听商品右侧表格已添加的数据并转化数据格式
  1270. watch(
  1271. adsTableData,
  1272. (newValue, oldValue) => {
  1273. newValue.forEach((item) => {
  1274. if (!isItemInList(item, addedAdsTableItems.value)) {
  1275. addedAdsTableItems.value.push({ sku: item.sku, asin: item.asin })
  1276. }
  1277. })
  1278. if (adsTableData.value.length !== 0) {
  1279. adsSave.value = false
  1280. } else {
  1281. adsSave.value = true
  1282. }
  1283. },
  1284. { deep: true }
  1285. )
  1286. async function createAds() {
  1287. try {
  1288. const requestData = {
  1289. profile_id: profile.value.profile_id,
  1290. campaignId: respCampaignId.value,
  1291. adGroupId: respAdGroupId.value,
  1292. asinsku: addedAdsTableItems.value,
  1293. state: 'PAUSED',
  1294. }
  1295. const filteredRequestData = Object.fromEntries(Object.entries(requestData).filter(([_, v]) => v != null))
  1296. const resp = await request({
  1297. url: '/api/ad_manage/spads/create/',
  1298. method: 'POST',
  1299. data: filteredRequestData,
  1300. })
  1301. console.log('🚀 ~ createAds ~ resp-->>', resp)
  1302. commodityLoading.value = false
  1303. if (resp.data.success.length > 0) {
  1304. adsSave.value = false
  1305. targetGroupBidSave.value = false
  1306. adsTableData.value = []
  1307. ElMessage({
  1308. message: '商品创建成功',
  1309. type: 'success',
  1310. })
  1311. } else {
  1312. ElMessage.error('商品创建失败!')
  1313. }
  1314. } catch (error) {
  1315. console.error('请求失败:', error)
  1316. }
  1317. }
  1318. function submitAdsForm() {
  1319. commodityLoading.value = true
  1320. createAds()
  1321. }
  1322. // ------------------------------------------目标组设置出价------------------------------------------
  1323. let targetGroupBidSave = ref(true)
  1324. let targetGroupLoading = ref(false)
  1325. const targetGroupFormRef = ref<FormInstance>()
  1326. interface TargetGroupForm {
  1327. closeMatch: boolean
  1328. broadMatch: boolean
  1329. similarProducts: boolean
  1330. relatedProducts: boolean
  1331. closeMatchInp: string
  1332. broadMatchInp: string
  1333. similarProductsInp: string
  1334. relatedProductsInp: string
  1335. }
  1336. const targetGroupRuleForm = reactive<TargetGroupForm>({
  1337. closeMatch: true,
  1338. broadMatch: true,
  1339. similarProducts: true,
  1340. relatedProducts: true,
  1341. closeMatchInp: '1',
  1342. broadMatchInp: '1',
  1343. similarProductsInp: '1',
  1344. relatedProductsInp: '1',
  1345. })
  1346. const targetGroupRules = computed(() => ({
  1347. closeMatchInp: [
  1348. { required: true, message: '请输入默认出价', trigger: 'blur' },
  1349. { validator: validateDefaultBidInp, trigger: 'blur' },
  1350. ],
  1351. broadMatchInp: [
  1352. { required: true, message: '请输入默认出价', trigger: 'blur' },
  1353. { validator: validateDefaultBidInp, trigger: 'blur' },
  1354. ],
  1355. similarProductsInp: [
  1356. { required: true, message: '请输入默认出价', trigger: 'blur' },
  1357. { validator: validateDefaultBidInp, trigger: 'blur' },
  1358. ],
  1359. relatedProductsInp: [
  1360. { required: true, message: '请输入默认出价', trigger: 'blur' },
  1361. { validator: validateDefaultBidInp, trigger: 'blur' },
  1362. ],
  1363. }))
  1364. function closeMatchChange() {
  1365. if (targetGroupRuleForm.closeMatch == false) {
  1366. targetGroupRuleForm.closeMatchInp = ''
  1367. targetGroupFormRef.value.clearValidate('closeMatchInp')
  1368. } else {
  1369. targetGroupRuleForm.closeMatchInp = '1'
  1370. // targetGroupFormRef.value.validateField('closeMatchInp')
  1371. }
  1372. }
  1373. function broadMatchChange() {
  1374. if (targetGroupRuleForm.broadMatch == false) {
  1375. targetGroupRuleForm.broadMatchInp = ''
  1376. targetGroupFormRef.value.clearValidate('broadMatchInp')
  1377. } else {
  1378. targetGroupRuleForm.broadMatchInp = '1'
  1379. // targetGroupFormRef.value.validateField('broadMatchInp')
  1380. }
  1381. }
  1382. function similarProductsChange() {
  1383. if (targetGroupRuleForm.similarProducts == false) {
  1384. targetGroupRuleForm.similarProductsInp = ''
  1385. targetGroupFormRef.value.clearValidate('similarProductsInp')
  1386. } else {
  1387. targetGroupRuleForm.similarProductsInp = '1'
  1388. // targetGroupFormRef.value.validateField('similarProductsInp')
  1389. }
  1390. }
  1391. function relatedProductsChange() {
  1392. if (targetGroupRuleForm.relatedProducts == false) {
  1393. targetGroupRuleForm.relatedProductsInp = ''
  1394. targetGroupFormRef.value.clearValidate('relatedProductsInp')
  1395. } else {
  1396. targetGroupRuleForm.relatedProductsInp = '1'
  1397. // targetGroupFormRef.value.validateField('relatedProductsInp')
  1398. }
  1399. }
  1400. async function createTargetGroup() {
  1401. try {
  1402. const requestData = {
  1403. profile_id: profile.value.profile_id,
  1404. adGroupId: respAdGroupId.value,
  1405. QUERY_HIGH_REL_MATCHES: targetGroupRuleForm.closeMatchInp,
  1406. QUERY_BROAD_REL_MATCHES: targetGroupRuleForm.broadMatchInp,
  1407. ASIN_SUBSTITUTE_RELATED: targetGroupRuleForm.similarProductsInp,
  1408. ASIN_ACCESSORY_RELATED: targetGroupRuleForm.relatedProductsInp,
  1409. QUERY_HIGH_REL_MATCHES_state: targetGroupRuleForm.closeMatch,
  1410. QUERY_BROAD_REL_MATCHES_state: targetGroupRuleForm.broadMatch,
  1411. ASIN_SUBSTITUTE_RELATED_state: targetGroupRuleForm.similarProducts,
  1412. ASIN_ACCESSORY_RELATED_state: targetGroupRuleForm.relatedProducts,
  1413. }
  1414. const filteredRequestData = Object.fromEntries(Object.entries(requestData).filter(([_, v]) => v != null))
  1415. const resp = await request({
  1416. url: '/api/ad_manage/sptargets/auto/updata/',
  1417. method: 'POST',
  1418. data: filteredRequestData,
  1419. })
  1420. console.log('🚀 ~ createTargetGroup ~ resp-->>', resp)
  1421. targetGroupLoading.value = false
  1422. if (respAdGroupId.value) {
  1423. ElMessage({
  1424. message: '目标组创建成功',
  1425. type: 'success',
  1426. })
  1427. } else {
  1428. ElMessage.error('目标组创建失败!')
  1429. }
  1430. } catch (error) {
  1431. console.error('请求失败:', error)
  1432. }
  1433. }
  1434. function submitTargetGroupForm() {
  1435. targetGroupLoading.value = true
  1436. console.log('submit!')
  1437. createTargetGroup()
  1438. }
  1439. // ------------------------------------------否定词------------------------------------------
  1440. const ruleFormRef = ref<FormInstance>()
  1441. interface RuleForm {
  1442. autoRedirect: string
  1443. // NEGATIVE_PHRASE: boolean
  1444. // NEGATIVE_EXACT: boolean
  1445. negativeTextarea: string
  1446. negativeGoodsTextarea: string
  1447. targetType: string
  1448. }
  1449. const ruleForm = reactive<RuleForm>({
  1450. autoRedirect: 'defaultBid',
  1451. // NEGATIVE_PHRASE: true,
  1452. // NEGATIVE_EXACT: true,
  1453. negativeTextarea: '',
  1454. negativeGoodsTextarea: '',
  1455. targetType: 'keyWords',
  1456. })
  1457. const rules = computed(() => ({
  1458. autoRedirect: [{ required: true, trigger: 'change' }],
  1459. // relatedProductsInp: getValidationRules('relatedProductsInp'),
  1460. // similarProductsInp: getValidationRules('similarProductsInp'),
  1461. // broadMatchInp: getValidationRules('broadMatchInp'),
  1462. // closeMatchInp: getValidationRules('closeMatchInp'),
  1463. }))
  1464. //------------------------------------------------------------------------------方法------------------------------------------------------------------------------
  1465. // 当单选按钮变化时更新showCard状态
  1466. function changeBid() {
  1467. console.log(ruleForm.autoRedirect)
  1468. showCard.value = ruleForm.autoRedirect === 'targetBid'
  1469. }
  1470. // 切换投放类型
  1471. function changeType() {}
  1472. // 处理分页器当前页变化
  1473. function handleCurrentChange(newPage) {
  1474. currentPage.value = newPage
  1475. loading.value = true
  1476. setTableData()
  1477. }
  1478. // 处理分页器每页显示条目数变化
  1479. function handleSizeChange(newSize) {
  1480. pageSize.value = newSize
  1481. currentPage.value = 1 // 重置到第一页
  1482. }
  1483. // ------------------------------------------投放类型模块------------------------------------------
  1484. async function changeTargetType() {
  1485. // console.log(ruleForm.targetType)
  1486. showCard.value = ruleForm.targetType === 'keyWords'
  1487. if (ruleForm.targetType === 'Goods') {
  1488. productOrientationLoading.value = true
  1489. await setProductOrientationData()
  1490. }
  1491. }
  1492. // ------------------------------------------商品定向模块------------------------------------------
  1493. const categoryBiddingType = ref('customBid')
  1494. const categoryBiddingTypeOptions = [
  1495. {
  1496. value: 'defaultBid',
  1497. label: '默认竞价',
  1498. },
  1499. {
  1500. value: 'customBid',
  1501. label: '自定义竞价',
  1502. },
  1503. ]
  1504. const categoryBidInput = ref('0.75')
  1505. const singleGoodsBidSelect = ref('customBid')
  1506. const singleGoodsBidTypeOptions = [
  1507. {
  1508. value: 'defaultBid',
  1509. label: '默认竞价',
  1510. },
  1511. {
  1512. value: 'customBid',
  1513. label: '自定义竞价',
  1514. },
  1515. ]
  1516. const singleGoodsBidInput = ref('0.75')
  1517. const expand = ref(true)
  1518. const accurate = ref(false)
  1519. const proposalTableData = ref([])
  1520. const searchClassifyTableData = ref([])
  1521. const productOrientationLoading = ref(false)
  1522. const dialogSelectLoading = ref(false)
  1523. const defaultProps = {
  1524. children: 'ch',
  1525. label: 'cna',
  1526. }
  1527. const countLoadig = ref(false)
  1528. const visible = ref(false)
  1529. let dialogTitle = ref('')
  1530. let categoryId = ref('')
  1531. const dialogselectValue = ref('')
  1532. let dialogOptions: any = ref([])
  1533. const dialogForm: any = reactive({
  1534. prices: {
  1535. lowest: undefined,
  1536. highest: undefined,
  1537. },
  1538. starRating: [0, 5],
  1539. dialogselectValue: [],
  1540. delivery: 'all',
  1541. isCount: false,
  1542. })
  1543. const dialogFormRef = ref()
  1544. const dialogRules = reactive({
  1545. prices: [{ validator: validatePrices, trigger: 'blur' }],
  1546. })
  1547. interface Mark {
  1548. style: CSSProperties
  1549. label: string
  1550. }
  1551. type Marks = Record<number, Mark | string>
  1552. const marks = reactive<Marks>({
  1553. 0: '0',
  1554. 1: '1',
  1555. 2: '2',
  1556. 3: '3',
  1557. 4: '4',
  1558. 5: '5',
  1559. })
  1560. let commodityCount = ref([])
  1561. let currentDialogIndex = ref(0)
  1562. let productOrientationTableData = ref([])
  1563. async function validatePrices(rule, value) {
  1564. if (value.highest !== '' && value.lowest !== '' && value.highest <= value.lowest) {
  1565. return Promise.reject('最高价格必须大于最低价格')
  1566. }
  1567. return Promise.resolve()
  1568. }
  1569. async function setProductOrientationData() {
  1570. try {
  1571. const resp = await request({
  1572. url: '/api/ad_manage/targetable/categories/',
  1573. method: 'GET',
  1574. params: {
  1575. profile_id: profile.value.profile_id,
  1576. },
  1577. })
  1578. searchClassifyTableData.value = resp.data
  1579. productOrientationLoading.value = false
  1580. } catch (error) {
  1581. console.error('请求失败:', error)
  1582. }
  1583. }
  1584. async function setDialogOption() {
  1585. try {
  1586. const resp = await request({
  1587. url: '/api/ad_manage/categories/brands/',
  1588. method: 'GET',
  1589. params: {
  1590. profile_id: profile.value.profile_id,
  1591. category_id: categoryId.value,
  1592. },
  1593. })
  1594. const options = resp.data
  1595. dialogForm.dialogOptions = options.brands.map((brand) => {
  1596. return {
  1597. label: brand.name,
  1598. value: brand.id,
  1599. }
  1600. })
  1601. dialogSelectLoading.value = false
  1602. } catch (error) {
  1603. console.error('请求失败:', error)
  1604. }
  1605. }
  1606. async function getCount(instanceId) {
  1607. try {
  1608. const resp = await request({
  1609. url: '/api/ad_manage/products/count/',
  1610. method: 'POST',
  1611. data: {
  1612. profile_id: profile.value.profile_id,
  1613. category_id: categoryId.value,
  1614. },
  1615. })
  1616. if (instanceId === currentDialogIndex.value) {
  1617. commodityCount.value = resp.data.AsinCounts
  1618. }
  1619. } catch (error) {
  1620. console.error('请求失败:', error)
  1621. } finally {
  1622. if (instanceId === currentDialogIndex.value) {
  1623. countLoadig.value = false
  1624. }
  1625. }
  1626. }
  1627. function dialogClose() {
  1628. currentDialogIndex.value++
  1629. resetDialogForm()
  1630. dialogForm.isCount = false
  1631. commodityCount.value = []
  1632. countLoadig.value = false
  1633. }
  1634. function resetDialogForm() {
  1635. dialogForm.prices.lowest = undefined
  1636. dialogForm.prices.highest = undefined
  1637. dialogForm.starRating = [0, 5]
  1638. dialogForm.dialogselectValue = []
  1639. dialogForm.delivery = 'all'
  1640. dialogForm.isCount = false
  1641. }
  1642. function isCountChanged() {
  1643. if (dialogForm.isCount) {
  1644. const instanceId = currentDialogIndex.value
  1645. countLoadig.value = true
  1646. getCount(instanceId)
  1647. } else {
  1648. countLoadig.value = false
  1649. commodityCount.value = []
  1650. }
  1651. }
  1652. function delCna(index) {
  1653. productOrientationTableData.value.splice(index, 1)
  1654. }
  1655. function delAllCna() {
  1656. productOrientationTableData.value = []
  1657. }
  1658. function singleGoodsBidSelectChanged() {
  1659. if (singleGoodsBidSelect.value === 'defaultBid' || categoryBiddingType.value === 'defaultBid') {
  1660. singleGoodsBidInput.value = ''
  1661. }
  1662. }
  1663. let singleGoodsSearchInp = ref('')
  1664. let searchTableData = ref([])
  1665. function setSearchTableData(asin = '', sku = '') {
  1666. return request({
  1667. url: '/api/sellers/listings/our/',
  1668. method: 'GET',
  1669. params: {
  1670. profile_id: profile.value.profile_id,
  1671. asin,
  1672. sku,
  1673. },
  1674. })
  1675. .then((resp) => {
  1676. searchTableData.value = resp.data
  1677. productOrientationLoading.value = false
  1678. })
  1679. .catch((error) => {
  1680. console.error('Error fetching data:', error)
  1681. productOrientationLoading.value = false
  1682. })
  1683. }
  1684. function singleGoodsSearchChaneged() {
  1685. productOrientationLoading.value = true
  1686. setSearchTableData()
  1687. }
  1688. function addSingleSearch(scope) {
  1689. console.log('🚀 ~ addSingleSearch ~ scope-->>', scope)
  1690. const typesToAdd = []
  1691. if (expand.value) {
  1692. typesToAdd.push('ASIN_EXPANDED_FROM')
  1693. }
  1694. if (accurate.value) {
  1695. typesToAdd.push('ASIN_SAME_AS')
  1696. }
  1697. const productTypeMap = {
  1698. ASIN_EXPANDED_FROM: '扩展',
  1699. ASIN_SAME_AS: '精确',
  1700. }
  1701. typesToAdd.forEach((productType) => {
  1702. const isAlreadyAdded = productOrientationTableData.value.some((item) => item.sku === scope.row.sku && item.productType === productType)
  1703. let bidValue = null
  1704. // 根据 categoryBiddingType.value 的值设置 bidValue
  1705. if (categoryBiddingType.value === 'defaultBid') {
  1706. bidValue = adGroupRuleForm.defaultBidInp
  1707. } else if (categoryBiddingType.value === 'customBid') {
  1708. bidValue = singleGoodsBidInput.value
  1709. }
  1710. if (!isAlreadyAdded) {
  1711. const newData = {
  1712. type: 'p',
  1713. asin: scope.row.asin,
  1714. sku: scope.row.sku,
  1715. productType: productType,
  1716. productTypeText: productTypeMap[productType],
  1717. bid: bidValue, // 添加 bid 值
  1718. }
  1719. productOrientationTableData.value.push(newData)
  1720. } else {
  1721. console.log(`${productType} item is already added.`)
  1722. }
  1723. })
  1724. }
  1725. let selectedLabels = ref([]) // 选中的label数组
  1726. function dialogSelectChange(event) {
  1727. console.log('🚀 ~ dialogSelectChange ~ event-->>', event)
  1728. // 使用 map 来转换每个选中项的 value 为其对应的 label
  1729. selectedLabels.value = event.map((selectedValue) => {
  1730. const selectedOption = dialogForm.dialogOptions.find((option) => option.value === selectedValue)
  1731. return selectedOption ? selectedOption.label : ''
  1732. })
  1733. console.log('🚀 ~ dialogSelectChange ~ selectedLabels-->>', selectedLabels.value)
  1734. }
  1735. let refineItem = ref([])
  1736. // 细化按钮功能
  1737. function refine(data) {
  1738. console.log('🚀 ~ refine ~ data-->>', data)
  1739. commodityCount.value = []
  1740. dialogTitle.value = data.cna
  1741. categoryId.value = data.cid
  1742. refineItem.value.push(data)
  1743. visible.value = true
  1744. dialogSelectLoading.value = true
  1745. setDialogOption()
  1746. }
  1747. // 弹框提交功能
  1748. function dialogFormSubmit() {
  1749. dialogFormRef.value.validate((valid) => {
  1750. if (valid) {
  1751. console.log('表单提交')
  1752. visible.value = false
  1753. const dialogClassification = dialogTitle.value
  1754. const dialogPrices_low = dialogForm.prices.lowest
  1755. const dialogPrices_high = dialogForm.prices.highest
  1756. const dialogStartRating = dialogForm.starRating
  1757. const ratingLow = dialogStartRating[0]
  1758. const ratingHigh = dialogStartRating[1]
  1759. const dialogDelivery = dialogForm.delivery
  1760. console.log('🚀 ~ dialogFormRef.value.validate ~ dialogDelivery-->>', dialogDelivery)
  1761. const deliveryMap = {
  1762. all: '所有',
  1763. eligible: '具备Prime资格',
  1764. diseligible: '不具备Prime资格',
  1765. }
  1766. let bidValue = null
  1767. // 根据 categoryBiddingType.value 的值设置 bidValue
  1768. if (categoryBiddingType.value === 'defaultBid') {
  1769. bidValue = adGroupRuleForm.defaultBidInp
  1770. } else if (categoryBiddingType.value === 'customBid') {
  1771. bidValue = singleGoodsBidInput.value
  1772. }
  1773. selectedLabels.value.forEach((brandLabel) => {
  1774. // 查找与当前 brandLabel 相对应的选项
  1775. const selectedOption = dialogForm.dialogOptions.find((option) => option.label === brandLabel)
  1776. // 获取对应的 brandId,如果没有找到则默认为空
  1777. const brandId = selectedOption ? selectedOption.value : ''
  1778. const refineObj = {
  1779. type: 'c',
  1780. classification: dialogClassification,
  1781. classificationId: categoryId.value,
  1782. brand: brandLabel,
  1783. brandId: brandId, // 使用找到的 brandId
  1784. bid: bidValue, // 添加 bid 值
  1785. low_price: dialogPrices_low,
  1786. high_price: dialogPrices_high,
  1787. low_rating: ratingLow,
  1788. high_rating: ratingHigh,
  1789. delivery: dialogDelivery,
  1790. deliveryText: deliveryMap[dialogDelivery],
  1791. }
  1792. console.log('🚀 ~ dialogFormRef.value.validate ~ refineObj-->>', refineObj)
  1793. productOrientationTableData.value.push(refineObj)
  1794. })
  1795. } else {
  1796. console.log('验证失败')
  1797. }
  1798. })
  1799. }
  1800. // 定向按钮功能
  1801. function orientate(node, data) {
  1802. console.log('🚀 ~ orientate ~ data-->>', data)
  1803. const exists = productOrientationTableData.value.some((item) => item.cid === data.cid)
  1804. let bidValue = null
  1805. // 根据 categoryBiddingType.value 的值设置 bidValue
  1806. if (categoryBiddingType.value === 'defaultBid') {
  1807. bidValue = adGroupRuleForm.defaultBidInp
  1808. } else if (categoryBiddingType.value === 'customBid') {
  1809. bidValue = singleGoodsBidInput.value
  1810. }
  1811. if (!exists) {
  1812. const newData = {
  1813. type: 'c',
  1814. classification: data.cna,
  1815. classificationId: data.cid,
  1816. bid: bidValue, // 将 bid 值添加到新数据中
  1817. }
  1818. productOrientationTableData.value.push(newData)
  1819. }
  1820. }
  1821. let productTargetBidList = ref([])
  1822. async function productTagetSave() {
  1823. console.log('tableData', productOrientationTableData.value)
  1824. // 检查是否存在 bid 为空的行
  1825. const hasEmptyBid = productOrientationTableData.value.some((row) => row.bid == null || row.bid === '')
  1826. // 直接返回,不继续执行
  1827. if (hasEmptyBid) {
  1828. console.log('存在空的 bid,不发送请求')
  1829. ElMessage.error('存在空的 bid,无法创建商品!')
  1830. return
  1831. }
  1832. productOrientationTableData.value.forEach((row) => {
  1833. productTargetBidList.value.push(row.bid)
  1834. })
  1835. console.log('productTargetBidList', productTargetBidList.value)
  1836. productOrientationLoading.value = true
  1837. try {
  1838. const requestData = {
  1839. profile_id: profile.value.profile_id,
  1840. adGroupId: respAdGroupId.value,
  1841. campaignId: respCampaignId.value,
  1842. expressionList: productOrientationTableData.value,
  1843. state: 'PAUSED',
  1844. }
  1845. const filteredRequestData = Object.fromEntries(Object.entries(requestData).filter(([_, v]) => v != null))
  1846. const resp = await request({
  1847. url: '/api/ad_manage/sptargets/manual/create/',
  1848. method: 'POST',
  1849. data: filteredRequestData,
  1850. })
  1851. console.log('🚀 ~ createTargetGroup ~ resp-->>', resp)
  1852. productOrientationLoading.value = false
  1853. if (respAdGroupId.value) {
  1854. ElMessage({
  1855. message: '商品创建成功',
  1856. type: 'success',
  1857. })
  1858. } else {
  1859. ElMessage.error('商品创建失败!')
  1860. }
  1861. } catch (error) {
  1862. console.error('请求失败:', error)
  1863. }
  1864. // 清空表格和 bid 列表
  1865. productOrientationTableData.value = []
  1866. productTargetBidList.value = []
  1867. }
  1868. // ------------------------------------------关键词定向模块------------------------------------------
  1869. const bidType = ref('customBid')
  1870. const keywordsLoading = ref(false)
  1871. const bidTypeOptions = [
  1872. {
  1873. value: 'suggestBid',
  1874. label: '建议出价',
  1875. },
  1876. {
  1877. value: 'customBid',
  1878. label: '自定义出价',
  1879. },
  1880. {
  1881. value: 'defaultBid',
  1882. label: '默认出价',
  1883. },
  1884. ]
  1885. const bidInput = ref('0.75')
  1886. const keyWordsTableData = ref([]) // 关键词定向左侧表格数据
  1887. const addedKeyWordsTableData = ref([]) // 关键词定向右侧表格数据
  1888. const keyWordsTextarea = ref('')
  1889. let broadType = ref(true)
  1890. let phraseType = ref(true)
  1891. let exactType = ref(true)
  1892. const MATCH_TYPE = {
  1893. BROAD: '广泛',
  1894. PHRASE: '词组',
  1895. EXACT: '精确',
  1896. }
  1897. const MATCH_TYPE_MAP = {
  1898. 广泛: 'BROAD',
  1899. 词组: 'PHRASE',
  1900. 精确: 'EXACT',
  1901. }
  1902. const successCount = ref('')
  1903. const errorCount = ref('')
  1904. watch(bidType, () => {
  1905. if (bidType.value === 'defaultBid') {
  1906. bidInput.value = adGroupRuleForm.defaultBidInp
  1907. } else if (bidType.value === 'customBid') {
  1908. bidInput.value = ''
  1909. } else {
  1910. bidInput.value = ''
  1911. }
  1912. })
  1913. function addKeyWords() {
  1914. const trimmedText = keyWordsTextarea.value.trim()
  1915. const items = trimmedText.split(/,|\n/)
  1916. items.forEach((item) => {
  1917. const trimmedItem = item.trim()
  1918. if (trimmedItem) {
  1919. if (broadType.value) {
  1920. addKeyWordEntry(trimmedItem, MATCH_TYPE.BROAD)
  1921. }
  1922. if (phraseType.value) {
  1923. addKeyWordEntry(trimmedItem, MATCH_TYPE.PHRASE)
  1924. }
  1925. if (exactType.value) {
  1926. addKeyWordEntry(trimmedItem, MATCH_TYPE.EXACT)
  1927. }
  1928. } else {
  1929. ElMessage({
  1930. message: '有空项目,未被添加到列表中',
  1931. type: 'warning',
  1932. })
  1933. }
  1934. })
  1935. keyWordsTextarea.value = ''
  1936. }
  1937. function addKeyWordEntry(keyword, matchType) {
  1938. let bidValue
  1939. switch (bidType.value) {
  1940. case 'customBid':
  1941. bidValue = bidInput.value
  1942. break
  1943. case 'defaultBid':
  1944. bidValue = adGroupRuleForm.defaultBidInp
  1945. break
  1946. default:
  1947. bidValue = ''
  1948. }
  1949. let keyWordEntry = {
  1950. keyword: keyword,
  1951. matchType: matchType,
  1952. bid: bidInput.value,
  1953. }
  1954. if (!addedKeyWordsTableData.value.some((n) => n.keyword === keyWordEntry.keyword && n.matchType === keyWordEntry.matchType)) {
  1955. addedKeyWordsTableData.value.push(keyWordEntry)
  1956. } else {
  1957. ElMessage({
  1958. message: `关键词 ${keyword} (${matchType}) 已存在,未被添加到列表中`,
  1959. type: 'warning',
  1960. })
  1961. }
  1962. }
  1963. function delSingleKeyWord(scope) {
  1964. const index = addedKeyWordsTableData.value.findIndex((item) => item.keyword === scope.row.keyword && item.matchType === scope.row.matchType)
  1965. if (index !== -1) {
  1966. addedKeyWordsTableData.value.splice(index, 1)
  1967. } else {
  1968. console.log('无效的索引,无法删除条目')
  1969. }
  1970. }
  1971. function delAllKeyWords() {
  1972. addedKeyWordsTableData.value = []
  1973. }
  1974. async function keyWordsSave() {
  1975. keywordsLoading.value = true
  1976. successCount.value = ''
  1977. errorCount.value = ''
  1978. const keywordList = addedKeyWordsTableData.value.map((kw) => ({
  1979. keywordText: kw.keyword,
  1980. bid: kw.bid,
  1981. matchType: MATCH_TYPE_MAP[kw.matchType],
  1982. }))
  1983. const requestData = {
  1984. profile_id: profile.value.profile_id,
  1985. campaignId: respCampaignId.value,
  1986. adGroupId: respAdGroupId.value,
  1987. keywordlist: keywordList,
  1988. state: 'PAUSED',
  1989. }
  1990. const filteredRequestData = Object.fromEntries(Object.entries(requestData).filter(([_, v]) => v != null))
  1991. try {
  1992. const resp = await request({
  1993. url: '/api/ad_manage/sptargets/add/keywords/',
  1994. method: 'POST',
  1995. data: filteredRequestData,
  1996. })
  1997. if (resp.data.success.length !== 0) {
  1998. ElMessage({
  1999. message: '关键词创建成功',
  2000. type: 'success',
  2001. })
  2002. successCount.value = resp.data.success.length
  2003. errorCount.value = resp.data.error.length
  2004. delAllKeyWords()
  2005. } else {
  2006. ElMessage.error('关键词创建失败!')
  2007. }
  2008. } catch (error) {
  2009. console.error('请求失败:', error)
  2010. } finally {
  2011. keywordsLoading.value = false
  2012. }
  2013. }
  2014. // ------------------------------------------否定词模块------------------------------------------
  2015. let negativeWordsLoading = ref(false)
  2016. let negativeList = reactive([])
  2017. let negativeWordsTextarea = ref('')
  2018. let NEGATIVE_PHRASE = ref(true)
  2019. let NEGATIVE_EXACT = ref(true)
  2020. let exactNegativeList = reactive([]) // 用于存储精确否定的数组
  2021. let phraseNegativeList = reactive([]) // 用于存储词组否定的数组
  2022. function addNegative() {
  2023. const trimmedText = negativeWordsTextarea.value.trim()
  2024. const items = trimmedText.split(/,|\n/)
  2025. items.forEach((item) => {
  2026. const trimmedItem = item.trim()
  2027. if (trimmedItem) {
  2028. let phraseEntry = '词组: ' + trimmedItem
  2029. let exactEntry = '精确: ' + trimmedItem
  2030. if (NEGATIVE_PHRASE.value && !negativeList.some((n) => n.negativeWords === phraseEntry)) {
  2031. negativeList.push({ negativeWords: phraseEntry })
  2032. phraseNegativeList.push(trimmedItem) // 添加到词组否定数组
  2033. }
  2034. if (NEGATIVE_EXACT.value && !negativeList.some((n) => n.negativeWords === exactEntry)) {
  2035. negativeList.push({ negativeWords: exactEntry })
  2036. exactNegativeList.push(trimmedItem) // 添加到精确否定数组
  2037. }
  2038. } else {
  2039. console.log('有空项目,未被添加到列表中')
  2040. }
  2041. })
  2042. negativeWordsTextarea.value = ''
  2043. }
  2044. function delAllNegative() {
  2045. // negativeList.splice(0, negativeList.length)
  2046. negativeList.length = 0
  2047. exactNegativeList.length = 0
  2048. phraseNegativeList.length = 0
  2049. }
  2050. function delSingleNegative(scope) {
  2051. const index = negativeList.findIndex((item) => item.negativeWords === scope.row.negativeWords)
  2052. if (index !== -1) {
  2053. // 确定被删除的项是词组还是精确
  2054. const isPhrase = scope.row.negativeWords.startsWith('词组: ')
  2055. const isExact = scope.row.negativeWords.startsWith('精确: ')
  2056. // 从 negativeList 删除
  2057. if (negativeList.length) {
  2058. negativeList.splice(index, 1)
  2059. console.log(`已删除索引为 ${index} 的条目`)
  2060. } else {
  2061. console.log('无效的索引,无法删除条目')
  2062. }
  2063. // 从 exactNegativeList 或 phraseNegativeList 删除
  2064. const trimmedItem = scope.row.negativeWords.substring(4).trim() // 从 '词组: ' 或 '精确: ' 后开始截取
  2065. if (isPhrase) {
  2066. const phraseIndex = phraseNegativeList.findIndex((item) => item === trimmedItem)
  2067. if (phraseIndex !== -1) {
  2068. phraseNegativeList.splice(phraseIndex, 1)
  2069. }
  2070. } else if (isExact) {
  2071. const exactIndex = exactNegativeList.findIndex((item) => item === trimmedItem)
  2072. if (exactIndex !== -1) {
  2073. exactNegativeList.splice(exactIndex, 1)
  2074. }
  2075. }
  2076. } else {
  2077. console.log('无效的索引,无法删除条目')
  2078. }
  2079. }
  2080. async function negativeWordsSave() {
  2081. negativeWordsLoading.value = true
  2082. console.log('negativeList', negativeList)
  2083. try {
  2084. const requestData = {
  2085. profile_id: profile.value.profile_id,
  2086. campaignId: respCampaignId.value,
  2087. adGroupId: respAdGroupId.value,
  2088. state: 'PAUSED',
  2089. EkeywordList: exactNegativeList,
  2090. PkeywordList: phraseNegativeList,
  2091. }
  2092. const filteredRequestData = Object.fromEntries(Object.entries(requestData).filter(([_, v]) => v != null))
  2093. const resp = await request({
  2094. url: '/api/ad_manage/sptargets/add/negative/keywords/',
  2095. method: 'POST',
  2096. data: filteredRequestData,
  2097. })
  2098. negativeWordsLoading.value = false
  2099. if (resp.data.negativeKeywordsuccess.length !== 0) {
  2100. ElMessage({
  2101. message: '否定词创建成功',
  2102. type: 'success',
  2103. })
  2104. delAllNegative()
  2105. } else {
  2106. ElMessage.error('否定词创建失败!')
  2107. }
  2108. } catch (error) {
  2109. console.error('请求失败:', error)
  2110. }
  2111. }
  2112. // ------------------------------------------否定商品模块------------------------------------------
  2113. let negativeGoodsLoading = ref(false)
  2114. const tableData = negativeList
  2115. let inputAddedNegGoods = ref([])
  2116. function setNegativeTableData(asin = '') {
  2117. negativeGoodsLoading.value = true
  2118. return request({
  2119. url: '/api/sellers/listings/all/',
  2120. method: 'GET',
  2121. params: {
  2122. page: currentPage.value,
  2123. limit: pageSize.value,
  2124. profile_id: profile.value.profile_id,
  2125. asin,
  2126. },
  2127. })
  2128. .then((resp) => {
  2129. negativeTableData.value = resp.data
  2130. inputAddedNegGoods.value = resp.data
  2131. negativeGoodsLoading.value = false
  2132. })
  2133. .catch((error) => {
  2134. console.error('Error fetching data:', error)
  2135. negativeGoodsLoading.value = false
  2136. })
  2137. }
  2138. // 输入tab的textarea
  2139. function addNegativeGoods() {
  2140. console.log('ruleForm.negativeGoodsTextarea', ruleForm.negativeGoodsTextarea)
  2141. loading.value = true
  2142. setNegativeTableData(ruleForm.negativeGoodsTextarea)
  2143. .then(() => {
  2144. addedNegetiveTableData.value = [...addedNegetiveTableData.value, ...inputAddedNegGoods.value]
  2145. })
  2146. .catch((error) => {
  2147. console.error('Error fetching data:', error)
  2148. })
  2149. .finally(() => {
  2150. loading.value = false
  2151. })
  2152. }
  2153. function addSingleNegativeGoods(scope) {
  2154. const isAlreadyAdded = addedNegetiveTableData.value.some((item) => item.asin === scope.row.asin)
  2155. if (!isAlreadyAdded) {
  2156. addedNegetiveTableData.value.push(scope.row)
  2157. } else {
  2158. console.log('Item is already added.')
  2159. }
  2160. }
  2161. function delAllNegativeGoods() {
  2162. addedNegetiveTableData.value = []
  2163. }
  2164. function delSingleNegativeGoods(scope) {
  2165. const index = addedNegetiveTableData.value.findIndex((item) => item.asin === scope.row.asin)
  2166. if (index !== -1) {
  2167. addedNegetiveTableData.value.splice(index, 1)
  2168. console.log('Item removed successfully.')
  2169. } else {
  2170. console.log('Item not found.')
  2171. }
  2172. }
  2173. function searchNegativeGoods(e) {
  2174. console.log(e)
  2175. if (e === '') {
  2176. negativeTableData.value = []
  2177. } else {
  2178. setNegativeTableData(e)
  2179. }
  2180. }
  2181. function handleAddedNegGoods(selection) {
  2182. addedSels = selection
  2183. }
  2184. function handleNegGoodsTabs(tab: TabsPaneContext, event: Event) {
  2185. // console.log(tab, event)
  2186. }
  2187. async function negativeGoodsSave() {
  2188. console.log(addedNegetiveTableData.value)
  2189. const asinList = addedNegetiveTableData.value.map((item) => item.asin)
  2190. negativeGoodsLoading.value = true
  2191. console.log('addedNegetiveTableData', addedNegetiveTableData.value)
  2192. try {
  2193. const requestData = {
  2194. profile_id: profile.value.profile_id,
  2195. campaignId: respCampaignId.value,
  2196. adGroupId: respAdGroupId.value,
  2197. asinList: asinList,
  2198. matchType: 'ASIN_SAME_AS',
  2199. state: 'PAUSED',
  2200. }
  2201. const filteredRequestData = Object.fromEntries(Object.entries(requestData).filter(([_, v]) => v != null))
  2202. const resp = await request({
  2203. url: '/api/ad_manage/sptargets/add/negative/targets/',
  2204. method: 'POST',
  2205. data: filteredRequestData,
  2206. })
  2207. negativeGoodsLoading.value = false
  2208. if (resp.data.success.length !== 0) {
  2209. ElMessage({
  2210. message: '否定商品创建成功',
  2211. type: 'success',
  2212. })
  2213. delAllNegative()
  2214. } else {
  2215. ElMessage.error('否定商品创建失败!')
  2216. }
  2217. } catch (error) {
  2218. console.error('请求失败:', error)
  2219. }
  2220. }
  2221. // ------------------------------------------自定义校验模块------------------------------------------
  2222. function checkBid(value, callback, bidField) {
  2223. const bid = parseFloat(value)
  2224. const budget = parseFloat(campaignRuleForm.budget)
  2225. // 检查值是否为最多两位小数的普通数字格式
  2226. const isNormalNumberWithTwoDecimals = /^-?\d+(\.\d{1,2})?$/.test(value)
  2227. if (!isNormalNumberWithTwoDecimals) {
  2228. callback(new Error('请输入数字值(最多两位小数)'))
  2229. } else if (isNaN(bid)) {
  2230. callback(new Error('请输入有效的数字'))
  2231. } else if (bid < 0.02 || bid > 1000) {
  2232. callback(new Error('值必须在0.02到1000之间'))
  2233. } else if (bid >= budget) {
  2234. callback(new Error('出价必须小于预算'))
  2235. } else {
  2236. callback()
  2237. }
  2238. }
  2239. // 自定义校验规则---自动定价和按目标设置出价模块
  2240. // function getValidationRules(fieldName) {
  2241. // // 默认校验规则
  2242. // const commonRules = [
  2243. // { required: true, message: '此项为必填项', trigger: 'blur' },
  2244. // { validator: (rule, value, callback) => checkBid(value, callback, fieldName), trigger: 'blur' },
  2245. // ]
  2246. // // 根据不同字段和状态返回特定的校验规则
  2247. // switch (fieldName) {
  2248. // case 'defaultBidInp':
  2249. // if (ruleForm.autoRedirect === 'defaultBid') {
  2250. // return commonRules
  2251. // }
  2252. // break
  2253. // case 'similarProductsInp':
  2254. // if (ruleForm.autoRedirect === 'targetBid' && ruleForm.similarProducts) {
  2255. // return commonRules
  2256. // } else if (ruleForm.similarProducts == false) {
  2257. // ruleFormRef.value?.clearValidate(fieldName)
  2258. // }
  2259. // break
  2260. // case 'relatedProductsInp':
  2261. // if (ruleForm.autoRedirect === 'targetBid' && ruleForm.relatedProducts) {
  2262. // return commonRules
  2263. // } else if (ruleForm.relatedProducts == false) {
  2264. // ruleFormRef.value?.clearValidate(fieldName)
  2265. // }
  2266. // break
  2267. // case 'broadMatchInp':
  2268. // if (ruleForm.autoRedirect === 'targetBid' && ruleForm.broadMatch) {
  2269. // return commonRules
  2270. // } else if (ruleForm.broadMatch == false) {
  2271. // ruleFormRef.value?.clearValidate(fieldName)
  2272. // }
  2273. // break
  2274. // case 'closeMatchInp':
  2275. // // 仅当autoRedirect为'targetBid'且closeMatch开启时校验closeMatchInp
  2276. // if (ruleForm.autoRedirect === 'targetBid' && ruleForm.closeMatch) {
  2277. // return commonRules
  2278. // } else if (ruleForm.closeMatch == false) {
  2279. // ruleFormRef.value?.clearValidate(fieldName)
  2280. // }
  2281. // break
  2282. // default:
  2283. // return []
  2284. // }
  2285. // // 如果不满足上述条件,则无需校验
  2286. // return []
  2287. // }
  2288. // 监听表单字段变化,根据不同字段和状态返回特定的校验规则
  2289. // watch(
  2290. // [() => ruleForm.autoRedirect, () => ruleForm.closeMatch, () => ruleForm.broadMatch, () => ruleForm.similarProducts, () => ruleForm.relatedProducts],
  2291. // () => {
  2292. // // 定义需要更新校验规则的字段
  2293. // const fields = ['defaultBidInp', 'closeMatchInp', 'broadMatchInp', 'similarProductsInp', 'relatedProductsInp']
  2294. // fields.forEach((field) => {
  2295. // rules.value[field] = getValidationRules(field)
  2296. // })
  2297. // }
  2298. // )
  2299. // 修改表头样式
  2300. const headerCellStyle = (args) => {
  2301. if (args.rowIndex === 0) {
  2302. return {
  2303. backgroundColor: 'rgba(245, 245, 245, 0.9)',
  2304. }
  2305. }
  2306. }
  2307. function changeNegTableHeader(args) {
  2308. if (args.rowIndex === 0) {
  2309. return {
  2310. color: '#505968',
  2311. }
  2312. }
  2313. }
  2314. function changeKeyWordsTableHeader(args) {
  2315. if (args.rowIndex === 0) {
  2316. return {
  2317. color: '#505968',
  2318. backgroundColor: 'rgba(245, 245, 245, 0.9)',
  2319. }
  2320. }
  2321. }
  2322. onMounted(() => {
  2323. setTableData()
  2324. getAdMix()
  2325. // const myTest = route.query
  2326. // console.log('myTest', myTest)
  2327. })
  2328. defineOptions({
  2329. name: 'SpCreateCampaigns',
  2330. })
  2331. </script>
  2332. <style lang="scss" scoped>
  2333. :deep(.el-form--default.el-form--label-top .el-form-item .el-form-item__label) {
  2334. font-weight: 500;
  2335. }
  2336. .column-item .el-radio-group {
  2337. display: inline-flex;
  2338. font-size: 0;
  2339. flex-direction: column;
  2340. align-items: flex-start;
  2341. }
  2342. .radio-description {
  2343. font-size: 12px;
  2344. color: #666;
  2345. margin-top: -18px;
  2346. margin-left: 22px;
  2347. }
  2348. .radio-description-2 {
  2349. font-size: 12px;
  2350. color: #666;
  2351. margin-top: -10px;
  2352. }
  2353. .column-margin-bottom label.el-radio.is-bordered {
  2354. margin-bottom: 10px;
  2355. padding: 35px;
  2356. }
  2357. ::v-deep(.column-margin-bottom label.el-radio.is-bordered span.el-radio__inner) {
  2358. margin-top: -18px;
  2359. margin-left: -15px;
  2360. }
  2361. .gap-items {
  2362. display: flex;
  2363. justify-content: flex-start;
  2364. width: 100%;
  2365. margin-bottom: 20px;
  2366. }
  2367. .gap-item {
  2368. width: 200px;
  2369. margin-left: 30px;
  2370. color: #0b0d0d;
  2371. }
  2372. .demo-tabs > .el-tabs__content {
  2373. padding: 52px;
  2374. color: #6b778c;
  2375. font-size: 32px;
  2376. font-weight: 600;
  2377. }
  2378. /* 广告组商品Tab栏 */
  2379. ::v-deep(.el-tabs__nav-scroll) {
  2380. overflow: hidden;
  2381. margin-left: 20px;
  2382. }
  2383. ::v-deep(.el-tabs__nav-wrap::after) {
  2384. height: 2px !important;
  2385. }
  2386. ::v-deep(.el-table__inner-wrapper::before) {
  2387. background-color: white;
  2388. }
  2389. // 表格内容边距
  2390. div {
  2391. & #pane-first,
  2392. & #pane-second {
  2393. margin: 10px;
  2394. }
  2395. }
  2396. // 输入底部样式
  2397. ::v-deep(.card-box .el-card__body) {
  2398. display: flex;
  2399. align-items: center;
  2400. justify-content: space-between;
  2401. padding: 12px;
  2402. }
  2403. .card-header {
  2404. display: flex;
  2405. justify-content: space-between;
  2406. align-items: center;
  2407. }
  2408. .box-card {
  2409. width: 100%;
  2410. // margin: 10px 0 10px 10px;
  2411. margin-right: 10px;
  2412. }
  2413. .single-line {
  2414. color: rgb(30, 33, 41);
  2415. overflow: hidden;
  2416. display: -webkit-box;
  2417. -webkit-box-orient: vertical;
  2418. -webkit-line-clamp: 1;
  2419. white-space: pre-wrap;
  2420. word-break: break-word;
  2421. }
  2422. .data-color {
  2423. color: rgb(30, 33, 41);
  2424. }
  2425. .img-box {
  2426. width: 60px;
  2427. height: 60px;
  2428. margin-top: 5px;
  2429. border: 1px solid rgb(194, 199, 207);
  2430. border-radius: 4px;
  2431. }
  2432. .target-group-item {
  2433. margin-top: 15px;
  2434. }
  2435. .suggested-bid-item {
  2436. margin-left: 230px;
  2437. margin-right: 60px;
  2438. }
  2439. .bid-input {
  2440. width: 200px;
  2441. margin-left: 15px;
  2442. }
  2443. ::v-deep(.goods-orientation-tabs .el-tabs__nav-scroll) {
  2444. margin-left: -20px !important;
  2445. }
  2446. ::v-deep(.category-tabs .el-tabs__nav) {
  2447. margin-left: 20px;
  2448. }
  2449. ::v-deep(.goods-orientation-tabs #tab-1) {
  2450. /* 商品定向Tab栏 */
  2451. border-right: 0;
  2452. }
  2453. .custom-tree-node {
  2454. /* el-tree自定义样式 */
  2455. flex: 1;
  2456. display: flex;
  2457. align-items: center;
  2458. justify-content: space-between;
  2459. font-size: 14px;
  2460. padding-right: 8px;
  2461. }
  2462. .dialog-head {
  2463. /* 弹窗样式 */
  2464. display: flex;
  2465. flex-direction: row;
  2466. justify-content: space-between;
  2467. }
  2468. </style>