Selaa lähdekoodia

add 客户优化页面

haveyou 1 vuosi sitten
vanhempi
sitoutus
b1ba7ba864

+ 5 - 0
src/styles/index.scss

@@ -157,6 +157,7 @@ body {
 
   .el-text {
     align-self: flex-start;
+    text-align: left;
   }
 
   //toolbar的状态选择
@@ -175,6 +176,10 @@ body {
       padding: 9px 15px;
     }
   }
+  .el-tag__content{
+    display: flex;
+    width: 100%;
+  }
 }
 
 //搜索况下时间选择框的样式

+ 24 - 0
src/views/optimization/complain/components/complain-item.vue

@@ -0,0 +1,24 @@
+<template>
+  <div class="complain-item flex mt-2 pr-4">
+    <el-avatar
+      shape="square"
+      :size="70"
+      fit="cover"
+      src="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg"
+    />
+
+    <div class="flex-1 flex flex-col  ml-2">
+      <div class="flex justify-between">
+        <ele-text size="small" type="info">用户</ele-text>
+        <ele-text size="small" type="info">2024-10-23 10:20:30</ele-text>
+      </div>
+      <div class="mt-2">
+        <ele-text>买家发起投诉,投诉原因:快递不取件,投诉说明:快递说发不了,不取件</ele-text>
+      </div>
+    </div>
+  </div>
+  <el-divider />
+
+</template>
+
+<script setup></script>

+ 83 - 0
src/views/optimization/complain/components/page-edit.vue

@@ -0,0 +1,83 @@
+<!-- 编辑弹窗 -->
+<template>
+  <ele-modal
+    form
+    :width="960"
+    v-model="visible"
+    title="投诉状态及操作"
+    @open="handleOpen"
+  >
+    <div class="flex w-full">
+      <div style="flex: 1.5">
+        <el-tag size="large">协商中</el-tag>
+
+        <ele-text style="margin-top: 15px"
+          >1.请点击协商(留言)与对方协商。</ele-text
+        >
+        <ele-text>2.也可点击赔付优惠券,投诉完结。</ele-text>
+        <el-divider />
+        <el-button>协商(留言)</el-button>
+        <el-button type="danger">赔付优惠券</el-button>
+        <el-divider />
+
+        <div class="common-title">协商历史</div>
+        <complain-item></complain-item>
+      </div>
+      <div class="flex-1 complain-detail">
+        <div class="common-title">投诉详情</div>
+        <pro-form :items="formItems" :model="form" class="mt-6"></pro-form>
+      </div>
+    </div>
+
+    <template #footer>
+      <el-button @click="handleCancel">关闭</el-button>
+    </template>
+  </ele-modal>
+</template>
+
+<script setup>
+  import { ref, reactive, nextTick } from 'vue';
+  import ProForm from '@/components/ProForm/index.vue';
+  import complainItem from '@/views/optimization/complain/components/complain-item.vue'
+
+  /** 弹窗是否打开 */
+  const visible = defineModel({ type: Boolean });
+
+  /** 关闭弹窗 */
+  const handleCancel = () => {
+    visible.value = false;
+  };
+
+  /** 弹窗打开事件 */
+  const handleOpen = () => {
+    visible.value = true;
+    nextTick(() => console.log('打开'));
+  };
+
+  const formItems = reactive([
+    { type: 'text', label: '投诉编号:', prop: 'code' },
+    { type: 'text', label: '投诉原因:', prop: 'reason' },
+    { type: 'text', label: '投诉说明:', prop: 'desc' },
+    { type: 'text', label: '投诉时间:', prop: 'createTime' },
+    { type: 'text', label: '发起人:', prop: 'createName' },
+    { type: 'text', label: '订单编号:', prop: 'orderCode' },
+  ]);
+  const form = reactive({
+    code: '1234567890',
+    reason: '商品质量差',
+    desc: '商品包装破损严重,质量很差',
+    createTime: '2022-01-01 12:00:00',
+    createName: '张三',
+    orderCode: '1234567890'
+  });
+
+  defineExpose({
+    handleOpen
+  });
+</script>
+<style lang="scss">
+  .complain-detail {
+    border-left: 1px solid #e6e6e6;
+    padding-left: 15px;
+  }
+</style>

+ 60 - 0
src/views/optimization/complain/components/page-search.vue

