|
@@ -0,0 +1,808 @@
|
|
|
+<template>
|
|
|
+ <div style="width: 100%; margin-top: 20px">
|
|
|
+ <el-divider content-position="left">
|
|
|
+ <span style="font-size: 18px; font-weight: 700">商品定向</span>
|
|
|
+ </el-divider>
|
|
|
+ <div style="width: 100%; height: 600px; display: flex; border: 1px solid #e5e7eb; border-radius: 6px" v-loading="productOrientationLoading">
|
|
|
+ <div style="width: 50%; border-right: 1px solid #e5e7eb">
|
|
|
+ <el-tabs
|
|
|
+ type="border-card"
|
|
|
+ stretch
|
|
|
+ class="goods-orientation-tabs"
|
|
|
+ style="border: 0; border-right: 0; border-bottom-left-radius: 6px; border-top-left-radius: 5px; overflow: hidden">
|
|
|
+ <el-tab-pane label="品类" style="border-top-left-radius: 6px">
|
|
|
+ <div style="display: flex; align-items: center">
|
|
|
+ <span style="width: 40px">竞价:</span>
|
|
|
+ <el-select v-model="categoryBiddingType" @change="singleGoodsBidSelectChanged" class="m-2" placeholder="Select">
|
|
|
+ <el-option v-for="item in categoryBiddingTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
|
|
|
+ </el-select>
|
|
|
+ <el-input v-model="singleGoodsBidInput" :disabled="categoryBiddingType === 'defaultBid'" style="width: 200px">
|
|
|
+ <template #prepend>$</template>
|
|
|
+ </el-input>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-tabs v-model="categoryTabs" class="category-tabs">
|
|
|
+ <el-tab-pane label="建议" name="first">
|
|
|
+ <el-table :data="proposalTableData" style="width: 100%" height="422">
|
|
|
+ <el-table-column prop="proposal" label="建议" width="520">
|
|
|
+ <template #header> 0建议 </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="address" label="Address">
|
|
|
+ <template #header>
|
|
|
+ <el-button type="primary" size="normal" link @click="handleGoodsAdd">全部添加</el-button>
|
|
|
+ </template>
|
|
|
+ <template #default="scope">
|
|
|
+ <el-button type="primary" size="small" @click="addSingleGoods(scope)" text>添加</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </el-tab-pane>
|
|
|
+ <el-tab-pane label="搜索" name="second">
|
|
|
+ <el-input placeholder="请输入关键词过滤" />
|
|
|
+ <el-scrollbar height="390px">
|
|
|
+ <el-tree :data="searchClassifyTableData" :props="defaultProps">
|
|
|
+ <template #default="{ node, data }">
|
|
|
+ <span class="custom-tree-node">
|
|
|
+ <span style="width: 75%">{{ node.label }}</span>
|
|
|
+ <span style="color: rgb(50, 108, 216)" v-if="data.ta == true">
|
|
|
+ <a @click="refine(data)"> 细化 </a>
|
|
|
+ <a style="margin-left: 8px" @click="orientate(node, data)"> 定向 </a>
|
|
|
+ </span>
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-tree>
|
|
|
+ </el-scrollbar>
|
|
|
+ <el-dialog v-model="visible" :title="`细化分类: ${dialogTitle}`" @close="dialogClose" destroy-on-close>
|
|
|
+ <div style="display: flex; justify-content: space-between">
|
|
|
+ <span>根据特定品牌、价格范围、星级和Prime配送资格,细化分类</span>
|
|
|
+ <span>
|
|
|
+ <el-checkbox v-model="dialogForm.isCount" label="显示商品数量" @change="isCountChanged" />
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <el-form :model="dialogForm" :rules="dialogRules" ref="dialogFormRef" style="margin-top: 20px">
|
|
|
+ <el-form-item style="padding-left: 140px">
|
|
|
+ <span style="margin-right: 10px; color: #616266; font-weight: 500">品牌</span>
|
|
|
+ <el-select
|
|
|
+ v-model="dialogForm.dialogselectValue"
|
|
|
+ @change="dialogSelectChange"
|
|
|
+ multiple
|
|
|
+ placeholder="请选择"
|
|
|
+ :loading="dialogSelectLoading">
|
|
|
+ <el-option v-for="item in dialogForm.dialogOptions" :key="item.value" :label="item.label" :value="item.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item prop="prices" style="padding-left: 112px; margin-top: 10px">
|
|
|
+ <span style="margin-right: 10px; color: #616266; font-weight: 500">价格范围</span>
|
|
|
+ <el-input-number v-model="dialogForm.prices.lowest" :min="1" :controls="false" placeholder="无最低商品价格" />
|
|
|
+ --
|
|
|
+ <el-input-number v-model="dialogForm.prices.highest" :min="1" :controls="false" placeholder="无最高商品价格" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item prop="starRating" style="padding-left: 85px; margin-top: 10px">
|
|
|
+ <span style="margin-right: 15px; color: #616266; font-weight: 500">查看星级评定</span>
|
|
|
+ <el-slider v-model="dialogForm.starRating" range show-stops :max="5" :marks="marks" style="width: 70%" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item prop="delivery" style="padding-left: 140px; margin-top: 30px">
|
|
|
+ <span style="margin-right: 10px; color: #616266; font-weight: 500">配送</span>
|
|
|
+ <el-radio-group v-model="dialogForm.delivery">
|
|
|
+ <el-radio label="all" style="font-weight: 400">所有</el-radio>
|
|
|
+ <el-radio label="eligible" style="font-weight: 400">具备Prime资格</el-radio>
|
|
|
+ <el-radio label="diseligible" style="font-weight: 400">不具备Prime资格</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <div style="display: flex; justify-content: space-between">
|
|
|
+ <span v-loading="countLoadig"
|
|
|
+ >定位到的商品数量:
|
|
|
+ <span v-if="dialogForm.isCount == true">{{ commodityCount[0]?.min }} - {{ commodityCount[0]?.max }}</span></span
|
|
|
+ >
|
|
|
+ <span class="dialog-footer">
|
|
|
+ <el-button @click="visible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="dialogFormSubmit">确定</el-button>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </el-tab-pane>
|
|
|
+ </el-tabs>
|
|
|
+ </el-tab-pane>
|
|
|
+ <el-tab-pane label="单个商品">
|
|
|
+ <div style="display: flex; align-items: center">
|
|
|
+ <span style="width: 40px">竞价:</span>
|
|
|
+ <el-select class="m-2" v-model="singleGoodsBidSelect" @change="singleGoodsBidSelectChanged">
|
|
|
+ <el-option v-for="item in singleGoodsBidTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
|
|
|
+ </el-select>
|
|
|
+ <el-input v-model="singleGoodsBidInput" :disabled="singleGoodsBidSelect == 'defaultBid'" style="width: 200px">
|
|
|
+ <template #prepend>$</template>
|
|
|
+ </el-input>
|
|
|
+ <!-- <div style="margin-left: 20px">
|
|
|
+ <span style="margin-right: 10px">类型:</span>
|
|
|
+ <el-checkbox v-model="expand" label="扩展" />
|
|
|
+ <el-checkbox v-model="accurate" label="精准" />
|
|
|
+ </div> -->
|
|
|
+ </div>
|
|
|
+ <el-tabs v-model="singleGoodsTabs" class="category-tabs">
|
|
|
+ <el-tab-pane label="建议" name="first">
|
|
|
+ <el-table :data="proposalTableData" style="width: 100%" height="342">
|
|
|
+ <el-table-column prop="proposal" label="商品" width="520" />
|
|
|
+ <el-table-column prop="address" label="类型" />
|
|
|
+ <el-table-column prop="operational" label="操作" />
|
|
|
+ </el-table>
|
|
|
+ </el-tab-pane>
|
|
|
+ <el-tab-pane label="搜索" name="second">
|
|
|
+ <el-input v-model="singleGoodsSearchInp" @change="singleGoodsSearchChaneged" placeholder="按ASIN搜索"></el-input>
|
|
|
+ <el-table :data="searchTableData" style="width: 100%" height="309">
|
|
|
+ <el-table-column prop="asin" label="商品" width="520">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div style="display: flex; align-items: center">
|
|
|
+ <img :src="row.image_link" style="width: 40px; height: 40px; margin-right: 10px" />
|
|
|
+ <span>{{ row.title }}</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="productTypes" label="类型">
|
|
|
+ <template #default="scope">
|
|
|
+ <div v-if="expand">扩展</div>
|
|
|
+ <div v-if="accurate">精准</div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="operational" label="操作">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-button class="button" text @click="addSingleSearch(scope)">添加</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </el-tab-pane>
|
|
|
+ <!-- TODO: 商品定向TextArea -->
|
|
|
+ <el-tab-pane label="输入" name="third">待完成</el-tab-pane>
|
|
|
+ </el-tabs>
|
|
|
+ </el-tab-pane>
|
|
|
+ </el-tabs>
|
|
|
+ </div>
|
|
|
+ <div style="width: 50%">
|
|
|
+ <el-card class="box-card" shadow="never" style="border: none">
|
|
|
+ <template #header>
|
|
|
+ <div class="card-header">
|
|
|
+ <span style="font-weight: 550; font-size: 15px; color: #1f2128">已添加: {{ productOrientationTableData.length }}</span>
|
|
|
+ <el-button class="button" type="danger" text bg @click="delAllCna">全部删除</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div class="card-body">
|
|
|
+ <el-table
|
|
|
+ height="460"
|
|
|
+ :data="productOrientationTableData"
|
|
|
+ style="width: 100%"
|
|
|
+ :header-row-style="changeKeyWordsTableHeader"
|
|
|
+ :header-cell-style="headerCellStyle">
|
|
|
+ <el-table-column prop="cna" label="分类 & 商品" width="300">
|
|
|
+ <template #default="scope">
|
|
|
+ <div v-if="scope.row.cna || scope.row.classification">
|
|
|
+ 分类: <span style="color: #000000">{{ scope.row.cna ? scope.row.cna : scope.row.classification }}</span>
|
|
|
+ </div>
|
|
|
+ <div v-if="scope.row.asin">
|
|
|
+ {{ scope.row.asin ? scope.row.asin : '--' }}
|
|
|
+ </div>
|
|
|
+ <div v-if="scope.row.brand">
|
|
|
+ 品牌: <span style="color: #000000">{{ scope.row.brand }}</span>
|
|
|
+ </div>
|
|
|
+ <div v-if="scope.row.low_price || scope.row.high_price">
|
|
|
+ 品牌价格:
|
|
|
+ <span style="color: #000000">
|
|
|
+ {{ scope.row.low_price ? '$' + scope.row.low_price : '--' }} -
|
|
|
+ {{ scope.row.high_price ? '$' + scope.row.high_price : '--' }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div v-if="scope.row.low_rating || scope.row.high_rating">
|
|
|
+ 评分: <span style="color: #000000">{{ scope.row.low_rating }} - {{ scope.row.high_rating }}</span>
|
|
|
+ </div>
|
|
|
+ <div v-if="scope.row.deliveryText">
|
|
|
+ 配送: <span style="color: #000000">{{ scope.row.deliveryText }}</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="type" label="类型">
|
|
|
+ <template #default="scope">
|
|
|
+ {{ scope.row.productTypeText ? scope.row.productTypeText : '--' }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="bid" label="竞价">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-input-number v-model="scope.row.bid" :min="0.02" :max="1000000" :controls="false" size="small" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="operate" label="操作" width="60" align="right">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-button text size="small" @click="delCna(scope.$index)">删除</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ <div style="display: flex; justify-content: space-around; margin-top: -8px">
|
|
|
+ <el-button type="primary" plain @click="productTagetSave">保存</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { CSSProperties, Ref, inject, reactive, ref } from 'vue'
|
|
|
+import { useShopInfo } from '/@/stores/shopInfo'
|
|
|
+import { storeToRefs } from 'pinia'
|
|
|
+import { ElMessage } from 'element-plus'
|
|
|
+import { request } from '/@/utils/service'
|
|
|
+
|
|
|
+
|
|
|
+let adsTableData = ref([])
|
|
|
+let selections = [] // 添加选中的项
|
|
|
+let addedSels = [] // 删除选中的项
|
|
|
+
|
|
|
+const singleGoodsTabs = ref('first')
|
|
|
+const respCampaignId = inject<Ref>('respCampaignId')
|
|
|
+const respAdGroupId = inject<Ref>('respAdGroupId')
|
|
|
+const shopInfo = useShopInfo()
|
|
|
+const { profile } = storeToRefs(shopInfo)
|
|
|
+const categoryTabs = ref('first')
|
|
|
+const categoryBiddingType = ref('customBid')
|
|
|
+const categoryBiddingTypeOptions = [
|
|
|
+ {
|
|
|
+ value: 'defaultBid',
|
|
|
+ label: '默认竞价',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: 'customBid',
|
|
|
+ label: '自定义竞价',
|
|
|
+ },
|
|
|
+]
|
|
|
+const singleGoodsBidSelect = ref('customBid')
|
|
|
+const singleGoodsBidTypeOptions = [
|
|
|
+ {
|
|
|
+ value: 'defaultBid',
|
|
|
+ label: '默认竞价',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ value: 'customBid',
|
|
|
+ label: '自定义竞价',
|
|
|
+ },
|
|
|
+]
|
|
|
+const singleGoodsBidInput = ref('0.75')
|
|
|
+const expand = ref(true)
|
|
|
+const accurate = ref(false)
|
|
|
+const proposalTableData = ref([])
|
|
|
+const searchClassifyTableData = ref([])
|
|
|
+const productOrientationLoading = ref(false)
|
|
|
+const dialogSelectLoading = ref(false)
|
|
|
+const defaultProps = {
|
|
|
+ children: 'ch',
|
|
|
+ label: 'cna',
|
|
|
+}
|
|
|
+const countLoadig = ref(false)
|
|
|
+const visible = ref(false)
|
|
|
+let dialogTitle = ref('')
|
|
|
+let categoryId = ref('')
|
|
|
+
|
|
|
+const dialogForm: any = reactive({
|
|
|
+ prices: {
|
|
|
+ lowest: undefined,
|
|
|
+ highest: undefined,
|
|
|
+ },
|
|
|
+ starRating: [0, 5],
|
|
|
+ dialogselectValue: [],
|
|
|
+ delivery: 'all',
|
|
|
+ isCount: false,
|
|
|
+})
|
|
|
+const dialogFormRef = ref()
|
|
|
+const dialogRules = reactive({
|
|
|
+ prices: [{ validator: validatePrices, trigger: 'blur' }],
|
|
|
+})
|
|
|
+
|
|
|
+interface Mark {
|
|
|
+ style: CSSProperties
|
|
|
+ label: string
|
|
|
+}
|
|
|
+type Marks = Record<number, Mark | string>
|
|
|
+const marks = reactive<Marks>({
|
|
|
+ 0: '0',
|
|
|
+ 1: '1',
|
|
|
+ 2: '2',
|
|
|
+ 3: '3',
|
|
|
+ 4: '4',
|
|
|
+ 5: '5',
|
|
|
+})
|
|
|
+let commodityCount = ref([])
|
|
|
+let currentDialogIndex = ref(0)
|
|
|
+let productOrientationTableData = ref([])
|
|
|
+
|
|
|
+async function validatePrices(rule, value) {
|
|
|
+ if (value.highest !== '' && value.lowest !== '' && value.highest <= value.lowest) {
|
|
|
+ return Promise.reject('最高价格必须大于最低价格')
|
|
|
+ }
|
|
|
+ return Promise.resolve()
|
|
|
+}
|
|
|
+
|
|
|
+async function setProductOrientationData() {
|
|
|
+ try {
|
|
|
+ const resp = await request({
|
|
|
+ url: '/api/ad_manage/targetable/categories/',
|
|
|
+ method: 'GET',
|
|
|
+ params: {
|
|
|
+ profile_id: profile.value.profile_id,
|
|
|
+ },
|
|
|
+ })
|
|
|
+ searchClassifyTableData.value = resp.data
|
|
|
+ productOrientationLoading.value = false
|
|
|
+ } catch (error) {
|
|
|
+ console.error('请求失败:', error)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function setDialogOption() {
|
|
|
+ try {
|
|
|
+ const resp = await request({
|
|
|
+ url: '/api/ad_manage/categories/brands/',
|
|
|
+ method: 'GET',
|
|
|
+ params: {
|
|
|
+ profile_id: profile.value.profile_id,
|
|
|
+ category_id: categoryId.value,
|
|
|
+ },
|
|
|
+ })
|
|
|
+ const options = resp.data
|
|
|
+ dialogForm.dialogOptions = options.brands.map((brand) => {
|
|
|
+ return {
|
|
|
+ label: brand.name,
|
|
|
+ value: brand.id,
|
|
|
+ }
|
|
|
+ })
|
|
|
+ dialogSelectLoading.value = false
|
|
|
+ } catch (error) {
|
|
|
+ console.error('请求失败:', error)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function getCount(instanceId) {
|
|
|
+ try {
|
|
|
+ const resp = await request({
|
|
|
+ url: '/api/ad_manage/products/count/',
|
|
|
+ method: 'POST',
|
|
|
+ data: {
|
|
|
+ profile_id: profile.value.profile_id,
|
|
|
+ category_id: categoryId.value,
|
|
|
+ },
|
|
|
+ })
|
|
|
+ if (instanceId === currentDialogIndex.value) {
|
|
|
+ commodityCount.value = resp.data.AsinCounts
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('请求失败:', error)
|
|
|
+ } finally {
|
|
|
+ if (instanceId === currentDialogIndex.value) {
|
|
|
+ countLoadig.value = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function dialogClose() {
|
|
|
+ currentDialogIndex.value++
|
|
|
+ resetDialogForm()
|
|
|
+ dialogForm.isCount = false
|
|
|
+ commodityCount.value = []
|
|
|
+ countLoadig.value = false
|
|
|
+}
|
|
|
+
|
|
|
+function resetDialogForm() {
|
|
|
+ dialogForm.prices.lowest = undefined
|
|
|
+ dialogForm.prices.highest = undefined
|
|
|
+ dialogForm.starRating = [0, 5]
|
|
|
+ dialogForm.dialogselectValue = []
|
|
|
+ dialogForm.delivery = 'all'
|
|
|
+ dialogForm.isCount = false
|
|
|
+}
|
|
|
+
|
|
|
+function isCountChanged() {
|
|
|
+ if (dialogForm.isCount) {
|
|
|
+ const instanceId = currentDialogIndex.value
|
|
|
+ countLoadig.value = true
|
|
|
+ getCount(instanceId)
|
|
|
+ } else {
|
|
|
+ countLoadig.value = false
|
|
|
+ commodityCount.value = []
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function delCna(index) {
|
|
|
+ productOrientationTableData.value.splice(index, 1)
|
|
|
+}
|
|
|
+
|
|
|
+function delAllCna() {
|
|
|
+ productOrientationTableData.value = []
|
|
|
+}
|
|
|
+
|
|
|
+function singleGoodsBidSelectChanged() {
|
|
|
+ if (singleGoodsBidSelect.value === 'defaultBid' || categoryBiddingType.value === 'defaultBid') {
|
|
|
+ singleGoodsBidInput.value = ''
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+let singleGoodsSearchInp = ref('')
|
|
|
+let searchTableData = ref([])
|
|
|
+function setSearchTableData(asin = '', sku = '') {
|
|
|
+ return request({
|
|
|
+ url: '/api/sellers/listings/our/',
|
|
|
+ method: 'GET',
|
|
|
+ params: {
|
|
|
+ profile_id: profile.value.profile_id,
|
|
|
+ asin,
|
|
|
+ sku,
|
|
|
+ },
|
|
|
+ })
|
|
|
+ .then((resp) => {
|
|
|
+ searchTableData.value = resp.data
|
|
|
+ productOrientationLoading.value = false
|
|
|
+ })
|
|
|
+ .catch((error) => {
|
|
|
+ console.error('Error fetching data:', error)
|
|
|
+ productOrientationLoading.value = false
|
|
|
+ })
|
|
|
+}
|
|
|
+function singleGoodsSearchChaneged() {
|
|
|
+ productOrientationLoading.value = true
|
|
|
+ setSearchTableData()
|
|
|
+}
|
|
|
+function addSingleSearch(scope) {
|
|
|
+ console.log('🚀 ~ addSingleSearch ~ scope-->>', scope)
|
|
|
+
|
|
|
+ const typesToAdd = []
|
|
|
+ if (expand.value) {
|
|
|
+ typesToAdd.push('ASIN_EXPANDED_FROM')
|
|
|
+ }
|
|
|
+ if (accurate.value) {
|
|
|
+ typesToAdd.push('ASIN_SAME_AS')
|
|
|
+ }
|
|
|
+ const productTypeMap = {
|
|
|
+ ASIN_EXPANDED_FROM: '扩展',
|
|
|
+ ASIN_SAME_AS: '精确',
|
|
|
+ }
|
|
|
+
|
|
|
+ typesToAdd.forEach((productType) => {
|
|
|
+ const isAlreadyAdded = productOrientationTableData.value.some((item) => item.sku === scope.row.sku && item.productType === productType)
|
|
|
+
|
|
|
+ if (!isAlreadyAdded) {
|
|
|
+ const newData = {
|
|
|
+ type: 'p',
|
|
|
+ asin: scope.row.asin,
|
|
|
+ sku: scope.row.sku,
|
|
|
+ productType: productType,
|
|
|
+ productTypeText: productTypeMap[productType],
|
|
|
+ }
|
|
|
+ productOrientationTableData.value.push(newData)
|
|
|
+ } else {
|
|
|
+ console.log(`${productType} item is already added.`)
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+let selectedLabels = ref([]) // 选中的label数组
|
|
|
+function dialogSelectChange(event) {
|
|
|
+ console.log('🚀 ~ dialogSelectChange ~ event-->>', event)
|
|
|
+
|
|
|
+ // 使用 map 来转换每个选中项的 value 为其对应的 label
|
|
|
+ selectedLabels.value = event.map((selectedValue) => {
|
|
|
+ const selectedOption = dialogForm.dialogOptions.find((option) => option.value === selectedValue)
|
|
|
+ return selectedOption ? selectedOption.label : ''
|
|
|
+ })
|
|
|
+
|
|
|
+ console.log('🚀 ~ dialogSelectChange ~ selectedLabels-->>', selectedLabels.value)
|
|
|
+}
|
|
|
+
|
|
|
+let refineItem = ref([])
|
|
|
+// 细化按钮功能
|
|
|
+function refine(data) {
|
|
|
+ console.log('🚀 ~ refine ~ data-->>', data)
|
|
|
+ commodityCount.value = []
|
|
|
+ dialogTitle.value = data.cna
|
|
|
+ categoryId.value = data.cid
|
|
|
+ refineItem.value.push(data)
|
|
|
+ visible.value = true
|
|
|
+ dialogSelectLoading.value = true
|
|
|
+ setDialogOption()
|
|
|
+}
|
|
|
+// 弹框提交功能
|
|
|
+function dialogFormSubmit() {
|
|
|
+ dialogFormRef.value.validate((valid) => {
|
|
|
+ if (valid) {
|
|
|
+ console.log('表单提交')
|
|
|
+ visible.value = false
|
|
|
+ const dialogClassification = dialogTitle.value
|
|
|
+ const dialogPrices_low = dialogForm.prices.lowest
|
|
|
+ const dialogPrices_high = dialogForm.prices.highest
|
|
|
+ const dialogStartRating = dialogForm.starRating
|
|
|
+ const ratingLow = dialogStartRating[0]
|
|
|
+ const ratingHigh = dialogStartRating[1]
|
|
|
+ const dialogDelivery = dialogForm.delivery
|
|
|
+ console.log('🚀 ~ dialogFormRef.value.validate ~ dialogDelivery-->>', dialogDelivery)
|
|
|
+ const deliveryMap = {
|
|
|
+ all: '所有',
|
|
|
+ eligible: '具备Prime资格',
|
|
|
+ diseligible: '不具备Prime资格',
|
|
|
+ }
|
|
|
+
|
|
|
+ selectedLabels.value.forEach((brandLabel) => {
|
|
|
+ // 查找与当前 brandLabel 相对应的选项
|
|
|
+ const selectedOption = dialogForm.dialogOptions.find((option) => option.label === brandLabel)
|
|
|
+ // 获取对应的 brandId,如果没有找到则默认为空
|
|
|
+ const brandId = selectedOption ? selectedOption.value : ''
|
|
|
+ const refineObj = {
|
|
|
+ type: 'c',
|
|
|
+ classification: dialogClassification,
|
|
|
+ classificationId: categoryId.value,
|
|
|
+ brand: brandLabel,
|
|
|
+ brandId: brandId, // 使用找到的 brandId
|
|
|
+ low_price: dialogPrices_low,
|
|
|
+ high_price: dialogPrices_high,
|
|
|
+ low_rating: ratingLow,
|
|
|
+ high_rating: ratingHigh,
|
|
|
+ delivery: dialogDelivery,
|
|
|
+ deliveryText: deliveryMap[dialogDelivery],
|
|
|
+ }
|
|
|
+ console.log('🚀 ~ dialogFormRef.value.validate ~ refineObj-->>', refineObj)
|
|
|
+ productOrientationTableData.value.push(refineObj)
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ console.log('验证失败')
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 定向按钮功能
|
|
|
+function orientate(node, data) {
|
|
|
+ console.log('🚀 ~ orientate ~ data-->>', data)
|
|
|
+ const exists = productOrientationTableData.value.some((item) => item.cid === data.cid)
|
|
|
+
|
|
|
+ if (!exists) {
|
|
|
+ const newData = {
|
|
|
+ type: 'c',
|
|
|
+ classification: data.cna,
|
|
|
+ classificationId: data.cid,
|
|
|
+ }
|
|
|
+ productOrientationTableData.value.push(newData)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+let productTargetBidList = ref([])
|
|
|
+async function productTagetSave() {
|
|
|
+ console.log('tableData', productOrientationTableData.value)
|
|
|
+ // 检查是否存在 bid 为空的行
|
|
|
+ const hasEmptyBid = productOrientationTableData.value.some((row) => row.bid == null || row.bid === '')
|
|
|
+ // 直接返回,不继续执行
|
|
|
+ if (hasEmptyBid) {
|
|
|
+ console.log('存在空的 bid,不发送请求')
|
|
|
+ ElMessage.error('存在空的 bid,无法创建商品!')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ productOrientationTableData.value.forEach((row) => {
|
|
|
+ productTargetBidList.value.push(row.bid)
|
|
|
+ })
|
|
|
+ console.log('productTargetBidList', productTargetBidList.value)
|
|
|
+ productOrientationLoading.value = true
|
|
|
+ try {
|
|
|
+ const requestData = {
|
|
|
+ profile_id: profile.value.profile_id,
|
|
|
+ adGroupId: respAdGroupId.value,
|
|
|
+ campaignId: respCampaignId.value,
|
|
|
+ expressionList: productOrientationTableData.value,
|
|
|
+ state: 'PAUSED',
|
|
|
+ }
|
|
|
+ const filteredRequestData = Object.fromEntries(Object.entries(requestData).filter(([_, v]) => v != null))
|
|
|
+ const resp = await request({
|
|
|
+ url: '/api/ad_manage/sptargets/manual/create/',
|
|
|
+ method: 'POST',
|
|
|
+ data: filteredRequestData,
|
|
|
+ })
|
|
|
+
|
|
|
+ console.log('🚀 ~ createTargetGroup ~ resp-->>', resp)
|
|
|
+ productOrientationLoading.value = false
|
|
|
+
|
|
|
+ if (respAdGroupId.value) {
|
|
|
+ ElMessage({
|
|
|
+ message: '商品创建成功',
|
|
|
+ type: 'success',
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ ElMessage.error('商品创建失败!')
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('请求失败:', error)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清空表格和 bid 列表
|
|
|
+ productOrientationTableData.value = []
|
|
|
+ productTargetBidList.value = []
|
|
|
+}
|
|
|
+// 点击表格选项触发事件
|
|
|
+function handleSelectionChange(selection) {
|
|
|
+ selections = selection
|
|
|
+}
|
|
|
+// 获取addedTable中已选中的项
|
|
|
+function handleAddedGoodsChange(selection) {
|
|
|
+ addedSels = selection
|
|
|
+}
|
|
|
+// 添加已选中的项
|
|
|
+function handleGoodsAdd() {
|
|
|
+ // 过滤掉已经存在于addedData.value中的项
|
|
|
+ const newSelections = selections.filter(
|
|
|
+ (sel) => !adsTableData.value.some((added) => added.sku === sel.sku) // 使用sku作为唯一标识
|
|
|
+ )
|
|
|
+ // 如果有新的不重复项,加入到addedData.value中
|
|
|
+ if (newSelections.length > 0) {
|
|
|
+ adsTableData.value.push(...newSelections)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function addSingleGoods(scope) {
|
|
|
+ // console.log('scope', scope.row)
|
|
|
+ const isAlreadyAdded = adsTableData.value.some((item) => item.sku === scope.row.sku)
|
|
|
+ if (!isAlreadyAdded) {
|
|
|
+ adsTableData.value.push(scope.row)
|
|
|
+ } else {
|
|
|
+ console.log('Item is already added.')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const headerCellStyle = (args) => {
|
|
|
+ if (args.rowIndex === 0) {
|
|
|
+ return {
|
|
|
+ backgroundColor: 'rgba(245, 245, 245, 0.9)',
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function changeKeyWordsTableHeader(args) {
|
|
|
+ if (args.rowIndex === 0) {
|
|
|
+ return {
|
|
|
+ color: '#505968',
|
|
|
+ backgroundColor: 'rgba(245, 245, 245, 0.9)',
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+::v-deep(.el-form--default.el-form--label-top .el-form-item .el-form-item__label) {
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+.column-item .el-radio-group {
|
|
|
+ display: inline-flex;
|
|
|
+ font-size: 0;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: flex-start;
|
|
|
+}
|
|
|
+.radio-description {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #666;
|
|
|
+ margin-top: -18px;
|
|
|
+ margin-left: 22px;
|
|
|
+}
|
|
|
+.radio-description-2 {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #666;
|
|
|
+ margin-top: -10px;
|
|
|
+}
|
|
|
+.column-margin-bottom label.el-radio.is-bordered {
|
|
|
+ margin-bottom: 10px;
|
|
|
+ padding: 35px;
|
|
|
+}
|
|
|
+::v-deep(.column-margin-bottom label.el-radio.is-bordered span.el-radio__inner) {
|
|
|
+ margin-top: -18px;
|
|
|
+ margin-left: -15px;
|
|
|
+}
|
|
|
+.gap-items {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-start;
|
|
|
+ width: 100%;
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+.gap-item {
|
|
|
+ width: 200px;
|
|
|
+ margin-left: 30px;
|
|
|
+ color: #0b0d0d;
|
|
|
+}
|
|
|
+.demo-tabs > .el-tabs__content {
|
|
|
+ padding: 52px;
|
|
|
+ color: #6b778c;
|
|
|
+ font-size: 32px;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+/* 广告组商品Tab栏 */
|
|
|
+::v-deep(.el-tabs__nav-scroll) {
|
|
|
+ overflow: hidden;
|
|
|
+ margin-left: 20px;
|
|
|
+}
|
|
|
+::v-deep(.el-tabs__nav-wrap::after) {
|
|
|
+ height: 2px !important;
|
|
|
+}
|
|
|
+::v-deep(.el-table__inner-wrapper::before) {
|
|
|
+ background-color: white;
|
|
|
+}
|
|
|
+// 表格内容边距
|
|
|
+div {
|
|
|
+ & #pane-first,
|
|
|
+ & #pane-second {
|
|
|
+ margin: 10px;
|
|
|
+ }
|
|
|
+}
|
|
|
+// 输入底部样式
|
|
|
+::v-deep(.card-box .el-card__body) {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 12px;
|
|
|
+}
|
|
|
+.card-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.box-card {
|
|
|
+ width: 100%;
|
|
|
+ // margin: 10px 0 10px 10px;
|
|
|
+ margin-right: 10px;
|
|
|
+}
|
|
|
+.single-line {
|
|
|
+ color: rgb(30, 33, 41);
|
|
|
+ overflow: hidden;
|
|
|
+ display: -webkit-box;
|
|
|
+ -webkit-box-orient: vertical;
|
|
|
+ -webkit-line-clamp: 1;
|
|
|
+ white-space: pre-wrap;
|
|
|
+ word-break: break-word;
|
|
|
+}
|
|
|
+.data-color {
|
|
|
+ color: rgb(30, 33, 41);
|
|
|
+}
|
|
|
+.img-box {
|
|
|
+ width: 60px;
|
|
|
+ height: 60px;
|
|
|
+ margin-top: 5px;
|
|
|
+ border: 1px solid rgb(194, 199, 207);
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+.target-group-item {
|
|
|
+ margin-top: 15px;
|
|
|
+}
|
|
|
+.suggested-bid-item {
|
|
|
+ margin-left: 230px;
|
|
|
+ margin-right: 60px;
|
|
|
+}
|
|
|
+.bid-input {
|
|
|
+ width: 200px;
|
|
|
+ margin-left: 15px;
|
|
|
+}
|
|
|
+
|
|
|
+::v-deep(.goods-orientation-tabs .el-tabs__nav-scroll) {
|
|
|
+ margin-left: -20px !important;
|
|
|
+}
|
|
|
+::v-deep(.category-tabs .el-tabs__nav) {
|
|
|
+ margin-left: 20px;
|
|
|
+}
|
|
|
+::v-deep(.goods-orientation-tabs #tab-1) {
|
|
|
+ /* 商品定向Tab栏 */
|
|
|
+ border-right: 0;
|
|
|
+}
|
|
|
+.custom-tree-node {
|
|
|
+ /* el-tree自定义样式 */
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ font-size: 14px;
|
|
|
+ padding-right: 8px;
|
|
|
+}
|
|
|
+.dialog-head {
|
|
|
+ /* 弹窗样式 */
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ justify-content: space-between;
|
|
|
+}
|
|
|
+</style>
|
|
|
+
|