Browse Source

Merge branch 'master' into wang

WanGxC 8 months ago
parent
commit
173cd2d19e
25 changed files with 1467 additions and 443 deletions
  1. 7 7
      package-lock.json
  2. 1 1
      package.json
  3. 14 4
      src/views/efTools/automation/api.ts
  4. 197 45
      src/views/efTools/automation/components/adActivityDialog.vue
  5. 130 19
      src/views/efTools/automation/components/targetRuleDialog.vue
  6. 22 26
      src/views/efTools/automation/index.vue
  7. 3 3
      src/views/efTools/utils/enum.ts
  8. 9 1
      src/views/reportManage/TaskManage/api.ts
  9. 126 0
      src/views/reportManage/TaskManage/components/shopInformation.vue
  10. 227 170
      src/views/reportManage/TaskManage/index.vue
  11. 274 0
      src/views/reportManage/TaskManage/utils/columns.ts
  12. 1 1
      src/views/reportManage/TaskManage/utils/enum.ts
  13. 37 13
      src/views/reportManage/dataCenter/api.ts
  14. 17 11
      src/views/reportManage/dataCenter/combinedDisplay/components/DatePicker/index.vue
  15. 3 1
      src/views/reportManage/dataCenter/combinedDisplay/components/monthDatePicker/index.vue
  16. 46 13
      src/views/reportManage/dataCenter/combinedDisplay/components/tableData/mainData.vue
  17. 11 2
      src/views/reportManage/dataCenter/combinedDisplay/components/tableData/monthlyComparativeData.vue
  18. 14 9
      src/views/reportManage/dataCenter/normalDisplay/components/DatePicker/index.vue
  19. 62 15
      src/views/reportManage/dataCenter/normalDisplay/components/Selector/index.vue
  20. 58 28
      src/views/reportManage/dataCenter/normalDisplay/components/TableDataDisplay.vue
  21. 181 53
      src/views/reportManage/dataCenter/normalDisplay/components/TableDataEntry.vue
  22. 1 1
      src/views/reportManage/dataCenter/normalDisplay/index.vue
  23. 6 6
      src/views/reportManage/dataCenter/utils/columns.ts
  24. 17 11
      src/views/system/log/operationLog/crud.tsx
  25. 3 3
      src/views/system/user/crud.tsx

+ 7 - 7
package-lock.json

@@ -27,7 +27,7 @@
 				"echarts": "^5.5.1",
 				"echarts-gl": "^2.0.9",
 				"echarts-wordcloud": "^2.1.0",
-				"element-plus": "^2.7.6",
+				"element-plus": "^2.8.0",
 				"element-tree-line": "^0.2.1",
 				"font-awesome": "^4.7.0",
 				"js-cookie": "^3.0.1",