@@ -0,0 +1,60 @@
+<!-- 搜索表单 -->
+<template>
+  <ele-card :body-style="{ paddingBottom: '8px' }">
+    <ProSearch
+      :items="formItems"
+      ref="searchRef"
+      @search="search"
+      :initKeys="initKeys"
+    ></ProSearch>
+  </ele-card>
+</template>
+
+<script setup>
+  import { reactive, ref, defineEmits } from 'vue';
+  import ProSearch from '@/components/CommonPage/ProSearch2.vue';
+
+  let { proxy } = getCurrentInstance();
+  const emit = defineEmits(['search']);
+
+  const formItems = reactive([
+    { type: 'input', label: '订单编号', prop: 'orderCode' },
+    { type: 'input', label: '投诉编号', prop: 'code' },
+    {
+      type: 'datetimerange',
+      label: '投诉时间',
+      prop: 'time',
+      props: {
+        format: 'YYYY-MM-DD HH:mm:ss',
+        valueFormat: 'YYYY-MM-DD HH:mm:ss'
+      },
+      colProps: { span: 6 }
+    },
+    {
+      type: 'dictSelect',
+      label: '投诉原因',
+      prop: 'content',
+      props: { code: 'optimization_content' },
+      colProps: { span: 3 }
+
+    },
+    {
+      type: 'dictSelect',
+      label: '投诉原因',
+      prop: 'status',
+      props: { code: 'complain_status' },
+      colProps: { span: 3 }
+    }
+  ]);
+
+  const initKeys = reactive({
+    userName: '',
+    restrictType: ''
+  });
+
+  const searchRef = ref(null);
+  /** 搜索 */
+  const search = (data) => {
+    emit('search', { ...data });
+  };
+</script>

+ 118 - 0
src/views/optimization/complain/index.vue

@@ -0,0 +1,118 @@
+<template>
+  <ele-page flex-table>
+    <page-search @search="reload"></page-search>
+    <common-table
+      ref="pageRef"
+      :pageConfig="pageConfig"
+      :columns="columns"
+      :tools="false"
+    >
+      <template #baseinfo="{ row }">
+        <div class="flex flex-col items-start">
+          <el-tag size="large" class="w-full">
+            <el-text class="flex-1">投诉编号:32939021321</el-text>
+            <el-text class="flex-1">订单编号:32939021321</el-text>
+          </el-tag>
+          <div class="demo-image__preview mt-2">
+            <el-image
+              style="width: 70px; height: 70px;border-radius: 5px"
+              src="https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg"
+              fit="cover"
+            />
+          </div>
+        </div>
+      </template>
+      <template #action="{ row }">
+        <div>
+          <el-button
+            type="primary"
+            link
+            v-permission="'optimization:list:detail'"
+            @click="handleDetail(row)"
+          >
+            详情
+          </el-button>
+        </div>
+      </template>
+    </common-table>
+    <page-edit ref="pageEditRef" @refresh="reload()" />
+  </ele-page>
+</template>
+
+<script setup>
+  import { ref, reactive } from 'vue';
+  import CommonTable from '@/components/CommonPage/CommonTable.vue';
+  import pageSearch from './components/page-search.vue';
+  import pageEdit from './components/page-edit.vue';
+  import { useDictData } from '@/utils/use-dict-data';
+
+  defineOptions({ name: 'complainList' });
+  const [contentDicts, statusDicts] = useDictData([
+    'optimization_content',
+    'complain_status'
+  ]);
+
+  /** 表格列配置 */
+  const columns = ref([
+    {
+      label: '基础信息',
+      prop: 'baseinfo',
+      align: 'center',
+      minWidth: 240,
+      slot: 'baseinfo'
+    },
+    {
+      label: '投诉人',
+      prop: 'createName',
+      align: 'center'
+    },
+    {
+      label: '投诉时间',
+      prop: 'createTime',
+      align: 'center',
+      width: 180
+    },
+    {
+      label: '投诉原因',
+      prop: 'useStatus',
+      align: 'center',
+      formatter: (row) =>
+        contentDicts.value.find((d) => d.dictValue == row.useStatus)?.dictLabel
+    },
+    {
+      label: '投诉状态',
+      prop: 'complainStatus',
+      align: 'center',
+      formatter: (row) =>
+        statusDicts.value.find((d) => d.dictValue == row.useStatus)?.dictLabel
+    },
+    {
+      columnKey: 'action',
+      label: '操作',
+      width: 120,
+      align: 'center',
+      slot: 'action'
+    }
+  ]);
+
+  /** 页面组件实例 */
+  const pageRef = ref(null);
+
+  const pageConfig = reactive({
+    pageUrl: '/baseinfo/godown/pagelist',
+    exportUrl: '/baseinfo/godown/export',
+    fileName: '投诉管理',
+    cacheKey: 'complainTable'
+  });
+
+  //刷新表格
+  function reload(where) {
+    pageRef.value?.reload(where);
+  }
+
+  //处理
+  const pageEditRef = ref(null);
+  function handleDetail(row) {
+    pageEditRef.value?.handleOpen(row);
+  }
+</script>

+ 53 - 0
src/views/optimization/fallback/components/deal-fallback.vue

