ソースを参照

✨ feat<任务管理>: 计划销售额管理

xinyan 7 ヶ月 前
コミット
d49337decb

+ 40 - 1
src/views/reportManage/TaskManage/utils/columns.ts

@@ -277,4 +277,43 @@ export const shopInfoColumns = ref([
   { 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 },
-]);
+]);
+
+// export const planColumns = ref([
+//   { type: 'seq', width: 70 },
+//   { field: 'platformNumber', title: '平台编号', align: 'center', width: 90 ,},
+//   { field: 'platformName', title: '平台名称', align: 'center', width: 142 },
+//   { field: 'country', title: '国家', align: 'center', width: 90 },
+//   { field: 'brandName', title: '品牌', align: 'center', width: 90 },
+//   { field: 'operater', title: '运营', align: 'center', width: 100 },
+// ]);
+//
+// export const operateColumns = ref([
+//   { title: '操作', width: 120, slots: { default: 'operate' }, align: 'center' , fixed: 'right'},
+// ])
+
+export const planColumns = ref([
+  { field: 'platformNumber', title: '平台编号', fixed: 'left', width: 81, align: 'center' },
+  { field: 'platformName', title: '平台名称', fixed: 'left', width: 142, align: 'center' },
+  { field: 'country', title: '国家', fixed: 'left', width: 80, align: 'center' },
+  { field: 'brandName', title: '品牌', fixed: 'left', width: 60 },
+  { field: 'operater', title: '运营', fixed: 'left', width: 80, align: 'center' },
+  // { field: 'status', title: '状态', width: 80, formatter: ({ cellValue }) => cellValue === 1 ? '启用' : '禁用' },
+  // { field: 'line', title: '线', width: 80 },
+  // { field: 'ipaddress', title: 'IP 地址', width: 150 },
+  // { field: 'company', title: '公司', width: 200 },
+  // { field: 'platform', title: '平台', width: 100 },
+  { field: '1月.planSales', title: '1月计划销售额', width: 120,editRender: {}, slots: { edit: 'january' }, align: 'center' },
+  { field: '2月.planSales', title: '2月计划销售额', width: 120, editRender: {}, slots: { edit: 'february' }, align: 'center' },
+  { field: '3月.planSales', title: '3月计划销售额', width: 120, editRender: {}, slots: { edit: 'march' }, align: 'center' },
+  { field: '4月.planSales', title: '4月计划销售额', width: 120, editRender: {}, slots: { edit: 'april' }, align: 'center' },
+  { field: '5月.planSales', title: '5月计划销售额', width: 120, editRender: {}, slots: { edit: 'may' }, align: 'center' },
+  { field: '6月.planSales', title: '6月计划销售额', width: 120, editRender: {}, slots: { edit: 'june' }, align: 'center' },
+  { field: '7月.planSales', title: '7月计划销售额', width: 120, editRender: {}, slots: { edit: 'july' }, align: 'center' },
+  { field: '8月.planSales', title: '8月计划销售额', width: 120, editRender: {}, slots: { edit: 'august' }, align: 'center' },
+  { field: '9月.planSales', title: '9月计划销售额', width: 120, editRender: {}, slots: { edit: 'september' }, align: 'center' },
+  { field: '10月.planSales', title: '10月计划销售额', width: 120, editRender: {}, slots: { edit: 'october' }, align: 'center' },
+  { field: '11月.planSales', title: '11月计划销售额', width: 120, editRender: {}, slots: { edit: 'november' }, align: 'center' },
+  { field: '12月.planSales', title: '12月计划销售额', width: 120, editRender: {}, slots: { edit: 'december' }, align: 'center' },
+  { title: '操作', width: 120, slots: { default: 'operate' }, align: 'center' , fixed: 'right'},
+])

+ 27 - 0
src/views/reportManage/dataCenter/api.ts

@@ -263,3 +263,30 @@ export function exportMonthData(query) {
     responseType: 'blob'
   });
 }
+
+// 计划销售额
+export function getPlanSalesData(query) {
+  return request({
+    url: '/api/report_manage/summary-tasks/data/plan/',
+    method: 'GET',
+    params: query,
+  });
+}
+
+// 创建
+export function postCreatePlanSalesData(body) {
+  return request({
+    url: '/api/report_manage/plan/create/many/',
+    method: 'POST',
+    data: body,
+  });
+}
+
+// 更新
+export function postUpdatePlanSalesData(body) {
+  return request({
+    url: `/api/report_manage/plan/update/many/`,
+    method: 'POST',
+    data: body,
+  });
+}

