Przeglądaj źródła

调整动态路由

Lijy 1 rok temu
rodzic
commit
f77662d01a

+ 13 - 6
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/header/header.vue

@@ -7,10 +7,10 @@
     <div v-for="(routerItem, routerItemIdex) in routerList"
       :class="`border-b-2 border-transparent hover:border-white p-2 mr-4 cursor-pointer multipleyHeader ${activeRouter?.path === routerItem.path ? 'border-white' : ''}`"
       :key="routerItem.path" ref="childDivs" v-show="visibleItems.includes(routerItemIdex)">
-      <div v-if="routerItem.children && routerItem.children.length <= 0" @click="setCurrentRouter(routerItem)" class="text-nowrap">
+      <div v-if="routerItem.children && routerItem.children.length <= 0 && routerItem?.isMenu" @click="setCurrentRouter(routerItem)" class="text-nowrap">
         {{ routerItem.name }}
       </div>
-      <div v-else class="flex justify-center items-center">
+      <div v-if="routerItem.children && routerItem.children.length > 0" class="flex justify-center items-center">
         <el-dropdown>
           <div class="text-white w-full h-full headerText">
             {{ routerItem.name }}
@@ -43,14 +43,16 @@
 </template>
   
 <script lang="ts" setup>
-import { onMounted, ref, watchEffect } from 'vue';
-import { RouteRecordRaw, useRouter } from 'vue-router';
+import { onMounted, ref, watchEffect, watch } from 'vue';
+import { RouteRecordRaw, useRouter, useRoute } from 'vue-router';
 import { useStore } from "../../store/index"
 import defaultCover from "../../assets/defaultCover.png";
 import loginLogin from '../../assets/login/login_logo.png'
 const { routers, clearStore } = useStore()
 const router = useRouter();
-const routerList = ref<RouteRecordRaw[]>([]);
+const route = useRoute()
+// const routerList = ref<RouteRecordRaw[]>([]);
+const routerList = ref<any[]>([]);
 const activeRouter = ref<RouteRecordRaw>();
 
 const visibleItems = ref<number[]>([]);