@@ -0,0 +1,53 @@
+<!-- 搜索表单 -->
+<template>
+  <simple-form-modal
+    title="处理反馈"
+    :items="formItems"
+    ref="editRef"
+    :baseUrl="baseUrl"
+    @success="(data) => emit('done', data)"
+  ></simple-form-modal>
+</template>
+
+<script setup>
+  import { reactive, ref, defineEmits, getCurrentInstance } from 'vue';
+  import { useFormData } from '@/utils/use-form-data';
+  import SimpleFormModal from '@/components/CommonPage/SimpleFormModal.vue';
+  const { proxy } = getCurrentInstance();
+
+  //获取省市
+  const emit = defineEmits(['done']);
+
+  const formItems = reactive([
+    {
+      type: 'input',
+      label: '联系方式',
+      prop: 'mobile'
+    },
+    {
+      type: 'textarea',
+      label: '意见描述',
+      prop: 'desc'
+    },
+    {
+      type: 'imageUpload',
+      label: '反馈图片',
+      prop: 'image'
+    }
+  ]);
+
+  //默认值
+  const baseUrl = reactive({
+    add: '/baseinfo/schoolInfo/save',
+    update: '/baseinfo/schoolInfo/edit'
+  });
+  const formData = ref({});
+
+  const editRef = ref(null);
+  function handleOpen(data = {}, type) {
+    formData.value = Object.assign(formData.value, data || {});
+    editRef.value?.handleOpen(formData.value, type);
+  }
+
+  defineExpose({ handleOpen });
+</script>

+ 36 - 0
src/views/optimization/fallback/components/page-search.vue

@@ -0,0 +1,36 @@
+<!-- 搜索表单 -->
+<template>
+  <ele-card :body-style="{ paddingBottom: '8px' }">
+    <ProSearch
+      :items="formItems"
+      ref="searchRef"
+      @search="search"
+      :initKeys="initKeys"
+    ></ProSearch>
+  </ele-card>
+</template>
+
+<script setup>
+  import { reactive, ref, defineEmits } from 'vue';
+  import ProSearch from '@/components/CommonPage/ProSearch2.vue';
+
+  let { proxy } = getCurrentInstance();
+  const emit = defineEmits(['search']);
+
+  const formItems = reactive([
+    { type: 'select', label: '意见类型', prop: 'fallbackType' },
+    { type: 'select', label: '状态', prop: 'status' },
+    { type: 'input', label: '用户UID', prop: 'userName' },
+  ]);
+
+  const initKeys = reactive({
+    userName: '',
+    restrictType: ''
+  });
+
+  const searchRef = ref(null);
+  /** 搜索 */
+  const search = (data) => {
+    emit('search', { ...data });
+  };
+</script>

+ 139 - 0
src/views/optimization/fallback/index.vue