+ 408 - 0
src/views/reportManage/dataCenter/normalDisplay/components/PlanningSales.vue

@@ -0,0 +1,408 @@
+<script lang="ts" setup>
+/**
+ * @Name: PlanningSales.vue
+ * @Description: 计划销售额录入
+ * @Author: xinyan
+ */
+
+import {
+  getPlanSalesData,
+  postCreatePlanSalesData,
+  postUpdatePlanSalesData
+} from '/@/views/reportManage/dataCenter/api';
+import { planColumns } from '/@/views/reportManage/TaskManage/utils/columns';
+import { reactive, ref } from 'vue';
+import { VxeGridInstance } from 'vxe-table';
+import dayjs from 'dayjs';
+import Selector from '/@/views/reportManage/dataCenter/normalDisplay/components/Selector/index.vue';
+import { ElMessage, FormInstance, FormRules } from 'element-plus';
+
+// 年份选择器
+const currentYear = ref('');
+// 筛选查询
+const selectorRef = ref(null);
+const taskIds = ref({});
+
+// 表单数据
+const dialogVisible = ref(false);
+const formRef = ref<FormInstance>();
+const formData = reactive({
+  january: 3213,
+  february: 3213,
+  march: 3213,
+  april: 3213,
+  may: 3213,
+  june: 3123,
+  july: 312,
+  august: 312,
+  september: 453,
+  october: 543534,
+  november: 543,
+  december: 543
+});
+const rules = reactive<FormRules>({
+  january: [{ required: true, message: '请输入1月的计划销售额', trigger: 'blur' }],
+  february: [{ required: true, message: '请输入2月的计划销售额', trigger: 'blur' }],
+  march: [{ required: true, message: '请输入3月的计划销售额', trigger: 'blur' }],
+  april: [{ required: true, message: '请输入4月的计划销售额', trigger: 'blur' }],
+  may: [{ required: true, message: '请输入5月的计划销售额', trigger: 'blur' }],
+  june: [{ required: true, message: '请输入6月的计划销售额', trigger: 'blur' }],
+  july: [{ required: true, message: '请输入7月的计划销售额', trigger: 'blur' }],
+  august: [{ required: true, message: '请输入8月的计划销售额', trigger: 'blur' }],
+  september: [{ required: true, message: '请输入9月的计划销售额', trigger: 'blur' }],
+  october: [{ required: true, message: '请输入10月的计划销售额', trigger: 'blur' }],
+  november: [{ required: true, message: '请输入11月的计划销售额', trigger: 'blur' }],
+  december: [{ required: true, message: '请输入12月的计划销售额', trigger: 'blur' }]
+});
+// 表单项配置,用于动态生成输入框
+const formItems = [
+  { field: 'january', label: '1月' },
+  { field: 'february', label: '2月' },
+  { field: 'march', label: '3月' },
+  { field: 'april', label: '4月' },
+  { field: 'may', label: '5月' },
+  { field: 'june', label: '6月' },
+  { field: 'july', label: '7月' },
+  { field: 'august', label: '8月' },
+  { field: 'september', label: '9月' },
+  { field: 'october', label: '10月' },
+  { field: 'november', label: '11月' },
+  { field: 'december', label: '12月' }
+];
+
+// 表格
+const currentRow = ref('');
+const months = [
+  '1月', '2月', '3月', '4月', '5月',
+  '6月', '7月', '8月', '9月', '10月',
+  '11月', '12月'
+];
+const originalDataMap = new Map();
+const xGrid = ref<VxeGridInstance>();
+const activeEditRow = ref(null);
+
+const gridOptions = reactive({
+  border: 'inner',
+  // showOverflow: true,
+  height: 750,
+  loading: false,
+  round: true,
+  editConfig: {
+    trigger: 'manual',
+    mode: 'row',
+    showStatus: true,
+    autoClear: false,
+  },
+  pagerConfig: {
+    enabled: true,
+    total: 20,
+    currentPage: 1,
+    pageSize: 20,
+    pageSizes: [10, 20, 30],
+  },
+  columns: planColumns,
+  data: [],
+});
+
+function setDefaultDate() {
+  currentYear.value = dayjs().format('YYYY');
+}
+
+const disabledDate = (time: Date) => {
+  return time.getTime() > Date.now();
+};
+
+const resetForm = (formEl: FormInstance | undefined) => {
+  if (!formEl) return;
+  formEl.resetFields();
+};
+
+async function handleSave() {
+  const monthMap = {
+    january: 1,
+    february: 2,
+    march: 3,
+    april: 4,
+    may: 5,
+    june: 6,
+    july: 7,
+    august: 8,
+    september: 9,
+    october: 10,
+    november: 11,
+    december: 12
+  };
+  const monthPlanSales = Object.keys(formData).map((month) => ({
+    month: monthMap[month], // 使用映射表获取对应的月份数字
+    planSales: parseFloat(formData[month]) || 0 // 计划销售额转换为数字
+  }));
+
+  const body = {
+    year_date: currentYear.value,
+    task: currentRow.value.task || 0,
+    month_plan_sales: monthPlanSales,
+  };
+  try {
+    await postCreatePlanSalesData(body);
+    dialogVisible.value = false;
+  } catch (error) {
+    console.error('提交数据时出错:', error);
+  }
+}
+
+const submitForm = async (formEl: FormInstance | undefined) => {
+  if (!formEl) return;
+  await formEl.validate((valid, fields) => {
+    if (valid) {
+      handleSave();
+      ElMessage.success('创建成功');
+    } else {
+      ElMessage.error('添加失败,请检查填写数据');
+    }
+  });
+};
+
+const gridEvents = {
+  pageChange({ currentPage, pageSize }) {
+    if (gridOptions.pagerConfig) {
+      gridOptions.pagerConfig.currentPage = currentPage;
+      gridOptions.pagerConfig.pageSize = pageSize;
+    }
+    getList();
+  }
+};
+
+const isMonthIdExist = (row) => {
+  return months.some(month => row[month] && row[month].id);
+};
+
+const hasActiveEditRow = (row) => {
+  const $grid = xGrid.value;
+  if ($grid) {
+    return $grid.isEditByRow(row);
+  }
+  return false;
+};
+
+const clearRowEvent = (row) => {
+  activeEditRow.value = false;
+  const $grid = xGrid.value;
+  if ($grid) {
+    const originalData = originalDataMap.get(row.id);
+    if (originalData) {
+      Object.assign(row, originalData);
+      originalDataMap.delete(row.id);
+    }
+    $grid.clearEdit();
+  }
+};
+
+const handelEditRow = (row: RowVO) => {
+  activeEditRow.value = true;
+  const $grid = xGrid.value;
+  if ($grid) {
+    originalDataMap.set(row.id, { ...row });
+    $grid.setEditRow(row);
+  }
+};
+
+async function create(row) {
+  currentRow.value = row;
+  dialogVisible.value = true;
+}
+
+async function saveRow(row) {
+  try {
+    const body={
+      id: row.id,
+      planSales: row.planSales,
+    }
+    // await postUpdatePlanSalesData(body);
+    console.log("=>(PlanningSales.vue:219) body", body);
+  }catch (error){
+    console.error('Error updating plan sales data:', error);
+    ElMessage.error('更新失败,请检查填写数据');
+  }
+}
+
+async function getList() {
+  try {
+    gridOptions.loading = true;
+    const response = await getPlanSalesData({
+      page: gridOptions.pagerConfig.currentPage,
+      limit: gridOptions.pagerConfig.pageSize,
+      year_date: currentYear.value,
+      task_ids: taskIds.value,
+    });
+    gridOptions.data = response.data;
+    gridOptions.pagerConfig.total = response.total;
+  } catch (error) {
+    console.error('Error fetching task data:', error);
+  } finally {
+    gridOptions.loading = false;
+  }
+}
+
+function updateDataChange(newId) {
+  if (selectorRef.value) {
+    if (gridOptions.pagerConfig) {
+      gridOptions.pagerConfig.currentPage = 1;
+    }
+    taskIds.value = newId.value;
+    getList();
+  }
+}
+
+watch(currentYear, (newVal, oldVal) => {
+  if (newVal !== oldVal) {
+    currentYear.value = dayjs(newVal).format('YYYY');
+    getList();
+  }
+});
+
+const cellStyle = () => {
+  return {
+    fontSize: '13px',
+    fontWeight: '500',
+  };
+};
+
+const headerCellStyle = () => {
+  return {
+    fontSize: '13px',
+  };
+};
+function getNestedValue(row, field) {
+  const fields = field.split('.'); // 将字段名按 '.' 分割
+  return fields.reduce((acc, part) => acc && acc[part], row); // 依次访问嵌套属性
+}
+
+function setNestedValue(row, field, value) {
+  const fields = field.split('.');
+  const lastField = fields.pop();
+  const target = fields.reduce((acc, part) => acc && acc[part], row); // 找到最后一个嵌套对象
+  if (target) {
+    target[lastField] = value; // 设置值
+  }
+}
+
+// 函数:解析路径
+function getFieldValue(obj, path) {
+  return path.split('.').reduce((o, key) => (o ? o[key] : ''), obj);
+}
+
+// 函数:设置路径的值
+function setFieldValue(obj, path, value) {
+  const keys = path.split('.');
+  keys.reduce((o, key, index) => {
+    if (index === keys.length - 1) {
+      o[key] = value;
+    }
+    return o[key] || {};
+  }, obj);
+}
+onMounted(() => {
+  setDefaultDate();
+  getList();
+});
+</script>
+
+<template>
+  <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 class="demo-date-picker">
+        <div class="block">
+          <span class="demonstration">年份:</span>
+          <el-date-picker
+              v-model="currentYear"
+              :disabled-date="disabledDate"
+              placeholder="Pick a year"
+              style="width: 150px"
+              type="year"
+          />
+        </div>
+      </div>
+    </div>
+  </el-card>
+  <el-card class="mx-8">
+    <vxe-grid ref="xGrid" :cell-style="cellStyle" :header-cell-style="headerCellStyle" v-bind="gridOptions"
+              v-on="gridEvents">
+      <template #operate="{ row }">
+        <template v-if="hasActiveEditRow(row)">
+          <el-button link size="small" @click="clearRowEvent(row)">取消</el-button>
+          <el-button link size="small" type="warning" @click="saveRow(row)">保存</el-button>
+        </template>
+        <template v-else>
+          <el-button :disabled="!isMonthIdExist(row)||activeEditRow" link size="small" type="success" @click="handelEditRow(row)">
+            修改
+          </el-button>
+        </template>
+        <el-button v-if="!hasActiveEditRow(row)" :disabled="isMonthIdExist(row)||activeEditRow" link size="small" type="primary"
+                   @click="create(row)">创建
+        </el-button>
+      </template>
+      <template v-for="col in planColumns" #[`${col.slots?.edit}`]="{ row }">
+        <el-input v-model="getFieldValue(row, col.field)" @input="val => setFieldValue(row, col.field, val)" />
+      </template>
+    </vxe-grid>
+  </el-card>
+  <el-dialog v-model="dialogVisible" :before-close="handleClose" style="border-radius: 10px;"
+             title="创建计划销售额" width="600">
+    <template #title>
+      <span class="text-xl">创建计划销售额</span>
+      <div class="mt-2" style="display: flex; align-items: center;color: darkgray">
+        <div style="margin-right: 8px;">
+          平台编号:
+          <span class="italic pl-1 pr-2">{{ currentRow.platformNumber }}</span>
+        </div>
+        <div>
+          平台名称:
+          <span class="italic pl-1">{{ currentRow.platformName }}</span>
+        </div>
+      </div>
+    </template>
+    <el-form
+        ref="formRef"
+        :model="formData"
+        :rules="rules"
+        label-position="top"
+        label-width="auto"
+        status-icon
+    >
+      <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 5px">
+        <el-form-item v-for="item in formItems" :label="`${item.label}计划销售额`" :prop="item.field">
+          <el-input v-model="formData[item.field]"></el-input>
+        </el-form-item>
+      </div>
+    </el-form>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="dayFormVisible = false ;resetForm(formRef)">取消</el-button>
+        <el-button type="primary" @click="submitForm(formRef)">确定</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<style scoped>
+.demo-date-picker {
+  display: flex;
+  flex-direction: row-reverse;
+}
+
+.block {
+  display: flex;
+  align-items: center;
+  flex-wrap: nowrap;
+  overflow: hidden;
+  /* width: 100%; */
+}
+
+.demo-date-picker .demonstration {
+  color: var(--el-text-color-secondary);
+  font-size: 14px;
+  white-space: nowrap;
+  margin-right: 2px;
+}
+</style>

