Gogs 2 месяцев назад
Родитель
Сommit
5a562aae9b
2 измененных файлов с 192 добавлено и 28 удалено
  1. 121 0
      src/views/order/ordervalue/index.vue
  2. 71 28
      src/views/order/shopvalue/index.vue

+ 121 - 0
src/views/order/ordervalue/index.vue

@@ -24,6 +24,9 @@
 
     </div>
 
+    <button class="upload-btn" :disabled="uploadingOrder" @click="triggerOrderUpload">{{ uploadingOrder ? '上传中...' : '上传CSV导入' }}</button>
+    <input ref="orderUploadInput" type="file" accept=".csv" multiple style="display: none;" @change="handleOrderUploadChange">
+
   </div>
 
     </header>
@@ -132,6 +135,8 @@ export default {
 
       currentDateRange: { start: '', end: '' },
 
+      uploadingOrder: false,
+
       kpiData: {
 
         gmv: '?0',
@@ -188,6 +193,96 @@ export default {
 
   methods: {
 
+    triggerOrderUpload() {
+
+      if (this.uploadingOrder) {
+
+        return;
+
+      }
+
+      if (this.$refs.orderUploadInput) {
+
+        this.$refs.orderUploadInput.click();
+
+      }
+
+    },
+
+    async handleOrderUploadChange(event) {
+
+      const files = Array.from((event && event.target && event.target.files) || []);
+
+      if (!files.length) {
+
+        return;
+
+      }
+
+      const invalid = files.find(file => !String(file.name || '').toLowerCase().endsWith('.csv'));
+
+      if (invalid) {
+
+        this.$modal.msgError('仅支持上传CSV文件');
+
+        event.target.value = '';
+
+        return;
+
+      }
+
+      await this.uploadOrderFiles(files);
+
+      event.target.value = '';
+
+    },
+
+    async uploadOrderFiles(files) {
+
+      this.uploadingOrder = true;
+
+      try {
+
+        const formData = new FormData();
+
+        files.forEach(file => formData.append('files', file));
+
+        const { data } = await axios.post('/api/import/import-data/upload', formData, {
+
+          headers: { 'Content-Type': 'multipart/form-data' }
+
+        });
+
+        if (data && data.success) {
+
+          this.$modal.msgSuccess(data.message || '上传并导入成功');
+
+          await this.initDashboard();
+
+          return;
+
+        }
+
+        this.$modal.msgError((data && data.message) || '上传导入失败');
+
+      } catch (error) {
+
+        const msg = error && error.response && error.response.data && error.response.data.message
+
+          ? error.response.data.message
+
+          : (error && error.message ? error.message : '上传导入失败');
+
+        this.$modal.msgError(msg);
+
+      } finally {
+
+        this.uploadingOrder = false;
+
+      }
+
+    },
+
     pad2(value) {
 
       return String(value).padStart(2, '0');
@@ -706,6 +801,32 @@ export default {
 
 }
 
+.upload-btn {
+
+  padding: 5px 12px;
+
+  border: 1px solid #188df0;
+
+  background-color: #188df0;
+
+  color: #fff;
+
+  border-radius: 4px;
+
+  cursor: pointer;
+
+  font-size: 13px;
+
+}
+
+.upload-btn:disabled {
+
+  opacity: 0.7;
+
+  cursor: not-allowed;
+
+}
+
 /* 样式部分和之前一样,不用修改 */
 
 .order-value-view {

+ 71 - 28
src/views/order/shopvalue/index.vue

@@ -8,6 +8,12 @@
       </div>
     </header>
 
+    <!-- 上传操作(第一排) -->
+    <div class="upload-row">
+      <button class="btn-upload" :disabled="uploadingShop" @click="triggerShopUpload">{{ uploadingShop ? '上传中...' : '上传CSV导入' }}</button>
+      <input ref="shopUploadInput" type="file" accept=".csv" multiple style="display: none;" @change="handleShopUploadChange">
+    </div>
+
     <!-- 新增:Top 5 商品贡献分析 (移到顶部) -->
     <div class="chart-card">
       <div class="top-product-contribution-container">
@@ -35,20 +41,6 @@
       </div>
     </div>
 
-    <!-- 3. 筛选与操作栏 -->
-    <div class="filter-bar">
-      <div class="filters">
-        <span>筛选:</span>
-        <select><option>全部时间</option></select>
-        <select><option>全部渠道</option></select>
-        <select><option>全部区域</option></select>
-      </div>
-      <div class="actions">
-        <button class="btn-secondary">导出数据</button>
-        <button class="btn-primary" @click="fetchData">刷新</button>
-      </div>
-    </div>
-
     <!-- 4. 图表卡片区域 -->
     <div class="charts-container">
       <!-- 部门图表:销量 + 销售金额 -->
@@ -97,7 +89,8 @@ export default {
         top5TotalSales: 0,
         contributionRatio: 0,
         top5Products: []
-      }
+      },
+      uploadingShop: false
     };
   },
   mounted() {
@@ -108,6 +101,55 @@ export default {
     window.removeEventListener('resize', this.handleResize);
   },
   methods: {
+    triggerShopUpload() {
+      if (this.uploadingShop) return;
+      if (this.$refs.shopUploadInput) {
+        this.$refs.shopUploadInput.click();
+      }
+    },
+    async handleShopUploadChange(event) {
+      const files = Array.from((event && event.target && event.target.files) || []);
+      if (!files.length) {
+        return;
+      }
+      const invalid = files.find(file => !String(file.name || '').toLowerCase().endsWith('.csv'));
+      if (invalid) {
+        this.$modal.msgError('仅支持上传CSV文件');
+        event.target.value = '';
+        return;
+      }
+      await this.uploadShopFiles(files);
+      event.target.value = '';
+    },
+    async uploadShopFiles(files) {
+      this.uploadingShop = true;
+      try {
+        const formData = new FormData();
+        files.forEach(file => formData.append('files', file));
+        const { data } = await axios.post('/api/shop/import/import-sales-data/upload', formData, {
+          headers: { 'Content-Type': 'multipart/form-data' }
+        });
+        if (data && data.success) {
+          this.$modal.msgSuccess(data.message || '上传并导入成功');
+          await this.fetchData();
+          return;
+        }
+        if (data && data.debug) {
+          console.error('[shop-upload-debug]', data.debug);
+        }
+        this.$modal.msgError((data && data.message) || '上传导入失败');
+      } catch (error) {
+        if (error && error.response && error.response.data && error.response.data.debug) {
+          console.error('[shop-upload-debug]', error.response.data.debug);
+        }
+        const msg = error && error.response && error.response.data && error.response.data.message
+          ? error.response.data.message
+          : (error && error.message ? error.message : '上传导入失败');
+        this.$modal.msgError(msg);
+      } finally {
+        this.uploadingShop = false;
+      }
+    },
     formatNumber(num) {
       if (!num) return '0';
       return new Intl.NumberFormat().format(Number(num).toFixed(0));
@@ -446,21 +488,22 @@ export default {
 .kpi-comparison.positive { color: #00b42a; }
 .kpi-comparison.negative { color: #f53f3f; }
 
-.filter-bar {
+.upload-row {
   display: flex;
-  justify-content: space-between;
-  align-items: center;
-  background: #fff;
-  padding: 16px 20px;
-  border-radius: 4px;
-  border: 1px solid #e5e6eb;
+  justify-content: flex-end;
   margin-bottom: 20px;
 }
-.filters { display: flex; align-items: center; gap: 16px; font-size: 14px; color: #4e5969; }
-.filters select { padding: 4px 8px; border-radius: 4px; border: 1px solid #e5e6eb; }
-.actions { display: flex; gap: 12px; }
-.btn-primary { background-color: #1677ff; color: #fff; border: none; padding: 6px 16px; border-radius: 4px; cursor: pointer; }
-.btn-secondary { background-color: #f2f3f5; color: #4e5969; border: 1px solid #e5e6eb; padding: 6px 16px; border-radius: 4px; cursor: pointer; }
+.btn-upload {
+  background-color: #2a7f62;
+  color: #fff;
+  border: none;
+  padding: 12px 28px;
+  border-radius: 8px;
+  cursor: pointer;
+  font-size: 16px;
+  font-weight: 600;
+}
+.btn-upload:disabled { opacity: 0.7; cursor: not-allowed; }
 
 .charts-container {
   display: flex;
@@ -535,4 +578,4 @@ export default {
   color: #333;
   margin-bottom: 20px;
 }
-</style>
+</style>