@@ -0,0 +1,139 @@
+<template>
+  <ele-page flex-table>
+    <page-search @search="reload" />
+    <common-table
+      ref="pageRef"
+      :pageConfig="pageConfig"
+      :columns="columns"
+      :tools="false"
+    >
+      <template #toolbar>
+        <el-button
+          type="danger"
+          plain
+          :icon="DeleteOutlined"
+          v-permission="'optimization:fallback:batchDelete'"
+          @click="handleBatchDelete()"
+        >
+          批量删除
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          v-permission="'optimization:fallback:export'"
+          @click="handleExportExcel"
+          :icon="DownloadOutlined"
+        >
+          导出EXCEL
+        </el-button>
+      </template>
+      <template #action="{ row }">
+        <div>
+          <el-button
+            type="primary"
+            link
+            v-permission="'optimization:fallback:detail'"
+            @click="handleUpdate(row, 'detail')"
+          >
+            [详情]
+          </el-button>
+          <el-button
+            type="primary"
+            link
+            v-permission="'optimization:fallback:deal'"
+            @click="handleUpdate(row)"
+          >
+            去处理
+          </el-button>
+        </div>
+      </template>
+    </common-table>
+    <deal-fallback ref="dealFallbackRef" @done="reload()" />
+  </ele-page>
+</template>
+
+<script setup>
+  import { ref, reactive } from 'vue';
+  import CommonTable from '@/components/CommonPage/CommonTable.vue';
+  import dealFallback from '@/views/optimization/fallback/components/deal-fallback.vue';
+  import pageSearch from '@/views/optimization/fallback/components/page-search.vue';
+  import { useDictData } from '@/utils/use-dict-data';
+
+  defineOptions({ name: 'fallbackList' });
+  const [useStatusDicts] = useDictData(['sys_normal_disable']);
+
+  /** 表格列配置 */
+  const columns = ref([
+    {
+      type: 'selection',
+      columnKey: 'selection',
+      width: 50,
+      align: 'center',
+      fixed: 'left'
+    },
+    { type: 'index', label: '序号', width: 60, align: 'center' },
+    {
+      label: '状态',
+      prop: 'useStatus',
+      align: 'center',
+      formatter: (row) =>
+        useStatusDicts.value.find((d) => d.dictValue == row.useStatus)
+          ?.dictLabel
+    },
+    { label: '用户UID', prop: 'uid', align: 'center' },
+    { label: '联系方式', prop: 'mobile', align: 'center' },
+    { label: '意见类型', prop: 'type', align: 'center' },
+    { label: '反馈时间', prop: 'createTime', align: 'center', width: 180 },
+    {
+      label: '意见描述',
+      prop: 'paymentCode',
+      align: 'center',
+      minWidth: 200
+    },
+    {
+      columnKey: 'action',
+      label: '操作',
+      width: 160,
+      align: 'center',
+      slot: 'action'
+    }
+  ]);
+
+  /** 页面组件实例 */
+  const pageRef = ref(null);
+
+  const pageConfig = reactive({
+    pageUrl: '/baseinfo/godown/pagelist',
+    exportUrl: '/baseinfo/godown/export',
+    fileName: '意见反馈',
+    cacheKey: 'fallbackTable'
+  });
+
+  //刷新表格
+  function reload(where) {
+    pageRef.value?.reload(where);
+  }
+
+  //批量删除
+  function handleBatchDelete(row) {
+    let selections = row ? [row] : pageRef.value?.getSelections();
+    let ids = selections.map((item) => item.id).join(',');
+    let url = `/baseinfo/schoolInfo/removeById/${ids}`;
+    pageRef.value?.operatBatch({
+      title: '确认删除?',
+      method: 'post',
+      url,
+      row
+    });
+  }
+  //导出excel
+  function handleExportExcel() {
+    pageRef.value?.exportData('意见反馈');
+  }
+
+  //去处理
+  const dealFallbackRef = ref(null);
+  function handleUpdate(row, type) {
+    dealFallbackRef.value?.handleOpen(row, type);
+  }
+</script>

+ 74 - 0
src/views/optimization/list/components/page-edit.vue

@@ -0,0 +1,74 @@
+<!-- 搜索表单 -->
+<template>
+  <simple-form-modal
+    :title="title"
+    :items="formItems"
+    ref="editRef"
+    :baseUrl="baseUrl"
+    @success="(data) => emit('success', data)"
+  ></simple-form-modal>
+</template>
+
+<script setup>
+  import { reactive, ref, defineEmits, getCurrentInstance } from 'vue';
+  import { useFormData } from '@/utils/use-form-data';
+  import SimpleFormModal from '@/components/CommonPage/SimpleFormModal.vue';
+  const { proxy } = getCurrentInstance();
+
+  //获取省市
+  const provinceList = ref([]);
+  const cityList = ref([]);
+  const title = ref('新增');
+  const emit = defineEmits(['success']);
+
+  const formItems = computed(() => {
+    return [
+      {
+        type: 'dictRadio',
+        label: '类型',
+        prop: 'optimizationType',
+        props: { code: 'optimization_type' },
+        required: true
+      },
+      {
+        type: 'dictCheckbox',
+        label: '提交内容',
+        prop: 'optimizationContent',
+        props: { code: 'optimization_content' },
+        required: true
+      },
+      {
+        type: 'imageUpload',
+        label: '附件图片',
+        prop: 'image',
+        required: true,
+        props: {
+          limit: 1
+        }
+      },
+      {
+        type: 'dictRadio',
+        label: '状态',
+        prop: 'status',
+        props: { code: 'sys_normal_disable' },
+        required: true
+      }
+    ];
+  });
+  //默认值
+  const baseUrl = reactive({
+    add: '/baseinfo/schoolInfo/save',
+    update: '/baseinfo/schoolInfo/edit'
+  });
+  const formData = ref({ schoolTag: '1' });
+
+  const editRef = ref(null);
+
+  function handleOpen(data = {}) {
+    title.value = data && data.id ? '编辑' : '新增';
+    formData.value = Object.assign(formData.value, data || {});
+    editRef.value?.handleOpen(formData.value);
+  }
+
+  defineExpose({ handleOpen });
+</script>

+ 115 - 0
src/views/optimization/list/index.vue