+ 25 - 9
src/views/reportManage/dataCenter/normalDisplay/components/TableDataDisplay.vue

@@ -2,7 +2,9 @@
 import { computed, defineProps, inject, onMounted, reactive, ref, Ref, watch } from 'vue';
 import { useRouter } from 'vue-router';
 import {
-  exportDayData, exportMonthData, exportWeekData,
+  exportDayData,
+  exportMonthData,
+  exportWeekData,
   getDayData,
   getDayTotalData,
   getMonthData,
@@ -12,7 +14,6 @@ import {
 } from '/src/views/reportManage/dataCenter/api';
 import { dayDataColumns, monthDataColumns, weekDataColumns } from '../../utils/columns';
 import dayjs from 'dayjs';
-import { exportTaskData } from '/@/views/reportManage/TaskManage/api';
 import { ElMessage } from 'element-plus';
 
 
@@ -339,7 +340,17 @@ const handleImport = () => {
   window.open(url, '_blank');
 };
 
-async function handleExport(taskIds, apiFunc, startDate, endDate,dateTypeKey) {
+const handelPlanSales = () => {
+  const url = router.resolve({
+    name: 'PlanningSales',
+    // query: {
+    //   dateType: dateType.value,
+    // }
+  }).href;
+  window.open(url, '_blank');
+};
+
+async function handleExport(taskIds, apiFunc, startDate, endDate, dateTypeKey) {
   try {
     gridOptions.loading = true;
     const response = await apiFunc({
@@ -355,11 +366,11 @@ async function handleExport(taskIds, apiFunc, startDate, endDate,dateTypeKey) {
     // 根据 dateTypeKey 设置文件名
     let fileName = '';
     if (dateTypeKey === 'day') {
-      fileName = `${startDate.value}~${endDate.value}日数据.xlsx`;  // 当为天时
+      fileName = `${ startDate.value }~${ endDate.value }日数据.xlsx`;  // 当为天时
     } else if (dateTypeKey === 'week') {
-      fileName = `${startDate.value}~${endDate.value}周数据.xlsx`;  // 当为周时
+      fileName = `${ startDate.value }~${ endDate.value }周数据.xlsx`;  // 当为周时
     } else if (dateTypeKey === 'month') {
-      fileName = `${startDate.value}~${endDate.value}月数据.xlsx`; // 当为月时
+      fileName = `${ startDate.value }~${ endDate.value }月数据.xlsx`; // 当为月时
     }
     link.href = url;
     link.setAttribute('download', fileName);
@@ -373,15 +384,15 @@ async function handleExport(taskIds, apiFunc, startDate, endDate,dateTypeKey) {
 }
 
 async function handleExportDay() {
-  await handleExport(taskIds.value, exportDayData, dayStartDate, dayEndDate,'day');
+  await handleExport(taskIds.value, exportDayData, dayStartDate, dayEndDate, 'day');
 }
 
 async function handleExportWeek() {
-  await handleExport(taskIds.value, exportWeekData, weekStartDate, weekEndDate,'week');
+  await handleExport(taskIds.value, exportWeekData, weekStartDate, weekEndDate, 'week');
 }
 
 async function handleExportMonth() {
-  await handleExport(taskIds.value, exportMonthData, monthStartDate, monthEndDate,'month');
+  await handleExport(taskIds.value, exportMonthData, monthStartDate, monthEndDate, 'month');
 }
 
 async function handelExportCurrent() {
@@ -481,6 +492,11 @@ onMounted(() => {
               v-bind="currentGridOptions" v-on="gridEvents" @sort-change="handleSortChange">
       <template #toolbar_buttons>
         <el-button icon="plus" target="_blank" type="primary" @click="handleImport">数据录入</el-button>
+        <el-button v-if="dateType === 'month'" text bg
+                   icon="plus" target="_blank"
+                   type="i" @click="handelPlanSales">
+          计划销售额
+        </el-button>
       </template>
       <template #toolbar_tools>
         <div class="pr-2.5">