Bläddra i källkod

Merge branch 'cheney' into dev

WanGxC 6 månader sedan
förälder
incheckning
31fb69311a

+ 18 - 1
src/views/product-manage/Columns.ts

@@ -255,4 +255,21 @@ export const CompetitorMonitorColumns = [
   }
 ];
 
-
+export const HistoricalColumns = [
+  {
+    field: 'create_datetime', title: '时 间', align: 'center', fixed: 'left',
+    slots: { default: 'create_datetime' }
+  },
+  {
+    field: 'field', title: '变更项', align: 'center',
+    slots: { default: 'field' }
+  },
+  {
+    field: 'old_val', title: '原 值', align: 'center',
+    slots: { default: 'old_val' }
+  },
+  {
+    field: 'new_val', title: '现 值', align: 'center',
+    slots: { default: 'new_val' }
+  },
+] 

+ 20 - 7
src/views/product-manage/comment-detail/component/AverageMonthly.vue

@@ -65,9 +65,8 @@ function updateChart() {
         type: 'value',
         name: '总量',
         nameTextStyle: {
-          fontWeight: 'bold',
           fontSize: 14,
-          color: '#333'
+          color: '#3480CE',
         },
         splitLine: {
           show: true
@@ -75,7 +74,7 @@ function updateChart() {
         axisLine: {
           show: true,
           lineStyle: {
-            color: '#333',
+            color: '#3480CE',
             width: 1
           }
         }
@@ -85,9 +84,8 @@ function updateChart() {
         type: 'value',
         name: '平均分',
         nameTextStyle: {
-          fontWeight: 'bold',
           fontSize: 14,
-          color: '#333'
+          color: '#F19A38',
         },
         splitLine: {
           show: false
@@ -95,7 +93,7 @@ function updateChart() {
         axisLine: {
           show: true,
           lineStyle: {
-            color: '#333',
+            color: '#F19A38',
             width: 1
           }
         }
@@ -104,6 +102,7 @@ function updateChart() {
     series: [
       {
         yAxisIndex: 0,
+        color: '#3480CE',
         name: '总量',
         type: 'bar',
         barWidth: '18px',
@@ -113,8 +112,22 @@ function updateChart() {
       },
       {
         yAxisIndex: 1,
+        color: '#F19A38',
         name: '平均分',
-        type: 'line'
+        type: 'line',
+        areaStyle: {
+          // 设置渐变颜色
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            {
+              offset: 0,
+              color: 'rgba(241,154,56, 0.1)'
+            },
+            {
+              offset: 1,
+              color: 'rgba(241,154,56, 0)'
+            }
+          ])
+        }
       }
     ]
   };

+ 59 - 0
src/views/product-manage/comment-detail/component/TitleCard.vue

@@ -0,0 +1,59 @@
+<script lang="ts" setup>
+/**
+ * @Name: TitleCard.vue
+ * @Description: 评论详情-标题卡片
+ * @Author: Cheney
+ */
+
+import { handleCopy } from '/@/utils/useCopyText';
+import VerticalDivider from '/@/components/VerticalDivider/index.vue';
+import { DocumentCopy, Picture as IconPicture } from '@element-plus/icons-vue';
+
+
+const props = defineProps({
+  img: String,
+  url: String,
+  title: String,
+  asin: String
+});
+const { img, url, title, asin } = props;
+</script>
+
+<template>
+  <el-card body-class="flex justify-between items-center gap-5" class="border-none sticky top-5 z-10">
+    <div class="flex">
+      <el-image :src="`https://d1ge0kk1l5kms0.cloudfront.net/images/I/${img}.jpg`" class="mr-3"
+                fit="fill"
+                lazy style="min-width: 78px; height: 78px;">
+        <template #error>
+          <div class="flex justify-center items-center h-full w-full text-2xl"
+               style="background:var(--el-fill-color-light)">
+            <el-icon>
+              <icon-picture />
+            </el-icon>
+          </div>
+        </template>
+      </el-image>
+      <div>
+        <el-link :href="url"
+                 :underline="false"
+                 style="font-size: 18px;"
+                 type="primary">
+          <span class="line-clamp-2 text-ellipsis whitespace-normal">{{ title || '--' }}</span>
+        </el-link>
+        <div class="flex items-center">
+          <div class="font-semibold italic">{{ asin }}</div>
+          <VerticalDivider />
+          <el-icon class="ml-2 cursor-pointer" @click="handleCopy(asin)">
+            <DocumentCopy />
+          </el-icon>
+        </div>
+      </div>
+    </div>
+    <!--<el-button :icon="Back" plain round type="info" @click="handleBack">返 回</el-button>-->
+  </el-card>
+</template>
+
+<style scoped>
+
+</style>

+ 2 - 37
src/views/product-manage/comment-detail/index.vue

@@ -5,12 +5,8 @@
  * @Author: Cheney
  */
 
-import { DocumentCopy, Picture as IconPicture } from '@element-plus/icons-vue';
-import NegativeLabel from '/@/views/product-manage/comment-detail/component/NegativeLabel.vue';
-import NegativeClassification from '/@/views/product-manage/comment-detail/component/NegativeClassification.vue';
 import AverageMonthly from '/@/views/product-manage/comment-detail/component/AverageMonthly.vue';
-import VerticalDivider from '/@/components/VerticalDivider/index.vue';
-import { handleCopy } from '/@/utils/useCopyText';
+import TitleCard from './component/TitleCard.vue';
 
 
 const isShowComment = defineModel({ default: false });
@@ -37,38 +33,7 @@ const { rowData } = props;
       <div class="sticky top-0" style="background-color:#F3F4FB; min-height: 20px; z-index: 2"></div>
       <div class="px-5">
         <!-- Title Card -->
-        <el-card body-class="flex justify-between items-center gap-5" class="border-none sticky top-5 z-10">
-          <div class="flex">
-            <el-image :src="`https://d1ge0kk1l5kms0.cloudfront.net/images/I/${rowData.img}.jpg`" class="mr-3"
-                      fit="fill"
-                      lazy style="min-width: 78px; height: 78px;">
-              <template #error>
-                <div class="flex justify-center items-center h-full w-full text-2xl"
-                     style="background:var(--el-fill-color-light)">
-                  <el-icon>
-                    <icon-picture />
-                  </el-icon>
-                </div>
-              </template>
-            </el-image>
-            <div>
-              <el-link :href="rowData.url"
-                       :underline="false"
-                       style="font-size: 18px;"
-                       type="primary">
-                <span class="line-clamp-2 text-ellipsis whitespace-normal">{{ rowData.title || '--' }}</span>
-              </el-link>
-              <div class="flex items-center">
-                <div class="font-semibold italic">{{ rowData.asin }}</div>
-                <VerticalDivider />
-                <el-icon class="ml-2 cursor-pointer" @click="handleCopy(rowData.asin)">
-                  <DocumentCopy />
-                </el-icon>
-              </div>
-            </div>
-          </div>
-          <!--<el-button :icon="Back" plain round type="info" @click="handleBack">返 回</el-button>-->
-        </el-card>
+        <TitleCard :asin="rowData.asin" :img="rowData.img" :title="rowData.title" :url="rowData.url" />
         <!-- Chart -->
         <!--<el-row :gutter="20" class="mt-5" style="z-index: 1">-->
         <!--  <el-col :span="12">-->

+ 9 - 2
src/views/product-manage/competitor-monitor/component/DataTableSlot.vue

@@ -57,7 +57,7 @@ function starsPercent(goods: any) {
 }
 
 function showDetail(detail: any) {
-  emit(`${detail}`, row);
+  emit(`${ detail }`, row);
 }
 </script>
 
@@ -178,7 +178,7 @@ function showDetail(detail: any) {
           </PermissionButton>
         </el-tooltip>
         <el-tooltip :enterable="false" :show-arrow="false" content="历史详情" hide-after="0"
-                    placement="top" popper-class="custom-btn-tooltip">
+                    placement="top" popper-class="custom-btn-tooltip-2">
           <PermissionButton :color="'#6466F1'" circle plain type="success" @click="showDetail('show-history')">
             <el-icon>
               <Timer />
@@ -238,4 +238,11 @@ function showDetail(detail: any) {
   border: 1px solid #67C23A !important;
   font-size: 14px;
 }
+
+.custom-btn-tooltip-2 {
+  background-color: #F0F0FE !important;
+  color: #606266 !important;
+  border: 1px solid #6466F1 !important;
+  font-size: 14px;
+}
 </style>

+ 1 - 2
src/views/product-manage/component/ProductInfo.vue

@@ -61,9 +61,8 @@ const props = defineProps({
           <span style="color: #1d2129;">
             {{ item.asin || '--' }}
           </span>
-
         </p>
-        <el-tooltip :content="item.sku" effect="light" show-after="300">
+        <el-tooltip :content="item.sku" effect="light" show-after="300" :disabled="!item.sku">
           <p class="m-0 p-0 line-clamp-1 text-ellipsis">
             SKU:
             <span style="color: #1d2129;">

+ 18 - 0
src/views/product-manage/historical-detail/api.ts

@@ -0,0 +1,18 @@
+import { request } from '/@/utils/service';
+
+
+export function getChartData(query: any) {
+  return request({
+    url: '/api/choice/goods/price-chart/',
+    method: 'GET',
+    params: query
+  });
+}
+
+export function getTableData(query: any) {
+  return request({
+    url: '/api/choice/goods_changes/',
+    method: 'GET',
+    params: query
+  });
+}

+ 70 - 0
src/views/product-manage/historical-detail/component/ChangeValue.vue

@@ -0,0 +1,70 @@
+<script lang="ts" setup>
+import XEUtils from 'xe-utils';
+
+
+const props = defineProps<{
+  field: string;
+  value: string;
+}>();
+
+const badgeMap: { [key: number]: string } = {
+  0: '',
+  1: 'LD',
+  2: '7DD',
+  3: 'DOTD/TD'
+};
+
+const loadJsonString = (val: string) => XEUtils.toStringJSON(val);
+
+const isArray = computed(() => XEUtils.includes([ 'bullet_point', 'variants' ], props.field));
+
+const formatedVal = computed(() => {
+  if (props.field === 'discount') {
+    return `${ props.value }%`;
+  } else if (props.field === 'coupon' || props.field === 'price') {
+    return `$${ props.value }`;
+  } else if (props.field === 'badge') {
+    const badgeValue = Number(props.value);
+    return badgeMap[badgeValue] || ''; // 添加默认值以处理无效的 badge 值
+  }
+  return props.value;
+});
+</script>
+
+<template>
+  <div>
+    <template v-if="props.field==='imgs'">
+      <span v-for="(img, index) in loadJsonString(props.value)" :key="index">
+        <el-tooltip effect="light" placement="bottom-start">
+          <el-image
+              :src="`https://m.media-amazon.com/images/I/${img}.jpg`"
+              fit="fill"
+              lazy
+              style="width: 40px; margin-right: 5px;"
+          />
+          <template #content>
+            <el-image
+                :src="`https://m.media-amazon.com/images/I/${img}.jpg`"
+                lazy
+                style="width: 250px"
+            />
+          </template>
+        </el-tooltip>
+      </span>
+    </template>
+    <template v-else-if="isArray">
+      <ul class="in-table-ul">
+        <li v-for="(val, index) in loadJsonString(props.value)" :key="index">
+          <span>{{ val }}</span>
+        </li>
+      </ul>
+    </template>
+    <span v-else>{{ formatedVal }}</span>
+  </div>
+</template>
+
+<style scoped>
+.in-table-ul {
+  padding-left: 20px;
+}
+</style>

+ 153 - 0
src/views/product-manage/historical-detail/component/DataTable.vue

@@ -0,0 +1,153 @@
+<script lang="ts" setup>
+/**
+ * @Name: DataTable.vue
+ * @Description: 历史详情Table
+ * @Author: Cheney
+ */
+
+import { Refresh } from '@element-plus/icons-vue';
+import * as api from '../api';
+import { HistoricalColumns } from '/@/views/product-manage/Columns';
+import { usePagination } from '/@/utils/usePagination';
+import { useTableData } from '/@/utils/useTableData';
+import ChangeValue from './ChangeValue.vue';
+
+
+const props = defineProps({
+  asin: String,
+  country: String
+});
+const { asin, country } = props;
+
+const { tableOptions, handlePageChange } = usePagination(fetchList);
+
+const gridRef = ref();
+const gridOptions: any = reactive({
+  id: 'historical-detail-table',
+  keepSource: true,
+  size: 'small',
+  border: false,
+  round: true,
+  stripe: true,
+  currentRowHighLight: true,
+  height: 800,
+  customConfig: {
+    storage: true
+  },
+  toolbarConfig: {
+    size: 'large',
+    custom: true,
+    slots: {
+      tools: 'toolbar_tools'
+    }
+  },
+  rowConfig: {
+    isHover: true
+  },
+  columnConfig: {
+    resizable: true
+  },
+  pagerConfig: {
+    total: tableOptions.value.total,
+    page: tableOptions.value.page,
+    limit: tableOptions.value.limit
+  },
+  loading: false,
+  loadingConfig: {
+    icon: 'vxe-icon-indicator roll',
+    text: '正在拼命加载中...'
+  },
+  columns: '',
+  data: ''
+});
+
+const tagMap: any = {
+  price: { label: '价格', color: 'primary' },
+  discount: { label: '折扣', color: 'primary' },
+  coupon: { label: '优惠券', color: 'primary' },
+  imgs: { label: '图片', color: 'warning' },
+  title: { label: '标题', color: 'warning' },
+  bullet_point: { label: '五点信息', color: 'warning' },
+  all_score: { label: '综合评分', color: 'warning' },
+  badge: { label: '限时处理', color: 'danger' },
+  variants: { label: '变体', color: 'danger' },
+  score: { label: '子asin评分', color: 'success' },
+  reviews: { label: '子asin评论数', color: 'success' }
+};
+
+onBeforeMount(() => {
+  fetchList();
+});
+
+async function fetchList() {
+  gridOptions.data = [];
+  gridOptions.columns = [];
+
+  const query = {
+    asin,
+    country_code: country
+  };
+
+  await useTableData(api.getTableData, query, gridOptions);
+  await gridRef.value.loadColumn(HistoricalColumns);
+  gridOptions.showHeader = Boolean(gridOptions.data?.length);
+}
+
+function handleRefresh() {
+  fetchList();
+}
+</script>
+
+<template>
+  <el-card class="border-none my-5">
+    <vxe-grid ref="gridRef"
+              v-bind="gridOptions">
+      <!-- 工具栏右侧 -->
+      <template #toolbar_tools>
+        <el-button circle class="toolbar-btn mr-2" @click="handleRefresh">
+          <el-icon>
+            <Refresh />
+          </el-icon>
+        </el-button>
+      </template>
+      <template #top>
+        <div class="mb-2"></div>
+      </template>
+      <!-- 分页 -->
+      <template #pager>
+        <vxe-pager
+            v-model:currentPage="gridOptions.pagerConfig.page"
+            v-model:pageSize="gridOptions.pagerConfig.limit"
+            :total="gridOptions.pagerConfig.total"
+            class="mt-1.5"
+            @page-change="handlePageChange"
+        />
+      </template>
+      <!-- 自定义列插槽 -->
+      <template #create_datetime="{row}">
+        {{ row.create_datetime || '--' }}
+      </template>
+      <template #field="{ row }">
+        <el-tag v-if="tagMap[row.field]" :type="tagMap[row.field].color" effect="plain">
+          {{ tagMap[row.field].label }}
+        </el-tag>
+      </template>
+      <template #old_val="{row}">
+        <ChangeValue :field="row.field" :value="row.old_val" />
+      </template>
+      <template #new_val="{row}">
+        <ChangeValue :field="row.field" :value="row.new_val" />
+      </template>
+    </vxe-grid>
+  </el-card>
+
+</template>
+
+<style scoped>
+.toolbar-btn {
+  width: 34px;
+  height: 34px;
+  font-size: 18px
+}
+
+</style>

+ 142 - 0
src/views/product-manage/historical-detail/component/PriceChart.vue

@@ -0,0 +1,142 @@
+<script setup lang="ts">
+/**
+ * @Name: TrendChart.vue
+ * @Description:
+ * @Author: Cheney
+ */
+
+import * as echarts from 'echarts';
+import * as api from '../api';
+import { useResponse } from '/@/utils/useResponse';
+
+
+const props = defineProps({
+  asin: String,
+  country: String
+});
+const { asin, country } = props;
+
+let chartRef: any = useTemplateRef('chartRef');
+let chart: echarts.ECharts | null = null;
+let resizeObserver: ResizeObserver | null = null;
+let chartData: any = [];
+
+onBeforeMount(() => {
+  fetchChartData();
+});
+
+onMounted(() => {
+  initChart();
+});
+
+onUnmounted(() => {
+  resizeObserver?.disconnect();
+  chart?.dispose();
+});
+
+function updateChart() {
+  if (!chart || chartData.length === 0) return;
+
+  const chartOptions = {
+    title: {
+      text: '商品价格趋势'
+    },
+    tooltip: {
+      trigger: 'axis'
+    },
+    legend: {
+      data: [ '价格' ]
+    },
+    grid: {
+      top: '20%',
+      left: '10%',
+      right: '10%',
+      bottom: '10%'
+    },
+    dataset: {
+      dimensions: [ 'create_datetime', 'new_val' ],
+      source: chartData
+    },
+    xAxis: {
+      type: 'category'
+    },
+    yAxis: [
+      {
+        type: 'value',
+        name: '价格',
+        nameTextStyle: {
+          fontWeight: 'bold',
+          fontSize: 14,
+          color: '#333'
+        },
+        splitLine: {
+          show: true
+        },
+        axisLine: {
+          show: true,
+          lineStyle: {
+            color: '#333',
+            width: 1
+          }
+        }
+      }
+    ],
+    series: [
+      {
+        name: '价格',
+        type: 'line',
+        areaStyle: {
+          // 设置渐变颜色
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            {
+              offset: 0,
+              color: 'rgba(84,112,198, 0.4)'
+            },
+            {
+              offset: 1,
+              color: 'rgba(84,112,198, 0.1)'
+            }
+          ])
+        }
+      }
+    ]
+  };
+
+  chart.setOption(chartOptions);
+}
+
+function initChart() {
+  if (!chartRef.value) return;
+
+  chart = echarts.init(chartRef.value);
+  updateChart();
+
+  resizeObserver = new ResizeObserver(() => {
+    chart?.resize();
+  });
+  if (chartRef.value) {
+    resizeObserver.observe(chartRef.value);
+  }
+}
+
+async function fetchChartData() {
+  const res = await useResponse(api.getChartData, { asin, country_code: country });
+  if (res.code === 2000 && res.data) {
+    chartData = res.data;
+    updateChart();
+  }
+}
+</script>
+
+<template>
+  <el-card class="border-none mt-2">
+    <div ref="chartRef" class="chart"></div>
+  </el-card>
+</template>
+
+<style scoped>
+.chart {
+  width: 100%;
+  height: 400px;
+}
+</style>

+ 7 - 19
src/views/product-manage/historical-detail/index.vue

@@ -6,6 +6,8 @@
  */
 
 import { useTableHeight } from '/@/utils/useCustomHeight';
+import PriceChart from '/@/views/product-manage/historical-detail/component/PriceChart.vue';
+import DataTable from '/@/views/product-manage/historical-detail/component/DataTable.vue';
 
 
 const isShowHistory = defineModel({ default: false });
@@ -15,8 +17,6 @@ const props = defineProps({
   title: String
 });
 const { rowData, title } = props;
-console.log("(index.vue: 15)=> rowData", rowData);
-
 
 const heightObj = { topBar: 50, cardMargin: 8, cardPadding: 20 };
 const { tableHeight } = useTableHeight(heightObj);
@@ -30,28 +30,16 @@ const { tableHeight } = useTableHeight(heightObj);
         ref="editDrawer"
         v-model="isShowHistory"
         :show-close="false"
+        :title="`${title} - 历史详情`"
         direction="btt"
         size="85%"
-        style="background-color:#F3F4FB;"
-        :title="`${title} - 历史详情`">
-      <div class="sticky top-0" style="background-color:#F3F4FB; min-height: 20px; z-index: 2"></div>
+        style="background-color:#F3F4FB;">
+      <div class="sticky top-0" style="background-color:#F3F4FB; min-height: 20px; z-index: 999"></div>
       <div class="px-5">
-        <el-card>12341</el-card>
-        <el-card :body-style="{ height: tableHeight + 'px' }">
-          <div class="h-full overflow-hidden">
-            <vxe-table height="100%">
-              <vxe-column type="seq"></vxe-column>
-              <vxe-column type="seq"></vxe-column>
-              <vxe-column type="seq"></vxe-column>
-              <vxe-column type="seq"></vxe-column>
-              <vxe-column type="seq"></vxe-column>
-            </vxe-table>
-          </div>
-        </el-card>
+        <PriceChart :asin="rowData.asin" :country="rowData.country_code" />
+        <DataTable :asin="rowData.asin" :country="rowData.country_code" />
       </div>
-      
     </el-drawer>
-
   </div>
 
 </template>

+ 9 - 8
src/views/product-manage/product-monitor/component/DataTableSlot.vue

@@ -37,7 +37,7 @@ function onConfirm() {
 }
 
 function showDetail(detail: any) {
-  emit(`${detail}`, row);
+  emit(`${ detail }`, row);
 }
 
 </script>
@@ -45,7 +45,7 @@ function showDetail(detail: any) {
 <template>
   <div class="font-medium">
     <div v-if="field === 'product_info'">
-      <ProductInfo :img-width="50" :item="row.goods"/>
+      <ProductInfo :img-width="50" :item="row.goods" />
     </div>
     <div v-else-if="field === 'country_code'">
       <el-tag :disable-transitions="true" :style="{ color: color, borderColor: color }" effect="plain" round>
@@ -125,10 +125,10 @@ function showDetail(detail: any) {
       </template>
     </div>
     <div v-else-if="field === 'stars'" class="flex flex-col font-normal" style="min-width: 170px">
-      <ProgressBar :row="row" percentage="ratings"/>
+      <ProgressBar :row="row" percentage="ratings" />
     </div>
     <div v-else-if="field === 'all_stars'" class="flex flex-col font-normal" style="min-width: 170px">
-      <ProgressBar :row="row" percentage="all_rate"/>
+      <ProgressBar :row="row" percentage="all_rate" />
     </div>
     <div v-else-if="field === 'status'">
       <el-tag :disable-transitions="true" :type=statusType>
@@ -141,7 +141,7 @@ function showDetail(detail: any) {
                     placement="top" popper-class="custom-btn-tooltip">
           <PermissionButton circle plain type="success" @click="showDetail('show-comment')">
             <el-icon>
-              <Tickets/>
+              <Tickets />
             </el-icon>
           </PermissionButton>
         </el-tooltip>
@@ -149,7 +149,7 @@ function showDetail(detail: any) {
                     placement="top" popper-class="custom-btn-tooltip-2">
           <PermissionButton :color="'#6466F1'" circle plain type="success" @click="showDetail('show-history')">
             <el-icon>
-              <Timer/>
+              <Timer />
             </el-icon>
           </PermissionButton>
         </el-tooltip>
@@ -157,7 +157,7 @@ function showDetail(detail: any) {
       <div class="flex justify-center gap-2">
         <PermissionButton circle plain type="warning" @click="handleEdit">
           <el-icon>
-            <Operation/>
+            <Operation />
           </el-icon>
         </PermissionButton>
         <el-popconfirm
@@ -170,7 +170,7 @@ function showDetail(detail: any) {
           <template #reference>
             <PermissionButton circle plain type="danger">
               <el-icon>
-                <Delete/>
+                <Delete />
               </el-icon>
             </PermissionButton>
           </template>
@@ -207,6 +207,7 @@ function showDetail(detail: any) {
   border: 1px solid #67C23A !important;
   font-size: 14px;
 }
+
 .custom-btn-tooltip-2 {
   background-color: #F0F0FE !important;
   color: #606266 !important;