@@ -0,0 +1,115 @@
+<template>
+  <ele-page flex-table>
+    <common-table
+      ref="pageRef"
+      :pageConfig="pageConfig"
+      :columns="columns"
+      :tools="false"
+    >
+      <template #action="{ row }">
+        <div>
+          <el-button
+            type="primary"
+            link
+            v-permission="'optimization:list:detail'"
+            @click="handleUpdate(row)"
+          >
+            [编辑]
+          </el-button>
+          <el-button
+            :type="row.useStatus == 1 ? 'warning' : 'primary'"
+            link
+            v-permission="'optimization:list:changeStatus'"
+            @click="handleChangeStatus(row)"
+          >
+            {{ row.useStatus == 1 ? '[停用]' : '[启用]' }}
+          </el-button>
+          <el-button
+            type="danger"
+            link
+            v-permission="'optimization:list:delete'"
+            @click="handleDelete(row)"
+          >
+            [删除]
+          </el-button>
+        </div>
+      </template>
+    </common-table>
+    <page-edit ref="pageEditRef" @refresh="reload()" />
+  </ele-page>
+</template>
+
+<script setup>
+  import { ref, reactive } from 'vue';
+  import CommonTable from '@/components/CommonPage/CommonTable.vue';
+  import pageEdit from '@/views/optimization/list/components/page-edit.vue';
+  import { useDictData } from '@/utils/use-dict-data';
+
+  defineOptions({ name: 'optimizationList' });
+  const [useStatusDicts] = useDictData(['use_status']);
+
+  /** 表格列配置 */
+  const columns = ref([
+    { label: '类型', prop: 'uid', align: 'center' },
+    {
+      label: '提交内容',
+      prop: 'paymentCode',
+      align: 'center',
+      minWidth: 160
+    },
+    {
+      label: '状态',
+      prop: 'useStatus',
+      align: 'center',
+      formatter: (row) =>
+        useStatusDicts.value.find((d) => d.dictValue == row.useStatus)
+          ?.dictLabel
+    },
+    {
+      columnKey: 'action',
+      label: '操作',
+      width: 220,
+      align: 'center',
+      slot: 'action'
+    }
+  ]);
+
+  /** 页面组件实例 */
+  const pageRef = ref(null);
+
+  const pageConfig = reactive({
+    pageUrl: '/baseinfo/godown/pagelist',
+    exportUrl: '/baseinfo/godown/export',
+    fileName: '客户优化管理',
+    cacheKey: 'optimizationTable'
+  });
+
+  //刷新表格
+  function reload(where) {
+    pageRef.value?.reload(where);
+  }
+
+  //停用/启用
+  function handleChangeStatus(row) {
+    let message = row.useStatus == 1 ? '确认停用?' : '确认启用?';
+    let data = JSON.parse(JSON.stringify(row));
+    data.useStatus = row.useStatus == 1 ? 0 : 1;
+    pageRef.value?.messageBoxConfirm({
+      message,
+      fetch: () => request.post('/baseinfo/godown/update', data)
+    });
+  }
+  //删除
+  function handleDelete(row) {
+    pageRef.value?.messageBoxConfirm({
+      message: '确认删除?',
+      fetch: () => request.post('/baseinfo/godown/delete', { id: row.id })
+    });
+  }
+
+  //编辑
+  const pageEditRef = ref(null);
+  function handleUpdate(row) {
+    pageEditRef.value?.handleOpen(row);
+  }
+</script>

+ 51 - 0
src/views/optimization/orderAnalysis/components/pie-chart.vue

@@ -0,0 +1,51 @@
+<template>
+  <v-chart ref="saleChartRef" style="height: 420px" :option="options" />
+</template>
+<script setup>
+  import VChart from 'vue-echarts';
+  import { use } from 'echarts/core';
+  import { CanvasRenderer } from 'echarts/renderers';
+  import { PieChart } from 'echarts/charts';
+  import {
+    GridComponent,
+    TooltipComponent,
+    LegendComponent,
+    ToolboxComponent
+  } from 'echarts/components';
+
+  // 按需加载echarts
+  use([
+    CanvasRenderer,
+    PieChart,
+    GridComponent,
+    TooltipComponent,
+    LegendComponent
+  ]);
+
+  const options = reactive({
+    legend: {
+      top: 'bottom'
+    },
+    series: [
+      {
+        name: 'Nightingale Chart',
+        type: 'pie',
+        radius: [50, 150],
+        center: ['50%', '50%'],
+        roseType: 'area',
+        itemStyle: {
+          borderRadius: 8
+        },
+        data: [
+          { value: 40, name: '快递不取件' },
+          { value: 38, name: '快递不上门' },
+          { value: 32, name: '时间不合适' },
+          { value: 30, name: '发现更贵的平台' },
+          { value: 28, name: '不想卖了' },
+          { value: 26, name: '信息填写错误' },
+          { value: 22, name: '以上都不是' }
+        ]
+      }
+    ]
+  });
+</script>

+ 31 - 0
src/views/optimization/orderAnalysis/components/rank-list.vue

