index.vue 60 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458
  1. <template>
  2. <div>
  3. <el-card>
  4. <div style="padding-left: 5px">
  5. <!-- 广告活动 -->
  6. <div style="font-size: 20px; font-weight: bold">广告活动</div>
  7. <hr />
  8. <br />
  9. <el-form
  10. :label-position="labelPosition"
  11. ref="ruleFormRef"
  12. :model="ruleForm"
  13. :rules="rules"
  14. label-width="120px"
  15. class="demo-ruleForm"
  16. :size="formSize"
  17. status-icon>
  18. <el-form-item label="广告活动名称" prop="name" style="width: 350px">
  19. <el-input v-model="ruleForm.name" />
  20. </el-form-item>
  21. <el-form-item label="广告组合" prop="adMix">
  22. <el-select v-model="ruleForm.adMix" placeholder="请选择">
  23. <el-option label="Zone one" value="shanghai" />
  24. <el-option label="Zone two" value="beijing" />
  25. </el-select>
  26. </el-form-item>
  27. <el-form-item prop="date1" label="开始日期" style="width: 350px">
  28. <el-date-picker v-model="ruleForm.date1" type="date" label="开始日期" placeholder="开始日期" style="width: 100%" />
  29. </el-form-item>
  30. <el-form-item prop="date2" label="结束日期" style="width: 350px">
  31. <el-date-picker v-model="ruleForm.date2" label="结束日期" placeholder="结束日期" style="width: 100%" />
  32. </el-form-item>
  33. <el-form-item prop="budget" label="每日预算" style="width: 350px">
  34. <el-input v-model="ruleForm.budget" maxlength="7" oninput="value=value.indexOf('.') > -1?value.slice(0, value.indexOf('.') + 3):value" />
  35. </el-form-item>
  36. <el-form-item label="投放类型" prop="type" class="column-item">
  37. <el-radio-group v-model="ruleForm.type" @click="changeType">
  38. <div>
  39. <el-radio label="auto">自动</el-radio>
  40. <div class="radio-description">定向与您推广商品相似的关键词和商品</div>
  41. </div>
  42. <div>
  43. <el-radio label="manual">手动</el-radio>
  44. <div class="radio-description">选择关键词或商品以定向购物者搜索并设置自定义出价</div>
  45. </div>
  46. </el-radio-group>
  47. </el-form-item>
  48. <el-form-item label="竞价策略" prop="bidStrategy" class="column-item column-margin-bottom">
  49. <el-radio-group v-model="ruleForm.bidStrategy">
  50. <div>
  51. <el-radio label="dynamicBid_Low" border>
  52. 动态竞价-仅降低
  53. <div class="radio-description-2">当您的广告不太可能带来销售时,我们将实时降低您的竞价</div>
  54. </el-radio>
  55. </div>
  56. <div>
  57. <el-radio label="dynamicBid_HighAndLow" border>
  58. 动态竞价-提高和降低
  59. <div class="radio-description-2">
  60. 当您的广告很有可能带来销售时,我们将实时提高您的竞价(最高可达 100%),并在您的广告不太可能带来销售时降低您的竞价
  61. </div>
  62. </el-radio>
  63. </div>
  64. <el-radio label="staticBid" border>
  65. 固定竞价
  66. <div class="radio-description-2">我们将使用您的确切竞价和您设置的任何手动调整,而不会根据售出可能性对您的竞价进行更改</div>
  67. </el-radio>
  68. </el-radio-group>
  69. </el-form-item>
  70. <el-form-item label="按展示位置调整出价" prop="placeBid">
  71. <p style="color: #8e9196">除了出价策略外,您还可以将出价提高多达900%</p>
  72. <div class="gap-items">
  73. <div class="gap-item">搜索结果顶部(首页)</div>
  74. <el-input v-model="ruleForm.placeBid" class="gap-item">
  75. <template #append>%</template>
  76. </el-input>
  77. </div>
  78. <div class="gap-items">
  79. <div class="gap-item">商品首页</div>
  80. <el-input v-model="ruleForm.firstPage" class="gap-item">
  81. <template #append>%</template>
  82. </el-input>
  83. </div>
  84. <div class="gap-items">
  85. <div class="gap-item">搜索结果的其余位置</div>
  86. <el-input v-model="ruleForm.other" class="gap-item">
  87. <template #append>%</template>
  88. </el-input>
  89. </div>
  90. </el-form-item>
  91. <!-- 广告组 -->
  92. <div style="font-size: 20px; font-weight: bold; margin-top: 30px">广告组</div>
  93. <hr />
  94. <el-form-item label="广告组名称" prop="adGroupName" style="width: 350px; margin-top: 20px">
  95. <el-input v-model="ruleForm.adGroupName" />
  96. </el-form-item>
  97. <!-- 广告组商品表格 -->
  98. <el-form-item label="商品" prop="commodity" style="width: 100%; margin-top: 20px">
  99. <div style="width: 100%; height: 620px; display: flex; border: 1px solid #c2c7cf; border-radius: 6px">
  100. <div style="width: 50%; border-right: 1px solid #c2c7cf">
  101. <el-tabs v-model="activeName" class="demo-tabs">
  102. <el-tab-pane label="搜索" name="first">
  103. <div style="margin-bottom: 10px">
  104. <el-input v-model="searchInp" placeholder="Please input" class="input-with-select" @change="inpChange" clearable>
  105. <template #prepend>
  106. <el-select v-model="select" style="width: 100px" @change="selChange">
  107. <el-option label="名称" value="name" />
  108. <el-option label="ASIN" value="asin" />
  109. <el-option label="SKU" value="sku" />
  110. </el-select>
  111. </template>
  112. <template #append>
  113. <el-select v-model="select2" style="width: 100px">
  114. <el-option label="最新优先" value="latest" />
  115. <el-option label="最早优先" value="earliest" />
  116. <el-option label="优选广告" value="optimal" />
  117. </el-select>
  118. </template>
  119. </el-input>
  120. </div>
  121. <el-table
  122. height="490"
  123. style="width: 100%"
  124. v-loading="loading"
  125. :data="fullTableData"
  126. :header-cell-style="headerCellStyle"
  127. @selection-change="handleSelectionChange">
  128. <el-table-column type="selection" width="50" />
  129. <el-table-column prop="asin" label="商品">
  130. <template #default="scope">
  131. <div style="display: flex; align-items: center">
  132. <div style="margin-right: 8px; line-height: normal">
  133. <el-image class="img-box" :src="scope.row.image_link" />
  134. </div>
  135. <div>
  136. <el-tooltip class="box-item" effect="dark" :content="scope.row.title" placement="top">
  137. <div class="single-line">{{ scope.row.title ? scope.row.title : '--' }}</div>
  138. </el-tooltip>
  139. <div class="data-color">
  140. <span style="font-weight: 500; color: rgb(30, 33, 41)">${{ scope.row.price ? scope.row.price : '--' }}</span>
  141. <span style="margin: 0 5px; color: #cacdd4">|</span>
  142. <span style="color: #6d7784">{{ scope.row.quantity }}</span>
  143. </div>
  144. <span>
  145. ASIN: <span class="data-color" style="margin-right: 8px">{{ scope.row.asin ? scope.row.asin : '--' }}</span>
  146. </span>
  147. <span>
  148. SKU: <span class="data-color">{{ scope.row.sku ? scope.row.sku : '--' }}</span>
  149. </span>
  150. </div>
  151. </div>
  152. </template>
  153. </el-table-column>
  154. <el-table-column prop="name" label="Name" width="120" align="right">
  155. <template #header>
  156. <el-button type="primary" size="normal" link @click="handleGoodsAdd">添加已选中</el-button>
  157. </template>
  158. <template #default="scope">
  159. <el-button type="primary" size="small" @click="addSingleGoods(scope)" text>添加</el-button>
  160. </template>
  161. </el-table-column>
  162. </el-table>
  163. <el-pagination
  164. @current-change="handleCurrentChange"
  165. @size-change="handleSizeChange"
  166. :current-page="currentPage"
  167. :page-size="pageSize"
  168. :total="totalItems"
  169. layout="prev, pager, next" />
  170. </el-tab-pane>
  171. <el-tab-pane label="输入" name="second">
  172. <el-input
  173. style="padding: 10px"
  174. v-model="goodsTextarea"
  175. :rows="20"
  176. type="textarea"
  177. placeholder="请输入ASIN,多个ASIN使用逗号、空格或换行符分隔。(未完成)"
  178. maxlength="11000" />
  179. <div style="display: flex; flex-direction: row-reverse; margin-top: 10px">
  180. <el-button v-for="button in buttons" :key="button.text" :type="button.type" link @click="addGods">{{ button.text }}</el-button>
  181. </div>
  182. <!-- <el-row :gutter="12" style="margin-top: 50px">
  183. <el-col :span="12">
  184. <el-card class="card-box" shadow="never" style="border: none; background-color: #e3f0de; height: 50px">
  185. <div style="line-height: 15px">成功添加:</div>
  186. <div style="line-height: 15px">
  187. <el-icon style="color: #8fc47d; font-size: 25px"><SuccessFilled /></el-icon>
  188. </div>
  189. </el-card>
  190. </el-col>
  191. <el-col :span="12">
  192. <el-card class="card-box" shadow="never" style="border: none; background-color: #fbe6e3; height: 50px">
  193. <div style="line-height: 15px">未成功添加:</div>
  194. <div style="line-height: 15px">
  195. <el-icon style="color: #e3918d; font-size: 25px"><CircleCloseFilled /></el-icon>
  196. </div>
  197. </el-card>
  198. </el-col>
  199. </el-row> -->
  200. </el-tab-pane>
  201. </el-tabs>
  202. </div>
  203. <div style="width: 50%">
  204. <el-card class="box-card" shadow="never">
  205. <template #header>
  206. <div class="card-header">
  207. <span style="font-weight: 550; font-size: 15px; color: #1f2128">已添加: {{ addedData.length }}</span>
  208. <el-button class="button" text bg @click="delAllGoods">全部删除</el-button>
  209. </div>
  210. </template>
  211. <!-- <div v-for="o in 4" :key="o" class="text item">{{ 'List item ' + o }}</div> -->
  212. <div class="card-body"></div>
  213. <!-- <template #footer>Footer content</template> -->
  214. </el-card>
  215. <div style="padding: 0 10px 0 10px; margin-top: -12px">
  216. <el-table
  217. :data="addedData"
  218. height="520"
  219. style="width: 100%"
  220. :header-cell-style="headerCellStyle"
  221. @selection-change="handleAddedGoodsChange">
  222. <el-table-column type="selection" width="50" />
  223. <el-table-column prop="asin" label="ASIN">
  224. <template #default="scope">
  225. <div style="display: flex; align-items: center">
  226. <div style="margin-right: 8px; line-height: normal">
  227. <el-image class="img-box" :src="scope.row.image_link" />
  228. </div>
  229. <div>
  230. <el-tooltip class="box-item" effect="dark" :content="scope.row.title" placement="top">
  231. <div class="single-line">{{ scope.row.title ? scope.row.title : '--' }}</div>
  232. </el-tooltip>
  233. <div class="data-color">
  234. <span style="font-weight: 500; color: rgb(30, 33, 41)">${{ scope.row.price ? scope.row.price : '--' }}</span>
  235. <span style="margin: 0 5px; color: #cacdd4">|</span>
  236. <span style="color: #6d7784">{{ scope.row.quantity }}</span>
  237. </div>
  238. <span
  239. >ASIN:
  240. <span class="data-color" style="margin-right: 8px">{{ scope.row.asin ? scope.row.asin : '--' }}</span>
  241. </span>
  242. <span
  243. >SKU:
  244. <span class="data-color">{{ scope.row.sku ? scope.row.sku : '--' }}</span>
  245. </span>
  246. </div>
  247. </div>
  248. </template>
  249. </el-table-column>
  250. <el-table-column prop="name" label="Name" width="120" align="right">
  251. <template #header>
  252. <el-button type="primary" size="normal" link @click="delSelectedGoods">删除已选中</el-button>
  253. </template>
  254. <template #default="scope">
  255. <el-button type="primary" size="small" @click="delSingleGoods(scope)" text>删除</el-button>
  256. </template>
  257. </el-table-column>
  258. </el-table>
  259. </div>
  260. </div>
  261. </div>
  262. </el-form-item>
  263. <!-- 自动定向 -->
  264. <div class="column-item" v-if="ruleForm.type == 'auto'">
  265. <p style="color: #606266; font-weight: 450"><span style="color: #e47470">*</span> 自动定向</p>
  266. <el-radio-group v-model="ruleForm.autoRedirect" @change="changeBid">
  267. <div style="display: flex">
  268. <el-radio label="defaultBid">设置默认出价</el-radio>
  269. <el-form-item prop="defaultBidInp">
  270. <el-input v-model="ruleForm.defaultBidInp" label="ruleForm.defaultBidInp" style="width: 200px">
  271. <template #prepend>$</template>
  272. </el-input>
  273. </el-form-item>
  274. </div>
  275. <div>
  276. <el-radio label="targetBid">按目标组设置出价</el-radio>
  277. </div>
  278. </el-radio-group>
  279. </div>
  280. <!-- 自动定向---按目标组设置出价 -->
  281. <el-card v-if="showCard && ruleForm.type == 'auto'" class="box-card">
  282. <div>
  283. <div style="color: #8e9095">
  284. <span>目标群体</span>
  285. <span class="suggested-bid-item">建议竞价</span>
  286. <span>竞价</span>
  287. </div>
  288. <div style="display: flex">
  289. <el-switch v-model="ruleForm.closeMatch" size="small" active-text="紧密匹配" />
  290. <span class="suggested-bid-item">--</span>
  291. <el-form-item prop="closeMatchInp">
  292. <el-input :disabled="!ruleForm.closeMatch" v-model="ruleForm.closeMatchInp" placeholder="Please input" class="bid-input">
  293. <template #prepend>$</template>
  294. </el-input>
  295. </el-form-item>
  296. </div>
  297. <div>
  298. <div style="display: flex">
  299. <el-switch v-model="ruleForm.broadMatch" size="small" active-text="广泛匹配" />
  300. <span class="suggested-bid-item">--</span>
  301. <el-form-item prop="broadMatchInp">
  302. <el-input :disabled="!ruleForm.broadMatch" v-model="ruleForm.broadMatchInp" placeholder="Please input" class="bid-input">
  303. <template #prepend>$</template>
  304. </el-input>
  305. </el-form-item>
  306. </div>
  307. </div>
  308. <div>
  309. <div style="display: flex">
  310. <el-switch v-model="ruleForm.similarProducts" size="small" active-text="同类商品" />
  311. <span class="suggested-bid-item">--</span>
  312. <el-form-item prop="similarProductsInp">
  313. <el-input
  314. :disabled="!ruleForm.similarProducts"
  315. v-model="ruleForm.similarProductsInp"
  316. placeholder="Please input"
  317. class="bid-input">
  318. <template #prepend>$</template>
  319. </el-input>
  320. </el-form-item>
  321. </div>
  322. </div>
  323. <div>
  324. <div style="display: flex">
  325. <el-switch v-model="ruleForm.relatedProducts" size="small" active-text="关联商品" />
  326. <span class="suggested-bid-item">--</span>
  327. <el-form-item prop="relatedProductsInp">
  328. <el-input
  329. :disabled="!ruleForm.relatedProducts"
  330. v-model="ruleForm.relatedProductsInp"
  331. placeholder="Please input"
  332. class="bid-input">
  333. <template #prepend>$</template>
  334. </el-input>
  335. </el-form-item>
  336. </div>
  337. </div>
  338. </div>
  339. </el-card>
  340. <!-- 投放类型 -->
  341. <div class="column-item" v-if="ruleForm.type == 'manual'">
  342. <p style="color: #606266; font-weight: 450"><span style="color: #e47470">*</span> 投放类型</p>
  343. <el-radio-group v-model="ruleForm.targetType" @change="changeTargetType">
  344. <div style="display: flex">
  345. <el-radio label="keyWords" style="align-items: flex-start; margin-top: 5px">
  346. <div style="margin-top: -4px">关键词投放</div>
  347. <div>选择关键词以帮助您的商品出现在购物者搜索中</div>
  348. </el-radio>
  349. </div>
  350. <div>
  351. <el-radio label="Goods" style="align-items: flex-start; margin-top: 20px">
  352. <div style="margin-top: -4px">商品投放</div>
  353. <div>选择关键词以帮助您的商品出现在购物者搜索中</div>
  354. </el-radio>
  355. </div>
  356. </el-radio-group>
  357. </div>
  358. <!-- 关键词定向 -->
  359. <div style="font-size: 20px; font-weight: bold; margin-top: 30px" v-if="ruleForm.targetType == 'keyWords' && ruleForm.type == 'manual'">
  360. 关键词定向
  361. </div>
  362. <hr v-if="ruleForm.targetType == 'keyWords' && ruleForm.type == 'manual'" />
  363. <el-form-item style="width: 100%; margin-top: 20px" v-if="ruleForm.targetType == 'keyWords' && ruleForm.type == 'manual'">
  364. <div style="width: 100%; height: 520px; display: flex; border: 1px solid #c2c7cf; border-radius: 6px">
  365. <div style="width: 50%; border-right: 1px solid #c2c7cf">
  366. <el-tabs v-model="keyWordsTabs" class="demo-tabs" @tab-click="handleGoodsTabs">
  367. <div style="margin: 8px">
  368. <p style="margin-left: 8px; margin-bottom: -8px; font-weight: 500; color: #616266">竞价:</p>
  369. <div style="display: flex; align-items: center">
  370. <el-select v-model="bidType" class="m-2" placeholder="Select" style="width: 450px">
  371. <el-option v-for="item in bidTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
  372. </el-select>
  373. <el-input v-model="bidInput" placeholder="Please input">
  374. <template #prepend>$</template>
  375. </el-input>
  376. </div>
  377. <div style="display: flex">
  378. <span style="margin: 0 10px 0 8px; font-weight: 500; color: #616266">匹配类型: </span>
  379. <el-checkbox v-model="widelyType" label="广泛" />
  380. <el-checkbox v-model="phraseType" label="词组" />
  381. <el-checkbox v-model="exactType" label="精确" />
  382. </div>
  383. </div>
  384. <el-tab-pane label="建议" name="first">
  385. <el-table
  386. height="345"
  387. style="width: 100%; padding-left: 5px"
  388. v-loading="loading"
  389. :data="keyWordsTableData"
  390. :header-cell-style="headerCellStyle"
  391. :header-row-style="changeKeyWordsTableHeader">
  392. <el-table-column prop="asin" label="关键词"> </el-table-column>
  393. <el-table-column prop="name" label="匹配类型"> </el-table-column>
  394. <el-table-column prop="name" label="建议出价" width="120"> </el-table-column>
  395. </el-table>
  396. </el-tab-pane>
  397. <el-tab-pane label="输入" name="second">
  398. <el-input v-model="keyWordsTextarea" :rows="10" type="textarea" placeholder="未完成" style="padding-left: 5px" />
  399. <el-button type="primary" size="small" text bg @click="addKeyWords">添加</el-button>
  400. </el-tab-pane>
  401. </el-tabs>
  402. </div>
  403. <div style="width: 50%">
  404. <el-card class="box-card" shadow="never">
  405. <template #header>
  406. <div class="card-header">
  407. <span style="font-weight: 550; font-size: 15px; color: #1f2128">已添加: {{ addedData.length }}</span>
  408. <el-button class="button" text bg @click="delAllKeyWords">全部删除</el-button>
  409. </div>
  410. </template>
  411. <div class="card-body">
  412. <el-table
  413. :data="addedKeyWordsTableData"
  414. style="width: 100%"
  415. :header-row-style="changeKeyWordsTableHeader"
  416. :header-cell-style="headerCellStyle">
  417. <el-table-column prop="keyword" label="关键词" width="auto" />
  418. <el-table-column prop="bid" label="出价" />
  419. <el-table-column prop="suggestBid" label="建议出价" />
  420. <el-table-column prop="operate" label="操作" width="60" align="right" />
  421. </el-table>
  422. </div>
  423. </el-card>
  424. <div style="padding: 0 10px 0 10px; margin-top: -12px"></div>
  425. </div>
  426. </div>
  427. </el-form-item>
  428. <div
  429. style="font-size: 20px; font-weight: bold; margin-top: 30px"
  430. v-if="ruleForm.type === 'auto' || (ruleForm.targetType === 'keyWords' && ruleForm.type === 'manual')">
  431. 否定词
  432. </div>
  433. <hr v-if="ruleForm.type === 'auto' || (ruleForm.targetType === 'keyWords' && ruleForm.type === 'manual')" />
  434. <!-- 否定词表格 -->
  435. <el-form-item
  436. style="width: 100%; margin-top: 20px"
  437. v-if="ruleForm.type === 'auto' || (ruleForm.targetType === 'keyWords' && ruleForm.type === 'manual')">
  438. <div style="width: 100%; height: 520px; display: flex; border: 1px solid #c2c7cf; border-radius: 6px">
  439. <div style="width: 50%; border-right: 1px solid #c2c7cf">
  440. <div style="margin: 10px 0">
  441. <span style="margin-left: 25px; color: #e47470">*</span>
  442. <span style="color: #666666; margin-right: 10px">匹配类型: </span>
  443. <el-checkbox v-model="ruleForm.phraseNegation" label="词组否定" />
  444. <el-checkbox v-model="ruleForm.preciseNegation" label="精确否定" />
  445. </div>
  446. <el-input
  447. v-model="ruleForm.negativeTextarea"
  448. :rows="17"
  449. type="textarea"
  450. placeholder="请输入关键词,多个关键词使用逗号或者换行符分隔。(最多添加1000个关键词)"
  451. maxlength="11000"
  452. style="padding: 0 20px" />
  453. <div style="display: flex; flex-direction: row-reverse; margin-top: 10px">
  454. <el-button style="margin-right: 18px" type="primary" text bg @click="addNegative">添加</el-button>
  455. </div>
  456. </div>
  457. <div style="width: 50%">
  458. <el-card class="box-card" shadow="never">
  459. <template #header>
  460. <div class="card-header">
  461. <span style="font-weight: 550; font-size: 15px; color: #1f2128">已添加: {{ addedData.length }}</span>
  462. <el-button class="button" text bg @click="delAllNegative">全部删除</el-button>
  463. </div>
  464. </template>
  465. <div class="card-body">
  466. <el-table :data="tableData" style="width: 100%" :header-row-style="changeNegTableHeader">
  467. <el-table-column prop="negativeWords" label="否定词" width="auto" />
  468. <el-table-column prop="operate" label="操作" width="60" align="right">
  469. <template #default="scope">
  470. <el-button type="primary" size="small" @click="delSingleNegative(scope)" text>删除</el-button>
  471. </template>
  472. </el-table-column>
  473. </el-table>
  474. </div>
  475. </el-card>
  476. <div style="padding: 0 10px 0 10px; margin-top: -12px"></div>
  477. </div>
  478. </div>
  479. </el-form-item>
  480. <!-- 商品定向 -->
  481. <div style="font-size: 20px; font-weight: bold; margin-top: 30px" v-if="ruleForm.targetType == 'Goods'">商品定向</div>
  482. <hr v-if="ruleForm.targetType == 'Goods'" />
  483. <el-form-item style="width: 100%; margin-top: 20px" v-if="ruleForm.targetType == 'Goods'">
  484. <div style="width: 100%; height: 520px; display: flex; border: 1px solid #c2c7cf; border-radius: 6px" v-loading="productOrientationLoading">
  485. <div style="width: 50%; border-right: 1px solid #c2c7cf">
  486. <el-tabs type="border-card" stretch class="goods-orientation-tabs" style="border-bottom-left-radius: 6px;">
  487. <el-tab-pane label="品类" style="border-top-left-radius: 6px;">
  488. <div style="display: flex; align-items: center">
  489. <span style="width: 40px">竞价:</span>
  490. <el-select v-model="categoryBiddingType" class="m-2" placeholder="Select">
  491. <el-option v-for="item in categoryBiddingTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
  492. </el-select>
  493. <el-input v-model="categoryBidInput" placeholder="Please input" style="width: 200px">
  494. <template #prepend>$</template>
  495. </el-input>
  496. </div>
  497. <el-tabs v-model="categoryTabs" class="category-tabs">
  498. <el-tab-pane label="建议" name="first">
  499. <el-table :data="proposalTableData" style="width: 100%" height="342">
  500. <el-table-column prop="proposal" label="建议" width="520">
  501. <template #header>
  502. 0建议
  503. </template>
  504. </el-table-column>
  505. <el-table-column prop="address" label="Address">
  506. <template #header>
  507. <el-button type="primary" size="normal" link @click="handleGoodsAdd">全部添加</el-button>
  508. </template>
  509. <template #default="scope">
  510. <el-button type="primary" size="small" @click="addSingleGoods(scope)" text>添加</el-button>
  511. </template>
  512. </el-table-column>
  513. </el-table>
  514. </el-tab-pane>
  515. <el-tab-pane label="搜索" name="second">
  516. <el-input placeholder="请输入关键词过滤" />
  517. <el-table :data="searchClassifyTableData" style="width: 100%" height="309">
  518. <el-table-column prop="categorie_name" label="亚马逊分类">
  519. </el-table-column>
  520. </el-table>
  521. </el-tab-pane>
  522. </el-tabs>
  523. </el-tab-pane>
  524. <el-tab-pane label="单个商品">
  525. <div style="display: flex; align-items: center">
  526. <span style="width: 40px">竞价:</span>
  527. <el-select v-model="categoryBiddingType" class="m-2" placeholder="Select">
  528. <el-option v-for="item in categoryBiddingTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
  529. </el-select>
  530. <el-input v-model="categoryBidInput" placeholder="Please input" style="width: 200px">
  531. <template #prepend>$</template>
  532. </el-input>
  533. <div style="margin-left: 20px">
  534. <span style="margin-right: 10px">类型:</span>
  535. <el-checkbox v-model="expand" label="拓展" />
  536. <el-checkbox v-model="accurate" label="精准" />
  537. </div>
  538. </div>
  539. <el-tabs v-model="singleGoodsTabs" class="category-tabs">
  540. <el-tab-pane label="建议" name="first">
  541. <el-table :data="proposalTableData" style="width: 100%" height="342">
  542. <el-table-column prop="proposal" label="商品" width="520" />
  543. <el-table-column prop="address" label="类型" />
  544. <el-table-column prop="operational" label="操作" />
  545. </el-table>
  546. </el-tab-pane>
  547. <el-tab-pane label="搜索" name="second">
  548. <el-input placeholder="按ASIN搜索"></el-input>
  549. <el-table :data="proposalTableData" style="width: 100%" height="342">
  550. <el-table-column prop="proposal" label="商品" width="520" />
  551. <el-table-column prop="address" label="类型" />
  552. <el-table-column prop="operational" label="操作" />
  553. </el-table>
  554. </el-tab-pane>
  555. <!-- TODO: 商品定向TextArea -->
  556. <el-tab-pane label="输入" name="third">待完成</el-tab-pane>
  557. </el-tabs>
  558. </el-tab-pane>
  559. </el-tabs>
  560. </div>
  561. <div style="width: 50%">
  562. <el-card class="box-card" shadow="never">
  563. <template #header>
  564. <div class="card-header">
  565. <span style="font-weight: 550; font-size: 15px; color: #1f2128">已添加: {{ addedData.length }}</span>
  566. <el-button class="button" text bg @click="delAllKeyWords">全部删除</el-button>
  567. </div>
  568. </template>
  569. <div class="card-body">
  570. <el-table
  571. :data="addedKeyWordsTableData"
  572. style="width: 100%"
  573. :header-row-style="changeKeyWordsTableHeader"
  574. :header-cell-style="headerCellStyle">
  575. <el-table-column prop="keyword" label="关键词" width="auto" />
  576. <el-table-column prop="bid" label="出价" />
  577. <el-table-column prop="suggestBid" label="建议出价" />
  578. <el-table-column prop="operate" label="操作" width="60" align="right" />
  579. </el-table>
  580. </div>
  581. </el-card>
  582. <div style="padding: 0 10px 0 10px; margin-top: -12px"></div>
  583. </div>
  584. </div>
  585. </el-form-item>
  586. <div
  587. style="font-size: 20px; font-weight: bold; margin-top: 30px"
  588. v-if="ruleForm.type == 'auto' || (ruleForm.targetType == 'Goods' && ruleForm.type === 'manual')">
  589. 否定商品
  590. </div>
  591. <hr v-if="ruleForm.type == 'auto' || (ruleForm.targetType == 'Goods' && ruleForm.type === 'manual')" />
  592. <!-- 否定商品表格 -->
  593. <el-form-item
  594. prop="matchType"
  595. style="width: 100%; margin-top: 20px"
  596. v-if="ruleForm.type == 'auto' || (ruleForm.targetType == 'Goods' && ruleForm.type === 'manual')">
  597. <div style="width: 100%; height: 520px; display: flex; border: 1px solid #c2c7cf; border-radius: 6px">
  598. <div style="width: 50%; border-right: 1px solid #c2c7cf">
  599. <el-tabs v-model="negativeTabs" class="demo-tabs" @tab-click="handleNegGoodsTabs">
  600. <el-tab-pane label="搜索" name="first">
  601. <div style="margin-bottom: 10px">
  602. <el-input placeholder="按ASIN搜索" v-model="negativeInput" @change="searchNegativeGoods" clearable />
  603. </div>
  604. <el-table
  605. height="415"
  606. style="width: 100%"
  607. v-loading="loading"
  608. :data="negativeTableData"
  609. :header-cell-style="headerCellStyle"
  610. :show-header="false">
  611. <el-table-column prop="asin" label="商品">
  612. <template #default="scope">
  613. <div style="display: flex; align-items: center">
  614. <div style="margin-right: 8px; line-height: normal">
  615. <el-image class="img-box" :src="scope.row.image_link" />
  616. </div>
  617. <div>
  618. <el-tooltip class="box-item" effect="dark" :content="scope.row.title" placement="top">
  619. <div class="single-line">{{ scope.row.title ? scope.row.title : '--' }}</div>
  620. </el-tooltip>
  621. <span>
  622. ASIN: <span class="data-color" style="margin-right: 8px">{{ scope.row.asin ? scope.row.asin : '--' }}</span>
  623. </span>
  624. </div>
  625. </div>
  626. </template>
  627. </el-table-column>
  628. <el-table-column prop="name" label="Name" width="120" align="right">
  629. <template #header> </template>
  630. <template #default="scope">
  631. <el-button type="primary" size="small" @click="addSingleNegativeGoods(scope)" text>添加</el-button>
  632. </template>
  633. </el-table-column>
  634. </el-table>
  635. </el-tab-pane>
  636. <el-tab-pane label="输入" name="second">
  637. <el-input
  638. v-model="ruleForm.negativeGoodsTextarea"
  639. :rows="17"
  640. type="textarea"
  641. placeholder="未完成"
  642. maxlength="11000"
  643. style="padding: 10px 10px" />
  644. <div style="display: flex; flex-direction: row-reverse; margin-top: 10px">
  645. <el-button style="margin-right: 10px" type="primary" text bg @click="addNegativeGoods">添加</el-button>
  646. </div>
  647. </el-tab-pane>
  648. </el-tabs>
  649. </div>
  650. <div style="width: 50%">
  651. <el-card class="box-card" shadow="never">
  652. <template #header>
  653. <div class="card-header">
  654. <span style="font-weight: 550; font-size: 15px; color: #1f2128">已添加: {{ addedNegetiveTableData.length }}</span>
  655. <el-button class="button" text bg @click="delAllNegativeGoods">全部删除</el-button>
  656. </div>
  657. </template>
  658. <div class="card-body"></div>
  659. </el-card>
  660. <div style="padding: 0 10px 0 10px; margin-top: -12px">
  661. <el-table
  662. :data="addedNegetiveTableData"
  663. height="415"
  664. style="width: 100%"
  665. :header-cell-style="headerCellStyle"
  666. @selection-change="handleAddedNegGoods">
  667. <el-table-column prop="asin" label="商品">
  668. <template #default="scope">
  669. <div style="display: flex; align-items: center">
  670. <div style="margin-right: 8px; line-height: normal">
  671. <el-image class="img-box" :src="scope.row.image_link" />
  672. </div>
  673. <div>
  674. <el-tooltip class="box-item" effect="dark" :content="scope.row.title" placement="top">
  675. <div class="single-line">{{ scope.row.title ? scope.row.title : '--' }}</div>
  676. </el-tooltip>
  677. <span
  678. >ASIN:
  679. <span class="data-color" style="margin-right: 8px">{{ scope.row.asin ? scope.row.asin : '--' }}</span>
  680. </span>
  681. </div>
  682. </div>
  683. </template>
  684. </el-table-column>
  685. <el-table-column label="操作" width="120" align="right">
  686. <template #default="scope">
  687. <el-button type="primary" size="small" @click="delSingleNegativeGoods(scope)" text>删除</el-button>
  688. </template>
  689. </el-table-column>
  690. </el-table>
  691. </div>
  692. </div>
  693. </div>
  694. </el-form-item>
  695. <br />
  696. <el-form-item>
  697. <el-button type="negativeGoods" @click="submitForm(ruleFormRef)">Create</el-button>
  698. <el-button @click="resetForm(ruleFormRef)">Reset</el-button>
  699. </el-form-item>
  700. </el-form>
  701. </div>
  702. </el-card>
  703. </div>
  704. </template>
  705. <script lang="ts" setup>
  706. import { onMounted, reactive, ref, computed, watch } from 'vue'
  707. import { useRoute } from 'vue-router'
  708. import type { FormInstance, FormRules, TabsPaneContext } from 'element-plus'
  709. import { useShopInfo } from '/@/stores/shopInfo'
  710. import { usePublicData } from '/@/stores/publicData'
  711. import { storeToRefs } from 'pinia'
  712. import { useRouter } from 'vue-router'
  713. import { request } from '/@/utils/service'
  714. const negativeTableData = ref([])
  715. const addedNegetiveTableData = ref([])
  716. const router = useRouter()
  717. const route = useRoute()
  718. const shopInfo = useShopInfo()
  719. const { profile } = storeToRefs(shopInfo)
  720. const loading = ref(true)
  721. const fullTableData = ref([]) // 表格数据
  722. let addedData = ref([]) // 已添加的商品数据
  723. let selections = [] // 添加选中的项
  724. let addedSels = [] // 删除选中的项
  725. const currentPage = ref() // 当前页
  726. const pageSize = ref(20) // 每页显示条目数
  727. const totalItems = ref() // 数据总量
  728. const showCard = ref(false)
  729. const activeName = ref('first')
  730. const negativeTabs = ref('first')
  731. const keyWordsTabs = ref('first')
  732. const productOrientationTabs = ref('first')
  733. const categoryTabs = ref('first')
  734. const singleGoodsTabs = ref('first')
  735. const searchInp = ref('')
  736. const select = ref('name')
  737. const select2 = ref('latest')
  738. const buttons = [{ type: 'primary', text: '添加' }] as const
  739. const negativeInput = ref('')
  740. let widelyType = ref(true)
  741. let phraseType = ref(true)
  742. let exactType = ref(true)
  743. // 表单相关数据
  744. const formSize = ref('default')
  745. const labelPosition = ref('top')
  746. const ruleFormRef = ref<FormInstance>()
  747. interface RuleForm {
  748. name: string
  749. adMix: string
  750. count: string
  751. date1: string
  752. date2: string
  753. budget: string
  754. delivery: boolean
  755. type: string
  756. bidStrategy: string
  757. placeBid: string
  758. firstPage: string
  759. other: string
  760. adGroupName: string
  761. autoRedirect: string
  762. defaultBidInp: string
  763. closeMatch: boolean
  764. broadMatch: boolean
  765. similarProducts: boolean
  766. relatedProducts: boolean
  767. closeMatchInp: string
  768. broadMatchInp: string
  769. similarProductsInp: string
  770. relatedProductsInp: string
  771. phraseNegation: boolean
  772. preciseNegation: boolean
  773. negativeTextarea: string
  774. negativeGoodsTextarea: string
  775. targetType: string
  776. }
  777. const ruleForm = reactive<RuleForm>({
  778. name: 'Hello',
  779. adMix: '',
  780. count: '',
  781. date1: '',
  782. date2: '',
  783. budget: '',
  784. delivery: false,
  785. type: 'auto',
  786. bidStrategy: 'dynamicBid_Low',
  787. placeBid: '',
  788. firstPage: '',
  789. other: '',
  790. adGroupName: '',
  791. autoRedirect: 'defaultBid',
  792. defaultBidInp: '',
  793. closeMatch: true,
  794. broadMatch: true,
  795. similarProducts: true,
  796. relatedProducts: true,
  797. closeMatchInp: '666',
  798. broadMatchInp: '666',
  799. similarProductsInp: '666',
  800. relatedProductsInp: '666',
  801. phraseNegation: true,
  802. preciseNegation: true,
  803. negativeTextarea: '',
  804. negativeGoodsTextarea: '',
  805. targetType: 'keyWords',
  806. })
  807. const rules = computed(() => ({
  808. name: [{ required: true, message: 'Please input Activity name', trigger: 'blur' }],
  809. adMix: [{ required: false, message: 'Please select Activity zone', trigger: 'change' }],
  810. count: [{ required: true, message: 'Please select Activity count', trigger: 'change' }],
  811. date1: [{ required: true, type: 'date', message: 'Please pick a date', trigger: 'change' }],
  812. date2: [{ required: false, type: 'date', message: 'Please pick a time', trigger: 'change' }],
  813. budget: [
  814. { required: true, message: '请输入预算', trigger: 'blur' },
  815. { pattern: /^(?:[1-9]\d{0,5}|1000000)(?:\.\d{1,2})?$/, message: '预算必须是1到1000000之间的数字,小数点后最多两位', trigger: 'blur' },
  816. ],
  817. type: [{ required: false, trigger: 'change' }],
  818. bidStrategy: [{ required: true, message: 'Please select activity resource', trigger: 'change' }],
  819. placeBid: [{ required: false, pattern: /^[0-9]{1,3}$/, message: '必须是0~900之间的整数百分比', trigger: 'change' }],
  820. firstPage: [{ required: false, pattern: /^[0-9]{1,3}$/, message: '必须是0~900之间的整数百分比', trigger: 'change' }],
  821. other: [{ required: false, pattern: /^[0-9]{1,3}$/, message: '必须是0~900之间的整数百分比', trigger: 'change' }],
  822. adGroupName: [{ required: true, message: 'Please input Activity name', trigger: 'blur' }],
  823. autoRedirect: [{ required: true, trigger: 'change' }],
  824. defaultBidInp: getValidationRules('defaultBidInp'),
  825. relatedProductsInp: getValidationRules('relatedProductsInp'),
  826. similarProductsInp: getValidationRules('similarProductsInp'),
  827. broadMatchInp: getValidationRules('broadMatchInp'),
  828. closeMatchInp: getValidationRules('closeMatchInp'),
  829. }))
  830. //------------------------------------------------------------------------------方法------------------------------------------------------------------------------
  831. // 当单选按钮变化时更新showCard状态
  832. function changeBid() {
  833. console.log(ruleForm.autoRedirect)
  834. showCard.value = ruleForm.autoRedirect === 'targetBid'
  835. }
  836. // 切换投放类型
  837. function changeType() {}
  838. // 处理分页器当前页变化
  839. function handleCurrentChange(newPage) {
  840. currentPage.value = newPage
  841. loading.value = true
  842. setTableData()
  843. }
  844. // 处理分页器每页显示条目数变化
  845. function handleSizeChange(newSize) {
  846. pageSize.value = newSize
  847. currentPage.value = 1 // 重置到第一页
  848. }
  849. // ------------------------------------------投放类型模块------------------------------------------
  850. async function changeTargetType() {
  851. console.log(ruleForm.targetType)
  852. showCard.value = ruleForm.targetType === 'keyWords'
  853. if(ruleForm.targetType === 'Goods') {
  854. productOrientationLoading.value = true
  855. await setProductOrientationData()
  856. }
  857. }
  858. // ------------------------------------------商品定向模块------------------------------------------
  859. const categoryBiddingType = ref('customBid')
  860. const categoryBiddingTypeOptions = [
  861. {
  862. value: 'defaultBid',
  863. label: '默认竞价',
  864. },
  865. {
  866. value: 'customBid',
  867. label: '自定义竞价',
  868. },
  869. ]
  870. const categoryBidInput = ref('0.75')
  871. const expand = ref(true)
  872. const accurate = ref(false)
  873. const proposalTableData = ref([])
  874. const searchClassifyTableData = ref([])
  875. const productOrientationLoading = ref(false)
  876. async function setProductOrientationData() {
  877. try {
  878. const resp = await request({
  879. url: '/api/ad_manage/targetable/categories/',
  880. method: 'GET',
  881. params: {
  882. profile_id: profile.value.profile_id,
  883. },
  884. })
  885. searchClassifyTableData.value = resp.data
  886. console.log('searchClassifyTableData', searchClassifyTableData.value)
  887. productOrientationLoading.value = false
  888. } catch (error) {
  889. console.error("请求失败:", error)
  890. }
  891. }
  892. // ------------------------------------------关键词定向模块------------------------------------------
  893. const bidType = ref('customBid')
  894. const bidTypeOptions = [
  895. // 竞价select下拉选项
  896. {
  897. value: 'suggestBid',
  898. label: '建议出价',
  899. },
  900. {
  901. value: 'customBid',
  902. label: '自定义出价',
  903. },
  904. {
  905. value: 'defaultBid',
  906. label: '默认出价',
  907. },
  908. ]
  909. const bidInput = ref('0.75')
  910. const keyWordsTableData = ref([]) // 关键词定向左侧表格数据
  911. const addedKeyWordsTableData = ref([]) // 关键词定向右侧表格数据
  912. const keyWordsTextarea = ref('')
  913. function delAllKeyWords() {
  914. // 删除已经添加的关键词
  915. addedKeyWordsTableData.value = []
  916. }
  917. function addKeyWords() {
  918. // 添加关键词
  919. if (keyWordsTextarea.value) {
  920. addedKeyWordsTableData.value.push({
  921. keyWords: keyWordsTextarea.value,
  922. })
  923. keyWordsTextarea.value = ''
  924. }
  925. }
  926. // ------------------------------------------广告组商品表格模块------------------------------------------
  927. const goodsTextarea = ref('')
  928. function setTableData(asin = '', sku = '') {
  929. return request({
  930. url: '/api/sellers/listings/',
  931. method: 'GET',
  932. params: {
  933. page: currentPage.value,
  934. limit: pageSize.value,
  935. profile_id: profile.value.profile_id,
  936. asin,
  937. sku,
  938. },
  939. })
  940. .then((resp) => {
  941. fullTableData.value = resp.data
  942. totalItems.value = resp.total
  943. currentPage.value = resp.page
  944. loading.value = false
  945. })
  946. .catch((error) => {
  947. console.error('Error fetching data:', error)
  948. loading.value = false
  949. })
  950. }
  951. function addSingleGoods(scope) {
  952. console.log('scope', scope.row)
  953. const isAlreadyAdded = addedData.value.some((item) => item.sku === scope.row.sku)
  954. if (!isAlreadyAdded) {
  955. addedData.value.push(scope.row)
  956. } else {
  957. console.log('Item is already added.')
  958. }
  959. }
  960. function addGods() {
  961. const inputData = goodsTextarea.value
  962. const asins = inputData.split(/[\n,]+/)
  963. asins.forEach((asin) => {
  964. if (asin.trim()) {
  965. setTableData(asin.trim())
  966. .then((response) => {
  967. // 假设你的数据是在 response.data 中
  968. console.log(`Data for ASIN ${asin}:`, response) // 更新这里来正确地访问数据
  969. })
  970. .catch((error) => {
  971. console.error(`Error fetching data for ASIN ${asin}:`, error)
  972. })
  973. }
  974. })
  975. }
  976. function delSingleGoods(scope) {
  977. const index = addedData.value.findIndex((item) => item.sku === scope.row.sku)
  978. if (index !== -1) {
  979. addedData.value.splice(index, 1)
  980. console.log('Item removed successfully.')
  981. } else {
  982. console.log('Item not found.')
  983. }
  984. }
  985. function delAllGoods() {
  986. addedData.value = []
  987. // addedData.value.splice(0, addedData.value.length)
  988. }
  989. // 删除第二个table中已经选中的项
  990. function delSelectedGoods() {
  991. addedData.value = addedData.value.filter((item) => !addedSels.includes(item))
  992. addedSels = []
  993. }
  994. function inpChange(e) {
  995. const value = e
  996. if (select.value === 'asin') {
  997. loading.value = true
  998. setTableData(value)
  999. } else if (select.value === 'sku') {
  1000. loading.value = true
  1001. setTableData('', value)
  1002. }
  1003. }
  1004. function selChange(e) {
  1005. console.log('e', e)
  1006. const value = e
  1007. if (select.value === 'asin' && searchInp.value) {
  1008. loading.value = true
  1009. setTableData(value)
  1010. } else if (select.value === 'sku' && searchInp.value) {
  1011. loading.value = true
  1012. setTableData('', value)
  1013. }
  1014. }
  1015. // 点击表格选项触发事件
  1016. function handleSelectionChange(selection) {
  1017. selections = selection
  1018. }
  1019. // 获取addedTable中已选中的项
  1020. function handleAddedGoodsChange(selection) {
  1021. addedSels = selection
  1022. }
  1023. // 添加已选中的项
  1024. function handleGoodsAdd() {
  1025. // 过滤掉已经存在于addedData.value中的项
  1026. const newSelections = selections.filter(
  1027. (sel) => !addedData.value.some((added) => added.sku === sel.sku) // 使用sku作为唯一标识
  1028. )
  1029. // 如果有新的不重复项,加入到addedData.value中
  1030. if (newSelections.length > 0) {
  1031. addedData.value.push(...newSelections)
  1032. }
  1033. }
  1034. // 点击Tab
  1035. const handleGoodsTabs = (tab: TabsPaneContext, event: Event) => {
  1036. console.log(tab, event)
  1037. }
  1038. // ------------------------------------------否定词模块------------------------------------------
  1039. function addNegative() {
  1040. // 删除 negativeTextarea 前后的空格
  1041. const trimmedText = ruleForm.negativeTextarea.trim()
  1042. // 使用逗号和换行符分割文本
  1043. const items = trimmedText.split(/,|\n/)
  1044. // 遍历分割后的每个项目
  1045. items.forEach((item) => {
  1046. const trimmedItem = item.trim() // 删除每个项目前后的空格
  1047. if (trimmedItem) {
  1048. // 如果两者都是 true,则为每个创建两个条目
  1049. if (ruleForm.phraseNegation && ruleForm.preciseNegation) {
  1050. negativeList.push({ negativeWords: '词组: ' + trimmedItem })
  1051. negativeList.push({ negativeWords: '精确: ' + trimmedItem })
  1052. } else if (ruleForm.phraseNegation) {
  1053. negativeList.push({ negativeWords: '词组: ' + trimmedItem })
  1054. } else if (ruleForm.preciseNegation) {
  1055. negativeList.push({ negativeWords: '精确: ' + trimmedItem })
  1056. }
  1057. } else {
  1058. console.log('有空项目,未被添加到列表中')
  1059. }
  1060. })
  1061. // 清空输入框
  1062. ruleForm.negativeTextarea = ''
  1063. console.log(negativeList)
  1064. }
  1065. function delAllNegative() {
  1066. // negativeList.splice(0, negativeList.length)
  1067. negativeList.length = 0
  1068. }
  1069. function delSingleNegative(scope) {
  1070. const index = negativeList.findIndex((item) => item.negativeWords === scope.row.negativeWords)
  1071. if (negativeList.length) {
  1072. negativeList.splice(index, 1)
  1073. console.log(`已删除索引为 ${index} 的条目`)
  1074. } else {
  1075. console.log('无效的索引,无法删除条目')
  1076. }
  1077. }
  1078. // ------------------------------------------否定商品模块------------------------------------------
  1079. let negativeList = reactive([])
  1080. const tableData = negativeList
  1081. let inputAddedNegGoods = ref([])
  1082. function setNegativeTableData(asin = '') {
  1083. return request({
  1084. url: '/api/sellers/listings/',
  1085. method: 'GET',
  1086. params: {
  1087. page: currentPage.value,
  1088. limit: pageSize.value,
  1089. profile_id: profile.value.profile_id,
  1090. asin,
  1091. },
  1092. })
  1093. .then((resp) => {
  1094. negativeTableData.value = resp.data
  1095. inputAddedNegGoods.value = resp.data
  1096. loading.value = false
  1097. })
  1098. .catch((error) => {
  1099. console.error('Error fetching data:', error)
  1100. loading.value = false
  1101. })
  1102. }
  1103. // 输入tab的textarea
  1104. function addNegativeGoods() {
  1105. console.log('ruleForm.negativeGoodsTextarea', ruleForm.negativeGoodsTextarea)
  1106. loading.value = true
  1107. setNegativeTableData(ruleForm.negativeGoodsTextarea)
  1108. .then(() => {
  1109. addedNegetiveTableData.value = [...addedNegetiveTableData.value, ...inputAddedNegGoods.value]
  1110. })
  1111. .catch((error) => {
  1112. console.error('Error fetching data:', error)
  1113. })
  1114. .finally(() => {
  1115. loading.value = false
  1116. })
  1117. }
  1118. function addSingleNegativeGoods(scope) {
  1119. console.log('scope', scope.row)
  1120. const isAlreadyAdded = addedNegetiveTableData.value.some((item) => item.sku === scope.row.sku)
  1121. if (!isAlreadyAdded) {
  1122. addedNegetiveTableData.value.push(scope.row)
  1123. } else {
  1124. console.log('Item is already added.')
  1125. }
  1126. }
  1127. function delAllNegativeGoods() {
  1128. addedNegetiveTableData.value = []
  1129. }
  1130. function delSingleNegativeGoods(scope) {
  1131. const index = addedNegetiveTableData.value.findIndex((item) => item.sku === scope.row.sku)
  1132. if (index !== -1) {
  1133. addedNegetiveTableData.value.splice(index, 1)
  1134. console.log('Item removed successfully.')
  1135. } else {
  1136. console.log('Item not found.')
  1137. }
  1138. }
  1139. function searchNegativeGoods(e) {
  1140. console.log(e)
  1141. if (e === '') {
  1142. negativeTableData.value = []
  1143. } else {
  1144. setNegativeTableData(e)
  1145. }
  1146. }
  1147. function handleAddedNegGoods(selection) {
  1148. addedSels = selection
  1149. }
  1150. function handleNegGoodsTabs(tab: TabsPaneContext, event: Event) {
  1151. // console.log(tab, event)
  1152. }
  1153. // ------------------------------------------自定义校验模块------------------------------------------
  1154. function checkBid(value, callback, bidField) {
  1155. const bid = parseFloat(value)
  1156. const budget = parseFloat(ruleForm.budget)
  1157. // 检查值是否为最多两位小数的普通数字格式
  1158. const isNormalNumberWithTwoDecimals = /^-?\d+(\.\d{1,2})?$/.test(value)
  1159. if (!isNormalNumberWithTwoDecimals) {
  1160. callback(new Error('请输入数字值(最多两位小数)'))
  1161. } else if (isNaN(bid)) {
  1162. callback(new Error('请输入有效的数字'))
  1163. } else if (bid < 0.02 || bid > 1000) {
  1164. callback(new Error('值必须在0.02到1000之间'))
  1165. } else if (bid >= budget) {
  1166. callback(new Error('出价必须小于预算'))
  1167. } else {
  1168. callback()
  1169. }
  1170. }
  1171. // 自定义校验规则---自动定价和按目标设置出价模块
  1172. function getValidationRules(fieldName) {
  1173. // 默认校验规则
  1174. const commonRules = [
  1175. { required: true, message: '此项为必填项', trigger: 'blur' },
  1176. { validator: (rule, value, callback) => checkBid(value, callback, fieldName), trigger: 'blur' },
  1177. ]
  1178. // 根据不同字段和状态返回特定的校验规则
  1179. switch (fieldName) {
  1180. case 'defaultBidInp':
  1181. if (ruleForm.autoRedirect === 'defaultBid') {
  1182. return commonRules
  1183. }
  1184. break
  1185. case 'similarProductsInp':
  1186. if (ruleForm.autoRedirect === 'targetBid' && ruleForm.similarProducts) {
  1187. return commonRules
  1188. } else if (ruleForm.similarProducts == false) {
  1189. ruleFormRef.value?.clearValidate(fieldName)
  1190. }
  1191. break
  1192. case 'relatedProductsInp':
  1193. if (ruleForm.autoRedirect === 'targetBid' && ruleForm.relatedProducts) {
  1194. return commonRules
  1195. } else if (ruleForm.relatedProducts == false) {
  1196. ruleFormRef.value?.clearValidate(fieldName)
  1197. }
  1198. break
  1199. case 'broadMatchInp':
  1200. if (ruleForm.autoRedirect === 'targetBid' && ruleForm.broadMatch) {
  1201. return commonRules
  1202. } else if (ruleForm.broadMatch == false) {
  1203. ruleFormRef.value?.clearValidate(fieldName)
  1204. }
  1205. break
  1206. case 'closeMatchInp':
  1207. // 仅当autoRedirect为'targetBid'且closeMatch开启时校验closeMatchInp
  1208. if (ruleForm.autoRedirect === 'targetBid' && ruleForm.closeMatch) {
  1209. return commonRules
  1210. } else if (ruleForm.closeMatch == false) {
  1211. ruleFormRef.value?.clearValidate(fieldName)
  1212. }
  1213. break
  1214. default:
  1215. return []
  1216. }
  1217. // 如果不满足上述条件,则无需校验
  1218. return []
  1219. }
  1220. // 监听表单字段变化,根据不同字段和状态返回特定的校验规则
  1221. watch(
  1222. [() => ruleForm.autoRedirect, () => ruleForm.closeMatch, () => ruleForm.broadMatch, () => ruleForm.similarProducts, () => ruleForm.relatedProducts],
  1223. () => {
  1224. // 定义需要更新校验规则的字段
  1225. const fields = ['defaultBidInp', 'closeMatchInp', 'broadMatchInp', 'similarProductsInp', 'relatedProductsInp']
  1226. fields.forEach((field) => {
  1227. rules.value[field] = getValidationRules(field)
  1228. })
  1229. }
  1230. )
  1231. // 表单提交
  1232. async function submitForm(formEl: FormInstance | undefined) {
  1233. if (!formEl) return
  1234. await formEl.validate((valid, fields) => {
  1235. if (valid) {
  1236. console.log('submit!')
  1237. } else {
  1238. console.log('error submit!', fields)
  1239. }
  1240. })
  1241. }
  1242. function resetForm(formEl: FormInstance | undefined) {
  1243. if (!formEl) return
  1244. formEl.resetFields()
  1245. }
  1246. // 修改表头样式
  1247. const headerCellStyle = (args) => {
  1248. if (args.rowIndex === 0) {
  1249. return {
  1250. backgroundColor: 'rgba(245, 245, 245, 0.9)',
  1251. }
  1252. }
  1253. }
  1254. function changeNegTableHeader(args) {
  1255. if (args.rowIndex === 0) {
  1256. return {
  1257. color: '#505968',
  1258. }
  1259. }
  1260. }
  1261. function changeKeyWordsTableHeader(args) {
  1262. if (args.rowIndex === 0) {
  1263. return {
  1264. color: '#505968',
  1265. backgroundColor: 'rgba(245, 245, 245, 0.9)',
  1266. }
  1267. }
  1268. }
  1269. onMounted(() => {
  1270. setTableData()
  1271. // const myTest = route.query
  1272. // console.log('myTest', myTest)
  1273. })
  1274. defineOptions({
  1275. name: 'SpCreateCampaigns',
  1276. })
  1277. </script>
  1278. <style lang="scss" scoped>
  1279. ::v-deep(.el-form--default.el-form--label-top .el-form-item .el-form-item__label) {
  1280. font-weight: 500;
  1281. }
  1282. .column-item .el-radio-group {
  1283. display: inline-flex;
  1284. font-size: 0;
  1285. flex-direction: column;
  1286. align-items: flex-start;
  1287. }
  1288. .radio-description {
  1289. font-size: 12px;
  1290. color: #666;
  1291. margin-top: -18px;
  1292. margin-left: 22px;
  1293. }
  1294. .radio-description-2 {
  1295. font-size: 12px;
  1296. color: #666;
  1297. margin-top: -10px;
  1298. }
  1299. .column-margin-bottom label.el-radio.is-bordered {
  1300. margin-bottom: 10px;
  1301. padding: 35px;
  1302. }
  1303. ::v-deep(.column-margin-bottom label.el-radio.is-bordered span.el-radio__inner) {
  1304. margin-top: -18px;
  1305. margin-left: -15px;
  1306. }
  1307. .gap-items {
  1308. display: flex;
  1309. justify-content: flex-start;
  1310. width: 100%;
  1311. margin-bottom: 20px;
  1312. }
  1313. .gap-item {
  1314. width: 200px;
  1315. margin-left: 30px;
  1316. color: #0b0d0d;
  1317. }
  1318. .demo-tabs > .el-tabs__content {
  1319. padding: 52px;
  1320. color: #6b778c;
  1321. font-size: 32px;
  1322. font-weight: 600;
  1323. }
  1324. /* 广告组商品Tab栏 */
  1325. ::v-deep(.el-tabs__nav-scroll) {
  1326. overflow: hidden;
  1327. margin-left: 20px;
  1328. }
  1329. ::v-deep(.el-tabs__nav-wrap::after) {
  1330. height: 2px !important;
  1331. }
  1332. ::v-deep(.el-table__inner-wrapper::before) {
  1333. background-color: white;
  1334. }
  1335. // 表格内容边距
  1336. div {
  1337. & #pane-first,
  1338. & #pane-second {
  1339. margin: 10px;
  1340. }
  1341. }
  1342. // 输入底部样式
  1343. ::v-deep(.card-box .el-card__body) {
  1344. display: flex;
  1345. align-items: center;
  1346. justify-content: space-between;
  1347. padding: 12px;
  1348. }
  1349. .card-header {
  1350. display: flex;
  1351. justify-content: space-between;
  1352. align-items: center;
  1353. }
  1354. .box-card {
  1355. width: 100%;
  1356. // margin: 10px 0 10px 10px;
  1357. margin-right: 10px;
  1358. border: none;
  1359. }
  1360. .single-line {
  1361. color: rgb(30, 33, 41);
  1362. overflow: hidden;
  1363. display: -webkit-box;
  1364. -webkit-box-orient: vertical;
  1365. -webkit-line-clamp: 1;
  1366. white-space: pre-wrap;
  1367. word-break: break-word;
  1368. }
  1369. .data-color {
  1370. color: rgb(30, 33, 41);
  1371. }
  1372. .img-box {
  1373. width: 60px;
  1374. height: 60px;
  1375. margin-top: 5px;
  1376. border: 1px solid rgb(194, 199, 207);
  1377. border-radius: 4px;
  1378. }
  1379. .target-group-item {
  1380. margin-top: 15px;
  1381. }
  1382. .suggested-bid-item {
  1383. margin-left: 230px;
  1384. margin-right: 60px;
  1385. }
  1386. .bid-input {
  1387. width: 200px;
  1388. margin-left: 15px;
  1389. }
  1390. ::v-deep(.goods-orientation-tabs .el-tabs__nav-scroll) {
  1391. margin-left: -20px !important;
  1392. }
  1393. ::v-deep(.category-tabs .el-tabs__nav) {
  1394. margin-left: 20px;
  1395. }
  1396. </style>