Parcourir la source

Merge branch 'cheney' into dev

# Conflicts:
#	src/views/product-manage/comment-detail/api.ts
#	src/views/product-manage/comment-detail/component/NegativeLabel.vue
WanGxC il y a 6 mois
Parent
commit
93d18721e5

+ 25 - 0
src/views/product-manage/comment-detail/api.ts

@@ -67,3 +67,28 @@ export function deleteNegativeTags(body: any) {
     data: body
   });
 }
+
+export function getWordCloudData(query: any) {
+  return request({
+    url: '/api/choice/reviews_labels/agg/',
+    method: 'GET',
+    params: query
+  });
+}
+
+export function getBarChartData(query: any) {
+  return request({
+    url: '/api/choice/reviews_labels/agg/',
+    method: 'GET',
+    params: query
+  });
+}
+
+
+export function getLineChartData(query: any) {
+  return request({
+    url: '/api/choice/reviews_labels/agg/',
+    method: 'GET',
+    params: query
+  });
+}

+ 198 - 3
src/views/product-manage/comment-detail/component/NegativeClassification.vue

@@ -4,15 +4,210 @@
  * @Description: 负面标签分类
  * @Author: Cheney
  */
+import * as echarts from 'echarts';
+import { useResponse } from '/@/utils/useResponse';
+import * as api from '/@/views/product-manage/comment-detail/api';
 
+
+const props = defineProps({
+  asin: String
+});
+const { asin } = props;
+
+let chartRef: any = useTemplateRef('chartRef');
+let chart: echarts.ECharts | null = null;
+let chartData: any = ref([]);
+
+let resizeObserver: ResizeObserver | null = null;
+
+let lineChartRef: any = useTemplateRef('lineChartRef');
+let lineChart: echarts.ECharts | null = null;
+let lineChartData: any = ref([]);
+
+onBeforeMount(() => {
+  fetchChartData();
+  fetchLineChartData();
+});
+
+onMounted(() => {
+  initChart();
+  initLineChart();
+});
+
+onUnmounted(() => {
+  resizeObserver?.disconnect();
+  chart?.dispose();
+});
+
+function updateChart() {
+  if (!chart || chartData.value.length === 0) return;
+
+  // 排序 chartData
+  chartData.value.sort((a: any, b: any) => a.value - b.value);
+
+  const chartOptions = {
+    title: {
+      text: '负面标签分类'
+    },
+    tooltip: {
+      trigger: 'axis'
+    },
+    grid: {
+      top: '12%',
+      left: '20%',
+      right: '10%',
+      bottom: '10%'
+    },
+    dataset: {
+      dimensions: [ 'name', 'value' ],
+      source: chartData.value
+    },
+    xAxis: {
+      type: 'value',
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: '#3480CE',
+          width: 1
+        }
+      }
+    },
+    yAxis: {
+      type: 'category',
+      name: '名称',
+      nameGap: 16,
+      axisLabel: {
+        interval: 0, // 确保所有标签都显示
+        rotate: 30,  // 标签倾斜 30 度
+        textStyle: {
+          fontSize: 12, // 设置字体大小,可根据需要调整
+          color: '#333' // 设置字体颜色
+        }
+      }
+    },
+    series: [
+      {
+        type: 'bar',
+        name: ' ',
+        color: '#3480CE',
+        barWidth: '18px'
+      }
+    ]
+  };
+
+  chart.setOption(chartOptions);
+}
+
+function updateLineChart() {
+  if (!lineChart || lineChartData.value.length === 0) return;
+
+  const lineChartOptions = {
+
+    tooltip: {
+      trigger: 'axis'
+    },
+    grid: {
+      top: '12%',
+      left: '5%',
+      right: '10%',
+      bottom: '10%'
+    },
+    dataset: {
+      dimensions: [ 'name', 'value' ],
+      source: lineChartData.value
+    },
+    xAxis: {
+      type: 'category',
+      name: '日期'
+    },
+    yAxis: {
+      type: 'value',
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: '#FF5733',
+          width: 1
+        }
+      }
+    },
+    series: [
+      {
+        name: 'all',
+        type: 'line',
+        smooth: true,
+        itemStyle: {
+          color: '#FF5733'
+        }
+      }
+    ]
+  };
+
+  lineChart.setOption(lineChartOptions);
+}
+
+function initChart() {
+  if (!chartRef.value) return;
+
+  chart = echarts.init(chartRef.value);
+  updateChart();
+
+  resizeObserver = new ResizeObserver(() => {
+    chart?.resize();
+  });
+  if (chartRef.value) {
+    resizeObserver.observe(chartRef.value);
+  }
+}
+
+function initLineChart() {
+  if (!lineChartRef.value) return;
+
+  lineChart = echarts.init(lineChartRef.value);
+  updateLineChart();
+
+  resizeObserver = new ResizeObserver(() => {
+    lineChart?.resize();
+  });
+  if (lineChartRef.value) {
+    resizeObserver.observe(lineChartRef.value);
+  }
+}
+
+async function fetchChartData() {
+  const res = await useResponse(api.getBarChartData, { asin, agg_field: 'kind' });
+  if (res.code === 2000 && res.data) {
+    chartData.value = res.data;
+    updateChart();
+  }
+}
+
+async function fetchLineChartData() {
+  const res = await useResponse(api.getLineChartData, { asin, agg_field: 'review_date' });
+  if (res.code === 2000 && res.data) {
+    lineChartData.value = res.data;
+    updateLineChart();
+  }
+}
 </script>
 
 <template>