@@ -0,0 +1,31 @@
+<template>
+  <ele-data-table
+    ref="tableRef"
+    class="flex-1"
+    row-key="id"
+    :columns="columns"
+    :data="dataList"
+  ></ele-data-table>
+</template>
+
+<script setup>
+  import SimpleTable from '@/components/CommonPage/SimpleTable.vue';
+  import request from '@/utils/request';
+
+  const columns = reactive([
+    { label: '排行榜', prop: 'rank', align: 'center' },
+    { label: '取消地区', prop: 'area', align: 'center' },
+    { label: '取消数量', prop: 'number', align: 'center' }
+  ]);
+
+  const dataList = reactive([
+    { rank: 1, area: '北京', number: 10 },
+    { rank: 2, area: '上海', number: 9 },
+    { rank: 3, area: '广州', number: 8 },
+    { rank: 4, area: '深圳', number: 7 },
+    { rank: 5, area: '成都', number: 6 },
+    { rank: 6, area: '杭州', number: 5 },
+    { rank: 7, area: '武汉', number: 4 },
+    { rank: 8, area: '西安', number: 3 },
+  ]);
+</script>

+ 50 - 0
src/views/optimization/orderAnalysis/index.vue

@@ -0,0 +1,50 @@
+<template>
+  <ele-page flex-table>
+    <common-table
+      ref="pageRef"
+      :pageConfig="pageConfig"
+      :columns="columns"
+      :tools="false"
+    >
+      <template #toolbar>
+        <div class="flex mb-6">
+          <pieChart class="flex-1"></pieChart>
+          <rank-list></rank-list>
+        </div>
+      </template>
+    </common-table>
+  </ele-page>
+</template>
+
+<script setup>
+  import { ref, reactive } from 'vue';
+  import CommonTable from '@/components/CommonPage/CommonTable.vue';
+  import pieChart from '@/views/optimization/orderAnalysis/components/pie-chart.vue';
+  import rankList from '@/views/optimization/orderAnalysis/components/rank-list.vue';
+  import { useDictData } from '@/utils/use-dict-data';
+
+  defineOptions({ name: 'orderCancelAnalysis' });
+
+  /** 表格列配置 */
+  const columns = ref([
+    { label: '订单编号', prop: 'orderCode', align: 'center' },
+    { label: '用户UID', prop: 'uid', align: 'center' },
+    { label: '取消原因', prop: 'cancelReason', align: 'center', minWidth: 200 },
+    { label: '取消时间', prop: 'createTime', align: 'center' }
+  ]);
+
+  /** 页面组件实例 */
+  const pageRef = ref(null);
+
+  const pageConfig = reactive({
+    pageUrl: '/baseinfo/godown/pagelist',
+    exportUrl: '/baseinfo/godown/export',
+    fileName: '订单取消分析',
+    cacheKey: 'orderCancelAnalysisTable'
+  });
+
+  //刷新表格
+  function reload(where) {
+    pageRef.value?.reload(where);
+  }
+</script>

+ 51 - 0
src/views/optimization/reportAnalysis/components/pie-chart.vue

@@ -0,0 +1,51 @@
+<template>
+  <v-chart ref="saleChartRef" style="height: 420px" :option="options" />
+</template>
+<script setup>
+  import VChart from 'vue-echarts';
+  import { use } from 'echarts/core';
+  import { CanvasRenderer } from 'echarts/renderers';
+  import { PieChart } from 'echarts/charts';
+  import {
+    GridComponent,
+    TooltipComponent,
+    LegendComponent,
+    ToolboxComponent
+  } from 'echarts/components';
+
+  // 按需加载echarts
+  use([
+    CanvasRenderer,
+    PieChart,
+    GridComponent,
+    TooltipComponent,
+    LegendComponent
+  ]);
+
+  const options = reactive({
+    legend: {
+      top: 'bottom'
+    },
+    series: [
+      {
+        name: 'Nightingale Chart',
+        type: 'pie',
+        radius: [50, 150],
+        center: ['50%', '50%'],
+        roseType: 'area',
+        itemStyle: {
+          borderRadius: 8
+        },
+        data: [
+          { value: 40, name: '快递不取件' },
+          { value: 38, name: '快递不上门' },
+          { value: 32, name: '时间不合适' },
+          { value: 30, name: '发现更贵的平台' },
+          { value: 28, name: '不想卖了' },
+          { value: 26, name: '信息填写错误' },
+          { value: 22, name: '以上都不是' }
+        ]
+      }
+    ]
+  });
+</script>

+ 31 - 0
src/views/optimization/reportAnalysis/components/rank-list.vue