@@ -103,7 +105,6 @@ const logout = () => {
 onMounted(() => {
   routerList.value = routers;
   activeRouter.value = routerList.value.find((item) => item.path === router.currentRoute.value.path);
-  //console.log("routerList", routerList);
 
   window.addEventListener('resize', updateVisibleItems);
   setTimeout(() => {
@@ -112,6 +113,12 @@ onMounted(() => {
 })
 watchEffect(() => {
   updateVisibleItems();
+  watch(() => route.path, (newPath, _oldPath) => {  
+      activeRouter.value = routerList.value.find((item) => item.path === newPath);
+    }, { 
+      immediate: false 
+    }
+  );  
 });
 </script>
   

+ 0 - 1
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/api.ts

@@ -1 +0,0 @@
-export const TEST_API = "/api/test"; //发送验证码

+ 9 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/constant.ts

@@ -0,0 +1,9 @@
+export const MOD = '/thread'
+export const prefix = '/clue'
+export const GETSYSFILED = '/sys-dict/getListByCode'
+export const GETPERSONNEL = '/user/getSimpleActiveUserList'
+export const GETTABLE = `${prefix}/listClue`
+export const GETDETAIL = `${prefix}/getDetail`
+export const UNDATECLAIM = `${prefix}/claim`
+export const UNDATEFORM = `${prefix}/insertAndUpdate`
+export const DELTEROW = `${prefix}/delete`

+ 24 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/detail/index.vue

@@ -0,0 +1,24 @@
+<template>
+    <div class="h-full flex">
+      <div class="p-5 w-80 pr-0">
+        <div class="bg-white w-full h-full shadow-md rounded-md flex flex-col">
+          <div class="flex-1 p-3 overflow-y-auto">
+            线索详情
+          </div>
+          <div class="w-full flex p-3 shadow-[0_-3px_5px_0px_rgba(0,0,0,0.2)]">
+            <El-button class="w-full">重置</El-Button>
+            <El-button type="primary" class="w-full">搜索</El-Button>
+          </div>
+        </div>
+      </div>
+      <div class="flex-1 p-5">
+        <div class="bg-white w-full h-full p-3 shadow-md rounded-md">222</div>
+      </div>
+    </div>
+  </template>
+  
+  <script lang="ts" setup>
+  
+  </script>
+  
+  <style lang="scss" scoped></style>

+ 198 - 5
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/index.vue

@@ -3,22 +3,215 @@
     <div class="p-5 w-80 pr-0">
       <div class="bg-white w-full h-full shadow-md rounded-md flex flex-col">
         <div class="flex-1 p-3 overflow-y-auto">
-
+          <el-form :model="filterCriteriaForm" label-width="70px" style="max-width: 600px">
+            <el-form-item label="线索名称">
+              <el-input v-model="filterCriteriaForm.clueName" clearable placeholder="请输入"></el-input>
+            </el-form-item>
+            <el-form-item label="线索来源">
+              <el-select v-model="filterCriteriaForm.clueSourceId" placeholder="请选择">
+                <el-option v-for="item in fixedData.ClueSources" :key="item.id" :label="item.name" :value="item.id" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="电话号码">
+              <el-input v-model="filterCriteriaForm.phone" clearable placeholder="请输入"></el-input>
+            </el-form-item>
+            <el-form-item label="邮箱">
+              <el-input v-model="filterCriteriaForm.email" clearable placeholder="请输入"></el-input>
+            </el-form-item>
+            <el-form-item label="客户行业">
+              <el-select v-model="filterCriteriaForm.customerIndustryId" placeholder="请选择">
+                <el-option v-for="item in fixedData.CustomIndustry" :key="item.id" :label="item.name" :value="item.id" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="客户级别">
+              <el-select v-model="filterCriteriaForm.customerLevelId" placeholder="请选择">
+                <el-option v-for="item in fixedData.CustomLevel" :key="item.id" :label="item.name" :value="item.id" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="负责人">
+              <el-select v-model="filterCriteriaForm.inchargerId" placeholder="请选择">
+                <el-option v-for="item in fixedData.Personnel" :key="item.id" :label="item.name" :value="item.id" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="创建时间">
+              <el-date-picker v-model="filterCriteriaForm.startTime" type="date" placeholder="请选择" :clearable="false"
+                format="YYYY-MM-DD" value-format="YYYY-MM-DD" />
+            </el-form-item>
+            <el-form-item label="">
+              <el-date-picker v-model="filterCriteriaForm.endTime" type="date" placeholder="请选择" :clearable="false"
+                format="YYYY-MM-DD" value-format="YYYY-MM-DD" />
+            </el-form-item>
+          </el-form>
         </div>
         <div class="w-full flex p-3 shadow-[0_-3px_5px_0px_rgba(0,0,0,0.2)]">
-          <El-button class="w-full">重置</El-Button>
-          <El-button type="primary" class="w-full">搜索</El-Button>
+          <El-button class="w-full" @click="resetTable()">重置</El-Button>
+          <El-button type="primary" class="w-full" @click="searchTable()">搜索</El-Button>
         </div>
       </div>
     </div>
-    <div class="flex-1 p-5">
-      <div class="bg-white w-full h-full p-3 shadow-md rounded-md">222</div>
+    <div class="flex-1 p-5 overflow-auto">
+      <div class="bg-white w-full h-full p-3 shadow-md rounded-md flex flex-col">
+        <div class="flex justify-end pb-3">
+          <el-button type="primary">回收站</el-button>
+          <el-button type="primary">新建线索</el-button>
+          <el-button type="primary" @click="batchTransfer()">批量转移</el-button>
+          <el-button type="primary" @clicl="batchDelete()">批量删除</el-button>
+          <el-button type="primary">导入</el-button>
+          <el-button type="primary">导出</el-button>
+        </div>
+        <div class="flex-1 w-full overflow-hidden">
+          <el-table ref="clueTableRef" :data="clueTable" border v-loading="allLoading.clueTableLading"
+            style="width: 100%;height: 100%;">
+            <el-table-column type="selection" width="55" />
+            <el-table-column prop="clueName" label="线索名称" width="180">
+              <template #default="scope">
+                <el-button link type="primary" size="large" @click.prevent="toClueTableDetail(scope.row)">{{ scope.row.clueName
+                }}</el-button>
+              </template>
+            </el-table-column>
+            <el-table-column prop="clueSourceId" label="线索来源" width="180"></el-table-column>
+            <el-table-column prop="phone" label="电话号码" width="180"></el-table-column>
+            <el-table-column prop="email" label="邮箱" width="180"></el-table-column>
+            <el-table-column prop="customerIndustryId" label="客户行业" width="180"></el-table-column>
+            <el-table-column prop="customerLevelId" label="客户级别" width="180"></el-table-column>
+            <el-table-column prop="inchargerId" label="负责人" width="180"></el-table-column>
+            <el-table-column prop="createName" label="创建人" width="180"></el-table-column>
+            <el-table-column prop="createTime" label="创建时间" width="180"></el-table-column>
+            <el-table-column label="操作" fixed="right" width="200">
+              <template #default="scope">
+                <el-button link type="primary" size="large">编辑</el-button>
+                <el-button link type="primary" size="large">新建任务</el-button>
+                <el-button link type="danger" size="large" @click.prevent="deleteRow(scope.$index)">删除</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+        <div class="flex justify-end pt-3">
+          <el-pagination layout="total, prev, pager, next, sizes" :total="clueTotalTable" :hide-on-single-page="true" />
+        </div>
+      </div>
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
+import { ref, reactive, onMounted, inject } from "vue";
+import { GETSYSFILED, MOD, GETPERSONNEL, GETTABLE, GETDETAIL, UNDATECLAIM, UNDATEFORM, DELTEROW } from './constant'
+import { getAllListByCode, getFromValue, resetFromValue, getFirstDayOfMonth, getLastDayOfMonth, formatDate } from '@/utils/tools'
+import { post, get } from "@/utils/request";
+import { useRouter, useRoute } from "vue-router";
+
+// 定义类型
+interface fixedDataInterface {
+  id: string | number,
+  companyId: string | number,
+  code: string,
+  name: string,
+  seq: string | number,
+}
+
+interface personnelInterface {
+  id: string | number,
+  name: string,
+  phone: string,
+  jobNumber: string
+}
+
+// 定义变量
+const route = useRoute()
+const router = useRouter()
+const globalPopup = inject<GlobalPopup>('globalPopup')
+const filterCriteriaForm = reactive<filterCriteriaFormType>({ // 筛选条件form
+  clueName: '',
+  clueSourceId: '',
+  phone: '',
+  email: '',
+  customerIndustryId: '',
+  customerLevelId: '',
+  inchargerId: '',
+  startTime: getFirstDayOfMonth(new Date()),
+  endTime: formatDate(new Date()),
+  pageIndex: 1,
+  pageFrom: 10
+})
+const allLoading = reactive({
+  clueTableLading: false,
+})
+const fixedData = reactive({
+  ClueSources: [] as fixedDataInterface[],
+  CustomIndustry: [] as fixedDataInterface[],
+  CustomLevel: [] as fixedDataInterface[],
+  Personnel: [] as personnelInterface[]
+})
+const clueTable = ref([{ clueName: '线索名称', clueSourceId: '线索来源', id: 123456789 }]) // 线索table数据
+const clueTotalTable = ref(0) // 线索 table 数据总数
+
+// 定义方法
+function searchTable() {
+  getClueTable()
+}
+
+function resetTable() {
+  let newResetForm = resetFromValue(filterCriteriaForm, { startTime: getFirstDayOfMonth(new Date()), endTime: formatDate(new Date()), pageIndex: 1, pageFrom: 10 })
+  Object.assign(filterCriteriaForm, newResetForm)
+  getClueTable()
+}
+
+function deleteRow(_row: any) {
+  console.log('点击了删除')
+}
+
+function batchTransfer() {
+  console.log('点击了批量转移')
+}
+
+function batchDelete() {
+  console.log('批量删除')
+}
+
+function toClueTableDetail(row: any) {
+  console.log('点击跳转详情')
+  router.push({ 
+    path: `${MOD}/detail`, 
+    query: { id: row.id } 
+  })
+}
+
+function getClueTable() {
+  allLoading.clueTableLading = true
+  let valueForm = getFromValue(filterCriteriaForm)
+  post(GETTABLE, { ...valueForm }).then((res: any) => {
+    allLoading.clueTableLading = false
+    console.log(res, '<==== 接口返回的数据')
+  }).catch((_error) => {
+    allLoading.clueTableLading = false
+  })
+}
+
+async function getSystemField() {
+  const systemField = getAllListByCode(['线索来源', '客户行业', '客户级别'])
+  for (let i in systemField) {
+    const { data } = await get(`${GETSYSFILED}?code=${systemField[i]}`)
+    for (let key of Object.keys(fixedData)) {
+      if (systemField[i] == key) {
+        Object.assign(fixedData, { [key]: data })
+      }
+    }
+  }
+
+  const { data } = await post(GETPERSONNEL, {})
+  fixedData.Personnel = data.map((item: any) => {
+    const { id, name, phone, jobNumber } = item
+    return {
+      id, name, phone, jobNumber
+    }
+  })
+}
 
+onMounted(() => {
+  getSystemField()
+  getClueTable()
+})
 </script>
 
 <style lang="scss" scoped></style>

+ 13 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/type.d.ts

@@ -0,0 +1,13 @@
+interface filterCriteriaFormType { // 线索筛选条件类型
+  clueName: string,
+  clueSourceId: string | number,
+  phone: string,
+  email: string,
+  customerIndustryId: string | number,
+  customerLevelId: string | number,
+  inchargerId: string | number,
+  startTime: string | number,
+  endTime: string | number,
+  pageIndex: string | number,
+  pageFrom: string | number
+}

+ 9 - 6
fhKeeper/formulahousekeeper/customerBuler-crm/src/router/index.ts

@@ -64,25 +64,28 @@ router.beforeEach((to, _from, next) => {
         const addNewRouter = newRouters.find(
           (item: any) => item.path == "/home"
         );
+      
+        let modules = import.meta.glob("@/pages/**/*.vue");
+        console.log(modules);
         routerList.forEach((item: any) => {
-          let filePath = item.path.split("/")[1];
+          let filePath = item.path.replace("/", "")
           if (item.children && item.children.length > 0) {
             item.children.forEach((child: any) => {
-              let childFilePath = child.path.split("/")[1];
+              let childFilePath = child.path.replace("/", "");
               addNewRouter?.children.push({
                 path: child.path,
                 name: child.name,
                 meta: {},
-                component: () =>
-                  import(`@/pages/${filePath}/${childFilePath}/index.vue`),
+                component: modules[`/src/pages/${childFilePath}/index.vue`]
               });
             });
           } else {
+            console.log(`/src/pages/${filePath}/index.vue`)
             addNewRouter?.children.push({
               path: item.path,
               name: item.name,
               meta: {},
-              component: () => import(`@/pages/${filePath}/index.vue`),
+              component: modules[`/src/pages/${filePath}/index.vue`],
             });
           }
         });
@@ -94,6 +97,6 @@ router.beforeEach((to, _from, next) => {
       next(`/login`);
     }
   }
-  //console.log(routerList);
+  console.log(routerList);
 });
 export default router;

+ 3 - 1
fhKeeper/formulahousekeeper/customerBuler-crm/src/type.d.ts

@@ -8,4 +8,6 @@ interface GlobalPopup extends Notify {
 interface Tree {
   label: string
   children?: Tree[]
-}
+}
+
+type ListByCodeType = ('线索来源'|'客户级别'|'客户行业'|'客户来源'|'商机阶段'|'产品类型'|'产品单位'|'订单类型')[] 

+ 67 - 7
fhKeeper/formulahousekeeper/customerBuler-crm/src/utils/tools.ts

@@ -40,19 +40,20 @@ export function getFromValue<T>(formData: T): T {
   }
   return result;
 }
-export function resetFromValue<T>(formData: T) {
+export function resetFromValue<T>(formData: T, resetForm: any = {}) {
   const result: any = {};
   for (const key in formData) {
-    result[key] = '';
+    result[key] = "";
   }
-  return result;
+  // return result;
+  return { ...result, ...resetForm }
 }
 
 /**
  * 更新部门数据
  * @param arr 部门数据源
  * @param flag 是否需要添加全部人员和未分配
- * @returns 
+ * @returns
  */
 export function updateDepTreeData(arr: any, flag: boolean = false) {
   const result = []; // 创建一个新数组来存储结果
@@ -65,16 +66,75 @@ export function updateDepTreeData(arr: any, flag: boolean = false) {
       result.push(arr[i]); // 将更新后的节点添加到结果数组
     }
   }
-  if(flag) {
+  if (flag) {
     result.splice(0, 0, {
       id: -1,
-      label: '全部人员',
+      label: "全部人员",
     });
     result.push({
       id: 0,
-      label: '未分配',
+      label: "未分配",
     });
     return result;
   }
   return result; // 返回更新后的数组,不包含id为-1或0的节点
 }
+
+const listByCode = [
+  { name: "线索来源", id: "ClueSources" },
+  { name: "客户级别", id: "CustomLevel" },
+  { name: "客户行业", id: "CustomIndustry" },
+  { name: "客户来源", id: "CustomSources" },
+  { name: "商机阶段", id: "BusinessStage" },
+  { name: "产品类型", id: "ProductType" },
+  { name: "产品单位", id: "ProductUnit" },
+  { name: "订单类型", id: "OrderType" },
+];
+
+/**
+ * 获取系统字段的数据
+ * @param arr 需要获取字典的中文名称
+ * @returns 接口所需要的id
+ */
+export function getAllListByCode(arr: ListByCodeType) {
+  const result = arr
+    .map((item) => {
+      const found = listByCode.find((obj) => obj.name === item);
+      return found ? found.id : null;
+    })
+    .filter(Boolean);
+
+  return result;
+}
+
+/**
+ * 获取当月第一天
+ * @param date 日期 new Date()
+ * @returns 
+ */
+export function getFirstDayOfMonth(date: Date) {  
+  const firstDay = new Date(date.getFullYear(), date.getMonth(), 1);  
+  return formatDate(firstDay);  
+}  
+
+/**
+ * 获取当月最后一天
+ * @param date 日期 new Date()
+ * @returns 
+ */
+export function getLastDayOfMonth(date: Date) {  
+  const nextMonth = new Date(date.getFullYear(), date.getMonth() + 1, 0);  
+  return formatDate(nextMonth);  
+}  
+
+/**
+ * 将 Date 对象格式化为 "YYYY-MM-DD" 的形式
+ * @param date 日期 new Date()
+ * @returns 
+ */
+export function formatDate(date: Date) {  
+  const year = date.getFullYear();  
+  const month = (1 + date.getMonth()).toString().padStart(2, '0');  
+  const day = date.getDate().toString().padStart(2, '0');  
+  return `${year}-${month}-${day}`;  
+}