-  <el-card shadow="hover" class="border-none">
-    负面标签分类
+  <el-card class="border-none" shadow="hover">
+    <el-row :gutter="10">
+      <el-col :span="12">
+        <div v-show="chartData.length > 0" ref="chartRef" class="chart"></div>
+        <el-empty v-if="chartData.length == 0" class="chart" description="暂无数据" />
+      </el-col>
+      <el-col :span="12">
+        <div v-show="lineChartData.length > 0" ref="lineChartRef" class="chart"></div>
+        <el-empty v-if="lineChartData.length == 0" class="chart" description="暂无数据" />
+      </el-col>
+    </el-row>
   </el-card>
 </template>
 
 <style scoped>
-
+.chart {
+  width: 100%;
+  height: 500px;
+}
 </style>

+ 138 - 0
src/views/product-manage/comment-detail/component/NegativeLabel.vue

@@ -89,6 +89,135 @@ function handleEdit(row) {
 	editOpen.value = true;
 	editData.value = row;
 }
+import { onBeforeUnmount, ref } from 'vue';
+import * as api from '../api';
+import * as echarts from 'echarts';
+import 'echarts-wordcloud';
+
+
+const props = defineProps({
+  asin: String
+});
+const { asin } = props;
+
+const loading = ref(false);
+
+let responseData: any = null;
+const chartRef: any = useTemplateRef('chartRef');
+let chart: echarts.ECharts | null = null;
+let resizeObserver: ResizeObserver | null = null;
+const hasData = ref(true);
+
+onBeforeMount(() => {
+  fetchWordCloudData();
+});
+
+onBeforeUnmount(() => {
+  // 清理 ResizeObserver
+  if (resizeObserver) {
+    resizeObserver.disconnect();
+  }
+  if (chart) {
+    chart.dispose();
+    chart = null;
+  }
+});
+
+async function fetchWordCloudData() {
+  loading.value = true;
+  const query = {
+    asin,
+    agg_field: 'raw_label'
+  };
+  try {
+    responseData = await api.getWordCloudData(query);
+    if (!responseData.data || responseData.data.length === 0) {
+      // 处理空数据的情况
+      hasData.value = false;
+      if (chart) {
+        chart.clear();
+      }
+      return;
+    }
+    hasData.value = true;
+    setOption();
+    // initChart(option);
+  } catch (error) {
+    console.error('==Error==', error);
+  } finally {
+    loading.value = false;
+  }
+}
+
+function setOption() {
+  const option = {
+    title: {
+      text: '负面标签'
+    },
+    tooltip: {
+      show: true
+    },
+    series: [
+      {
+        type: 'wordCloud',
+        width: '90%',
+        height: '85%',
+        // 词云图的形状
+        shape: 'star', // 可以是 'circle', 'cardioid', 'diamond', 'triangle-forward', 'triangle', 'pentagon', 'star'
+        // 控制字体大小
+        sizeRange: [12, 60],
+        // 文本旋转角度
+        rotationRange: [0, 0],
+        rotationStep: 45,
+        gridSize: 8,
+        drawOutOfBound: false,
+        shrinkToFit: false,
+        layoutAnimation: true,
+        // 全局的文字样式
+        textStyle: {
+          fontFamily: 'PingFang',
+          fontWeight: 'bold',
+          // Color can be a callback function or a color string
+          color: function () {
+            // Random color
+            return 'rgb(' + [
+              Math.round(Math.random() * 160),
+              Math.round(Math.random() * 160),
+              Math.round(Math.random() * 160)
+            ].join(',') + ')'
+          }
+        },
+        // 鼠标hover时的样式
+        emphasis: {
+          focus: 'self',
+          textStyle: {
+            textShadowBlur: 10,
+            textShadowColor: '#333'
+          }
+        },
+        // left: '10%',
+        // top: '10%',
+        // right: '10%',
+        // bottom: '10%',
+        data: responseData.data,
+      }
+    ],
+    backgroundColor: '#fff'
+  };
+
+  if (!chart) {
+    chart = echarts.init(chartRef.value);
+  }
+  chart.setOption(option);
+
+  // 添加 ResizeObserver 以处理图表大小变化
+  if (!resizeObserver) {
+    resizeObserver = new ResizeObserver(() => {
+      chart?.resize();
+    });
+    resizeObserver.observe(chartRef.value);
+  }
+}
 
 async function singleDelete(row: any) {
 	const res = await useResponse(api.deleteNegativeTags, row);
@@ -194,6 +323,15 @@ onMounted(() => {
 		<CreateLabelDialog v-if="createOpen" v-model="createOpen" :rowData="rowData" @refresh="fetchList"></CreateLabelDialog>
 		<EditLabelDialog v-if="editOpen" v-model="editOpen" :editData="editData" :rowData="rowData" @refresh="fetchList"></EditLabelDialog>
 	</div>
+  <el-card v-loading="loading" class="border-none" shadow="never">
+    <!-- 空状态 和 词云图 -->
+    <div class="w-full" style="min-height: 500px">
+      <div v-show="!loading && !hasData" style="min-height: 500px">
+        <el-empty :image-size="300" />
+      </div>
+      <div v-show="hasData" ref="chartRef" style="width: 100%; height: 500px"></div>
+    </div>
+  </el-card>
 </template>
 
 <style scoped>

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

@@ -34,7 +34,7 @@ const { img, url, title, asin } = props;
           </div>
         </template>
       </el-image>
-      <div>
+      <div class="flex flex-col justify-between">
         <el-link :href="url"
                  :underline="false"
                  style="font-size: 18px;"
@@ -44,7 +44,7 @@ const { img, url, title, asin } = props;
         <div class="flex items-center">
           <div class="font-semibold italic">{{ asin }}</div>
           <VerticalDivider />
-          <el-icon class="ml-2 cursor-pointer" @click="handleCopy(asin)">
+          <el-icon class="ml-2 cursor-pointer" @click="handleCopy(asin || '')">
             <DocumentCopy />
           </el-icon>
         </div>

+ 12 - 10
src/views/product-manage/comment-detail/index.vue

@@ -10,6 +10,8 @@ import NegativeLabel from '/@/views/product-manage/comment-detail/component/Nega
 import NegativeClassification from '/@/views/product-manage/comment-detail/component/NegativeClassification.vue';
 import AverageMonthly from '/@/views/product-manage/comment-detail/component/AverageMonthly.vue';
 import TitleCard from './component/TitleCard.vue';
+import NegativeLabel from '/@/views/product-manage/comment-detail/component/NegativeLabel.vue';
+import NegativeClassification from '/@/views/product-manage/comment-detail/component/NegativeClassification.vue';
 
 import VerticalDivider from '/@/components/VerticalDivider/index.vue';
 import { handleCopy } from '/@/utils/useCopyText';
@@ -40,16 +42,16 @@ const { rowData } = props;
       <div class="px-5">
         <!-- Title 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">-->
-        <!--    <NegativeLabel />-->
-        <!--  </el-col>-->
-        <!--  <el-col :span="12">-->
-        <!--    <NegativeClassification />-->
-        <!--  </el-col>-->
-        <!--</el-row>-->
-        <!-- Chart -->
+         <!--Chart -->
+        <el-row :gutter="20" class="mt-5" style="z-index: 1">
+          <el-col :span="8">
+            <NegativeLabel :asin="rowData.asin" />
+          </el-col>
+          <el-col :span="16">
+            <NegativeClassification :asin="rowData.asin" />
+          </el-col>
+        </el-row>
+         <!--Chart -->
         <div class="mt-5">
           <AverageMonthly :asin="rowData.asin" />
         </div>