@@ -6165,9 +6165,9 @@
 			"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
 		},
 		"node_modules/element-plus": {
-			"version": "2.7.6",
-			"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.7.6.tgz",
-			"integrity": "sha512-36sw1K23hYjgeooR10U6CiCaCp2wvOqwoFurADZVlekeQ9v5U1FhJCFGEXO6i/kZBBMwsE1c9fxjLs9LENw2Rg==",
+			"version": "2.8.0",
+			"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.8.0.tgz",
+			"integrity": "sha512-7ngapVlVlQAjocVqD4MUKvKXlBneT9DSDk2mmBOSLRFWNm/HLDT15ozmsvUBfy18sajnyUeSIHTtINE8gfrGMg==",
 			"dependencies": {
 				"@ctrl/tinycolor": "^3.4.1",
 				"@element-plus/icons-vue": "^2.3.1",
@@ -15474,9 +15474,9 @@
 			"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
 		},
 		"element-plus": {
-			"version": "2.7.6",
-			"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.7.6.tgz",
-			"integrity": "sha512-36sw1K23hYjgeooR10U6CiCaCp2wvOqwoFurADZVlekeQ9v5U1FhJCFGEXO6i/kZBBMwsE1c9fxjLs9LENw2Rg==",
+			"version": "2.8.0",
+			"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.8.0.tgz",
+			"integrity": "sha512-7ngapVlVlQAjocVqD4MUKvKXlBneT9DSDk2mmBOSLRFWNm/HLDT15ozmsvUBfy18sajnyUeSIHTtINE8gfrGMg==",
 			"requires": {
 				"@ctrl/tinycolor": "^3.4.1",
 				"@element-plus/icons-vue": "^2.3.1",

+ 1 - 1
package.json

@@ -27,7 +27,7 @@
 		"echarts": "^5.5.1",
 		"echarts-gl": "^2.0.9",
 		"echarts-wordcloud": "^2.1.0",
-		"element-plus": "^2.7.6",
+		"element-plus": "^2.8.0",
 		"element-tree-line": "^0.2.1",
 		"font-awesome": "^4.7.0",
 		"js-cookie": "^3.0.1",

+ 14 - 4
src/views/efTools/automation/api.ts

@@ -42,7 +42,7 @@ export function DelObj(id: DelReq) {
   });
 }
 
-//关联广告活动
+// 关联广告活动
 export function getRelationCampaign(query) {
   return request({
     url: '/api/ad_manage/template/relation_campaign_list/',
@@ -51,7 +51,7 @@ export function getRelationCampaign(query) {
   });
 }
 
-//广告组合下拉框
+// 广告组合下拉框
 export function getAdGroupList(query) {
   return request({
     url: '/api/ad_manage/ad_tree/portfolios',
@@ -60,11 +60,21 @@ export function getAdGroupList(query) {
   });
 }
 
-//广告关联活动保存
+// 广告关联活动保存
 export function updateAdCampaign(body) {
   return request({
     url: '/api/ad_manage/template/relation_campaign_enable/',
     method: 'POST',
     data: body,
   });
-}
+}
+
+// 获取定向规则列表
+export function getTargetingRuleList(query) {
+  return request({
+    url: '/api/ad_manage/template/target_select',
+    method: 'GET',
+    params: query,
+  });
+}
+

+ 197 - 45
src/views/efTools/automation/components/adActivityDialog.vue

@@ -9,6 +9,7 @@ import { getAdGroupList, getRelationCampaign } from '/@/views/efTools/automation
 import { storeToRefs } from 'pinia';
 import { useShopInfo } from '/@/stores/shopInfo';
 import { ElMessage } from 'element-plus';
+import TargetRuleDialog from '/@/views/efTools/automation/components/targetRuleDialog.vue';
 
 const props = defineProps({
   templateId: {
@@ -30,7 +31,14 @@ const { templateId } = toRefs(props);
 const { activeModel } = toRefs(props);
 
 const dialogVisible = ref(props.modelValue);
-//筛选条件
+const targetRuleDialogVisible = ref(false);
+
+// 定向规则
+//const adGroupId = ref('')
+//const campaignId = ref('')
+const selectedTargetedRow = ref(null);
+
+// 筛选条件
 const searchAdCampaign = ref('');
 const selectedCampaignType = ref('sp');
 const selectedAdGroup = ref('');
@@ -49,7 +57,7 @@ const campaignStatus = [
   { value: 'PAUSED', label: '已暂停' },
 ];
 
-//表格
+// 表格
 const currentPage = ref(1);
 const pageSize = ref(25);
 const total = ref(0);
@@ -89,7 +97,6 @@ async function fetchAdCampaign() {
       limit: pageSize.value,
     });
     adCampaignName.value = resp.data;
-
     total.value = resp.total;
     currentPage.value = resp.page;
     // 开始恢复勾选状态
@@ -110,45 +117,124 @@ async function fetchAdCampaign() {
 }
 
 function handleSelectionChange(selection) {
-  if (isRestoringSelection) return; // 恢复勾选时跳过该方法
-
-  selections = selection;
-  const newSelections = selections.filter(
-      (sel) => !selectedAds.value.some((added) => added.campaignId === sel.campaignId)
-  );
-  if (newSelections.length > 0) {
-    selectedAds.value.push(...newSelections);
-  }
-  // 处理取消选中的项
-  const removedSelections = selectedAds.value.filter(
-      (added) => !selections.some((sel) => sel.campaignId === added.campaignId)
-  );
-
-  if (removedSelections.length > 0) {
-    selectedAds.value = selectedAds.value.filter(
-        (added) => !removedSelections.some((removed) => removed.campaignId === added.campaignId)
-    );
-  }
+  if (isRestoringSelection) return;
+
+  const newSelectedAds = [];
+
+  selection.forEach(item => {
+    if (item.campaignGroupInfo) {
+      // 这是父节点(广告活动)
+      newSelectedAds.push({
+        campaignId: item.campaignId,
+        campaignName: item.campaignName,
+        campaignType: item.campaignType,
+        isParent: true
+      });
+    } else {
+      // 这是子节点(广告组)
+      const parent = adCampaignName.value.find(ad =>
+          ad.campaignGroupInfo && ad.campaignGroupInfo.some(group => group.adGroupId === item.adGroupId)
+      );
+      if (parent) {
+        // 添加父节点(如果还没有添加)
+        if (!newSelectedAds.some(ad => ad.campaignId === parent.campaignId)) {
+          newSelectedAds.push({
+            campaignId: parent.campaignId,
+            campaignName: parent.campaignName,
+            campaignType: parent.campaignType,
+            isParent: true
+          });
+        }
+        // 添加子节点(广告组)
+        newSelectedAds.push({
+          adGroupId: item.adGroupId,
+          adGroupName: item.adGroupName,
+          parentCampaignId: parent.campaignId,
+          parentCampaignName: parent.campaignName,
+          campaignType: parent.campaignType
+        });
+      }
+    }
+  });
+  selectedAds.value = newSelectedAds;
 }
 
+const groupedSelectedAds = computed(() => {
+  const groups = {};
+  selectedAds.value.forEach(ad => {
+    if (ad.isParent) {
+      if (!groups[ad.campaignId]) {
+        groups[ad.campaignId] = {
+          id: ad.campaignId,
+          name: ad.campaignName,
+          campaignType: ad.campaignType,
+          campaignGroupInfo: []
+        };
+      }
+    } else {
+      if (!groups[ad.parentCampaignId]) {
+        groups[ad.parentCampaignId] = {
+          id: ad.parentCampaignId,
+          name: ad.parentCampaignName,
+          campaignType: ad.campaignType,
+          campaignGroupInfo: []
+        };
+      }
+      groups[ad.parentCampaignId].campaignGroupInfo.push({
+        id: ad.adGroupId,
+        name: ad.adGroupName
+      });
+    }
+  });
+  return Object.values(groups);
+});
+
 function removeSelectedAd(index) {
   const removedAd = selectedAds.value.splice(index, 1)[0];
-  const targetIndex = adCampaignName.value.findIndex(ad => ad.campaignName === removedAd.campaignName);
-  if (targetIndex !== -1) {
-    const adTable = refTable.value;
-    adTable.toggleRowSelection(adCampaignName.value[targetIndex], false);
-  }
-}
 
-function removeAllSelectedAds() {
-  const adTable = refTable.value;
-  selectedAds.value.forEach(ad => {
-    const targetIndex = adCampaignName.value.findIndex(item => item.campaignName === ad.campaignName);
+  if (removedAd.parentCampaignId) {
+    // 查找同一父节点下的其他子节点
+    const remainingChildren = selectedAds.value.filter(ad => ad.parentCampaignId === removedAd.parentCampaignId);
+
+    // 如果父节点下还有其他子节点,父节点不删除
+    if (remainingChildren.length > 0) {
+      // 取消表格1中该子节点的勾选
+      const targetParent = adCampaignName.value.find(ad => ad.campaignId === removedAd.parentCampaignId);
+      if (targetParent) {
+        const targetChildIndex = targetParent.campaignGroupInfo.findIndex(group => group.adGroupId === removedAd.adGroupId);
+        if (targetChildIndex !== -1) {
+          const adTable = refTable.value;
+          adTable.toggleRowSelection(targetParent.campaignGroupInfo[targetChildIndex], false);
+        }
+      }
+    } else {
+      // 如果没有其他子节点了,移除父节点
+      const parentIndex = selectedAds.value.findIndex(ad => ad.campaignId === removedAd.parentCampaignId);
+      if (parentIndex !== -1) {
+        selectedAds.value.splice(parentIndex, 1);
+      }
+
+      // 取消表格1中父节点的勾选
+      const parentIndexInTable = adCampaignName.value.findIndex(ad => ad.campaignId === removedAd.parentCampaignId);
+      if (parentIndexInTable !== -1) {
+        const adTable = refTable.value;
+        adTable.toggleRowSelection(adCampaignName.value[parentIndexInTable], false);
+      }
+    }
+  } else {
+    // 如果删除的项是父节点
+    const targetIndex = adCampaignName.value.findIndex(ad => ad.campaignId === removedAd.campaignId);
     if (targetIndex !== -1) {
+      const adTable = refTable.value;
       adTable.toggleRowSelection(adCampaignName.value[targetIndex], false);
     }
-  });
+  }
+}
+
+function removeAllSelectedAds() {
   selectedAds.value = [];
+  const adTable = refTable.value;
+  adTable.clearSelection();
 }
 
 function cancel() {
@@ -197,6 +283,51 @@ async function fetchAdGroupList() {
   }
 }
 
+function handleSelectTarget(row) {
+  console.log("=>(adActivityDialog.vue:287) row", row);
+  // 获取父节点数据
+  const parent = adCampaignName.value.find(ad =>
+      ad.campaignGroupInfo && ad.campaignGroupInfo.some(group => group.adGroupId === row.adGroupId)
+  );
+  if (parent) {
+    // 构造包含父节点和子节点的数据对象
+    selectedTargetedRow.value = {
+      parentCampaignId: parent.campaignId,
+      parentCampaignName: parent.campaignName,
+      campaignType: parent.campaignType,
+      adGroupId: row.adGroupId,
+      adGroupName: row.adGroupName,
+      isSelected: row.isSelected || false, // 同步 isSelected 状态
+    };
+  }
+
+  targetRuleDialogVisible.value = true;
+}
+
+function handleConfirm(campaignKeywordInfo) {
+  selectedTargetedRow.value.keywordInfo = campaignKeywordInfo;
+  selectedTargetedRow.value.isSelected = true;
+
+  // 更新子节点状态
+  const parent = adCampaignName.value.find(ad =>
+      ad.campaignGroupInfo && ad.campaignGroupInfo.some(group => group.adGroupId === selectedTargetedRow.value.adGroupId)
+  );
+  if (parent) {
+    const group = parent.campaignGroupInfo.find(group => group.adGroupId === selectedTargetedRow.value.adGroupId);
+    if (group) {
+      group.isSelected = true; // 更新子节点的 isSelected 状态
+    }
+  }
+
+  // 勾选子节点行
+  const table = refTable.value;
+  const targetRow = parent.campaignGroupInfo.find(group => group.adGroupId === selectedTargetedRow.value.adGroupId);
+
+  if (table && targetRow) {
+    table.toggleRowSelection(targetRow, true); // 勾选目标子节点行
+  }
+}
+
 const headerCellStyle = (args) => {
   if (args.rowIndex === 0) {
     return {
@@ -244,13 +375,16 @@ watch(dialogVisible, (newValue) => {
 
 const treeProps = computed(() => {
   if (activeModel.value === 'adGroup' || activeModel.value === 'specified') {
-    return { children: 'campaignGroupInfo' };
+    return {
+      children: 'campaignGroupInfo',
+      checkStrictly: false
+    };
   }
   return {};
 });
 
 onMounted(() => {
-  //fetchAdGroupList();
+  fetchAdGroupList();
 });
 
 </script>
@@ -258,14 +392,13 @@ onMounted(() => {
 <template>
   <el-dialog
       v-model="dialogVisible"
-      class="custom-dialog"
       style="border-radius: 10px;"
       title="关联广告活动"
       width="1158px"
   >
     <div class="container">
       <div class="container-left">
-        <el-input v-model="searchAdCampaign" placeholder="请输入广告活动" style="width: 100%;"
+        <el-input v-model="searchAdCampaign" clearable placeholder="请输入广告活动" style="width: 100%;"
                   @change="fetchAdCampaign()"></el-input>
         <div class="custom-inline">
           <el-select v-model="selectedCampaignType" placeholder="选择广告类型">
@@ -299,12 +432,11 @@ onMounted(() => {
             v-loading="loading"
             :cell-style="cellStyle"
             :data="adCampaignName"
-            :row-key="'campaignId'"
             :header-cell-style="headerCellStyle"
+            :row-key="'adGroupId'"
+            :tree-props="treeProps"
             height="400"
             style="width: 100%;"
-            :tree-props="treeProps"
-            v-bind="activeModel === 'adGroup' || activeModel === 'specified' ? treeProps : {}"
             @selection-change="handleSelectionChange"
         >
           <el-table-column label="广告活动名称">
@@ -319,6 +451,14 @@ onMounted(() => {
               </el-tag>
               {{ scope.row.campaignName }}
               {{ scope.row.adGroupName }}
+              <el-button
+                  v-if="scope.row.adGroupName && activeModel==='specified'&&!scope.row.isSelected"
+                  class="btn-link"
+                  link
+                  @click="handleSelectTarget(scope.row)">
+                选择定向
+              </el-button>
+              <span v-else-if="scope.row.adGroupName">已选择</span>
             </template>
           </el-table-column>
           <el-table-column type="selection" width="55"></el-table-column>
@@ -341,14 +481,15 @@ onMounted(() => {
         <h3>已选择({{ selectedAds.length }})</h3>
         <el-table
             :cell-style="cellStyle"
-            :data="selectedAds"
+            :data="groupedSelectedAds"
             :header-cell-style="headerCellStyle"
-            height="484"
+            :row-key="'id'"
             :tree-props="treeProps"
-            v-bind="activeModel === 'adGroup' || activeModel === 'specified' ? treeProps : {}"
+            height="484"
             style="width: 100%; margin-top: 20px;"
+            v-bind="activeModel === 'adGroup' || activeModel === 'specified' ? treeProps : {}"
         >
-          <el-table-column label="广告活动" prop="campaignName">
+          <el-table-column label="广告活动" prop="name">
             <template #default="scope">
               <el-tag
                   v-if="scope.row.campaignType"
@@ -359,7 +500,7 @@ onMounted(() => {
                 {{ scope.row.campaignType }}
               </el-tag>
               {{ scope.row.campaignName }}
-              {{ scope.row.adGroupName }}
+              {{ scope.row.name }}
             </template>
           </el-table-column>
           <el-table-column
@@ -386,6 +527,11 @@ onMounted(() => {
       </div>
     </template>
   </el-dialog>
+  <TargetRuleDialog v-if="activeModel === 'specified'"
+                    v-model="targetRuleDialogVisible"
+                    :selectedTargetedRow="selectedTargetedRow"
+                    @confirm:targetRule="handleConfirm"
+  ></TargetRuleDialog>
 </template>
 
 <style scoped>
@@ -438,4 +584,10 @@ onMounted(() => {
   padding: 15px
 }
 
+.btn-link {
+  font-size: 13px;
+  color: #0085ff;
+  /* ling-heigt: 23px; */
+}
+
 </style>

+ 130 - 19
src/views/efTools/automation/components/targetRuleDialog.vue

@@ -4,40 +4,151 @@
  * @Description: 关联广告活动-选择定向弹窗
  * @Author: xinyan
  */
-import { reactive, ref } from 'vue';
+import { onMounted, reactive, ref, watch } from 'vue';
 import { MatchType } from '../../utils/enum';
+import { getTargetingRuleList } from '/@/views/efTools/automation/api';
+import { ElMessage } from 'element-plus';
+
+const props = defineProps({
+  modelValue: {
+    type: Boolean,
+    required: true,
+  },
+  selectedTargetedRow: {
+    type: Object,
+    required: true,
+  },
+});
+const emits = defineEmits(['update:modelValue', 'confirm:targetRule']);
+const targetRuleDialogVisible = ref(props.modelValue);
+const { selectedTargetedRow } = toRefs(props);
+const adGroupId = ref(props.adGroupId);
+const campaignKeywordInfo = ref(null);
 
 // 查询、筛选条件
 const matchType = ref('');
 const keyWord = ref('');
 
 const gridOptions = reactive({
+  height: 550,
+  showOverflow: true,
+  loading: false,
+  rowConfig: {
+    isHover: true,
+    height: 34
+  },
   columns: [
-    { field: 'keyword', title: '关键词' },
-    { field: 'match', title: '匹配方式' },
-    { type: 'checkbox', title: '全选' }
+    { field: 'keywordText', title: '关键词', width: 220 },
+    {
+      field: 'matchType',
+      title: '匹配方式',
+      formatter: ({ cellValue }) => getMatchTypeLabel(cellValue).label,
+    },
+    { type: 'checkbox', align: 'right', width: 55 }
   ],
   data: []
 });
 
+async function fetchTargetRuleList() {
+  try {
+    gridOptions.loading = true;
+    const resp = await getTargetingRuleList({
+      campaignType: selectedTargetedRow.value.campaignType,
+      campaignId: selectedTargetedRow.value.parentCampaignId,
+      adGroupId: selectedTargetedRow.value.adGroupId,
+      matchType: matchType.value,
+      search: keyWord.value,
+    });
+    gridOptions.data = resp.data.targetData;
+    gridOptions.loading = false;
+  } catch (error) {
+    ElMessage.error('请求定向数据失败');
+  }
+}
+
+function handleCheckChange({ records }) {
+  campaignKeywordInfo.value = records;
+}
+
+function getMatchTypeLabel(type: string) {
+  const matchType = MatchType.find(item => item.value === type);
+  if (matchType) {
+    return { label: matchType.label, type: type };
+  }
+  return { label: type, type: '' };
+}
+
+function cancel() {
+  targetRuleDialogVisible.value = false;
+}
+
+async function confirm() {
+  targetRuleDialogVisible.value = false;
+  emits('confirm:targetRule', campaignKeywordInfo.value);
+}
+
+const headerCellStyle = () => {
+  return {
+    fontSize: '13px',
+    height: '34px',
+  };
+};
+
+const cellStyle = () => {
+  return {
+    fontSize: '13px',
+    //fontWeight: '500',
+  };
+};
+
+watch(() => props.modelValue, (newValue) => {
+  targetRuleDialogVisible.value = newValue;
+});
+
+watch(targetRuleDialogVisible, (newValue) => {
+  emits('update:modelValue', newValue);
+});
+
+watch(() => props.selectedTargetedRow, () => {
+  fetchTargetRuleList();
+});
+
+onMounted(() => {
+  //fetchTargetRuleList();
+});
+
 </script>
 
 <template>
-  <el-select
-      v-model="matchType"
-      placeholder="Select"
-      size="large"
-      style="width: 240px"
-  >
-    <el-option
-        v-for="item in MatchType"
-        :key="item.value"
-        :label="item.label"
-        :value="item.value"
-    />
-  </el-select>
-  <el-input v-model="keyWord" placeholder="快速搜索关键词" style="width: 240px" />
-  <vxe-grid v-bind="gridOptions"></vxe-grid>
+  <el-dialog v-model="targetRuleDialogVisible"
+             style="border-radius: 10px;"
+             title="关联广告活动 > 选择定向"
+             width="1158px">
+    <div>
+      <el-select
+          v-model="matchType"
+          placeholder="全部匹配方式"
+          style="width: 128px; margin-bottom: 10px"
+          @change="fetchTargetRuleList"
+      >
+        <el-option
+            v-for="item in MatchType"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+        />
+      </el-select>
+    </div>
+    <el-input v-model="keyWord" class="mb-3" clearable placeholder="快速搜索关键词" @change="fetchTargetRuleList" />
+    <vxe-grid :cell-style="cellStyle" :header-cell-style="headerCellStyle" @checkbox-change="handleCheckChange"
+              v-bind="gridOptions"></vxe-grid>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="cancel">取消</el-button>
+        <el-button type="primary" @click="confirm">确定</el-button>
+      </div>
+    </template>
+  </el-dialog>
 </template>
 
 <style scoped>

+ 22 - 26
src/views/efTools/automation/index.vue

@@ -1,5 +1,5 @@
 <script lang="ts" setup>
-import { onMounted, provide, reactive, ref, Ref, watch } from 'vue';
+import { onMounted, provide, reactive, ref, Ref } from 'vue';
 import { Search } from '@element-plus/icons-vue';
 import { TemplateType } from '../utils/enum';
 import { DelObj, GetList } from '/@/views/efTools/automation/api';
@@ -128,7 +128,7 @@ const gridOptions = reactive({
     pageSizes: [10, 20, 30],
   },
   columns: [
-    { field: 'id', title: 'ID' ,width:140},
+    { field: 'id', title: 'ID', width: 140 },
     { field: 'name', title: '模板名称' },
     {
       field: 'rule.type',
@@ -161,11 +161,25 @@ const gridEvents = {
   },
 };
 
-watch(templateType, () => {
+function handleTypeChange() {
+  localStorage.setItem('templateType', JSON.stringify(templateType.value));
   getList();
-});
+}
+
+function handleTemplateListChange() {
+  localStorage.setItem('templateList', JSON.stringify(templateList.value));
+  getList();
+}
 
 async function getList() {
+  const savedTemplateType = localStorage.getItem('templateType');
+  if (savedTemplateType) {
+    templateType.value = JSON.parse(savedTemplateType);
+  }
+  const savedTemplateList = localStorage.getItem('templateList');
+  if (savedTemplateList) {
+    templateList.value = JSON.parse(savedTemplateList);
+  }
   try {
     gridOptions.loading = true;
     const response = await GetList({
@@ -197,25 +211,6 @@ function getTemplateTypeLabel(type: number) {
   return { label: '', type: '' };
 }
 
-function getTagType(type) {
-  switch (type) {
-    case 1:
-      return 'danger'; // 分时调价
-    case 2:
-      return 'success'; // 分时预算
-    case 3:
-      return 'info'; // 广告活动
-    case 4:
-      return 'warning'; // 定向规则
-    case 5:
-      return 'primary'; // 添加搜索词
-    case 6:
-      return ''; // 添加否定词
-    default:
-      return '';
-  }
-}
-
 const cellStyle = () => {
   return {
     fontSize: '13px',
@@ -244,9 +239,10 @@ onMounted(() => {
                 clearable
                 placeholder="模板名称"
                 style="width: 240px"
-                @change="getList"
+                @change="handleTemplateListChange"
       />
-      <el-select v-model="templateType" placeholder="Select" style="width: 240px">
+      <el-select v-model="templateType" placeholder="所有类型" style="width: 240px" value-key="value"
+                 @change="handleTypeChange">
         <el-option
             v-for="item in TemplateType"
             :key="item.value"
@@ -294,7 +290,7 @@ onMounted(() => {
           @refresh="refreshTable"></component>
     </div>
   </el-drawer>
-  <AdActivityDialog v-model="isDialogVisible" :templateId="templateId" :activeModel="activeModel"/>
+  <AdActivityDialog v-model="isDialogVisible" :activeModel="activeModel" :templateId="templateId" @confirm="handleConfirm"/>
   <AutomatedRuleTips v-model="autoInfo"></AutomatedRuleTips>
 </template>
 

+ 3 - 3
src/views/efTools/utils/enum.ts

@@ -10,7 +10,7 @@ export const TemplateType = [
 
 export const MatchType = [
   { label: '全部匹配方式', value: '' },
-  { label: '广泛匹配', value: 1 },
-  { label: '词组匹配', value: 2 },
-  { label: '精准匹配', value: 3 },
+  { label: '广泛匹配', value: 'BROAD' },
+  { label: '词组匹配', value: 'PHRASE' },
+  { label: '精准匹配', value: 'EXACT' },
 ];

+ 9 - 1
src/views/reportManage/TaskManage/api.ts

@@ -90,4 +90,12 @@ export function postSendMessage(body) {
     method: 'POST',
     data: body,
   });
-}
+}
+
+export function postNoticePerson(body) {
+  return request({
+    url: `/api/report_manage/summary-tasks/sendinform/people/`,
+    method: 'POST',
+    data: body,
+  });
+}

+ 126 - 0
src/views/reportManage/TaskManage/components/shopInformation.vue

@@ -0,0 +1,126 @@
+<script lang="ts" setup>
+/**
+ * @Name: shopInformation.vue
+ * @Description: 店铺信息详情页
+ * @Author: xinyan
+ */
+
+import Selector from '/@/views/reportManage/dataCenter/normalDisplay/components/Selector/index.vue';
+import { shopInfoColumns } from '/@/views/reportManage/TaskManage/utils/columns';
+import { VxeGridListeners } from 'vxe-table';
+import { onMounted, ref } from 'vue';
+import { getTasks } from '/@/views/reportManage/TaskManage/api';
+
+const selectorRef = ref(null);
+const filter = ref({}); // 筛选条件
+
+const gridOptions = reactive({
+  border: 'inner',
+  height: 930,
+  loading: false,
+  round: true,
+  rowConfig: {
+    isHover: true,
+  },
+  pagerConfig: {
+    enabled: true,
+    total: 20,
+    currentPage: 1,
+    pageSize: 20,
+    pageSizes: [10, 20, 30],
+  },
+  columns: shopInfoColumns,
+  data: [],
+});
+
+const gridEvents: VxeGridListeners<RowVO> = {
+  pageChange({ currentPage, pageSize }) {
+    if (gridOptions.pagerConfig) {
+      gridOptions.pagerConfig.currentPage = currentPage;
+      gridOptions.pagerConfig.pageSize = pageSize;
+      getTaskList();
+    }
+  },
+};
+
+// 获取任务列表
+async function getTaskList() {
+  try {
+    gridOptions.loading = true;
+    const response = await getTasks({
+      page: gridOptions.pagerConfig.currentPage,
+      limit: gridOptions.pagerConfig.pageSize,
+      ...filter.value,
+    });
+    gridOptions.data = response.data;
+    gridOptions.pagerConfig.total = response.total;
+  } catch (error) {
+    console.error('Error fetching task data:', error);
+  } finally {
+    gridOptions.loading = false;
+  }
+}
+
+// 筛选器变化
+function filteredDataChange(newList) {
+  filter.value = newList.value;
+  if (selectorRef.value) {
+    if (gridOptions.pagerConfig) {
+      gridOptions.pagerConfig.currentPage = 1;
+    }
+    getTaskList(newList.value);
+  }
+}
+
+// 表格样式
+const cellStyle = () => {
+  return {
+    fontSize: '12px',
+    fontWeight: '600',
+  };
+};
+
+const headerCellStyle = () => {
+  return {
+    fontSize: '14px',
+  };
+};
+
+onMounted(() => {
+  getTaskList();
+  //fetchOperationSelect();
+});
+</script>
+
+<template>
+  <div>
+    <el-card class=" my-3 mx-8" shadow="hover">
+      <Selector ref="selectorRef" :showOperationSearch="true" @update:filteredData="filteredDataChange" />
+    </el-card>
+  </div>
+  <el-card class="mx-8" shadow="hover">
+    <vxe-grid ref="xGrid" :cell-style="cellStyle" :header-cell-style="headerCellStyle" stripe
+              v-bind="gridOptions"
+              v-on="gridEvents">
+      <template #status_default="{ row }">
+        <el-switch
+            v-model="row.status"
+            disabled
+            :active-value="1"
+            :inactive-value="0"
+            inline-prompt
+            size="small"
+        />
+      </template>
+    </vxe-grid>
+  </el-card>
+
+</template>
+
+<style scoped>
+.el-card {
+  border: none;
+  box-shadow: none;
+}
+
+</style>

+ 227 - 170
src/views/reportManage/TaskManage/index.vue

@@ -9,14 +9,16 @@ import {
   getTasks,
   postCreateTask,
   postDeleteTask,
+  postNoticePerson,
   postSendMessage,
   postUpdateManyTask,
   postUpdateTask,
   postUpdateTaskStatus
 } from '/src/views/reportManage/TaskManage/api.ts';
 import { ComponentSize, ElMessage, FormInstance, FormRules } from 'element-plus';
-import { Delete, Plus } from '@element-plus/icons-vue';
+import { Delete, Message, Plus } from '@element-plus/icons-vue';
 import { dateType, requiredFields } from '/@/views/reportManage/TaskManage/utils/enum';
+import { taskColumns } from '/@/views/reportManage/TaskManage/utils/columns';
 
 const selectorRef = ref(null);
 const message = ref('');
@@ -36,6 +38,15 @@ interface taskRuleForm {
   ipaddress: string;
   company: string;
   platform: string;
+  companyEnglishName: string;
+  address: string;
+  juridicalPerson: string;
+  juridicalPersonCreditCard: string;
+  juridicalPersonCreditCardAddress: string;
+  receivablesAccount: string;
+  receivablesAccountCompany: string;
+  vatNumber: string;
+  vatCompany: string;
 }
 
 const formSize = ref<ComponentSize>('default');
@@ -74,6 +85,10 @@ const rules = reactive<FormRules>({
 const userDialogFormVisible = ref(false);
 const updateSelect = ref(1);
 
+//发送通知弹窗
+const noticeDialogFormVisible = ref(false);
+const noticeMessage = ref(null);
+
 //表格
 interface RowVO {
   platformNumber: string;
@@ -102,7 +117,7 @@ const gridOptions = reactive<VxeGridProps<RowVO>>({
   border: 'inner',
   keepSource: true,
   //showOverflow: true,
-  height: 930,
+  height: 780,
   loading: false,
   round: true,
   toolbarConfig: {
@@ -139,95 +154,7 @@ const gridOptions = reactive<VxeGridProps<RowVO>>({
     highlight: true,
     range: true,
   },
-  columns: [
-    { type: 'checkbox', width: 50 },
-    {
-      field: 'platformNumber',
-      title: '平台编号',
-      editRender: { autofocus: '.vxe-input--inner' },
-      slots: { edit: 'number_edit' },
-      minWidth: 97
-    },
-    {
-      field: 'platformName',
-      title: '平台名称',
-      editRender: { autofocus: '.vxe-input--inner' },
-      slots: { edit: 'name_edit' },
-      align: 'center',
-      minWidth: 150, isEditing: false
-    },
-    {
-      field: 'country',
-      title: '国家',
-      editRender: { autofocus: '.vxe-input--inner' },
-      slots: { edit: 'country_edit' },
-      minWidth: 89,
-      align: 'center'
-    },
-    {
-      field: 'brandName',
-      title: '品牌',
-      editRender: {},
-      slots: { edit: 'brand_edit' },
-      align: 'center',
-      minWidth: 89,
-    },
-    {
-      field: 'user_name',
-      title: '填写人',
-      editRender: {},
-      slots: {
-        edit: 'operation_edit',
-        //default: 'operation_default'
-      },
-      align: 'center',
-      minWidth: 104
-    },
-    {
-      field: 'operater',
-      title: '运营',
-      editRender: {},
-      slots: { edit: 'operater_name_edit' },
-      align: 'center',
-      minWidth: 104
-    },
-    {
-      field: 'currencyCode',
-      title: '平台币种',
-      editRender: {},
-      slots: { edit: 'currency_edit' },
-      align: 'center',
-      minWidth: 95
-    },
-    {
-      field: 'currencyCodePlatform',
-      title: '回款/余额币种',
-      editRender: {},
-      slots: { edit: 'currencyCodePlatform_edit' },
-      minWidth: 130,
-      align: 'center'
-    },
-    { field: 'line', title: '线路', editRender: {}, slots: { edit: 'line_edit' }, align: 'center', minWidth: 89 },
-    { field: 'ipaddress', title: 'IP地址', editRender: {}, slots: { edit: 'ipaddress_edit' }, minWidth: 138 },
-    {
-      field: 'company',
-      title: '注册公司',
-      editRender: {},
-      slots: { edit: 'company_edit' },
-      align: 'center',
-      minWidth: 89
-    },
-    {
-      field: 'platform',
-      title: '平台',
-      editRender: {},
-      slots: { edit: 'platform_edit' },
-      align: 'center',
-      minWidth: 89
-    },
-    { field: 'status', title: '状态', slots: { default: 'status_default' }, align: 'center', minWidth: 89 },
-    { title: '操作', width: 120, slots: { default: 'operate' } },
-  ],
+  columns: taskColumns,
   data: [],
 });
 
@@ -476,6 +403,36 @@ function updateUserCancel() {
   userDialogFormVisible.value = false;
 }
 
+// 指定人通知
+async function noticePerson() {
+  const $grid = xGrid.value;
+  if ($grid) {
+    const selectRecords = $grid.getCheckboxRecords();
+    const selectedIds = selectRecords.map(record => record.id);
+    const obj = {
+      task_list: selectedIds,
+      message: noticeMessage.value,
+    };
+    try {
+      const response = await postNoticePerson(obj);
+      if (response.code === 2000) {
+        ElMessage.success('通知成功');
+        noticeDialogFormVisible.value = false; // 关闭弹窗
+      } else {
+        ElMessage.error('通知失败');
+      }
+    } catch (error) {
+      ElMessage.error('通知失败');
+    }
+  }
+}
+
+
+function noticePersonCancel() {
+  noticeMessage.value = null;
+  noticeDialogFormVisible.value = false; // 关闭弹窗
+}
+
 // 删除任务
 async function deleteTask() {
   const $grid = xGrid.value;
@@ -558,6 +515,15 @@ async function updateRow(row) {
       ipaddress: row.ipaddress,
       company: row.company,
       platform: row.platform,
+      companyEnglishName: row.companyEnglishName,
+      address: row.address,
+      juridicalPerson: row.juridicalPerson,
+      juridicalPersonCreditCard: row.juridicalPersonCreditCard,
+      juridicalPersonCreditCardAddress: row.juridicalPersonCreditCardAddress,
+      receivablesAccount: row.receivablesAccount,
+      receivablesAccountCompany: row.receivablesAccountCompany,
+      vatNumber: row.vatNumber,
+      vatCompany: row.vatCompany,
     };
     try {
       const response = await postUpdateTask(updatedRowData);
@@ -630,6 +596,15 @@ async function createTask() {
     ipaddress: taskRuleForm.ipaddress,
     company: taskRuleForm.company,
     platform: taskRuleForm.platform,
+    companyEnglishName: taskRuleForm.companyEnglishName,
+    address: taskRuleForm.address,
+    juridicalPerson: taskRuleForm.juridicalPerson,
+    juridicalPersonCreditCard: taskRuleForm.juridicalPersonCreditCard,
+    juridicalPersonCreditCardAddress: taskRuleForm.juridicalPersonCreditCardAddress,
+    receivablesAccount: taskRuleForm.receivablesAccount,
+    receivablesAccountCompany: taskRuleForm.receivablesAccountCompany,
+    vatNumber: taskRuleForm.vatNumber,
+    vatCompany: taskRuleForm.vatCompany,
     user: taskRuleForm.operation,
     operater: [taskRuleForm.operater],
   };
@@ -698,17 +673,20 @@ async function sendMessage(selectedValue: string) {
 
 // 导出接口
 async function handleExport() {
-  gridOptions.loading = true;
-  const response = await exportTaskData();
-
-  const url = window.URL.createObjectURL(new Blob([response.data]));
-  const link = document.createElement('a');
-  link.href = url;
-  link.setAttribute('download', '店铺数据.xlsx');
-  document.body.appendChild(link);
-  link.click();
-  gridOptions.loading = false;
-  ElMessage.success('导出数据成功');
+  try {
+    gridOptions.loading = true;
+    const response = await exportTaskData(filter.value,);
+    const url = window.URL.createObjectURL(new Blob([response.data]));
+    const link = document.createElement('a');
+    link.href = url;
+    link.setAttribute('download', '店铺数据.xlsx');
+    document.body.appendChild(link);
+    link.click();
+    gridOptions.loading = false;
+    ElMessage.success('导出数据成功');
+  } catch (error) {
+    console.error('导出数据失败:', error);
+  }
 }
 
 // 获取填写人下拉框
@@ -756,17 +734,71 @@ function handelRowCurrencyCodePlatformSelect(item, row) {
   row.currencyCodePlatform = item;
 }
 
+const formItems = ref([
+  { label: '平台编号', prop: 'number', type: 'input', placeholder: '请输入平台编号' },
+  { label: '平台名称', prop: 'name', type: 'input', placeholder: '请输入平台名称' },
+  { label: '国家', prop: 'country', type: 'input', placeholder: '请输入国家' },
+  { label: '品牌', prop: 'brand', type: 'input', placeholder: '请输入品牌' },
+  {
+    label: '录入人员',
+    prop: 'operation',
+    type: 'select',
+    placeholder: '请选择录入人员',
+    multiple: true,
+    collapseTags: true,
+    collapseTagsTooltip: true,
+    options: operationList
+  },
+  { label: '运营', prop: 'operater', type: 'input', placeholder: '请输入运营' },
+  {
+    label: '平台币种',
+    prop: 'currency',
+    type: 'autocomplete',
+    placeholder: '请输入回款币种',
+    debounce: 100,
+    fetchSuggestions: querySearch,
+    triggerOnFocus: false,
+    clearable: true,
+    onSelect: handleSelect
+  },
+  {
+    label: '回款/余额币种',
+    prop: 'currencyCodePlatform',
+    type: 'autocomplete',
+    placeholder: '请输入回款/余额币种',
+    debounce: 100,
+    fetchSuggestions: querySearch,
+    triggerOnFocus: false,
+    clearable: true,
+    onSelect: handleCurrencyCodePlatformSelect
+  },
+  { label: '平台', prop: 'platform', type: 'input', placeholder: '请输入平台' },
+  { label: '线路', prop: 'line', type: 'input', placeholder: '请输入线路' },
+  { label: 'IP地址', prop: 'ipaddress', type: 'input', placeholder: '请输入IP地址' },
+  { label: '注册公司', prop: 'company', type: 'input', placeholder: '请输入注册公司' },
+  { label: '公司英文名称', prop: 'companyEnglishName', type: 'input', placeholder: '请输入公司英文名称' },
+  { label: '公司地址', prop: 'address', type: 'input', placeholder: '请输入公司地址' },
+  { label: '公司法人', prop: 'juridicalPerson', type: 'input', placeholder: '请输入公司法人' },
+  { label: '法人信用卡', prop: 'juridicalPersonCreditCard', type: 'input', placeholder: '请输入法人信用卡' },
+  { label: '信用卡地址', prop: 'juridicalPersonCreditCardAddress', type: 'input', placeholder: '请输入信用卡地址' },
+  { label: '收款账号', prop: 'receivablesAccount', type: 'input', placeholder: '请输入收款账号' },
+  { label: '收款账号公司', prop: 'receivablesAccountCompany', type: 'input', placeholder: '请输入收款账号公司' },
+  { label: 'VAT税号', prop: 'vatNumber', type: 'input', placeholder: '请输入VAT税号' },
+  { label: 'VAT税号公司名称', prop: 'vatCompany', type: 'input', placeholder: '请输入VAT税号公司名称' },
+]);
+
 // 表格样式
 const cellStyle = () => {
   return {
-    fontSize: '13px',
-    fontWeight: '500',
+    fontSize: '12px',
+    fontWeight: '600',
+    padding: 0,
   };
 };
 
 const headerCellStyle = () => {
   return {
-    fontSize: '14px',
+    fontSize: '13px',
   };
 };
 
@@ -781,13 +813,13 @@ onMounted(() => {
 <template>
   <div class="p-2.5">
     <el-card class="custom-card-style flex gap-1.5 justify-between" shadow="hover">
-      <Selector ref="selectorRef" :showOperationSearch="true" @update:filteredData="filteredDataChange" />
+      <Selector ref="selectorRef" @update:filteredData="filteredDataChange" />
     </el-card>
     <el-card class="my-3" shadow="hover">
       <vxe-grid ref="xGrid" :cell-style="cellStyle" :header-cell-style="headerCellStyle" stripe v-bind="gridOptions"
                 v-on="gridEvents" @edit-actived="handleEditActived" @edit-closed="handleEditClosed">
         <template #toolbar_buttons>
-          <el-button :icon="Plus"  type="primary" @click="dialogFormVisible = true"> 添加任务</el-button>
+          <el-button :icon="Plus" type="primary" @click="dialogFormVisible = true"> 添加任务</el-button>
           <!--<el-button plain type="success" @click="saveEvent">保存</el-button>-->
           <el-button :disabled="isDeleteDisabled" :icon="Delete" plain type="danger" @click="removeEvent">删除
           </el-button>
@@ -797,6 +829,9 @@ onMounted(() => {
         </template>
         <template #toolbar_tools>
           <div class="pr-2.5">
+            <el-tooltip content="指定店铺发送通知" placement="top">
+              <vxe-button v-if="!isDeleteDisabled" plain circle icon="vxe-icon-envelope" @click="noticeDialogFormVisible =true"></vxe-button>
+            </el-tooltip>
             <el-tooltip content="保存" placement="top">
               <vxe-button circle icon="vxe-icon-save" @click="saveEvent"></vxe-button>
             </el-tooltip>
@@ -905,6 +940,33 @@ onMounted(() => {
             </template>
           </el-autocomplete>
         </template>
+        <template #companyEnglishName_edit="{ row }">
+          <vxe-input v-model="row.companyEnglishName"></vxe-input>
+        </template>
+        <template #address_edit="{ row }">
+          <vxe-input v-model="row.address"></vxe-input>
+        </template>
+        <template #juridicalPerson_edit="{ row }">
+          <vxe-input v-model="row.juridicalPerson"></vxe-input>
+        </template>
+        <template #juridicalPersonCreditCard_edit="{ row }">
+          <vxe-input v-model="row.juridicalPersonCreditCard"></vxe-input>
+        </template>
+        <template #juridicalPersonCreditCardAddress_edit="{ row }">
+          <vxe-input v-model="row.juridicalPersonCreditCardAddress"></vxe-input>
+        </template>
+        <template #receivablesAccount_edit="{ row }">
+          <vxe-input v-model="row.receivablesAccount"></vxe-input>
+        </template>
+        <template #receivablesAccountCompany_edit="{ row }">
+          <vxe-input v-model="row.receivablesAccountCompany"></vxe-input>
+        </template>
+        <template #vatNumber_edit="{ row }">
+          <vxe-input v-model="row.vatNumber"></vxe-input>
+        </template>
+        <template #vatCompany_edit="{ row }">
+          <vxe-input v-model="row.vatCompany"></vxe-input>
+        </template>
       </vxe-grid>
     </el-card>
   </div>
@@ -919,69 +981,46 @@ onMounted(() => {
         label-width="auto"
         status-icon
         style="max-width: 600px">
-      <el-form-item label="平台编号" prop="number">
-        <el-input v-model="taskRuleForm.number" placeholder="请输入平台编号" />
-      </el-form-item>
-      <el-form-item label="平台名称" prop="name">
-        <el-input v-model="taskRuleForm.name" placeholder="请输入平台名称" />
-      </el-form-item>
-      <el-form-item label="国家" prop="country">
-        <el-input v-model="taskRuleForm.country" placeholder="请输入国家" />
-      </el-form-item>
-      <el-form-item label="品牌" prop="brand">
-        <el-input v-model="taskRuleForm.brand" placeholder="请输入品牌" />
-      </el-form-item>
-      <el-form-item label="录入人员" prop="operation">
-        <el-select v-model="taskRuleForm.operation" collapse-tags collapse-tags-tooltip multiple
-                   placeholder="请选择录入人员">
-          <el-option v-for="item in operationList" :key="item.value" :label="item.label"
-                     :value="item.value"></el-option>
-        </el-select>
-      </el-form-item>
-      <el-form-item label="运营" prop="operater">
-        <el-input v-model="taskRuleForm.operater" placeholder="请输入运营" />
-      </el-form-item>
-      <el-form-item label="平台币种" prop="currency">
-        <el-autocomplete
-            v-model="taskRuleForm.currency"
-            :debounce="100"
-            :fetch-suggestions="querySearch"
-            :trigger-on-focus="false"
-            clearable
-            placeholder="请输入回款币种"
-            @select="handleSelect"
-        >
-          <template v-slot="{ item }">
-            <div>{{ item }}</div> <!-- 确保正确显示每个选项 -->
-          </template>
-        </el-autocomplete>
-      </el-form-item>
-      <el-form-item label="回款/余额币种" prop="currencyCodePlatform">
-        <el-autocomplete
-            v-model="taskRuleForm.currencyCodePlatform"
-            :debounce="100"
-            :fetch-suggestions="querySearch"
-            :trigger-on-focus="false"
-            clearable
-            placeholder="请输入回款/余额币种"
-            @select="handleCurrencyCodePlatformSelect"
-        >
-          <template v-slot="{ item }">
-            <div>{{ item }}</div> <!-- 确保正确显示每个选项 -->
-          </template>
-        </el-autocomplete>
-      </el-form-item>
-      <el-form-item label="线路" prop="line">
-        <el-input v-model="taskRuleForm.line" placeholder="请输入线路" />
-      </el-form-item>
-      <el-form-item label="IP地址" prop="ipaddress">
-        <el-input v-model="taskRuleForm.ipaddress" placeholder="请输入IP地址" />
-      </el-form-item>
-      <el-form-item label="注册公司" prop="company">
-        <el-input v-model="taskRuleForm.company" placeholder="请输入注册公司" />
-      </el-form-item>
-      <el-form-item label="平台" prop="platform">
-        <el-input v-model="taskRuleForm.platform" placeholder="请输入平台" />
+      <el-form-item
+          v-for="(item, index) in formItems"
+          :key="index"
+          :label="item.label"
+          :prop="item.prop"
+      >
+        <template v-if="item.type === 'input'">
+          <el-input v-model="taskRuleForm[item.prop]" :placeholder="item.placeholder" />
+        </template>
+        <template v-else-if="item.type === 'select'">
+          <el-select
+              v-model="taskRuleForm[item.prop]"
+              :collapse-tags="item.collapseTags"
+              :collapse-tags-tooltip="item.collapseTagsTooltip"
+              :multiple="item.multiple"
+              :placeholder="item.placeholder"
+          >
+            <el-option
+                v-for="option in item.options"
+                :key="option.value"
+                :label="option.label"
+                :value="option.value"
+            ></el-option>
+          </el-select>
+        </template>
+        <template v-else-if="item.type === 'autocomplete'">
+          <el-autocomplete
+              v-model="taskRuleForm[item.prop]"
+              :clearable="item.clearable"
+              :debounce="item.debounce"
+              :fetch-suggestions="item.fetchSuggestions"
+              :placeholder="item.placeholder"
+              :trigger-on-focus="item.triggerOnFocus"
+              @select="item.onSelect"
+          >
+            <template v-slot="{ item }">
+              <div>{{ item }}</div>
+            </template>
+          </el-autocomplete>
+        </template>
       </el-form-item>
     </el-form>
     <template #footer>
@@ -1013,6 +1052,18 @@ onMounted(() => {
       </div>
     </template>
   </el-dialog>
+  <el-dialog v-model="noticeDialogFormVisible" align-center
+             style="border-radius: 10px;" title="指定店铺发送通知" width="500">
+    <el-form-item align-center label="消息内容:" width="500">
+      <el-input v-model="noticeMessage" type="textarea"></el-input>
+    </el-form-item>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="noticePersonCancel">取消</el-button>
+        <el-button type="primary" @click="noticePerson"> 确认</el-button>
+      </div>
+    </template>
+  </el-dialog>
 </template>
 
 <style scoped>
@@ -1027,4 +1078,10 @@ onMounted(() => {
   border: none;
   box-shadow: none;
 }
+
+:deep(.vxe-table--header .vxe-header--row th .vxe-cell,
+.vxe-table--body .vxe-body--row td .vxe-cell) {
+  padding-left: 5px !important;
+  padding-right: 0 !important;
+}
 </style>

+ 274 - 0
src/views/reportManage/TaskManage/utils/columns.ts

@@ -0,0 +1,274 @@
+import { ref } from 'vue';
+
+// 任务管理表格列
+export const taskColumns = ref([
+  { type: 'checkbox', width: 50 },
+  {
+    field: 'platformNumber',
+    title: '平台编号',
+    editRender: { autofocus: '.vxe-input--inner' },
+    slots: { edit: 'number_edit' },
+    minWidth: 87
+  },
+  {
+    field: 'platformName',
+    title: '平台名称',
+    editRender: { autofocus: '.vxe-input--inner' },
+    slots: { edit: 'name_edit' },
+    align: 'center',
+    minWidth: 138, isEditing: false
+  },
+  {
+    field: 'country',
+    title: '国家',
+    editRender: { autofocus: '.vxe-input--inner' },
+    slots: { edit: 'country_edit' },
+    minWidth: 85,
+    align: 'center'
+  },
+  {
+    field: 'brandName',
+    title: '品牌',
+    editRender: {},
+    slots: { edit: 'brand_edit' },
+    align: 'center',
+    minWidth: 89,
+  },
+  {
+    field: 'user_name',
+    title: '填写人',
+    editRender: {},
+    slots: {
+      edit: 'operation_edit',
+      //default: 'operation_default'
+    },
+    align: 'center',
+    minWidth: 104
+  },
+  {
+    field: 'operater',
+    title: '运营',
+    editRender: {},
+    slots: { edit: 'operater_name_edit' },
+    align: 'center',
+    minWidth: 104
+  },
+  {
+    field: 'currencyCode',
+    title: '平台币种',
+    editRender: {},
+    slots: { edit: 'currency_edit' },
+    align: 'center',
+    minWidth: 90
+  },
+  {
+    field: 'currencyCodePlatform',
+    title: '回款/余额币种',
+    editRender: {},
+    slots: { edit: 'currencyCodePlatform_edit' },
+    minWidth: 120,
+    align: 'center'
+  },
+  {
+    field: 'platform',
+    title: '平台',
+    editRender: {},
+    slots: { edit: 'platform_edit' },
+    align: 'center',
+    minWidth: 89
+  },
+  { field: 'line', title: '线路', editRender: {}, slots: { edit: 'line_edit' }, align: 'center', minWidth: 89 },
+  { field: 'ipaddress', title: 'IP地址', editRender: {}, slots: { edit: 'ipaddress_edit' }, minWidth: 135 },
+  {
+    field: 'company',
+    title: '注册公司',
+    editRender: {},
+    slots: { edit: 'company_edit' },
+    align: 'center',
+    minWidth: 184
+  },
+  {
+    field: 'companyEnglishName',
+    title: '公司英文名称',
+    editRender: {},
+    slots: { edit: 'companyEnglishName_edit' },
+    align: 'center',
+    minWidth: 126
+  },
+  {
+    field: 'address',
+    title: '公司地址',
+    editRender: {},
+    slots: { edit: 'address_edit' },
+    align: 'center',
+    minWidth: 262
+  },
+  {
+    field: 'juridicalPerson',
+    title: '公司法人',
+    editRender: {},
+    slots: { edit: 'juridicalPerson_edit' },
+    align: 'center',
+    minWidth: 95
+  },
+  {
+    field: 'juridicalPersonCreditCard',
+    title: '法人信用卡',
+    editRender: {},
+    slots: { edit: 'juridicalPersonCreditCard_edit' },
+    align: 'center',
+    minWidth: 105
+  },
+  {
+    field: 'juridicalPersonCreditCardAddress',
+    title: '信用卡地址',
+    editRender: {},
+    slots: { edit: 'juridicalPersonCreditCardAddress_edit' },
+    align: 'center',
+    minWidth: 262
+  },
+  {
+    field: 'receivablesAccount',
+    title: '收款账号',
+    editRender: {},
+    slots: { edit: 'receivablesAccount_edit' },
+    align: 'center',
+    minWidth: 156
+  },
+  {
+    field: 'receivablesAccountCompany',
+    title: '收款账号公司',
+    editRender: {},
+    slots: { edit: 'receivablesAccountCompany_edit' },
+    align: 'center',
+    minWidth: 115
+  },
+  {
+    field: 'vatNumber',
+    title: 'VAT税号',
+    editRender: {},
+    slots: { edit: 'vatNumber_edit' },
+    align: 'center',
+    minWidth: 95
+  },
+  {
+    field: 'vatCompany',
+    title: 'VAT税号公司名称',
+    editRender: {},
+    slots: { edit: 'vatCompany_edit' },
+    align: 'center',
+    minWidth: 135
+  },
+
+  { field: 'status', title: '状态', slots: { default: 'status_default' }, align: 'center', minWidth: 89 },
+  { title: '操作', minWidth: 108, slots: { default: 'operate' }, fixed: 'right' },
+]);
+
+export const shopInfoColumns = ref([
+  {
+    field: 'platformNumber',
+    title: '平台编号',
+    minWidth: 87
+  },
+  {
+    field: 'platformName',
+    title: '平台名称',
+    align: 'center',
+    minWidth: 138, isEditing: false
+  },
+  {
+    field: 'country',
+    title: '国家',
+    minWidth: 85,
+    align: 'center'
+  },
+  {
+    field: 'brandName',
+    title: '品牌',
+    align: 'center',
+    minWidth: 89,
+  },
+  {
+    field: 'user_name',
+    title: '填写人',
+    align: 'center',
+    minWidth: 104
+  },
+  {
+    field: 'operater',
+    title: '运营',
+    align: 'center',
+    minWidth: 104
+  },
+  {
+    field: 'currencyCode',
+    title: '平台币种',
+    align: 'center',
+    minWidth: 90
+  },
+  {
+    field: 'currencyCodePlatform',
+    title: '回款/余额币种',
+    minWidth: 120,
+    align: 'center'
+  },
+  {
+    field: 'platform',
+    title: '平台',
+    align: 'center',
+    minWidth: 89
+  },
+  { field: 'line', title: '线路', align: 'center', minWidth: 89 },
+  { field: 'ipaddress', title: 'IP地址', minWidth: 135 },
+  {
+    field: 'company',
+    title: '注册公司',
+    align: 'center',
+    minWidth: 184
+  },
+  {
+    field: 'companyEnglishName',
+    title: '公司英文名称',
+    align: 'center',
+    minWidth: 120
+  },
+  {
+    field: 'address',
+    title: '公司地址',
+    align: 'center',
+    minWidth: 262
+  },
+  {
+    field: 'juridicalPerson',
+    title: '公司法人',
+    align: 'center',
+    minWidth: 95
+  },
+  {
+    field: 'juridicalPersonCreditCard',
+    title: '法人信用卡',
+    align: 'center',
+    minWidth: 105
+  },
+  {
+    field: 'juridicalPersonCreditCardAddress',
+    title: '信用卡地址',
+    align: 'center',
+    minWidth: 262
+  },
+  {
+    field: 'receivablesAccount',
+    title: '收款账号',
+    align: 'center',
+    minWidth: 156
+  },
+  {
+    field: 'receivablesAccountCompany',
+    title: '收款账号公司',
+    align: 'center',
+    minWidth: 115
+  },
+  { field: 'vatNumber', title: 'VAT税号', align: 'center', minWidth: 95 },
+  { field: 'vatCompany', title: 'VAT税号公司名称', align: 'center', minWidth: 135 },
+  { field: 'status', title: '状态', slots: { default: 'status_default' }, align: 'center', minWidth: 89 },
+]);

+ 1 - 1
src/views/reportManage/TaskManage/utils/enum.ts

@@ -2,7 +2,7 @@ export const dateType = [
   { value: 'day', label: '日', },
   { value: 'week', label: '周', },
   { value: 'month', label: '月', },
-]
+];
 
 export const requiredFields = [
   { field: 'platformNumber', title: '平台编号' },

+ 37 - 13
src/views/reportManage/dataCenter/api.ts

@@ -1,7 +1,6 @@
 import { request } from '/@/utils/service';
-import { UserPageQuery } from '@fast-crud/fast-crud';
 
-//卡片日数据
+// 卡片日数据
 export function getCardDayData(query) {
   return request({
     url: '/api/report_manage/data-day/sum/',
@@ -25,7 +24,8 @@ export function getCardMonthData(query) {
     params: query,
   });
 }
-//条形图日数据
+
+// 条形图日数据
 export function getLineForDay(query) {
   return request({
     url: '/api/report_manage/data-day/daily/',
@@ -50,7 +50,7 @@ export function getLineForMonth(query) {
   });
 }
 
-//合并展示月对比数据
+// 合并展示月对比数据
 export function getLineData(query) {
   return request({
     url: '/api/report_manage/data-month/compare/plan/',
@@ -84,7 +84,7 @@ export function getMonthData(query) {
   });
 }
 
-//普通展示表格数据汇总
+// 普通展示表格数据汇总
 export function getDayTotalData(query) {
   return request({
     url: `/api/report_manage/data-day/list/sum/`,
@@ -109,8 +109,7 @@ export function getMonthTotalData(query) {
   });
 }
 
-
-//日数据插入
+// 日数据插入
 export function postCreateDayData(body) {
   return request({
     url: `/api/report_manage/data-day/`,
@@ -160,10 +159,10 @@ export function getMonthTaskData(query) {
   });
 }
 
-//日数据更新
+// 日数据更新
 export function postUpdateDayData(body) {
   return request({
-    url: `/api/report_manage/data-day/${body.id}/`,
+    url: `/api/report_manage/data-day/${ body.id }/`,
     method: 'POST',
     data: body,
   });
@@ -171,7 +170,7 @@ export function postUpdateDayData(body) {
 
 export function postUpdateWeekData(body) {
   return request({
-    url: `/api/report_manage/data-week/${body.id}/`,
+    url: `/api/report_manage/data-week/${ body.id }/`,
     method: 'POST',
     data: body,
   });
@@ -179,13 +178,38 @@ export function postUpdateWeekData(body) {
 
 export function postUpdateMonthData(body) {
   return request({
-    url: `/api/report_manage/data-month/${body.id}/`,
+    url: `/api/report_manage/data-month/${ body.id }/`,
     method: 'POST',
     data: body,
   });
 }
 
-//主数据获取
+// 日数据前一天数据获取
+export function getDayBeforeData(query) {
+  return request({
+    url: '/api/report_manage/data-day/one/',
+    method: 'GET',
+    params: query,
+  });
+}
+
+export function getWeekBeforeData(query) {
+  return request({
+    url: '/api/report_manage/data-week/one/',
+    method: 'GET',
+    params: query,
+  });
+}
+
+export function getMonthBeforeData(query) {
+  return request({
+    url: '/api/report_manage/data-month/one/',
+    method: 'GET',
+    params: query,
+  });
+}
+
+// 主数据获取
 export function getMainData(query) {
   return request({
     url: '/api/report_manage/data-all/',
@@ -202,7 +226,7 @@ export function getMonthlyData(query) {
   });
 }
 
-//导出
+// 导出
 export function exportData(query) {
   return request({
     url: '/api/report_manage/data-all/download/',

+ 17 - 11
src/views/reportManage/dataCenter/combinedDisplay/components/DatePicker/index.vue

@@ -1,10 +1,11 @@
 <script lang="ts" setup>
-import { inject, onBeforeMount, onMounted, ref, Ref, watch } from 'vue';
+import { computed, inject, onMounted, ref, Ref } from 'vue';
 import dayjs from 'dayjs';
 import isoWeek from 'dayjs/plugin/isoWeek';
 import DateRangePicker from '/src/components/DateRangePicker/index.vue';
 import enLocale from 'element-plus/es/locale/lang/en';
 
+
 dayjs.extend(isoWeek);
 //import { storeToRefs } from 'pinia';
 //import { usePublicData } from '/src/stores/publicData';
@@ -17,13 +18,18 @@ const dateType = inject<Ref>('dateDimension');
 
 const dateRange = ref([
   dayjs().subtract(6, 'day').startOf('day').format('YYYY-MM-DD'),
-  dayjs().endOf('day').format('YYYY-MM-DD')
+  dayjs().subtract(1, 'day').endOf('day').format('YYYY-MM-DD')
 ]);
 
 const startWeek = ref(null);
 const endWeek = ref(null);
-const weekStartDate = ref<string | null>(dayjs().locale('en').subtract(1, 'week').startOf('week').format('YYYY-MM-DD'));
-const weekEndDate = ref<string | null>(dayjs().locale('en').endOf('week').format('YYYY-MM-DD'));
+const weekStartDate = ref<string | null>(dayjs().locale('en').subtract(2, 'week').startOf('week').format('YYYY-MM-DD'));console.log("=>(index.vue:27) weekStartDate", weekStartDate.value);
+const wEndDate = ref<string | null>(dayjs().locale('en').subtract(2, 'week').endOf('week').format('YYYY-MM-DD'));console.log("=>(index.vue:29) wEndDate", wEndDate.value);
+const wStartDate = ref<string | null>(dayjs().locale('en').subtract(1, 'week').startOf('week').format('YYYY-MM-DD'));
+const weekEndDate = ref<string | null>(dayjs().locale('en').subtract(1, 'week').endOf('week').format('YYYY-MM-DD'));
+
+const sFormat = computed(() => `${ weekStartDate.value } to ${ wEndDate.value }`);
+const wFormat = computed(() => `${ wStartDate.value } to ${ weekEndDate.value }`);
 
 const currentYear = new Date().getFullYear();
 const currentMonth = new Date().getMonth();
@@ -134,12 +140,12 @@ const weekDisabledDate = (time: Date) => {
 function setDefaultDate() {
   dateRange.value = [
     dayjs().subtract(6, 'day').startOf('day').format('YYYY-MM-DD'),
-    dayjs().endOf('day').format('YYYY-MM-DD')
+    dayjs().subtract(1, 'day').endOf('day').format('YYYY-MM-DD')
   ];
-  startWeek.value = dayjs().locale('en').subtract(1, 'week').startOf('week').format('YYYY-MM-DD');
-  endWeek.value = dayjs().locale('en').endOf('week').format('YYYY-MM-DD');
-  const startOfMonth = new Date(new Date().getFullYear(), new Date().getMonth() - 1, 1);
-  const endOfMonth = new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0); // 设置下个月的第一天减去1天,得到当前月的最后一天
+  startWeek.value = dayjs().locale('en').subtract(2, 'week').startOf('week').format('YYYY-MM-DD');
+  endWeek.value = dayjs().locale('en').subtract(1, 'week').endOf('week').format('YYYY-MM-DD');
+  const startOfMonth = new Date(new Date().getFullYear(), new Date().getMonth() - 2, 1);
+  const endOfMonth = new Date(new Date().getFullYear(), new Date().getMonth(), 0); // 设置下个月的第一天减去1天,得到当前月的最后一天
   monthlyDataTime.value = [startOfMonth, endOfMonth];
   const formattedStartDate = dayjs(startOfMonth).format('YYYY-MM-DD');
   const formattedEndDate = dayjs(endOfMonth).format('YYYY-MM-DD');
@@ -170,7 +176,7 @@ onMounted(() => {
               v-model="startWeek"
               :clearable="false"
               :disabled-date="disabledDate"
-              format="YYYY 第 WW 周"
+              :format="sFormat"
               placeholder="选择开始周"
               type="week"
               @change="handleStartWeekChange"
@@ -185,7 +191,7 @@ onMounted(() => {
               :clearable="false"
               :disabled="!startWeek"
               :disabled-date="weekDisabledDate"
-              format="YYYY 第 WW 周"
+              :format="wFormat"
               placeholder="选择结束周"
               type="week"
               @change="handleEndWeekChange"

+ 3 - 1
src/views/reportManage/dataCenter/combinedDisplay/components/monthDatePicker/index.vue

@@ -117,7 +117,9 @@ const disabledDate = (time) => {
   margin: 10px 0;
 }
 .demonstration {
-  font-size: 14px;
   color: var(--el-text-color-secondary);
+  font-size: 14px;
+  margin: 8px;
+  white-space: nowrap;
 }
 </style>

+ 46 - 13
src/views/reportManage/dataCenter/combinedDisplay/components/tableData/mainData.vue

@@ -39,6 +39,7 @@ const dateType = ref('');
 const salesOrder = ref(null);
 const salesField = ref(null);
 const sortStatus = ref(false);
+const totalSales = ref(null);
 
 const gridOptions = reactive({
   border: 'inner',
@@ -49,7 +50,7 @@ const gridOptions = reactive({
   id: 'mainDataCustomStorage',
   columnConfig: {
     resizable: true,
-    //isCurrent: true,
+    isCurrent: true,
   },
   rowConfig: {
     isHover: true,
@@ -167,6 +168,7 @@ async function fetchMainData(taskIds, resetPage = false) {
       sort: sortOrder.value,
       order_date: order_date.value,
       date_type: dateType.value,
+      order_total_sales_current_monthly: totalSales.value,
     });
     gridOptions.data = [totalRow.value, ...response.data];
     gridOptions.pagerConfig.total = response.total;
@@ -194,7 +196,7 @@ async function fetchMainData(taskIds, resetPage = false) {
 
         allColumns.forEach(key => {
           let isSortable = false;
-          if (key.includes('的销售额') && !key.includes('广告销售额') && !key.includes('增长率')) {
+          if (key.includes('的销售额') || key.includes('当月累计销售额')&& !key.includes('广告销售额') && !key.includes('增长率')) {
             isSortable = true;
           }
           const column = {
@@ -260,6 +262,11 @@ async function fetchMainData(taskIds, resetPage = false) {
   }
 }
 
+function clearSort() {
+  order_date.value = '';
+  salesOrder.value = null;
+}
+
 //排序
 function handleSortChange({ field, order }) {
   salesOrder.value = order;
@@ -269,21 +276,29 @@ function handleSortChange({ field, order }) {
     const match = field.match(/(\d{4}-\d{2}-\d{2})的销售额/);
     const matchRange = field.match(/(\d{4}-\d{2}-\d{2})~(\d{4}-\d{2}-\d{2})的销售额/);
     const matchMonth = field.match(/(\d{4}-\d{2})的销售额/);
+    const matchTotal = field.match(/当月累计销售额/);
     if (matchRange) {
       order_date.value = matchRange[1];
       dateType.value = 'week';
+      totalSales.value = '';
     } else if (match) {
       order_date.value = match[1];
       dateType.value = 'day';
+      totalSales.value = '';
     } else if (matchMonth) {
       order_date.value = `${ matchMonth[1] }-01`;
       dateType.value = 'month';
+      totalSales.value = '';
+    }else if (matchTotal) {
+      clearSort();
+      dateType.value = 'week';
+      totalSales.value = true;
     }
   }
   gridOptions.sortConfig.defaultSort.order = order;
   gridOptions.sortConfig.defaultSort.field = field;
   sortStatus.value = true;
-  fetchMainData(props.taskIds, true);
+  fetchMainData(taskIds.value, true);
 }
 
 // 导出数据接口
@@ -293,7 +308,7 @@ async function handleExport() {
     const params = {
       page: gridOptions.pagerConfig.currentPage,
       limit: gridOptions.pagerConfig.pageSize,
-      task_ids: props.taskIds,
+      task_ids: taskIds.value,
       day_start_date: dayStartDate.value,
       day_end_date: dayEndDate.value,
       week_start_date: weekStart.value,
@@ -335,16 +350,20 @@ function updateDataChange(newId) {
 }
 
 //以下是表格样式
-const cellStyle = ({ columnIndex }) => {
-  if (columnIndex < 6) {
+const cellStyle = (row) => {
+  if (row.$rowIndex === 0 && row.$columnIndex < 5) {
     return {
+      position: 'sticky',
+      top: 0,
+      zIndex: 30,
       fontSize: '12px',
-      fontWeight: '500',
+      fontWeight: '600',
     };
   }
   return {
     fontSize: '12px',
-    fontWeight: '500',
+    fontWeight: '600',
+    padding: '2px 0',
   };
 };
 
@@ -354,15 +373,29 @@ const cellStyleHandler = ({ column }) => {
   const dayDetailedFormat = /\d{4}-\d{2}-\d{2}/;
   const monthFormat = /\d{4}-\d{2}/;
   if (columnName.includes('~') || columnName.includes('截止') || columnName.includes('近90天平台退货率') || columnName.includes('余额')) {
-    return { fontSize: '12px', backgroundColor: '#b3ced7' };
+    return { fontSize: '12px', padding: '2px 0', backgroundColor: '#e0f2fe' };
   }
   if (monthFormat.test(columnName) && !dayDetailedFormat.test(columnName)) {
-    return { fontSize: '12px', backgroundColor: '#8cbacc' };
+    return { fontSize: '12px', padding: '2px 0', backgroundColor: 'rgba(186,230,253,0.9)' };
   }
   if (dayFormat.test(columnName)) {
-    return { fontSize: '12px', backgroundColor: '#d0dadf' };
+    return { fontSize: '12px', padding: '2px 0', backgroundColor: '#f0f9ff' };
+  }
+  return { fontSize: '12px', padding: '2px 0',};
+};
+
+const rowStyle = (row) => {
+  if (row.$rowIndex === 0) {
+    return {
+      background: '#ffffff',
+      position: 'sticky',
+      top: 0,
+      zIndex: 1,
+    };
   }
-  return { fontSize: '12px' };
+  return {
+    height: '48px',
+  };
 };
 
 function formatEmptyCell({ cellValue }) {
@@ -396,7 +429,7 @@ onMounted(() => {
     </el-card>
     <el-card class="mt-3">
       <slot name="table-header"></slot>
-      <vxe-grid :cell-style="cellStyle" :header-cell-style="cellStyleHandler" v-bind="gridOptions"
+      <vxe-grid :cell-style="cellStyle" :header-cell-style="cellStyleHandler" :row-style="rowStyle" v-bind="gridOptions" stripe
                 v-on="gridEvents" @sort-change="handleSortChange">
         <template #toolbar_buttons>
           <div class="mx-3.5">

+ 11 - 2
src/views/reportManage/dataCenter/combinedDisplay/components/tableData/monthlyComparativeData.vue

@@ -37,6 +37,7 @@ const gridOptions = reactive({
     }
   },
   columnConfig: {
+    isCurrent: true,
     resizable: true,
   },
   rowConfig: {
@@ -154,7 +155,8 @@ function formatEmptyCell({ cellValue }) {
 const cellStyle = () => {
   return {
     fontSize: '12px',
-    fontWeight: '500',
+    fontWeight: '600',
+    padding:0,
   };
 };
 
@@ -164,6 +166,13 @@ const headerCellStyle = () => {
   };
 };
 
+const rowStyle = () => {
+  return{
+    padding:0,
+    height: '50px',
+  }
+}
+
 </script>
 
 <template>
@@ -187,7 +196,7 @@ const headerCellStyle = () => {
     </el-card>
     <el-card class="mt-3">
       <slot name="table-header"></slot>
-      <vxe-grid :cell-style="cellStyle" :header-cell-style="headerCellStyle" v-bind="gridOptions" v-on="gridEvents">
+      <vxe-grid :cell-style="cellStyle" :header-cell-style="headerCellStyle" v-bind="gridOptions" v-on="gridEvents" :row-style="rowStyle">
         <template #platformNumber_default="{ row }">
           <div class="font-semibold">{{ row.platformNumber }}</div>
         </template>

+ 14 - 9
src/views/reportManage/dataCenter/normalDisplay/components/DatePicker/index.vue

@@ -18,13 +18,17 @@ const dateType = inject<Ref>('dateDimension');
 
 const dateRange = ref( [
   dayjs().subtract(6, 'day').startOf('day').format('YYYY-MM-DD'),
-  dayjs().endOf('day').format('YYYY-MM-DD')
+  dayjs().subtract(1, 'day').endOf('day').format('YYYY-MM-DD')
 ]);
 
 const startWeek = ref(null);
 const endWeek = ref(null);
 const weekStartDate = ref<string | null>(dayjs().locale('en').subtract(2, 'week').startOf('week').format('YYYY-MM-DD'));
-const weekEndDate = ref<string | null>(dayjs().locale('en').endOf('week').format('YYYY-MM-DD'));
+const wEndDate = ref<string | null>(dayjs().locale('en').subtract(2, 'week').endOf('week').format('YYYY-MM-DD'));
+const wStartDate = ref<string | null>(dayjs().locale('en').subtract(1, 'week').startOf('week').format('YYYY-MM-DD'));
+const weekEndDate = ref<string | null>(dayjs().locale('en').subtract(1, 'week').endOf('week').format('YYYY-MM-DD'));
+const sFormat = computed(() => `${ weekStartDate.value } to ${ wEndDate.value }`);
+const wFormat = computed(() => `${ wStartDate.value } to ${ weekEndDate.value }`);
 
 const currentYear = new Date().getFullYear();
 const currentMonth = new Date().getMonth();
@@ -139,19 +143,18 @@ function setDefaultDate() {
     case 'day':
       dateRange.value = [
         dayjs().subtract(6, 'day').startOf('day').format('YYYY-MM-DD'),
-        dayjs().endOf('day').format('YYYY-MM-DD')
+        dayjs().subtract(1, 'day').endOf('day').format('YYYY-MM-DD')
       ];
       dateChange();
-       //console.log(dateRange.value);
       break;
     case 'week':
       startWeek.value = dayjs().locale('en').subtract(2, 'week').startOf('week').format('YYYY-MM-DD');
-      endWeek.value = dayjs().locale('en').endOf('week').format('YYYY-MM-DD');
+      endWeek.value = dayjs().locale('en').subtract(1, 'week').endOf('week').format('YYYY-MM-DD');
       dateChange();
       break;
     case 'month':
-      const startOfMonth = new Date(new Date().getFullYear(), new Date().getMonth() - 1, 1);
-      const endOfMonth = new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0); // 设置下个月的第一天减去1天,得到当前月的最后一天
+      const startOfMonth = new Date(new Date().getFullYear(), new Date().getMonth() - 5, 1);
+      const endOfMonth = new Date(new Date().getFullYear(), new Date().getMonth(), 0); // 设置下个月的第一天减去1天,得到当前月的最后一天
       monthlyDataTime.value = [startOfMonth, endOfMonth];
       const formattedStartDate = dayjs(startOfMonth).format('YYYY-MM-DD');
       const formattedEndDate = dayjs(endOfMonth).format('YYYY-MM-DD');
@@ -188,10 +191,11 @@ watch(dateType, () => {
               :clearable="false"
               :disabled-date="disabledDate"
               class="date-picker"
-              format="YYYY 第 WW 周"
+              :format="sFormat"
               placeholder="选择开始周"
               type="week"
               @change="handleStartWeekChange"
+              style="width: 210px"
           />
         </el-config-provider>
         <span class="demonstration">至</span>
@@ -202,10 +206,11 @@ watch(dateType, () => {
               :disabled="!startWeek"
               :disabled-date="weekDisabledDate"
               class="date-picker"
-              format="YYYY 第 WW 周"
+              :format="wFormat"
               placeholder="选择结束周"
               type="week"
               @change="handleEndWeekChange"
+              style="width: 210px"
           />
         </el-config-provider>
       </div>

+ 62 - 15
src/views/reportManage/dataCenter/normalDisplay/components/Selector/index.vue

@@ -1,4 +1,4 @@
-<script setup lang="ts">
+<script lang="ts" setup>
 import { onMounted, ref, watch } from 'vue';
 import { getOperationSelect, getTasks, getTasksId } from '/@/views/reportManage/TaskManage/api';
 
@@ -17,6 +17,7 @@ const operationList = ref(localStorage.getItem('operationList') || '');
 const usersList = ref(JSON.parse(localStorage.getItem('usersList') || '[]'));
 const countryList = ref(JSON.parse(localStorage.getItem('countryList') || '[]'));
 const brandNameList = ref(JSON.parse(localStorage.getItem('brandNameList') || '[]'));
+const platformList = ref(JSON.parse(localStorage.getItem('platformList') || '[]'));
 
 const platformNumberOptions = ref([]);
 const platformNameOptions = ref([]);
@@ -24,6 +25,7 @@ const usersOptions = ref([]);
 const operationOptions = ref([]);
 const countryOptions = ref([]);
 const brandNameOptions = ref([]);
+const platformOptions = ref([]);
 
 function sortCountriesByInitial(countries) {
   return countries.sort((a, b) => a.localeCompare(b));
@@ -53,6 +55,7 @@ async function fetchAllTasks() {
   operationOptions.value = [...new Set(allData.map(item => item.operater))];
   countryOptions.value = sortCountriesByInitial([...new Set(allData.map(item => item.country))]);
   brandNameOptions.value = [...new Set(allData.map(item => item.brandName))];
+  platformOptions.value = [...new Set(allData.map(item => item.platform).filter(platform => platform !== null && platform !== undefined && platform !== ''))];
 }
 
 async function fetchUsersSelect() {
@@ -90,6 +93,7 @@ async function fetchFilteredData() {
   processFilterSingle(operationList.value, 'operater');
   processFilterMultiple(countryList, 'country', 'countrys');
   processFilterMultiple(brandNameList, 'brandName', 'brandNames');
+  processFilterMultiple(platformList, 'platform', 'platforms');
   if (usersList.value.length > 0) {
     filters.users = usersList.value.join(',');
   }
@@ -112,12 +116,25 @@ async function emitChange() {
   localStorage.setItem('platformNumberList', platformNumberList.value);
   localStorage.setItem('platformNameList', platformNameList.value);
   localStorage.setItem('operationList', operationList.value);
-  //localStorage.setItem('usersList', JSON.stringify(usersList.value));
+  localStorage.setItem('usersList', JSON.stringify(usersList.value));
   localStorage.setItem('countryList', JSON.stringify(countryList.value));
   localStorage.setItem('brandNameList', JSON.stringify(brandNameList.value));
+  localStorage.setItem('platformList', JSON.stringify(platformList.value));
 }
 
-watch([countryList, brandNameList, usersList], () => {
+// 快捷选择美洲区国家
+const selectCommonGroup1 = () => {
+  countryList.value = ['美国', '加拿大', '墨西哥'];
+  emitChange(); // 更新选择后的数据
+};
+
+// 快捷选择欧洲+英国国家
+const selectCommonGroup2 = () => {
+  countryList.value = ['比利时', '德国', '法国', '荷兰', '西班牙', '意大利', '英国'];
+  emitChange(); // 更新选择后的数据
+};
+
+watch([countryList, brandNameList, usersList, platformList], () => {
   emitChange();
 });
 
@@ -131,33 +148,63 @@ defineExpose({ fetchFilteredData, filteredData, updateData });
 </script>
 
 <template>
-  <div class="flex-container">
-    <el-input v-model="platformNumberList" @change="emitChange" placeholder="平台编号" class="flex-item" clearable></el-input>
-    <el-input v-model="platformNameList" @change="emitChange" placeholder="平台名称" class="flex-item" clearable></el-input>
-    <el-input v-model="operationList" @change="emitChange" placeholder="运营" class="flex-item" clearable></el-input>
-    <el-select v-if="props.showOperationSearch" v-model="usersList" multiple collapse-tags collapse-tags-tooltip placeholder="填写人" class="flex-item">
-      <el-option v-for="item in usersOptions" :key="item.value" :label="item.label" :value="item.value" />
+  <div class="flex gap-2.5 flex-wrap">
+    <el-input v-model="platformNumberList" class="flex-item" clearable placeholder="平台编号" style="width: 130px"
+              @change="emitChange"></el-input>
+    <el-input v-model="platformNameList" class="flex-item" clearable placeholder="平台名称" style="width: 130px"
+              @change="emitChange"></el-input>
+    <el-input v-model="operationList" class="flex-item" clearable placeholder="运营" style="width: 130px" @change="emitChange"></el-input>
+    <el-select
+        v-model="usersList"
+        class="flex-item"
+        multiple
+        filterable
+        clearable
+        collapse-tags
+        placeholder="填写人"
+        style="width: 175px;"
+    >
+      <el-option
+          v-for="item in usersOptions"
+          :key="item.value"
+          :label="item.label"
+          :value="item.value"
+      />
     </el-select>
-    <el-select v-model="countryList" multiple collapse-tags collapse-tags-tooltip placeholder="国家" class="flex-item">
+    <el-select v-model="countryList" class="flex-item" clearable collapse-tags collapse-tags-tooltip multiple placeholder="国家" style="width: 160px;">
       <el-option v-for="item in countryOptions" :key="item" :label="item" :value="item" />
+      <template #footer>
+        <el-button text size="small" @click="selectCommonGroup1">美洲区</el-button>
+        <el-button text size="small" @click="selectCommonGroup2">欧洲+英国</el-button>
+      </template>
     </el-select>
-    <el-select v-model="brandNameList" multiple collapse-tags collapse-tags-tooltip placeholder="品牌" class="flex-item">
+    <el-select v-model="brandNameList" class="flex-item" clearable collapse-tags collapse-tags-tooltip multiple style="width: 145px;"
+               placeholder="品牌">
       <el-option v-for="item in brandNameOptions" :key="item" :label="item" :value="item" />
     </el-select>
+    <el-select v-model="platformList" class="flex-item" clearable collapse-tags collapse-tags-tooltip multiple style="width: 145px;"
+               placeholder="平台">
+      <el-option v-for="item in platformOptions" :key="item" :label="item" :value="item" />
+    </el-select>
   </div>
 </template>
 
 <style scoped>
 .flex-container {
   display: flex;
-  flex-wrap: nowrap;
+  /* flex-wrap: wrap; */
   gap: 14.5px; /* 控制间距 */
 }
 
 .flex-item {
-  flex: 1 1 160px; /* 自适应大小并设定最小宽度 */
+  /* flex: 1 1 150px; !* 自适应大小并设定最小宽度 *! */
   min-width: 100px;
-  max-width: 186px;
-  height: 30px;
+  /* max-width: 180px; !* 设定最大宽度,确保不会变形 *! */
+  /* height: 28px; */
+  /* padding-top: 4px; */
+}
+
+.el-select {
+  /* max-width: 180px; !* 确保下拉框宽度不超过最大宽度 *! */
 }
 </style>

+ 58 - 28
src/views/reportManage/dataCenter/normalDisplay/components/TableDataDisplay.vue

@@ -4,7 +4,8 @@ import { useRouter } from 'vue-router';
 import {
   getDayData,
   getDayTotalData,
-  getMonthData, getMonthTotalData,
+  getMonthData,
+  getMonthTotalData,
   getWeekData,
   getWeekTotalData
 } from '/src/views/reportManage/dataCenter/api';
@@ -19,6 +20,9 @@ const props = defineProps({
   taskIds: Object,
 });
 
+const taskIds = ref(props.taskIds);
+const totalSales = ref(null);
+
 const dayStartDate = ref(null);
 const dayEndDate = ref(null);
 const weekStartDate = ref(null);
@@ -40,6 +44,7 @@ const sortStatus = ref(true);
 
 const gridOptions = reactive({
   border: 'inner',
+  keepSource: true,
   height: 900,
   align: null,
   round: true,
@@ -51,10 +56,12 @@ const gridOptions = reactive({
     }
   },
   columnConfig: {
+    isCurrent: true,
     resizable: true,
   },
   rowConfig: {
     isHover: true,
+    height: 48
   },
   pagerConfig: {
     enabled: true,
@@ -130,7 +137,7 @@ const loadSortState = () => {
 };
 const totalRow = ref({});
 
-async function fetchTotalData(taskIds, apiFunc,  startDate, endDate, dateTypeKey) {
+async function fetchTotalData(taskIds, apiFunc, startDate, endDate, dateTypeKey) {
   try {
     const resp = await apiFunc({
       data_start_date: startDate.value,
@@ -145,15 +152,15 @@ async function fetchTotalData(taskIds, apiFunc,  startDate, endDate, dateTypeKey
 }
 
 async function fetchDayTotal(taskIds) {
-  await fetchTotalData(taskIds, getDayTotalData, dayStartDate, dayEndDate,'day');
+  await fetchTotalData(taskIds, getDayTotalData, dayStartDate, dayEndDate, 'day');
 }
 
 async function fetchWeekTotal(taskIds) {
-  await fetchTotalData(taskIds, getWeekTotalData, weekStartDate, weekEndDate,'week');
+  await fetchTotalData(taskIds, getWeekTotalData, weekStartDate, weekEndDate, 'week');
 }
 
 async function fetchMonthTotal(taskIds) {
-  await fetchTotalData(taskIds, getMonthTotalData, monthStartDate, monthEndDate,'month');
+  await fetchTotalData(taskIds, getMonthTotalData, monthStartDate, monthEndDate, 'month');
 }
 
 async function fetchCurrentTotalData(taskIds) {
@@ -161,15 +168,14 @@ async function fetchCurrentTotalData(taskIds) {
     await fetchDayTotal(taskIds);
   } else if (dateType.value === 'week') {
     await fetchWeekTotal(taskIds);
-  }else if (dateType.value === 'month') {
+  } else if (dateType.value === 'month') {
     await fetchMonthTotal(taskIds);
   }
 }
 
-
 async function fetchData(taskIds, apiFunc, startDate, endDate, dataColumns, dateTypeKey) {
   try {
-    loadSortState();
+    //loadSortState();
     const resp = await apiFunc({
       page: gridOptions.pagerConfig.currentPage,
       limit: gridOptions.pagerConfig.pageSize,
@@ -178,6 +184,7 @@ async function fetchData(taskIds, apiFunc, startDate, endDate, dataColumns, date
       task_ids: taskIds,
       sort: sortOrder.value,
       order_date: order_date.value,
+      order_total_sales_current_monthly: totalSales.value,
     });
     gridOptions[dateType.value].data = [totalRow.value, ...resp.data];
     gridOptions.pagerConfig.total = resp.total;
@@ -200,7 +207,7 @@ async function fetchData(taskIds, apiFunc, startDate, endDate, dataColumns, date
         //"的销售额"字段可以排序
         allColumns.forEach(key => {
           let isSortable = false;
-          if (key.includes('的销售额') && !key.includes('增长率')) {
+          if (key.includes('的销售额') || key.includes('当月累计销售额') && !key.includes('增长率')) {
             isSortable = true;
           }
           const column = {
@@ -290,19 +297,31 @@ function handleSortChange({ field, order }) {
     const match = field.match(/(\d{4}-\d{2}-\d{2})的销售额/);
     const matchRange = field.match(/(\d{4}-\d{2}-\d{2})~(\d{4}-\d{2}-\d{2})的销售额/);
     const matchMonth = field.match(/(\d{4}-\d{2})的销售额/);
+    const matchTotal = field.match(/当月累计销售额/);
     if (matchRange) {
       order_date.value = matchRange[1];
+      totalSales.value = '';
     } else if (match) {
       order_date.value = match[1];
+      totalSales.value = '';
     } else if (matchMonth) {
       order_date.value = `${ matchMonth[1] }-01`;
+      totalSales.value = '';
+    } else if (matchTotal) {
+      clearSort();
+      totalSales.value = true;
     }
   }
   saveSortState(); // 保存排序状态
   gridOptions.sortConfig.defaultSort.order = order;
   gridOptions.sortConfig.defaultSort.field = field;
   sortStatus.value = false;
-  fetchCurrentData(props.taskIds, true)
+  fetchCurrentData(props.taskIds, true);
+}
+
+function clearSort() {
+  order_date.value = '';
+  salesOrder.value = null;
 }
 
 const handleImport = () => {
@@ -328,10 +347,20 @@ function formatEmptyCell({ cellValue }) {
   return cellValue;
 }
 
-const cellStyle = () => {
+const cellStyle = (row) => {
+  if (row.$rowIndex === 0 && row.$columnIndex < 5) {
+    return {
+      position: 'sticky',
+      top: 0,
+      zIndex: 30,
+      fontSize: '12px',
+      fontWeight: '600',
+    };
+  }
   return {
     fontSize: '12px',
     fontWeight: '600',
+    padding: 0,
   };
 };
 
@@ -341,6 +370,21 @@ const headerCellStyle = () => {
   };
 };
 
+const rowStyle = (row) => {
+  if (row.$rowIndex === 0) {
+    return {
+      background: '#def6fe',
+      position: 'sticky',
+      top: 0,
+      zIndex: 1,
+    };
+  }
+  return {
+    padding: 0,
+    height: '50px',
+  };
+};
+
 watch([() => props.taskIds, currentDate], async ([newTaskIds, newCurrentDate]) => {
   sortStatus.value = true;
 
@@ -354,8 +398,7 @@ watch([() => props.taskIds, currentDate], async ([newTaskIds, newCurrentDate]) =
     monthStartDate.value = dayjs(newCurrentDate.startDate).format('YYYY-MM-DD');
     monthEndDate.value = dayjs(newCurrentDate.endDate).format('YYYY-MM-DD');
   }
-  sortOrder.value = '';
-  order_date.value = '';
+  clearSort();
   if (newTaskIds) {
     taskIdsAvailable.value = true;
   }
@@ -372,21 +415,18 @@ onMounted(() => {
 
 <template>
   <div>
-    <vxe-grid :cell-style="cellStyle" :header-cell-style="headerCellStyle"
+    <vxe-grid :cell-style="cellStyle" :header-cell-style="headerCellStyle" :row-style="rowStyle"
               stripe
               v-bind="currentGridOptions" v-on="gridEvents" @sort-change="handleSortChange">
       <template #toolbar_buttons>
         <el-button icon="plus" target="_blank" type="primary" @click="handleImport">数据录入</el-button>
       </template>
       <template #platformNumber_default="{ row }">
-        <div class="font-semibold">{{ row.platformNumber }}</div>
+        <div class="font-semibold" style="padding: 0">{{ row.platformNumber }}</div>
       </template>
       <template #platformName_default="{ row }">
         <div class="font-semibold" style="color: #164285">{{ row.platformName }}</div>
       </template>
-      <template #brandName_default="{ row }">
-        {{ row.brandName }}
-      </template>
     </vxe-grid>
   </div>
 </template>
@@ -402,14 +442,4 @@ onMounted(() => {
   padding-right: 0 !important;
 }
 
-.custom-btn {
-  border: 1px solid #0085ff;
-  padding: 8px
-}
-
-.custom-btn:hover {
-  background-color: #0085ff;
-  color: #a6d8fc;
-  border: 1px solid #0085ff;
-}
 </style>

+ 181 - 53
src/views/reportManage/dataCenter/normalDisplay/components/TableDataEntry.vue

@@ -5,8 +5,11 @@ import dayjs from 'dayjs';
 import isoWeek from 'dayjs/plugin/isoWeek';
 import { useRoute } from 'vue-router';
 import {
+  getDayBeforeData,
   getDayTaskData,
+  getMonthBeforeData,
   getMonthTaskData,
+  getWeekBeforeData,
   getWeekTaskData,
   postCreateDayData,
   postCreateMonthData,
@@ -19,6 +22,8 @@ import { dayColumns, monthColumns, weekColumns } from '../../utils/columns';
 import { ComponentSize, ElMessage, FormInstance, FormRules } from 'element-plus';
 import enLocale from 'element-plus/es/locale/lang/en';
 import Selector from '/@/views/reportManage/dataCenter/normalDisplay/components/Selector/index.vue';
+import { Warning } from '@element-plus/icons-vue';
+
 
 dayjs.extend(isoWeek);
 
@@ -67,8 +72,13 @@ const shortcuts = [
   },
 ];
 
+const yDayDate = ref(null);
+const yWeekDay = ref(null);
+const yMonthDay = ref(null);
+
 function handleDayChange(value) {
   dailySalesTime.value = dayjs(value).format('YYYY-MM-DD');
+  yDayDate.value = dayjs(dailySalesTime.value).subtract(1, 'day').format('YYYY-MM-DD');
   fetchCurrentTaskData();
 }
 
@@ -80,6 +90,7 @@ const handleWeekChange = () => {
     weeklyAdTime.value = adDate.format('YYYY-WW');
     adStartDate.value = adDate.startOf('week').format('YYYY-MM-DD');
     adEndDate.value = adDate.endOf('week').format('YYYY-MM-DD');
+    yWeekDay.value = dayjs(entryEndDate.value).locale('en').endOf('week').subtract(1, 'week').format('YYYY-MM-DD');
   }
   if (initialLoad) {
     initialLoad = false; // 只在初次加载时避免多次调用
@@ -96,6 +107,7 @@ const handleMonthChange = (value) => {
     const lastDay = new Date(year, month, 0).getDate();
     startDate.value = `${ year }-${ String(month).padStart(2, '0') }-01`;
     endDate.value = `${ year }-${ String(month).padStart(2, '0') }-${ lastDay }`;
+    yMonthDay.value = dayjs(endDate.value).subtract(1, 'month').endOf('month').format('YYYY-MM-DD');
     fetchCurrentTaskData();
   } else {
     startDate.value = null;
@@ -115,15 +127,17 @@ function setDefaultDate() {
   switch (dateType) {
     case 'day':
       dailySalesTime.value = dayjs().subtract(1, 'day').format('YYYY-MM-DD');
+      yDayDate.value = dayjs(dailySalesTime.value).subtract(1, 'day').format('YYYY-MM-DD');
       break;
     case 'week':
       weeklyEntryTime.value = dayjs().locale('en').subtract(1, 'week').startOf('week').format('YYYY-MM-DD');
       handleWeekChange();
       break;
     case 'month':
-      monthlyEntryTime.value = new Date();
+      monthlyEntryTime.value = dayjs().subtract(1, 'month').format('YYYY-MM-DD');
       startDate.value = dayjs(monthlyEntryTime.value).startOf('month').format('YYYY-MM-DD');
       endDate.value = dayjs(monthlyEntryTime.value).endOf('month').format('YYYY-MM-DD');
+      yMonthDay.value = dayjs(endDate.value).subtract(1, 'month').endOf('month').format('YYYY-MM-DD');
   }
 }
 
@@ -148,7 +162,10 @@ interface taskDataForm {
 }
 
 const formSize = ref<ComponentSize>('default');
+const isSubmitting = ref(false);
 const dayFormVisible = ref(false);
+const dialogVisible = ref(false);
+const dialogMessage = ref('');
 const taskDataFormRef = ref<FormInstance>();
 const taskDataForm = reactive({
   sales_original: null,
@@ -158,7 +175,6 @@ const taskDataForm = reactive({
   impression: null,
   ad_click: null,
   ad_order: null,
-  // money_by_other: null,
   session: null,
   order: null,
   availableSalesDay: null,
@@ -194,6 +210,11 @@ const rules = reactive<FormRules>({
 const flatDayColumns = ref(flattenColumns(dayColumns.value));
 const flatWeekColumns = ref(flattenColumns(weekColumns.value));
 const flatMonthColumns = ref(flattenColumns(monthColumns.value));
+
+const ySalesData = ref({
+  sales_original: '',
+  total_sales_current_monthly_original: '',
+});
 //表格
 let taskId = 0;
 let currentId = 0;
@@ -227,10 +248,11 @@ interface RowVO {
 const xGrid = ref<VxeGridInstance<RowVO>>();
 const originalDataMap = new Map();
 const currentScrollLeft = ref(0);
+const activeEditRow = ref(null);
 
 const gridOptions = reactive({
   border: 'inner',
-  height: 900,
+  height: 750,
   align: null,
   round: true,
   loading: false,
@@ -254,7 +276,7 @@ const gridOptions = reactive({
     trigger: 'manual',
     mode: 'row',
     showStatus: true,
-
+    autoClear: false,
   },
   day: {
     columns: dayColumns,
@@ -273,7 +295,6 @@ const gridOptions = reactive({
 const gridEvents = {
   pageChange({ currentPage, pageSize }) {
     if (gridOptions.pagerConfig) {
-
       gridOptions.pagerConfig.currentPage = currentPage;
       gridOptions.pagerConfig.pageSize = pageSize;
     }
@@ -290,6 +311,7 @@ const hasActiveEditRow = (row: RowVO) => {
 };
 
 const clearRowEvent = (row: RowVO) => {
+  activeEditRow.value = false;
   const $grid = xGrid.value;
   if ($grid) {
     const originalData = originalDataMap.get(row.id);
@@ -302,6 +324,7 @@ const clearRowEvent = (row: RowVO) => {
 };
 
 const handelEditRow = (row: RowVO) => {
+  activeEditRow.value = true;
   const $grid = xGrid.value;
   if ($grid) {
     // 记录当前滚动条位置
@@ -312,7 +335,6 @@ const handelEditRow = (row: RowVO) => {
     // 强制恢复滚动条位置
     setTimeout(() => {
       bodyWrapper.scrollLeft = currentScrollLeft.value;
-      console.log('After setTimeout, scrollLeft:', bodyWrapper.scrollLeft);
     }, 0);
   }
 };
@@ -384,9 +406,9 @@ function fetchCurrentTaskData() {
 
 const editEvent = async (row: RowVO) => {
   taskId = row.task;
-  // console.log('row', taskId);
   currentId = row.id;
   Object.assign(taskDataForm, row);
+  await currentSalesTip();
   dayFormVisible.value = true;
 };
 
@@ -428,9 +450,13 @@ function createDayDataRules() {
   if (dateType === 'day') {
     rules.ad_sales_original[0].required = false;
     rules.ad_cost_original[0].required = false;
-  } else {
-    rules.ad_sales_original[0].required = true;
-    rules.ad_cost_original[0].required = true;
+  }
+  if (dateType === 'month') {
+    rules.ad_sales_original[0].required = false;
+    rules.ad_cost_original[0].required = false;
+    rules.impression[0].required = false;
+    rules.ad_click[0].required = false;
+    rules.ad_order[0].required = false;
   }
 }
 
@@ -536,27 +562,43 @@ async function createMonthData() {
 
 //创建任务
 const submitForm = async (formEl: FormInstance | undefined) => {
-  if (!formEl) return;
-  await formEl.validate(async (valid, fields) => {
-    if (valid) {
-      if (dateType === 'day') {
-        const fieldsToValidate = ['sales_original'];
-        if (!validateNumericFields(taskDataForm, fieldsToValidate)) return; // 验证字段
-        await createDayData();
-      }
-      if (dateType === 'week') {
-        const fieldsToValidate = ['sales_original', 'ad_sales_original', 'ad_cost_original', 'total_sales_current_monthly_original', 'impression', 'ad_click', 'ad_order', 'money_by_amazon', 'money_by_other', 'session', 'order', 'availableSalesDay', 'intransitInventory', 'overseasStorage', 'refundRate'];
-        if (!validateNumericFields(taskDataForm, fieldsToValidate)) return; // 验证字段
-        await createWeekData();
-      }
-      if (dateType === 'month') {
-        const fieldsToValidate = ['sales_original', 'ad_sales_original', 'ad_cost_original', 'impression', 'ad_click', 'ad_order',];
-        if (!validateNumericFields(taskDataForm, fieldsToValidate)) return; // 验证字段
-        await createMonthData();
+  const fieldsToWarn = validateForm();
+  if (fieldsToWarn.length > 0) {
+    dialogVisible.value = true;
+    dialogMessage.value = `${ fieldsToWarn.join(', ') }`;
+    return;
+  } else {
+    if (!formEl || isSubmitting.value) return; // 防止重复提交
+    await formEl.validate(async (valid, fields) => {
+      if (valid) {
+        isSubmitting.value = true; // 表单开始提交,禁用按钮
+        try {
+          await submit(formEl);
+        } finally {
+          isSubmitting.value = false; // 无论成功或失败,恢复按钮
+        }
       }
-      //taskDataFormRef.value.resetFields();
-    }
-  });
+    });
+  }
+};
+
+const submit = async () => {
+  dialogVisible.value = false;
+  if (dateType === 'day') {
+    const fieldsToValidate = ['sales_original'];
+    if (!validateNumericFields(taskDataForm, fieldsToValidate)) return; // 验证字段
+    await createDayData();
+  }
+  if (dateType === 'week') {
+    const fieldsToValidate = ['sales_original', 'ad_sales_original', 'ad_cost_original', 'total_sales_current_monthly_original', 'impression', 'ad_click', 'ad_order', 'money_by_amazon', 'money_by_other', 'session', 'order', 'availableSalesDay', 'intransitInventory', 'overseasStorage', 'refundRate'];
+    if (!validateNumericFields(taskDataForm, fieldsToValidate)) return; // 验证字段
+    await createWeekData();
+  }
+  if (dateType === 'month') {
+    const fieldsToValidate = ['sales_original'];
+    if (!validateNumericFields(taskDataForm, fieldsToValidate)) return; // 验证字段
+    await createMonthData();
+  }
 };
 
 // 更新日数据
@@ -665,6 +707,7 @@ async function updateMonthData(row: RowVO) {
 }
 
 const editRowEvent = async (row: any) => {
+  activeEditRow.value = false;
   const $grid = xGrid.value;
   if ($grid) {
     if (dateType === 'day') {
@@ -676,7 +719,7 @@ const editRowEvent = async (row: any) => {
       if (!validateNumericFields(row, fieldsToValidate)) return; // 验证字段
       await updateWeekData(row);
     } else if (dateType === 'month') {
-      const fieldsToValidate = ['sales_original', 'ad_sales_original', 'ad_cost_original', 'impression', 'ad_click', 'ad_order',];
+      const fieldsToValidate = ['sales_original'];
       if (!validateNumericFields(row, fieldsToValidate)) return; // 验证字段
       await updateMonthData(row);
     }
@@ -684,6 +727,47 @@ const editRowEvent = async (row: any) => {
   }
 };
 
+// 日数据提示
+async function salesTip(apiFunction, dateTypeKey) {
+  const dateMap = {
+    day: yDayDate.value,
+    week: yWeekDay.value,
+    month: yMonthDay.value,
+  };
+  const resp = await apiFunction({
+    task: taskId,
+    [`${ dateTypeKey }_end_date`]: dateMap[dateTypeKey],
+  });
+  if (dateType === 'week') {
+    ySalesData.value = resp.data;
+  } else {
+    ySalesData.value = resp.data[0];
+  }
+  return ySalesData.value;
+}
+
+async function daySalesTip() {
+  await salesTip(getDayBeforeData, 'day');
+}
+
+async function weekSalesTip() {
+  await salesTip(getWeekBeforeData, 'week');
+}
+
+async function monthSalesTip() {
+  await salesTip(getMonthBeforeData, 'month');
+}
+
+async function currentSalesTip() {
+  if (dateType === 'week') {
+    await weekSalesTip();
+  } else if (dateType === 'month') {
+    await monthSalesTip();
+  } else {
+    await daySalesTip();
+  }
+}
+
 const currentGridOptions = computed(() => {
   const selectedGridOptions = gridOptions[dateType] || gridOptions['day'];
   return {
@@ -737,17 +821,38 @@ const headerCellStyle = () => {
   };
 };
 
+const validateForm = () => {
+  const fieldsToWarn = [];
+  const fieldsToValidate = ['sales_original', 'total_sales_current_monthly_original'];
+  if (ySalesData.value) {
+    for (const key of fieldsToValidate) {
+      const newValue = taskDataForm[key];
+      const yDayValue = ySalesData.value[key];
+      const column = flatWeekColumns.value.find(col => col.field === key);
+      const title = column ? column.title : key;
+
+      if (newValue != null && yDayValue !== null && yDayValue !== undefined) {
+        const diffPercentage = Math.abs((newValue - yDayValue) / yDayValue) * 100;
+        if (diffPercentage > 50) {
+          fieldsToWarn.push(title);
+        }
+      }
+    }
+  }
+  return fieldsToWarn;
+};
+
 onMounted(() => {
   setDefaultDate();
-  fetchCurrentTaskData();
+  //fetchCurrentTaskData();
   createDayDataRules();
 });
 </script>
 
 <template>
   <div>
-    <el-card class=" my-3 mx-8">
-      <div class="custom-card-style flex gap-1.5 justify-between my-1.5 mx-2">
+    <el-card class=" my-3 mx-8 p-0">
+      <div class="flex gap-1.5 justify-between mx-2 items-center">
         <Selector ref="selectorRef" @update:updateData="updateDataChange" />
         <div v-if="dateType === 'day'" class="demo-date-picker">
           <div class="block">
@@ -757,6 +862,7 @@ onMounted(() => {
                 :clearable="false"
                 :disabled-date="disabledDate"
                 :shortcuts="shortcuts"
+                style="width: 170px"
                 type="Date"
                 @change="handleDayChange"
             />
@@ -764,7 +870,7 @@ onMounted(() => {
         </div>
         <div v-if="dateType === 'week'" class="demo-date-picker">
           <el-config-provider :locale="enLocale">
-            <div class="block">
+            <div class="block mt-2">
               <span class="demonstration">周广告数据时间:</span>
               <el-date-picker
                   v-model="weeklyAdTime"
@@ -817,12 +923,17 @@ onMounted(() => {
             <el-button link size="small" type="warning" @click="editRowEvent(row)">保存</el-button>
           </template>
           <template v-else>
-            <el-button :disabled="!row.id" link size="small" type="success" @click="handelEditRow(row)">修改</el-button>
+            <el-button :disabled="!row.id||activeEditRow" link size="small" type="success" @click="handelEditRow(row)">
+              修改
+            </el-button>
           </template>
-          <el-button v-if="!hasActiveEditRow(row)" :disabled="row.id" link size="small" type="primary"
+          <el-button v-if="!hasActiveEditRow(row)" :disabled="row.id||activeEditRow" link size="small" type="primary"
                      @click="editEvent(row)">创建
           </el-button>
         </template>
+        <template #total_header="{ row }">
+          <span>当月截止{{ entryEndDate }}销售额</span>
+        </template>
         <template #sales_original_edit="{ row }">
           <vxe-input v-model="row.sales_original"></vxe-input>
         </template>
@@ -909,30 +1020,47 @@ onMounted(() => {
     <template #footer>
       <div class="dialog-footer">
         <el-button @click="dayFormVisible = false ;resetForm(taskDataFormRef)">取消</el-button>
-        <el-button type="primary" @click="submitForm(taskDataFormRef)"> 确认</el-button>
+        <el-button
+            :disabled="isSubmitting"
+            type="primary"
+            @click="submitForm(taskDataFormRef)"
+        >
+          {{ isSubmitting ? '提交中...' : '确认' }}
+        </el-button>
       </div>
     </template>
   </el-dialog>
+  <el-dialog
+      v-model="dialogVisible"
+      align-center
+      style="border-radius: 10px;"
+      title="重要提示"
+      width="30%"
+  >
+    <template #title>
+      <el-button link style="font-size: 18px" type="warning">
+        <el-icon size="22px">
+          <warning />
+        </el-icon>
+        重要提示
+      </el-button>
+    </template>
+    <span>您输入的 </span>
+    <span style="color: #f1a055;">{{ dialogMessage }}</span>
+    <span> 相较于上次填入的数据偏离值超过 50% ,是否仍要提交?</span>
+    <template #footer>
+      <span class="dialog-footer">
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="submit(formEl)">确认</el-button>
+      </span>
+    </template>
+  </el-dialog>
 </template>
 
 <style lang="scss" scoped>
 .demo-date-picker {
   display: flex;
-  width: 50%;
-  padding: 0;
-  white-space: nowrap;
-  flex-direction: row-reverse;
-}
-
-.demo-date-picker .block:last-child {
-  border-right: none;
-}
-
-.block {
-  display: flex;
-  align-items: center;
-  flex-wrap: nowrap;
-  overflow: hidden;
+  flex-direction: column-reverse;
 }
 
 .demo-date-picker .demonstration {

+ 1 - 1
src/views/reportManage/dataCenter/normalDisplay/index.vue

@@ -78,7 +78,7 @@ onBeforeUnmount(() => {
 <template>
   <div class="px-3.5">
     <el-card body-style="padding: 10px" class="mb-3.5 mt-3.5">
-      <div class="custom-card-style flex gap-1.5 justify-between items-center my-1.5 mx-2 mb-5">
+      <div class="custom-card-style flex gap-1.5 justify-between items-start my-1.5 mx-2 mb-5">
         <Selector ref="selectorRef" @update:updateData="updateDataChange" />
         <DataPicker class="flex-item" style="display: flex; align-items: center;" @dateChange="handelDateChange" />
       </div>

+ 6 - 6
src/views/reportManage/dataCenter/utils/columns.ts

@@ -98,7 +98,8 @@ export const weekColumns = ref([
     ]
   },
   {
-    title: '当月累计销售额', align: 'center', children: [
+    title: '当月累计销售额', align: 'center', slots: { header: 'total_header' },
+    children: [
       { field: 'total_sales_current_monthly', title: '当月累计销售额', width: 120 },
       {
         field: 'total_sales_current_monthly_original',
@@ -200,7 +201,7 @@ export const monthColumns = ref([
         title: '销售额(本币)',
         editRender: {},
         slots: { edit: 'sales_original_edit' },
-        width: 123,
+        minWidth: 123,
       },
     ]
   },
@@ -212,7 +213,7 @@ export const monthColumns = ref([
         title: '广告销售额(本币)',
         editRender: {},
         slots: { edit: 'ad_sales_original_edit' },
-        width: 151,
+        minWidth: 151,
       },
     ]
   },
@@ -224,7 +225,7 @@ export const monthColumns = ref([
         title: '广告花费(本币)',
         editRender: {},
         slots: { edit: 'ad_cost_original_edit' },
-        width: 138,
+        minWidth: 138,
       },
     ]
   },
@@ -248,7 +249,7 @@ export const universal = [
     field: 'platformName',
     title: '平台名称',
     fixed: 'left',
-    minWidth: 93,
+    minWidth: 100,
     slots: { default: 'platformName_default' },
     align: 'center',
     titlePrefix: { icon: 'vxe-icon-goods-fill' },
@@ -276,7 +277,6 @@ export const universal = [
     minWidth: 80,
     align: 'center',
     titlePrefix: { icon: 'vxe-icon-brand' },
-    slots:{default: 'brandName_default'}
   },
   //{
   //  field: 'currencyCode',

+ 17 - 11
src/views/system/log/operationLog/crud.tsx

@@ -3,7 +3,12 @@ import { UserPageQuery, AddReq, DelReq, EditReq, CrudExpose, CrudOptions, Create
 
 export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
 	const pageRequest = async (query: UserPageQuery) => {
-		return await api.GetList(query);
+		const { search, ...otherParams } = query;
+		const newQueryParams = {
+			request_modular: search,
+			...otherParams
+		};
+		return await api.GetList(newQueryParams);
 	};
 	const editRequest = async ({ form, row }: EditReq) => {
 		form.id = row.id;
@@ -63,27 +68,28 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 					},
 				},
 				search: {
-					title: '关键词',
+					title: '请求模块搜索',
 					column: {
 						show: false,
 					},
 					search: {
+						autoSearchTrigger: false,
 						show: true,
 						component: {
 							props: {
 								clearable: true,
 							},
-							placeholder: '请输入关键词',
-						},
-					},
-					form: {
-						show: false,
-						component: {
-							props: {
-								clearable: true,
-							},
+							placeholder: '请输入请求模块',
 						},
 					},
+					//form: {
+					//	show: false,
+					//	component: {
+					//		props: {
+					//			clearable: true,
+					//		},
+					//	},
+					//},
 				},
 				request_modular: {
 					title: '请求模块',

+ 3 - 3
src/views/system/user/crud.tsx

@@ -198,7 +198,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
             data: dictionary('gender'),
           }),
           column: {
-            minWidth: 50, //最小列宽
+            minWidth: 80, //最小列宽
           },
           form: {
             value: 1,
@@ -228,7 +228,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
             },
           }),
           column: {
-            minWidth: 100, //最小列宽
+            minWidth: 180, //最小列宽
           },
           form: {
             rules: [
@@ -274,7 +274,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
             },
           }),
           column: {
-            minWidth: 100, //最小列宽
+            minWidth: 180, //最小列宽
           },
           form: {
             rules: [