@@ -0,0 +1,31 @@
+<template>
+  <ele-data-table
+    ref="tableRef"
+    class="flex-1"
+    row-key="id"
+    :columns="columns"
+    :data="dataList"
+  ></ele-data-table>
+</template>
+
+<script setup>
+  import SimpleTable from '@/components/CommonPage/SimpleTable.vue';
+  import request from '@/utils/request';
+
+  const columns = reactive([
+    { label: '排行榜', prop: 'rank', align: 'center' },
+    { label: '上报地区', prop: 'area', align: 'center' },
+    { label: '上报数量', prop: 'number', align: 'center' }
+  ]);
+
+  const dataList = reactive([
+    { rank: 1, area: '北京', number: 10 },
+    { rank: 2, area: '上海', number: 9 },
+    { rank: 3, area: '广州', number: 8 },
+    { rank: 4, area: '深圳', number: 7 },
+    { rank: 5, area: '成都', number: 6 },
+    { rank: 6, area: '杭州', number: 5 },
+    { rank: 7, area: '武汉', number: 4 },
+    { rank: 8, area: '西安', number: 3 },
+  ]);
+</script>

+ 50 - 0
src/views/optimization/reportAnalysis/index.vue

@@ -0,0 +1,50 @@
+<template>
+  <ele-page flex-table>
+    <common-table
+      ref="pageRef"
+      :pageConfig="pageConfig"
+      :columns="columns"
+      :tools="false"
+    >
+      <template #toolbar>
+        <div class="flex mb-6">
+          <pieChart class="flex-1"></pieChart>
+          <rank-list></rank-list>
+        </div>
+      </template>
+    </common-table>
+  </ele-page>
+</template>
+
+<script setup>
+  import { ref, reactive } from 'vue';
+  import CommonTable from '@/components/CommonPage/CommonTable.vue';
+  import pieChart from '@/views/optimization/orderAnalysis/components/pie-chart.vue';
+  import rankList from '@/views/optimization/orderAnalysis/components/rank-list.vue';
+  import { useDictData } from '@/utils/use-dict-data';
+
+  defineOptions({ name: 'orderCancelAnalysis' });
+
+  /** 表格列配置 */
+  const columns = ref([
+    { label: '订单编号', prop: 'orderCode', align: 'center' },
+    { label: '用户UID', prop: 'uid', align: 'center' },
+    { label: '取消原因', prop: 'cancelReason', align: 'center', minWidth: 200 },
+    { label: '取消时间', prop: 'createTime', align: 'center' }
+  ]);
+
+  /** 页面组件实例 */
+  const pageRef = ref(null);
+
+  const pageConfig = reactive({
+    pageUrl: '/baseinfo/godown/pagelist',
+    exportUrl: '/baseinfo/godown/export',
+    fileName: '订单取消分析',
+    cacheKey: 'orderCancelAnalysisTable'
+  });
+
+  //刷新表格
+  function reload(where) {
+    pageRef.value?.reload(where);
+  }
+</script>

+ 70 - 0
src/views/optimization/service/components/page-edit.vue

@@ -0,0 +1,70 @@
+<!-- 搜索表单 -->
+<template>
+  <simple-form-modal
+    :title="title"
+    :items="formItems"
+    ref="editRef"
+    :baseUrl="baseUrl"
+    @success="(data) => emit('success', data)"
+  ></simple-form-modal>
+</template>
+
+<script setup>
+  import { reactive, ref, defineEmits, getCurrentInstance } from 'vue';
+  import { useFormData } from '@/utils/use-form-data';
+  import SimpleFormModal from '@/components/CommonPage/SimpleFormModal.vue';
+  const { proxy } = getCurrentInstance();
+
+  //获取省市
+  const provinceList = ref([]);
+  const cityList = ref([]);
+  const title = ref('新增');
+  const emit = defineEmits(['success']);
+
+  const formItems = computed(() => {
+    return [
+      {
+        type: 'input',
+        label: '服务名称',
+        prop: 'name',
+        required: true
+      },
+      {
+        type: 'textarea',
+        label: '服务内容',
+        prop: 'content',
+        required: true
+      },
+      {
+        type: 'switch',
+        label: '前台展示',
+        prop: 'optimizationType',
+        props: { code: 'optimization_type' },
+        required: true
+      },
+      {
+        type: 'dictRadio',
+        label: '状态',
+        prop: 'status',
+        props: { code: 'sys_normal_disable' },
+        required: true
+      }
+    ];
+  });
+  //默认值
+  const baseUrl = reactive({
+    add: '/baseinfo/schoolInfo/save',
+    update: '/baseinfo/schoolInfo/edit'
+  });
+  const formData = ref({ schoolTag: '1' });
+
+  const editRef = ref(null);
+
+  function handleOpen(data = {}) {
+    title.value = data && data.id ? '编辑服务' : '新增服务';
+    formData.value = Object.assign(formData.value, data || {});
+    editRef.value?.handleOpen(formData.value);
+  }
+
+  defineExpose({ handleOpen });
+</script>

+ 159 - 0
src/views/optimization/service/index.vue

@@ -0,0 +1,159 @@
+<template>
+  <ele-page flex-table>
+    <common-table
+      ref="pageRef"
+      :pageConfig="pageConfig"
+      :columns="columns"
+      :tools="false"
+    >
+      <template #useStatus="{ row }">
+        <el-switch
+          v-model="row.useStatus"
+          style="--el-switch-on-color: #13ce66"
+          :active-value="1"
+          :inactive-value="0"
+          @change="statusSwitchChange($event, row, 'useStatus')"
+        />
+        <el-text style="margin-left: 5px">{{
+          row.useStatus == 0 ? '已关闭' : '已开启'
+        }}</el-text>
+      </template>
+      <template #action="{ row }">
+        <div>
+          <el-button
+            type="primary"
+            link
+            v-permission="'optimization:list:detail'"
+            @click="handleUpdate(row)"
+          >
+            [编辑]
+          </el-button>
+          <el-button
+            :type="row.useStatus == 1 ? 'warning' : 'primary'"
+            link
+            v-permission="'optimization:list:changeStatus'"
+            @click="handleChangeStatus(row)"
+          >
+            {{ row.useStatus == 1 ? '[停用]' : '[启用]' }}
+          </el-button>
+          <el-button
+            type="danger"
+            link
+            v-permission="'optimization:list:delete'"
+            @click="handleDelete(row)"
+          >
+            [删除]
+          </el-button>
+        </div>
+      </template>
+    </common-table>
+    <page-edit ref="pageEditRef" @refresh="reload()" />
+  </ele-page>
+</template>
+
+<script setup>
+  import { ref, reactive } from 'vue';
+  import CommonTable from '@/components/CommonPage/CommonTable.vue';
+  import pageEdit from './components/page-edit.vue';
+  import { useDictData } from '@/utils/use-dict-data';
+
+  defineOptions({ name: 'optimizationList' });
+  const [useStatusDicts] = useDictData(['use_status']);
+
+  /** 表格列配置 */
+  const columns = ref([
+    { label: '服务名称', prop: 'uid', align: 'center' },
+    {
+      label: '服务介绍',
+      prop: 'paymentCode',
+      align: 'center',
+      minWidth: 160
+    },
+    {
+      label: '前台页面展示',
+      prop: 'useStatus',
+      align: 'center',
+      slot: 'useStatus',
+      minWidth: 120
+    },
+    {
+      label: '状态',
+      prop: 'useStatus',
+      align: 'center',
+      formatter: (row) =>
+        useStatusDicts.value.find((d) => d.dictValue == row.useStatus)
+          ?.dictLabel
+    },
+    {
+      columnKey: 'action',
+      label: '操作',
+      width: 220,
+      align: 'center',
+      slot: 'action'
+    }
+  ]);
+
+  /** 页面组件实例 */
+  const pageRef = ref(null);
+
+  const pageConfig = reactive({
+    pageUrl: '/baseinfo/godown/pagelist',
+    exportUrl: '/baseinfo/godown/export',
+    fileName: '客户优化管理',
+    cacheKey: 'optimizationTable'
+  });
+
+  //刷新表格
+  function reload(where) {
+    pageRef.value?.reload(where);
+  }
+
+  //停用/启用
+  function handleChangeStatus(row) {
+    let message = row.useStatus == 1 ? '是否停用此服务?' : '是否启用此服务?';
+    let data = JSON.parse(JSON.stringify(row));
+    data.useStatus = row.useStatus == 1 ? 0 : 1;
+    pageRef.value?.messageBoxConfirm({
+      message,
+      fetch: () => request.post('/baseinfo/godown/update', data)
+    });
+  }
+  //删除
+  function handleDelete(row) {
+    pageRef.value?.messageBoxConfirm({
+      message: '是否删除此服务?',
+      fetch: () => request.post('/baseinfo/godown/delete', { id: row.id })
+    });
+  }
+
+  //修改状态
+  const statusSwitchChange = (value, row, key) => {
+    let data = JSON.parse(JSON.stringify(row));
+    let message = row.useStatus == 1 ? '是否开启前台展示?' : '是否关闭前台展示?';
+    ElMessageBox.confirm(message, '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '关闭',
+      type: 'warning'
+    })
+      .then(() => {
+        data[key] = value;
+        request.post('/baseinfo/godown/update', data).then((res) => {
+          if (res.data.code === 200) {
+            EleMessage.success('操作成功');
+            reload();
+          } else {
+            EleMessage.error(res.data.msg);
+          }
+        });
+      })
+      .catch(() => {
+        row[key] = value == 1 ? 0 : 1;
+      });
+  };
+
+  //编辑
+  const pageEditRef = ref(null);
+  function handleUpdate(row) {
+    pageEditRef.value?.handleOpen(row);
+  }
+</script>