瀏覽代碼

Merge branch 'master' of http://47.100.37.243:10191/wutt/manHourHousekeeper

douhl 11 月之前
父節點
當前提交
75b8735132
共有 38 個文件被更改,包括 1309 次插入222 次删除
  1. 133 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/components/detailcompinents/relatedTasks.vue
  2. 50 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/components/detailcompinents/type.d.ts
  3. 5 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/api.ts
  4. 23 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/api.ts
  5. 160 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/component/deteleTables.vue
  6. 209 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/component/stageSetting.vue
  7. 78 33
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/detail/index.vue
  8. 180 46
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/index.vue
  9. 10 2
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/type.d.ts
  10. 19 10
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/contacts/api.ts
  11. 45 20
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/contacts/index.vue
  12. 3 1
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/product/index.vue
  13. 3 1
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/constant.ts
  14. 80 7
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/detail/components/information.vue
  15. 85 51
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/detail/components/relatedTasks.vue
  16. 2 2
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/detail/index.vue
  17. 63 11
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/index.vue
  18. 5 1
      fhKeeper/formulahousekeeper/customerBuler-crm/src/type.d.ts
  19. 5 0
      fhKeeper/formulahousekeeper/management-crm/pom.xml
  20. 20 2
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/controller/BusinessOpportunityController.java
  21. 17 0
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/controller/CustomController.java
  22. 4 2
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/entity/BusinessOpportunity.java
  23. 1 1
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/mapper/BusinessOpportunityMapper.java
  24. 3 1
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/BusinessOpportunityService.java
  25. 2 0
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/CustomService.java
  26. 2 1
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/StageService.java
  27. 16 7
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/BusinessOpportunityServiceImpl.java
  28. 5 0
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/CustomServiceImpl.java
  29. 17 1
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/SalesOrderServiceImpl.java
  30. 2 1
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/StageServiceImpl.java
  31. 1 1
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/SysFormServiceImpl.java
  32. 31 0
      fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/util/BusinessItemProductListDeserializer.java
  33. 1 0
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/ProjectController.java
  34. 16 9
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ProjectServiceImpl.java
  35. 2 2
      fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ReportServiceImpl.java
  36. 3 4
      fhKeeper/formulahousekeeper/management-platform/src/main/resources/mapper/ProjectMapper.xml
  37. 6 4
      fhKeeper/formulahousekeeper/timesheet/src/components/vueMultipleDept.vue
  38. 2 1
      fhKeeper/formulahousekeeper/timesheet/src/views/corpreport/list.vue

+ 133 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/components/detailcompinents/relatedTasks.vue

@@ -0,0 +1,133 @@
+<template>
+    <div class="relatedTasks pl-4 pr-4 pt-3 pb-3 h-full flex flex-col">
+        <div class="flex justify-between">
+            <div class="title">相关任务</div>
+            <div>
+                <el-button type="primary" @click="newTask()">新建任务</el-button>
+            </div>
+        </div>
+        <div class="flex-1 overflow-auto pt-3">
+            <el-table :data="relatedTaskstable" border style="width: 100%;height: 100%;">
+                <el-table-column prop="taskName" label="任务名称">
+                    <template #default="scope">
+                        <el-button link type="primary" size="large" @click="toTask()">{{
+                            scope.row.taskName
+                        }}</el-button>
+                    </template>
+                </el-table-column>
+                <el-table-column prop="priorityStr" label="优先级" width="130" />
+                <el-table-column prop="statusStr" label="状态" width="130" />
+                <el-table-column prop="executorNamesStr" label="执行人" width="130" />
+                <el-table-column prop="startTimes" label="开始时间" width="130" />
+                <el-table-column prop="endTimes" label="截至时间" width="130" />
+            </el-table>
+        </div>
+    </div>
+
+    <!-- 新建任务 -->
+    <TaskModal :visible="allVisible.taskModalVisible" :edit-form="taskModalForm" :save-loading="taskLoading"
+        @close="closeTaskModal" @submit="submitForm" :title="'新建任务'" :disabled-list="props.disabledList" />
+</template>
+<script lang="ts" setup>
+import { PRIORITY } from '../TaskModal/api';
+import { STATUS } from '../../pages/tasks/api';
+import { formatDate } from '../../utils/times';
+import { createTaskFromType } from '../../utils/tools';
+import { ElNotification, NotificationParamsTyped } from 'element-plus'
+import { ref, reactive, onMounted, watchEffect } from 'vue'
+import { useRouter } from "vue-router";
+import TaskModal from '../TaskModal/index.vue'
+import { createTask } from '../TaskModal/taskFunction';
+import { ITEM_RENDER_EVT } from 'element-plus/es/components/virtual-list/src/defaults';
+import { Props, Emits } from './type';
+
+const props = defineProps<Props>()
+const emits = defineEmits<Emits>();
+
+type saveLoadingType = "1" | "2" | "3" | "4"; //1是没有保存, 2是正在保存, 3是保存成功, 4是保存失败
+
+const relatedTaskstable = ref([])
+const information = ref<any>({})
+const router = useRouter()
+const taskLoading = ref<saveLoadingType>('1')
+const taskModalForm = ref({}) // 任务弹窗表单
+const allVisible = reactive({
+    taskModalVisible: false
+})
+
+function submitForm(submitData: any, isClose: boolean) { // 任务提交
+    taskLoading.value = '2'
+    createTask(submitData, isClose).then((res) => {
+        const { saveLoading, isClose } = res
+        taskLoading.value = saveLoading
+        allVisible.taskModalVisible = isClose
+        globalPopup('新建成功', 'success')
+        emits('refreshData')
+    }).catch((err) => {
+        const { saveLoading, isClose, message } = err
+        taskLoading.value = saveLoading
+        allVisible.taskModalVisible = isClose
+        globalPopup(message, 'error')
+    })
+}
+
+function globalPopup(str: string, type: string) {
+    notificationTiop({
+      message: str || '成功',
+      type: type,
+      title: "提示",
+      duration: 2000
+    })
+}
+
+const notificationTiop = (options: NotificationParamsTyped) => {
+  ElNotification(options)
+}
+
+function newTask() {
+    const { id } = information.value
+    taskModalForm.value = { ...createTaskFromType(props.formTaskType), [props.filed as any]: id, }
+    allVisible.taskModalVisible = true
+}
+
+function toTask() {
+    router.push({
+        path: `/tasks`
+    })
+}
+
+function closeTaskModal() {
+    allVisible.taskModalVisible = false
+}
+
+// 接收参数赋值
+function receiveAssignment(item: any) {
+    console.log(item, '<==== 过来的值')
+    information.value = item.information
+    const dataVal = item.data
+    for (let i in dataVal) {
+        dataVal[i].executorNamesStr = (dataVal[i].executorNames || []).join(','),
+            dataVal[i].startTimes = dataVal[i].startDate ? formatDate(new Date(dataVal[i].startDate)) : '',
+            dataVal[i].endTimes = dataVal[i].endDate ? formatDate(new Date(dataVal[i].endDate)) : '',
+            dataVal[i].priorityStr = PRIORITY.find(item => item.value == dataVal[i].priority)?.label || '',
+            dataVal[i].statusStr = STATUS.find(item => item.value == dataVal[i].status)?.label || ''
+    }
+    relatedTaskstable.value = dataVal
+}
+
+watchEffect(() => {
+    receiveAssignment(props)
+});
+
+// 生命周期钩子
+onMounted(() => {
+});
+</script>
+<style scoped lang="scss">
+.relatedTasks {
+    .title {
+        font-size: 18px;
+        color: #000
+    }
+}
+</style>

+ 50 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/components/detailcompinents/type.d.ts

@@ -0,0 +1,50 @@
+type TASK_VALUE_TYPE = 0 | 1 | 2 | 3;
+type tableTypeFiled = {
+    id: number,
+    status: number,
+    priority: number,
+    executorNames: Array,
+    startDate: string,
+    endDate: string,
+}
+export interface Props {
+    /**
+     * data: 任务 Table 数据
+     */
+    data: tableTypeFiled[];
+    /**
+     * information: 详情数据
+     */
+    information: any;
+    /**
+     * disabledList:新建任务需要禁用的字段(参考 TaskModal 文件)
+     */
+    disabledList?: (
+        | "taskName"
+        | "priority"
+        | "taskType"
+        | "customId"
+        | "businessOpportunityId"
+        | "orderId"
+        | "clueId"
+        | "contactsId"
+        | "executorId"
+        | "startDate"
+        | "endDate"
+    )[];
+    /** 
+     * formTaskType: 任务类型  0是客户, 1是商机, 2是销售订单 ,3是线索
+     */
+    formTaskType: TASK_VALUE_TYPE,
+    /**
+     * 详情数据中需要关联的任务类型字段
+     */
+    filed?: 'customId' | 'businessOpportunityId' | 'orderId' | 'clueId' 
+}
+
+export interface Emits {
+    /**
+     *  新建任务后触发的更新数据事件
+     */
+    (event: "refreshData"): void;
+}

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

@@ -2,3 +2,8 @@ export const SENDVCODE = "/user/sendVcode";     //发送验证码
 export const REGISTER = "/user/insertCompany";  //注册
 export const REGISTER = "/user/insertCompany";  //注册
 export const LOGIN = "/user/loginAdmin";        //登录
 export const LOGIN = "/user/loginAdmin";        //登录
 export const IMPORTTIMELIST = "/sys-form/getExportTemplate" // 下载模板
 export const IMPORTTIMELIST = "/sys-form/getExportTemplate" // 下载模板
+
+export const SEX: sexTYpe[] = [
+    { label: "男", value: '1' },
+    { label: "女", value: '0' },
+];

+ 23 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/api.ts

@@ -1,13 +1,36 @@
 export const MOD = "/business";
 export const MOD = "/business";
+export const MODURL = "business";
 export const prefix = "/clue";
 export const prefix = "/clue";
 export const GETSYSFILED = "/sys-dict/getListByCode";
 export const GETSYSFILED = "/sys-dict/getListByCode";
 export const GETPERSONNEL = "/user/getSimpleActiveUserList";
 export const GETPERSONNEL = "/user/getSimpleActiveUserList";
 export const GETGENERATEFOEM = `/sys-form/getListByCode${MOD}`
 export const GETGENERATEFOEM = `/sys-form/getListByCode${MOD}`
 export const GETBUSINESSLIST = `/business-opportunity/list`
 export const GETBUSINESSLIST = `/business-opportunity/list`
 export const UPDATEINSET = `/business-opportunity/insertAndUpdate`
 export const UPDATEINSET = `/business-opportunity/insertAndUpdate`
+export const BUSINESSDETELE = `/business-opportunity/delete`
+export const BATCHTRANSFER = `/business-opportunity/claim`
+export const BUSIESS_DETELELIST = `/business-opportunity/deleterList`
+export const BUSIESS_ROWBACK = `/business-opportunity/rollBack`
+export const BUSIESS_PERDETELE = `/business-opportunity/deleterDelete`
+export const BUSIESS_GETSATE = `/business-opportunity/getStage`
+export const BUSIESS_SAVESAIE = `/business-opportunity/saveStage`
+export const BUSIESS_ALL = `/business-opportunity/getAll`
+export const BUSIESS_INFO = `/business-opportunity/getInfo`
+
 
 
 export const stageStatus = [
 export const stageStatus = [
     { id: 1, name: "赢单", progress: "100%" },
     { id: 1, name: "赢单", progress: "100%" },
     { id: 2, name: "输单", progress: "0%" },
     { id: 2, name: "输单", progress: "0%" },
     { id: 3, name: "无效", progress: "0%" }
     { id: 3, name: "无效", progress: "0%" }
+]
+
+export const tableColumn: businessTableColumnInterface[] = [
+    { prop: "name", label: "商机名称", width: "180", eventName: "toClueTableDetail" },
+    { prop: "customerName", label: "客户名称", width: "180" },
+    { prop: "contactsName", label: "联系人", width: "180", eventName: "showName" },
+    { prop: "amountOfMoney", label: "商机金额", width: "180" },
+    { prop: "stageValue", label: "商机阶段", width: "180" },
+    { prop: "expectedTransactionDate", label: "预计成交", width: "180" },
+    { prop: "inchargerName", label: "负责人", width: "180" },
+    { prop: "creatorName", label: "创建人", width: "180" },
+    { prop: "createTime", label: "创建时间", width: "180" }
 ]
 ]

+ 160 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/component/deteleTables.vue

@@ -0,0 +1,160 @@
+<template>
+    <el-dialog v-model="deteleBusinessDialogVisible" width="1000" :before-close="beForeCancel" :show-close="false"
+        top="10vh">
+        <template #header="{ close, titleId, titleClass }">
+            <div class="flex justify-between items-center border-b pb-3 dialog-header">
+                <h4 :id="titleId">商机回收站</h4>
+                <div>
+                    <el-button type="primary" v-loading="allLoading.batchRecoveryLoading" :disabled="batchTableData.length == 0"
+                        @click="batchOperation('恢复')">批量恢复</el-button>
+                    <el-button type="primary" v-loading="allLoading.batchDeteleLoading" :disabled="batchTableData.length == 0"
+                        @click="batchOperation('删除')">批量删除</el-button>
+                    <el-button @click="cancel()">取消</el-button>
+                </div>
+            </div>
+        </template>
+        <div class="h-[60vh] flex flex-col">
+            <div class="flex-1 w-full overflow-hidden">
+                <el-table ref="busiessTableRef" :data="deteleBusinessTable" border v-loading="allLoading.tableLoading"
+                    @selection-change="changeBatch" style="width: 100%;height: 100%;">
+                    <el-table-column type="selection" width="55" />
+                    <el-table-column v-for="(item, index) in tableColumn" :prop="item.prop" :label="item.label" :key="index"
+                        :width="item.width">
+                        <template #default="scope">
+                            <span>{{ scope.row[item.prop] }}</span>
+                        </template>
+                    </el-table-column>
+                    <el-table-column label="操作" fixed="right" width="120">
+                        <template #default="scope">
+                            <el-button link type="primary" size="large"
+                                @click="businessOperationItem(scope.row.id, scope.row.name, '恢复')">恢复</el-button>
+                            <el-button link type="danger" size="large"
+                                @click="businessOperationItem(scope.row.id, scope.row.name, '删除')">删除</el-button>
+                        </template>
+                    </el-table-column>
+                </el-table>
+            </div>
+            <div class="flex justify-end pt-3">
+                <el-pagination layout="total, prev, pager, next, sizes" :page-size="tableForm.pageFrom"
+                    @size-change="handleSizeChange" @current-change="handleCurrentChange" :total="businessTotalTable"
+                    :hide-on-single-page="true" />
+            </div>
+        </div>
+    </el-dialog>
+</template>
+<script lang="ts" setup>
+import { post } from '@/utils/request';
+import { ref, reactive, onMounted, watchEffect, watch, inject } from 'vue'
+import { BUSIESS_DETELELIST, BUSIESS_PERDETELE, BUSIESS_ROWBACK, tableColumn } from '../api';
+import { ElTable } from 'element-plus';
+import { confirmAction } from '@/utils/tools';
+import { formatDate } from '@/utils/times';
+
+type operationType = '恢复' | '删除'
+
+const emits = defineEmits(['closeVisible']);
+const globalPopup = inject<GlobalPopup>('globalPopup')
+const deteleBusinessTable = ref([])
+const deteleBusinessDialogVisible = ref(false)
+const businessTotalTable = ref(0)
+const batchTableData = ref([])
+const allLoading = reactive({
+    batchRecoveryLoading: false,
+    batchDeteleLoading: false,
+    tableLoading: false
+})
+
+const tableForm = reactive({
+    pageIndex: 1,
+    pageFrom: 10
+})
+
+const busiessTableRef = ref<InstanceType<typeof ElTable>>() // 线索table dom
+
+const props = defineProps<{
+    visibles: boolean
+}>()
+
+watch(() => props.visibles, (newVal) => {
+    deteleBusinessDialogVisible.value = newVal
+    if (newVal) {
+        getTableList()
+    }
+})
+
+function batchOperation(type: operationType) {
+    const value = batchTableData.value.map((item: any) => item.id).join(',')
+    const label = batchTableData.value.map((item: any) => item.name).join(',')
+    businessOperationItem(value, label, type, true)
+}
+
+function businessOperationItem(value: string | number, label: string, type: operationType, batch: boolean = false) {
+    confirmAction(`确定${batch ? '批量' : ''}${type}【${label}】商机吗?`).then(() => {
+        let url = type == '恢复' ? BUSIESS_ROWBACK : BUSIESS_PERDETELE
+        post(url, { ids: value }).then(res => {
+            if (res.code != 'ok') {
+                globalPopup?.showError(res.msg)
+                return
+            }
+            globalPopup?.showSuccess(`${type}成功`)
+            getTableList()
+            changeBatch(false)
+        }).catch((err) => {
+            globalPopup?.showError(err.message)
+        })
+    })
+}
+
+function changeBatch(flag: boolean = true) {
+    if (flag) {
+        batchTableData.value = busiessTableRef.value && busiessTableRef.value.getSelectionRows()
+    } else {
+        batchTableData.value = []
+        busiessTableRef.value && busiessTableRef.value.clearSelection()
+    }
+}
+
+function getTableList() {
+    allLoading.tableLoading = true
+    post(BUSIESS_DETELELIST, { ...tableForm }).then((res) => {
+        if (res.code == 'ok') {
+            const { data, total } = res.data
+            deteleBusinessTable.value = data.map((item: any) => {
+                return {
+                    ...item,
+                    expectedTransactionDate: formatDate(new Date(item.expectedTransactionDate))
+                }
+            })
+            businessTotalTable.value = total
+        }
+    }).finally(() => {
+        allLoading.tableLoading = false
+    })
+}
+
+function handleSizeChange(val: number) {
+    tableForm.pageIndex = 1
+    tableForm.pageFrom = val
+    getTableList()
+}
+
+function handleCurrentChange(val: number) {
+    tableForm.pageIndex = val
+    getTableList()
+}
+
+function cancel() {
+    emits('closeVisible', 'deteleBusinessVisible')
+}
+
+function beForeCancel(done: () => void) {
+    emits('closeVisible', 'deteleBusinessVisible')
+    done()
+}
+
+onMounted(() => {
+
+})
+
+</script>
+<style lang="scss" scoped></style>

+ 209 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/component/stageSetting.vue

@@ -0,0 +1,209 @@
+<template>
+    <div>
+        <el-dialog v-model="stageVisible" width="1000" :before-close="beForeCancel" :show-close="false" top="10vh">
+            <template #header="{ close, titleId, titleClass }">
+                <div class="flex justify-between items-center border-b pb-3 dialog-header">
+                    <h4 :id="titleId">阶段设置</h4>
+                    <div>
+                        <el-button type="primary" @click="addStage(false)">新增</el-button>
+                        <el-button type="primary" @click="saveState()" v-loading="allLoading.saveLoading">保存</el-button>
+                        <el-button @click="cancel()">取消</el-button>
+                    </div>
+                </div>
+            </template>
+            <div class="h-[60vh] flex flex-col">
+                <div class="flex-1 w-full overflow-hidden">
+                    <el-table :data="stageTableList" border v-loading="allLoading.tableLoading"
+                        style="width: 100%;height: 100%;">
+                        <el-table-column :prop="'seq'" :label="'顺序'" width="100">
+                            <template #default="scope">
+                                {{ scope.$index + 1 }}
+                            </template>
+                        </el-table-column>
+                        <el-table-column :prop="'name'" :label="'阶段名称'"></el-table-column>
+                        <el-table-column :prop="'plan'" :label="'进度'" width="100"></el-table-column>
+                        <el-table-column label="操作" fixed="right" width="200">
+                            <template #default="scope">
+                                <el-button link type="primary" size="large" @click="addStage(scope.row)">编辑</el-button>
+                                <el-button link type="danger" size="large" @click="deteStage(+scope.$index)">删除</el-button>
+                                <el-button link type="primary" size="large" @click="moveStage(+scope.$index, 'up')"
+                                    v-if="scope.$index != 0">上移</el-button>
+                                <el-button link type="primary" size="large" @click="moveStage(+scope.$index, 'down')"
+                                    v-if="scope.$index < stageTableList.length - 1">下移</el-button>
+                            </template>
+                        </el-table-column>
+                    </el-table>
+                </div>
+            </div>
+
+            <!-- 内层弹窗 -->
+            <el-dialog width="700px" v-model="allVisible.editVisible" append-to-body :show-close="false">
+                <template #header="{ close, titleId, titleClass }">
+                    <div class="flex justify-between items-center border-b pb-3 dialog-header">
+                        <h4 :id="titleId">添加阶段</h4>
+                        <div>
+                            <el-button type="primary" @click="editState(true)">保存并新增</el-button>
+                            <el-button type="primary" @click="editState(false)">保存</el-button>
+                            <el-button @click="allVisible.editVisible = false">取消</el-button>
+                        </div>
+                    </div>
+                </template>
+                <div class="h-[120px] flex flex-col pt-5">
+                    <div class="flex flex-row w-full items-center">
+                        <div class="w-[100px] mr-2 text-right">阶段名称:</div>
+                        <div class="flex-1">
+                            <el-input v-model="stageForm.name" placeholder="请输入阶段名称" clearable></el-input>
+                        </div>
+                    </div>
+                    <div class="flex flex-row w-full items-center pt-3">
+                        <div class="w-[100px] mr-2 text-right">进度:</div>
+                        <div class="flex-1">
+                            <el-input-number v-model="stageForm.plan" controls-position="right" :min="0"
+                                :max="100"></el-input-number>
+                            <span class="inline-block ml-2">%</span>
+                        </div>
+                    </div>
+                </div>
+            </el-dialog>
+        </el-dialog>
+    </div>
+</template>
+<script lang="ts" setup>
+import { post } from '@/utils/request';
+import { ref, reactive, onMounted, watch, inject } from 'vue'
+import { BUSIESS_GETSATE, BUSIESS_SAVESAIE } from '../api';
+import { List } from 'echarts';
+
+type moveStageType = 'up' | 'down';
+type stageFormType = {
+    name: string,
+    plan: number,
+    seq: number,
+    id?: number,
+    isFinish?: number
+}
+
+const emits = defineEmits(['closeVisible']);
+const globalPopup = inject<GlobalPopup>('globalPopup')
+const stageVisible = ref(false)
+const stageTableList = ref<stageFormType[]>([])
+const allLoading = reactive({
+    editLoading: false,
+    saveLoading: false,
+    tableLoading: false
+})
+const allVisible = reactive({
+    editVisible: false,
+})
+const stageForm = reactive<stageFormType>({
+    name: '',
+    plan: 0,
+    seq: 0,
+})
+
+const props = defineProps<{
+    visibles: boolean
+}>()
+
+watch(() => props.visibles, (newVal) => {
+    stageVisible.value = newVal
+    if (newVal) {
+        getTableList()
+    }
+})
+
+function saveState() {
+    let newList = JSON.parse(JSON.stringify(stageTableList.value))
+    let data = newList.map((item: stageFormType, index: number) => {
+        return {
+            ...item,
+            seq: index
+        }
+    })
+    allLoading.saveLoading = true
+    post(BUSIESS_SAVESAIE, {list: JSON.stringify(data)}).then(() => {
+        globalPopup?.showSuccess('保存成功')
+        cancel()
+    }).finally(() => {
+        allLoading.saveLoading = false
+    })
+}
+
+function editState(flag: boolean) {
+    if (!stageForm.name) {
+        globalPopup?.showWarning('请输入阶段名称')
+        return
+    }
+    stageTableList.value.push({ ...stageForm })
+    if (flag) {
+        resetStage()
+    }
+    allVisible.editVisible = flag
+}
+
+function addStage(item: any) {
+    const row = JSON.parse(JSON.stringify(item))
+    if (!item) {
+        resetStage()
+    } else {
+        Object.assign(stageForm, {
+            name: row.name,
+            plan: row.plan,
+            seq: row.seq
+        })
+    }
+    allVisible.editVisible = true
+}
+
+function resetStage() {
+    let newData = JSON.parse(JSON.stringify(stageTableList.value))
+    let maxnum = newData.sort((a: any, b: any) => { return b.seq - a.seq; })[0];
+    Object.assign(stageForm, {
+        name: '',
+        plan: 0,
+        seq: +maxnum + 1
+    })
+}
+
+function moveStage(index: number, stageType: moveStageType) {
+    const tableList = stageTableList.value
+    if (stageType == 'up') {
+        stageTableList.value = tableList.slice(0, index - 1).concat(tableList[index], tableList[index - 1], tableList.slice(index + 1))
+    }
+
+    if (stageType == 'down') {
+        stageTableList.value = tableList.slice(0, index).concat(tableList[index + 1], tableList[index], tableList.slice(index + 2))
+    }
+}
+
+function deteStage(index: number) {
+    stageTableList.value.splice(index, 1)
+}
+
+function getTableList() {
+    allLoading.tableLoading = true
+    post(BUSIESS_GETSATE, {}).then((res) => {
+        const { data } = res
+        let newData = data || []
+        stageTableList.value = newData.sort(function (a: any, b: any) {
+            return a.seq - b.seq;
+        });
+    }).finally(() => {
+        allLoading.tableLoading = false
+    })
+}
+
+function cancel() {
+    emits('closeVisible', 'stageSetVisible')
+}
+
+function beForeCancel(done: () => void) {
+    emits('closeVisible', 'stageSetVisible')
+    done()
+}
+
+onMounted(() => {
+});
+
+</script>
+<style scoped lang="scss"></style>

+ 78 - 33
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/detail/index.vue

@@ -1,29 +1,29 @@
 <template>
 <template>
   <div class="h-full flex p-3 flex-col businessDetail">
   <div class="h-full flex p-3 flex-col businessDetail">
-    <div class="w-full bg-white p-2 mb-2 shadow-md rounded-md flex items-center">
+    <div class="w-full bg-white p-2 mb-2 shadow-md rounded-md flex items-center" v-loading="allLoading.skeletonLoading">
       <div class="icon mr-4">
       <div class="icon mr-4">
         <el-link :underline="false" @click="backPath()">
         <el-link :underline="false" @click="backPath()">
           <el-icon class="el-icon--right"><icon-view /></el-icon> 返回商机列表
           <el-icon class="el-icon--right"><icon-view /></el-icon> 返回商机列表
         </el-link>
         </el-link>
       </div>
       </div>
       <div class="mr-8">
       <div class="mr-8">
-        <el-select v-model="value" placeholder="请选择" style="width: 150px">
+        <el-select v-model="optionVal" placeholder="请选择" style="width: 150px" filterable @change="getDetail()">
           <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
           <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
         </el-select>
         </el-select>
       </div>
       </div>
       <div class="flex-1 flex h-full justify-end overflow-auto scroll-bar-hide cursor-pointer" @wheel="handleScroll">
       <div class="flex-1 flex h-full justify-end overflow-auto scroll-bar-hide cursor-pointer" @wheel="handleScroll">
         <div :class="`${index === 0 ? 'startStep' : 'nextStep'} relative rounded-md flex items-center backGray pl-6 pr-6`"
         <div :class="`${index === 0 ? 'startStep' : 'nextStep'} relative rounded-md flex items-center backGray pl-6 pr-6`"
-          v-for="(item, index) in 2" :key="index">
-          <div class="pr-3 text-nowrap">验证客户</div>
-          <div class="text-nowrap">30%</div>
+          v-for="(item, index) in stageList" :key="index">
+          <div class="pr-3 text-nowrap">{{ item.name }}</div>
+          <div class="text-nowrap">{{ item.plan }}</div>
         </div>
         </div>
       </div>
       </div>
       <div class="relative rounded-md flex items-center itemPing backGray endStep item pl-6 pr-6 mr-4">
       <div class="relative rounded-md flex items-center itemPing backGray endStep item pl-6 pr-6 mr-4">
         <el-select v-model="stageStatusVal" placeholder="结束" style="width: 100px" class="selectClas">
         <el-select v-model="stageStatusVal" placeholder="结束" style="width: 100px" class="selectClas">
-          <el-option v-for="item in stageStatus" :key="item.id" :label="item.name" :value="item.id" />
+          <el-option v-for="(item, index) in stageListOption" :key="index" :label="item.label" :value="item.value" />
         </el-select>
         </el-select>
       </div>
       </div>
-      <div class="bg-[#0052CC] rounded-md text itemPing pl-2 pr-2 flex items-center">
+      <div class="bg-[#0052CC] rounded-md text itemPing pl-2 pr-2 flex items-center aloneText">
         <el-link :underline="false">推进至阶段【验证客户】</el-link>
         <el-link :underline="false">推进至阶段【验证客户】</el-link>
       </div>
       </div>
     </div>
     </div>
@@ -40,7 +40,7 @@
 
 
       <div class="w-full h-auto flex justify-between mt-2">
       <div class="w-full h-auto flex justify-between mt-2">
         <div class="bg-white shadow-md rounded-md" style="width: 65%;">
         <div class="bg-white shadow-md rounded-md" style="width: 65%;">
-          <RelatedTasks />
+          <DetailCompinents :data="detailCompinentsData" :information="businessInfo" :formTaskType="1" :filed="'businessOpportunityId'" :disabledList="['taskType', 'businessOpportunityId']" @refreshData="getDetail" />
         </div>
         </div>
         <div class="bg-white ml-2 shadow-md rounded-md flex-1">
         <div class="bg-white ml-2 shadow-md rounded-md flex-1">
           <OperationRecord />
           <OperationRecord />
@@ -61,39 +61,46 @@ import { ref, reactive, onMounted, inject } from "vue";
 import type { FormInstance, FormRules } from 'element-plus'
 import type { FormInstance, FormRules } from 'element-plus'
 import { Edit, ArrowLeft as IconView } from '@element-plus/icons-vue'
 import { Edit, ArrowLeft as IconView } from '@element-plus/icons-vue'
 import { backPath } from '@/utils/tools'
 import { backPath } from '@/utils/tools'
-import { stageStatus } from '../api'
+import { useRoute } from "vue-router";
+import { BUSIESS_ALL, BUSIESS_GETSATE, BUSIESS_INFO } from '../api'
 
 
 import Information from '../component/information.vue'
 import Information from '../component/information.vue'
 import Attachment from '../component/attachment.vue'
 import Attachment from '../component/attachment.vue'
-import RelatedTasks from '../component/relatedTasks.vue';
+// import RelatedTasks from '../component/relatedTasks.vue';
+import DetailCompinents from '@/components/detailcompinents/relatedTasks.vue'
 import OperationRecord from '../component/operationRecord.vue';
 import OperationRecord from '../component/operationRecord.vue';
 import Products from '../component/products.vue';
 import Products from '../component/products.vue';
+import { post } from "@/utils/request";
+import { number } from "echarts";
 
 
-const value = ref('')
+type stageListType = {
+  name: string,
+  plan: string
+}
+
+const route = useRoute()
+const detailCompinentsData = ref([])
+const optionVal = ref<any>('')
 const stageStatusVal = ref('')
 const stageStatusVal = ref('')
-const options = [
-  {
-    value: 'Option1',
-    label: 'Option1',
-  },
-  {
-    value: 'Option2',
-    label: 'Option2',
-  },
-  {
-    value: 'Option3',
-    label: 'Option3',
-  },
-  {
-    value: 'Option4',
-    label: 'Option4',
-  },
-  {
-    value: 'Option5',
-    label: 'Option5',
-  },
-]
+const options = ref<optionType[]>([])
+const allLoading = reactive({
+  skeletonLoading: false
+})
+
+const businessInfo = ref({})
+const stageListOption = ref<optionType[]>([])
+const stageList = ref<stageListType[]>([])
+
 
 
+function getDetail() {
+  allLoading.skeletonLoading
+  post(BUSIESS_INFO, { id: optionVal.value }).then(({ data }) => {
+    businessInfo.value = (data || []);
+    detailCompinentsData.value = data.taskList || []
+  }).finally(() => {
+    allLoading.skeletonLoading
+  })
+}
 
 
 function handleScroll(event: any) { // 滚表横向滚动
 function handleScroll(event: any) { // 滚表横向滚动
   if (event.deltaY) {
   if (event.deltaY) {
@@ -102,6 +109,31 @@ function handleScroll(event: any) { // 滚表横向滚动
     element.scrollLeft += event.deltaY;
     element.scrollLeft += event.deltaY;
   }
   }
 }
 }
+
+function getOptionAll() {
+  post(BUSIESS_ALL, {}).then((res) => {
+    const { data } = res
+    options.value = (data || []).map((item: any) => ({
+      value: item.id + '',
+      label: item.name
+    }))
+  })
+}
+
+function getSatge() {
+  post(BUSIESS_GETSATE, {}).then(({ data }) => {
+    stageList.value = (data || []).sort((a: any, b: any) => { return a.seq - b.seq; }).filter((item: any) => !item.isFinish).map((item: any) => ({ name: item.name, plan: item.plan }))
+    stageListOption.value = (data || []).sort((a: any, b: any) => { return a.seq - b.seq; }).filter((item: any) => item.isFinish).map((item: any) => ({ label: item.name, value: item.id }))
+  })
+}
+
+onMounted(() => {
+  const { id } = route.query
+  optionVal.value = id
+  getSatge()
+  getOptionAll()
+  getDetail()
+})
 </script>
 </script>
   
   
 <style lang="scss" scoped>
 <style lang="scss" scoped>
@@ -159,5 +191,18 @@ function handleScroll(event: any) { // 滚表横向滚动
     padding-top: 4px;
     padding-top: 4px;
     padding-bottom: 4px;
     padding-bottom: 4px;
   }
   }
+
+  .aloneText {
+    padding-top: 9px;
+    padding-bottom: 9px;
+  }
+}
+</style>
+<style lang="scss">
+.businessDetail {
+  .el-select__wrapper {
+    background-color: #F4F5F7;
+    box-shadow: none !important;
+  }
 }
 }
 </style>
 </style>

+ 180 - 46
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/index.vue

@@ -48,43 +48,31 @@
       <div class="bg-white w-full h-full p-3 shadow-md rounded-md flex flex-col">
       <div class="bg-white w-full h-full p-3 shadow-md rounded-md flex flex-col">
         <div class="flex justify-end pb-3">
         <div class="flex justify-end pb-3">
           <el-button type="primary" @click="showVisible('newBusinessisible')">新建商机</el-button>
           <el-button type="primary" @click="showVisible('newBusinessisible')">新建商机</el-button>
-          <el-button type="primary">批量转移</el-button>
-          <el-button type="primary">批量删除</el-button>
-          <el-button type="primary">阶段设置</el-button>
-          <el-button type="primary">回收站</el-button>
-          <el-button type="primary">导入</el-button>
-          <el-button type="primary">导出</el-button>
+          <el-button type="primary" @click="showVisible('batchTransferVisible')"
+            :disabled="batchTableData.length <= 0">批量转移</el-button>
+          <el-button type="primary" @click="batchDeteleItem()" :disabled="batchTableData.length <= 0">批量删除</el-button>
+          <el-button type="primary" @click="showVisible('stageSetVisible')">阶段设置</el-button>
+          <el-button type="primary" @click="showVisible('deteleBusinessVisible')">回收站</el-button>
+          <el-button type="primary" @click="showVisible('importVisible')">导入</el-button>
+          <el-button type="primary" @click="exportBusinessTableList()" :loading="allLoading.exoprtLoading">导出</el-button>
         </div>
         </div>
         <div class="flex-1 w-full overflow-hidden">
         <div class="flex-1 w-full overflow-hidden">
-          <el-table ref="clueTableRef" :data="businessTable" border v-loading="allLoading.businessTableLading"
-            style="width: 100%;height: 100%;">
+          <el-table ref="businessTableRef" :data="businessTable" border v-loading="allLoading.businessTableLading"
+            @selection-change="changeBatch" style="width: 100%;height: 100%;">
             <el-table-column type="selection" width="55" />
             <el-table-column type="selection" width="55" />
-            <el-table-column prop="name" label="商机名称" width="180">
+            <el-table-column v-for="(item, index) in tableColumn" :prop="item.prop" :label="item.label" :key="index" :width="item.width">
               <template #default="scope">
               <template #default="scope">
-                <el-button link type="primary" size="large" @click="toBusinessTableDetail(scope.row)">{{
-                  scope.row.name
-                }}</el-button>
+                <el-button link type="primary" size="large" @click="dealWithTableColumn(scope.row, item.eventName)" v-if="item.eventName">{{scope.row[item.prop]}}</el-button>
+                <template v-else>{{scope.row[item.prop]}}</template>
               </template>
               </template>
             </el-table-column>
             </el-table-column>
-            <el-table-column prop="customerName" label="客户名称" width="180"></el-table-column>
-            <el-table-column prop="contactsName" label="联系人" width="180">
-              <template #default="scope">
-                <el-button link type="primary" size="large">{{
-                  scope.row.contactsName
-                }}</el-button>
-              </template>
-            </el-table-column>
-            <el-table-column prop="amountOfMoney" label="商机金额" width="180"></el-table-column>
-            <el-table-column prop="stageValue" label="商机阶段" width="180"></el-table-column>
-            <el-table-column prop="expectedTransactionDate" label="预计成交" width="180"></el-table-column>
-            <el-table-column prop="inchargerName" label="负责人" width="180"></el-table-column>
-            <el-table-column prop="creatorName" 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">
             <el-table-column label="操作" fixed="right" width="200">
               <template #default="scope">
               <template #default="scope">
-                <el-button link type="primary" size="large" @click="editShowVisible('newBusinessisible', scope.row)">编辑</el-button>
+                <el-button link type="primary" size="large"
+                  @click="editShowVisible('newBusinessisible', scope.row)">编辑</el-button>
                 <el-button link type="primary" size="large" @click="newTask(scope.row)">新建任务</el-button>
                 <el-button link type="primary" size="large" @click="newTask(scope.row)">新建任务</el-button>
-                <el-button link type="danger" size="large">删除</el-button>
+                <el-button link type="danger" size="large"
+                  @click="businessDeteleItem(scope.row.id, scope.row.name)">删除</el-button>
               </template>
               </template>
             </el-table-column>
             </el-table-column>
           </el-table>
           </el-table>
@@ -117,29 +105,82 @@
       </div>
       </div>
     </el-dialog>
     </el-dialog>
 
 
+    <!-- 批量转移 -->
+    <el-dialog v-model="allVisible.batchTransferVisible" width="600" :show-close="false" top="10vh">
+      <template #header="{ close, titleId, titleClass }">
+        <div class="flex justify-between items-center border-b pb-3 dialog-header">
+          <h4 :id="titleId">{{ allText.transferText }}</h4>
+          <div>
+            <el-button type="primary" v-loading="allLoading.transferLoading" @click="transferBusiness()">转移</el-button>
+            <el-button @click="allVisible.batchTransferVisible = false">取消</el-button>
+          </div>
+        </div>
+      </template>
+      <div class="scroll-bar m-6">
+        <div class="flex mb-4">
+          <div class="w-20 flex items-center justify-end pr-4">转移至:</div>
+          <el-select v-model="transferPersonnel" placeholder="请选择" class="flex1">
+            <el-option v-for="item in fixedData.Personnel" :key="item.id" :label="item.name" :value="item.id" />
+          </el-select>
+        </div>
+        <div class="pl-3 text-[#e94a4a]">转移后,将看不到此商机</div>
+      </div>
+    </el-dialog>
+
+    <!-- 导入 -->
+    <el-dialog v-model="allVisible.importVisible" width="680" :show-close="false" top="10vh">
+      <template #header="{ close, titleId, titleClass }">
+        <div class="flex justify-between items-center border-b pb-3 dialog-header">
+          <h4 :id="titleId">导入产品</h4>
+          <div class="flex">
+            <el-upload class="upload-demo mr-3" :limit="1" :show-file-list="false" accept=".xlsx" :http-request="importBusiness">
+              <el-button type="primary" :loading="allLoading.importLoading">导入</el-button>
+            </el-upload>
+            <el-button @click="allVisible.importVisible = false">取消</el-button>
+          </div>
+        </div>
+      </template>
+      <div class="p-8">
+        <div class="ml-4 mr-4">
+          <div class="flex items-center">1、点击下载 <el-link type="primary" @click="downloadTemplate(MODURL, '商机导入模板.xlsx')">商机导入模板.xlsx</el-link></div>
+          <div class="mt-4">2、填写excel文件、商机名称、商机金额、商机阶段</div>
+        </div>
+      </div>
+    </el-dialog>
+
     <!-- 新建任务 -->
     <!-- 新建任务 -->
     <TaskModal :visible="allVisible.taskModalVisible" :edit-form="taskModalForm" :save-loading="taskLoading"
     <TaskModal :visible="allVisible.taskModalVisible" :edit-form="taskModalForm" :save-loading="taskLoading"
-      @close="closeVisible('taskModalVisible')" @submit="submitForm" :title="'新建任务'" :disabled-list="['taskType', 'businessOpportunityId']" />
+      @close="closeVisible('taskModalVisible')" @submit="submitForm" :title="'新建任务'"
+      :disabled-list="['taskType', 'businessOpportunityId']" />
+
+    <!-- 回收站 -->
+    <DeteleBusiness :visibles="allVisible.deteleBusinessVisible" @closeVisible="closeVisible" />
+
+    <!-- 阶段设置 -->
+    <StageSetting :visibles="allVisible.stageSetVisible" @closeVisible="closeVisible" />
   </div>
   </div>
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
 import { ref, reactive, onMounted, inject } from "vue";
 import { ref, reactive, onMounted, inject } from "vue";
-import type { FormInstance, FormRules } from 'element-plus'
+import type { ElTable, FormInstance, FormRules, UploadRequestOptions } from 'element-plus'
 import { useRouter, useRoute } from "vue-router";
 import { useRouter, useRoute } from "vue-router";
-import { GETSYSFILED, MOD, GETPERSONNEL, GETGENERATEFOEM, GETBUSINESSLIST, UPDATEINSET } from './api'
+import { GETSYSFILED, MOD, GETPERSONNEL, GETGENERATEFOEM, GETBUSINESSLIST, UPDATEINSET, BUSINESSDETELE, BATCHTRANSFER, MODURL, tableColumn, BUSIESS_GETSATE } from './api'
 import { GETTABLELIST } from '@/pages/product/api'
 import { GETTABLELIST } from '@/pages/product/api'
-import { post, get } from "@/utils/request";
-import { getAllListByCode, getFromValue, resetFromValue, getFirstDayOfMonth, createTaskFromType, formatDate } from '@/utils/tools'
+import { post, get, uploadFile } from "@/utils/request";
+import { getAllListByCode, getFromValue, resetFromValue, getFirstDayOfMonth, createTaskFromType, formatDate, confirmAction, downloadTemplate, downloadFile } from '@/utils/tools'
 import { createTask } from '@/components/TaskModal/taskFunction'
 import { createTask } from '@/components/TaskModal/taskFunction'
 import { formatDateTime } from '@/utils/times'
 import { formatDateTime } from '@/utils/times'
 import { GenerateForm } from '@zmjs/form-design';
 import { GenerateForm } from '@zmjs/form-design';
 import RelatedProducts from './component/relatedProducts.vue'
 import RelatedProducts from './component/relatedProducts.vue'
 import TaskModal from '@/components/TaskModal/index.vue'
 import TaskModal from '@/components/TaskModal/index.vue'
+import DeteleBusiness from './component/deteleTables.vue'
+import StageSetting from './component/stageSetting.vue'
 
 
 const route = useRoute()
 const route = useRoute()
 const router = useRouter()
 const router = useRouter()
 const globalPopup = inject<GlobalPopup>('globalPopup')
 const globalPopup = inject<GlobalPopup>('globalPopup')
+const businessTableRef = ref<InstanceType<typeof ElTable>>() // 商机table dom
 const businessTotalTable = ref(0)
 const businessTotalTable = ref(0)
 const generateForm = ref<typeof GenerateForm>() // 自定义表单dom
 const generateForm = ref<typeof GenerateForm>() // 自定义表单dom
 const relatedProductsRef = ref<typeof RelatedProducts>()
 const relatedProductsRef = ref<typeof RelatedProducts>()
@@ -152,18 +193,28 @@ const allLoading = reactive({
   businessTableLading: false,
   businessTableLading: false,
   businessSaveLading: false,
   businessSaveLading: false,
   newBusinessSaveLading: false,
   newBusinessSaveLading: false,
+  transferLoading: false,
+  importLoading: false,
+  exoprtLoading: false,
 })
 })
 const allVisible = reactive({
 const allVisible = reactive({
   newBusinessisible: false,
   newBusinessisible: false,
   recycleVisible: false,
   recycleVisible: false,
-  taskModalVisible: false
+  taskModalVisible: false,
+  batchTransferVisible: false,
+  deteleBusinessVisible: false,
+  stageSetVisible: false,
+  importVisible: false
 })
 })
 const allText = reactive({
 const allText = reactive({
-  newBusinessisibleText: '新建商机'
+  newBusinessisibleText: '新建商机',
+  transferText: '转移商机'
 }) // 所有文本
 }) // 所有文本
 
 
 const taskModalForm = ref({}) // 任务弹窗表单
 const taskModalForm = ref({}) // 任务弹窗表单
 const taskLoading = ref<saveLoadingType>("1");
 const taskLoading = ref<saveLoadingType>("1");
+const batchTableData = ref([]) // 批量数据
+const transferPersonnel = ref('') // 转移人
 
 
 const businessOpportunityForm = reactive<businessOpportunityFormType>({
 const businessOpportunityForm = reactive<businessOpportunityFormType>({
   name: '',
   name: '',
@@ -228,10 +279,79 @@ function submitForm(submitData: any, isClose: boolean) { // 任务提交
     const { saveLoading, isClose, message } = err
     const { saveLoading, isClose, message } = err
     taskLoading.value = saveLoading
     taskLoading.value = saveLoading
     allVisible.taskModalVisible = isClose
     allVisible.taskModalVisible = isClose
-    globalPopup?.showSuccess(message)
+    globalPopup?.showError(message)
+  })
+}
+
+function transferBusiness() {
+  const ids = batchTableData.value.map((item: any) => item.id).join(',')
+  allLoading.transferLoading = true
+  post(BATCHTRANSFER, { ids, inchargerId: transferPersonnel.value }).then(() => {
+    transferPersonnel.value = ''
+    globalPopup?.showSuccess('转移成功')
+    closeVisible('batchTransferVisible')
+    getBusinessTableList()
+  }).finally(() => {
+    allLoading.transferLoading = false
+  })
+}
+
+function batchDeteleItem() {
+  const value = batchTableData.value.map((item: any) => item.id).join(',')
+  const label = batchTableData.value.map((item: any) => item.name).join(',')
+  businessDeteleItem(value, label, true)
+}
+
+function businessDeteleItem(value: string | number, label: string, batch: boolean = false) {
+  confirmAction(`确定${batch ? '批量' : ''}删除【${label}】商机吗?`).then(() => {
+    post(BUSINESSDETELE, { ids: value }).then(res => {
+      if (res.code != 'ok') {
+        globalPopup?.showError(res.msg)
+        return
+      }
+      globalPopup?.showSuccess('删除成功')
+      changeBatch(false)
+      getBusinessTableList()
+    }).catch((err) => {
+      globalPopup?.showError(err.message)
+    })
+  })
+}
+
+async function importBusiness(param: UploadRequestOptions) {
+  allLoading.importLoading = true
+  const formData = new FormData();
+  formData.append('multipartFile', param.file)
+  const res = await uploadFile('接口名称', formData).finally(() => {
+    allLoading.importLoading = false
+  })
+  if (res.code == 'ok') {
+    globalPopup?.showSuccess('导入成功' || '')
+    getBusinessTableList()
+    return
+  }
+  globalPopup?.showError(res.msg || '')
+}
+
+function exportBusinessTableList() {
+  allLoading.exoprtLoading = true
+  let valueForm = getFromValue(businessOpportunityForm)
+  post('接口名称', {...valueForm}).then((res) => {
+    downloadFile(res.data, '商机表导出.xlsx')
+  }).finally(() => {
+    allLoading.exoprtLoading = false
   })
   })
 }
 }
 
 
+function changeBatch(flag: boolean = true) {
+  if (flag) {
+    batchTableData.value = businessTableRef.value && businessTableRef.value.getSelectionRows()
+  } else {
+    batchTableData.value = []
+    businessTableRef.value && businessTableRef.value.clearSelection()
+  }
+}
+
 function showVisible(type: keyof typeof allVisible) { // 显示弹窗
 function showVisible(type: keyof typeof allVisible) { // 显示弹窗
   allVisible[type] = true
   allVisible[type] = true
 }
 }
@@ -246,6 +366,7 @@ function handleClose(done: () => void) {
 
 
 function getBusinessTableList() {
 function getBusinessTableList() {
   const formValue = getFromValue(businessOpportunityForm)
   const formValue = getFromValue(businessOpportunityForm)
+  allLoading.businessTableLading = true
   post(GETBUSINESSLIST, { ...formValue }).then((res) => {
   post(GETBUSINESSLIST, { ...formValue }).then((res) => {
     const { data, total } = res.data
     const { data, total } = res.data
     businessTable.value = data.map((item: any) => {
     businessTable.value = data.map((item: any) => {
@@ -255,6 +376,8 @@ function getBusinessTableList() {
       }
       }
     })
     })
     businessTotalTable.value = total
     businessTotalTable.value = total
+  }).finally(() => {
+    allLoading.businessTableLading = false
   })
   })
 }
 }
 
 
@@ -271,15 +394,21 @@ function resetForm() {
 }
 }
 
 
 async function getSystemField() {
 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 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 row = await post(BUSIESS_GETSATE, {})
+  fixedData.BusinessStage = (row.data || []).map((item: any) => {
+    const { name, id, seq } = item
+    return { name, id, seq }
+  }).sort(function (a: any, b: any) {return a.seq - b.seq;});
 
 
   const { data } = await post(GETPERSONNEL, {})
   const { data } = await post(GETPERSONNEL, {})
   fixedData.Personnel = data.map((item: any) => {
   fixedData.Personnel = data.map((item: any) => {
@@ -294,13 +423,18 @@ async function getSystemField() {
 }
 }
 
 
 function toBusinessTableDetail(row: any) {
 function toBusinessTableDetail(row: any) {
-  console.log('点击跳转详情')
   router.push({
   router.push({
     path: `${MOD}/detail`,
     path: `${MOD}/detail`,
     query: { id: row.id }
     query: { id: row.id }
   })
   })
 }
 }
 
 
+function dealWithTableColumn(row: any, eventName: string) {
+  if(eventName == 'toClueTableDetail') {
+    toBusinessTableDetail(row)
+  }
+}
+
 function getProductTableList() {
 function getProductTableList() {
   post(GETTABLELIST, { pageIndex: -1, pageSize: -1 }).then((res) => {
   post(GETTABLELIST, { pageIndex: -1, pageSize: -1 }).then((res) => {
     if (res.code == 'ok') {
     if (res.code == 'ok') {

+ 10 - 2
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/business/type.d.ts

@@ -14,8 +14,8 @@ interface businessOpportunityFormType {
 
 
 interface fixedDataInterface {
 interface fixedDataInterface {
   id: string | number;
   id: string | number;
-  companyId: string | number;
-  code: string;
+  companyId?: string | number;
+  code?: string;
   name: string;
   name: string;
   seq: string | number;
   seq: string | number;
 }
 }
@@ -26,3 +26,11 @@ interface personnelInterface {
   phone: string;
   phone: string;
   jobNumber: string;
   jobNumber: string;
 }
 }
+
+interface businessTableColumnInterface {
+  prop: string;
+  label: string;
+  width: string;
+  eventName?: string;
+  event?: () => void;
+}

+ 19 - 10
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/contacts/api.ts

@@ -1,11 +1,15 @@
+import { SEX } from '../api.ts'
 export const MOD = 'contacts'
 export const MOD = 'contacts'
+export const URL = '/contacts'
 
 
 export const GETSYSFILED = "/sys-dict/getListByCode";
 export const GETSYSFILED = "/sys-dict/getListByCode";
 export const GETPERSONNEL = "/user/getSimpleActiveUserList";
 export const GETPERSONNEL = "/user/getSimpleActiveUserList";
-export const GETGENERATEFOEM = `sys-form/getListByCode${MOD}`
+export const GETGENERATEFOEM = `sys-form/getListByCode/${MOD}`
+
+export const URL_PAGECONTACTS = `${URL}/pageContacts`
 
 
 export const actionButtons: any[] = [
 export const actionButtons: any[] = [
-    { text: '批量转移' },
+    { text: '新建' },
     { text: '批量删除' },
     { text: '批量删除' },
     { text: '导入' },
     { text: '导入' },
     { text: '导出' },
     { text: '导出' },
@@ -13,12 +17,17 @@ export const actionButtons: any[] = [
 
 
 export const tableColumns: TableColumn[] = [
 export const tableColumns: TableColumn[] = [
     { prop: 'name', label: '联系人', event: 'toDetali', width: '150' },
     { prop: 'name', label: '联系人', event: 'toDetali', width: '150' },
-    { prop: 'mobile', label: '客户名称', width: '150' },
-    { prop: 'email', label: '电话号码', width: '200' },
-    { prop: 'wechat', label: '邮箱', width: '200' },
+    { prop: 'customName', label: '客户名称', width: '150' },
+    { prop: 'phone', label: '电话号码', width: '200' },
+    { prop: 'email', label: '邮箱', width: '200' },
     { prop: 'position', label: '职务', width: '100' },
     { prop: 'position', label: '职务', width: '100' },
-    { prop: 'company', label: '性别', width: '100' },
-    { prop: 'companya', label: '负责人', width: '100' },
-    { prop: 'companyb', label: '创建人', width: '100' },
-    { prop: 'companyc', label: '创建时间', width: '200' },
-]
+    { prop: 'sex', label: '性别', width: '100', event: 'getSex' },
+    { prop: 'ownerName', label: '负责人', width: '100' },
+    { prop: 'creatorName', label: '创建人', width: '100' },
+    { prop: 'createTime', label: '创建时间', width: '200' },
+]
+
+export const getSex = (val: number) => {
+    let sexItem = SEX.filter((item: sexTYpe) => item.value == val)
+    return sexItem.length > 0 ? sexItem[0].label : ''
+}

+ 45 - 20
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/contacts/index.vue

@@ -6,13 +6,19 @@
           <!-- 筛选条件 -->
           <!-- 筛选条件 -->
           <el-form :model="filterForm" label-width="70px" style="max-width: 600px">
           <el-form :model="filterForm" label-width="70px" style="max-width: 600px">
             <el-form-item v-for="(item, index) in filterItems" :key="index" :label="item.label">
             <el-form-item v-for="(item, index) in filterItems" :key="index" :label="item.label">
-              <el-input v-if="item.type === 'input'" v-model="filterForm[item.key as keyof FilterForm]" clearable placeholder="请输入"></el-input>
+              <el-input v-if="item.type === 'input'" v-model="filterForm[item.key as keyof FilterForm]" clearable
+                placeholder="请输入"></el-input>
               <el-select v-else v-model="filterForm[item.key as keyof FilterForm]" placeholder="请选择" clearable>
               <el-select v-else v-model="filterForm[item.key as keyof FilterForm]" placeholder="请选择" clearable>
                 <el-option v-for="option in item.options" :key="option.id" :label="option.name" :value="option.id" />
                 <el-option v-for="option in item.options" :key="option.id" :label="option.name" :value="option.id" />
               </el-select>
               </el-select>
             </el-form-item>
             </el-form-item>
           </el-form>
           </el-form>
         </div>
         </div>
+        <div class="w-full flex p-3 shadow-[0_-3px_5px_0px_rgba(0,0,0,0.2)]">
+          <El-button class="w-full" @click="resetForm()" :loading="allLoading.formTableLading">重置</El-Button>
+          <El-button type="primary" class="w-full" :loading="allLoading.formTableLading"
+            @click="getContactPerson()">搜索</El-Button>
+        </div>
       </div>
       </div>
     </div>
     </div>
     <div class="flex-1 p-5 overflow-auto">
     <div class="flex-1 p-5 overflow-auto">
@@ -25,10 +31,15 @@
           <!-- 表格 -->
           <!-- 表格 -->
           <el-table ref="clueTableRef" :data="formTable" border v-loading="allLoading.formTableLading"
           <el-table ref="clueTableRef" :data="formTable" border v-loading="allLoading.formTableLading"
             style="width: 100%;height: 100%;">
             style="width: 100%;height: 100%;">
-            <el-table-column v-for="(column, index) in tableColumns" :key="index" :prop="column.prop" :label="column.label" :width="column.width">
+            <el-table-column v-for="(column, index) in tableColumns" :key="index" :prop="column.prop"
+              :label="column.label" :width="column.width">
               <template #default="scope">
               <template #default="scope">
                 <template v-if="column.event === 'toDetali'">
                 <template v-if="column.event === 'toDetali'">
-                  <el-button link type="primary" size="large" @click="toDetali(scope.row)">{{ scope.row.name }}</el-button>
+                  <el-button link type="primary" size="large" @click="toDetali(scope.row)">{{ scope.row.name
+                  }}</el-button>
+                </template>
+                <template v-if="column.event === 'getSex'">
+                  {{ getSex(scope.row.sex) }}
                 </template>
                 </template>
               </template>
               </template>
             </el-table-column>
             </el-table-column>
@@ -43,8 +54,7 @@
         </div>
         </div>
         <div class="flex justify-end pt-3">
         <div class="flex justify-end pt-3">
           <!-- 分页 -->
           <!-- 分页 -->
-          <el-pagination layout="total, prev, pager, next, sizes" :total="formTablePaging.total"
-            :hide-on-single-page="true" />
+          <el-pagination layout="total, prev, pager, next, sizes" :total="tableTotal" :hide-on-single-page="true" />
         </div>
         </div>
       </div>
       </div>
     </div>
     </div>
@@ -55,7 +65,7 @@
 import { ref, reactive, onMounted, inject } from "vue";
 import { ref, reactive, onMounted, inject } from "vue";
 import { getAllListByCode, getFromValue, resetFromValue, getFirstDayOfMonth, getLastDayOfMonth, formatDate } from '@/utils/tools'
 import { getAllListByCode, getFromValue, resetFromValue, getFirstDayOfMonth, getLastDayOfMonth, formatDate } from '@/utils/tools'
 import { post, get } from "@/utils/request";
 import { post, get } from "@/utils/request";
-import { actionButtons, tableColumns, GETSYSFILED, GETPERSONNEL, GETGENERATEFOEM, MOD } from "./api";
+import { actionButtons, tableColumns, GETSYSFILED, GETPERSONNEL, GETGENERATEFOEM, MOD, URL_PAGECONTACTS, getSex } from "./api";
 import { useRouter, useRoute } from "vue-router";
 import { useRouter, useRoute } from "vue-router";
 
 
 const router = useRouter()
 const router = useRouter()
@@ -68,28 +78,26 @@ const filterForm = reactive<FilterForm>({ // 筛选条件
   createId: '',
   createId: '',
   email: '',
   email: '',
 });
 });
+const formTablePaging = reactive({ // 分页条件
+  pageIndex: 1,
+  pageSize: 10,
+})
+const tableTotal = ref(0)
 const selectData = reactive({ // 下拉数据
 const selectData = reactive({ // 下拉数据
   Personnel: [] as personnelInterface[],
   Personnel: [] as personnelInterface[],
   Customer: [] as any[],
   Customer: [] as any[],
 })
 })
 const filterItems = ref<FilterItem[]>([
 const filterItems = ref<FilterItem[]>([
-    { label: '联系人', key: 'contactPerson', type: 'input' },
-    { label: '客户名称', key: 'customerId', type: 'select', options: selectData.Customer },
-    { label: '电话号码', key: 'phoneNumber', type: 'input' },
-    { label: '邮箱', key: 'email', type: 'input' },
-    { label: '负责人', key: 'responsibleId', type: 'select', options: selectData.Personnel },
-    { label: '创建人', key: 'createId', type: 'select', options: selectData.Personnel },
+  { label: '联系人', key: 'contactPerson', type: 'input' },
+  { label: '客户名称', key: 'customerId', type: 'select', options: selectData.Customer },
+  { label: '电话号码', key: 'phoneNumber', type: 'input' },
+  { label: '邮箱', key: 'email', type: 'input' },
+  { label: '负责人', key: 'responsibleId', type: 'select', options: selectData.Personnel },
+  { label: '创建人', key: 'createId', type: 'select', options: selectData.Personnel },
 ])
 ])
-const formTablePaging = reactive({ // 分页条件
-  currentPage: 1,
-  pageSize: 10,
-  total: 0,
-})
 const generateFormData = ref([]) // 自定义表单数据
 const generateFormData = ref([]) // 自定义表单数据
 
 
-const formTable = ref([
-  {name: '联系人', mobile: '13311112222', email: '123@qq.com', responsible: '张三', createId: '张三'}
-]) // 表格数据
+const formTable = ref([]) // 表格数据
 const allLoading = reactive({ // 按钮加载 Loading
 const allLoading = reactive({ // 按钮加载 Loading
   formTableLading: false,
   formTableLading: false,
 })
 })
@@ -104,6 +112,22 @@ function toDetali(row: any) {
   })
   })
 }
 }
 
 
+function getContactPerson() {
+  allLoading.formTableLading = true
+  const formVal = getFromValue(filterForm)
+  post(URL_PAGECONTACTS, { ...formVal, ...formTablePaging }).then((res) => {
+    const { records, total } =  res.data
+    formTable.value = records
+    tableTotal.value = total
+  }).finally(() => {
+    allLoading.formTableLading = false
+  })
+}
+
+function resetForm() {
+  console.log('重置联系人');
+}
+
 async function getSystemField() {
 async function getSystemField() {
   const systemField = getAllListByCode([])
   const systemField = getAllListByCode([])
   for (let i in systemField) {
   for (let i in systemField) {
@@ -143,6 +167,7 @@ function setFilterItems() {
 
 
 onMounted(() => {
 onMounted(() => {
   getSystemField()
   getSystemField()
+  getContactPerson()
 })
 })
 </script>
 </script>
 
 

+ 3 - 1
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/product/index.vue

@@ -276,7 +276,9 @@ async function importProducts(param: UploadRequestOptions) {
   allLoading.importLoading = true
   allLoading.importLoading = true
   const formData = new FormData();
   const formData = new FormData();
   formData.append('multipartFile', param.file)
   formData.append('multipartFile', param.file)
-  const res = await uploadFile(UPLOADFILE, formData)
+  const res = await uploadFile(UPLOADFILE, formData).finally(() => {
+    allLoading.importLoading = false
+  })
   allLoading.importLoading = false
   allLoading.importLoading = false
   if (res.code == 'ok') {
   if (res.code == 'ok') {
     globalPopup?.showSuccess('导入成功' || '')
     globalPopup?.showSuccess('导入成功' || '')

+ 3 - 1
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/constant.ts

@@ -1,8 +1,10 @@
 export const MOD = '/thread'
 export const MOD = '/thread'
+export const IMPORMOD = 'Thread'
 export const prefix = '/clue'
 export const prefix = '/clue'
 export const GETSYSFILED = '/sys-dict/getListByCode'
 export const GETSYSFILED = '/sys-dict/getListByCode'
 export const GETPERSONNEL = '/user/getSimpleActiveUserList'
 export const GETPERSONNEL = '/user/getSimpleActiveUserList'
 export const GETTEMPLATE = `/sys-form/getListByCode${MOD}`
 export const GETTEMPLATE = `/sys-form/getListByCode${MOD}`
+export const GETTEMPLATETWO = `/sys-form/getListByCode/business`
 export const GETTABLE = `${prefix}/listClue`
 export const GETTABLE = `${prefix}/listClue`
 export const GETDETAIL = `${prefix}/getDetail`
 export const GETDETAIL = `${prefix}/getDetail`
 export const UNDATECLAIM = `${prefix}/claim`
 export const UNDATECLAIM = `${prefix}/claim`
@@ -14,4 +16,4 @@ export const REFIENAME = `${prefix}/reFileName`
 export const UPLOADFILE = `${prefix}/uploadFile`
 export const UPLOADFILE = `${prefix}/uploadFile`
 export const DEYELWCLUE = `${prefix}/listDeleterClue`
 export const DEYELWCLUE = `${prefix}/listDeleterClue`
 export const DETERDETELE = `${prefix}/deleterDelete`
 export const DETERDETELE = `${prefix}/deleterDelete`
-export const ROLLBACK = `${prefix}/rollback`
+export const ROLLBACK = `${prefix}/rollback`

+ 80 - 7
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/detail/components/information.vue

@@ -3,7 +3,7 @@
         <div class="flex justify-between">
         <div class="flex justify-between">
             <div class="title">基本信息</div>
             <div class="title">基本信息</div>
             <div>
             <div>
-                <el-button type="primary">转为商机</el-button>
+                <el-button type="primary" @click="showVisible('newBusinessisible')">转为商机</el-button>
                 <el-button type="primary" @click="claimClues()" v-if="!information.inchargerName">认领</el-button>
                 <el-button type="primary" @click="claimClues()" v-if="!information.inchargerName">认领</el-button>
                 <el-button type="primary" @click="showVisible('clueDialogVisible')" v-else>转移</el-button>
                 <el-button type="primary" @click="showVisible('clueDialogVisible')" v-else>转移</el-button>
                 <el-button type="primary" @click="editClue(information)">编辑</el-button>
                 <el-button type="primary" @click="editClue(information)">编辑</el-button>
@@ -104,16 +104,39 @@
                 <div class="pl-3 text-[#e94a4a]">转移后,将看不到此线索</div>
                 <div class="pl-3 text-[#e94a4a]">转移后,将看不到此线索</div>
             </div>
             </div>
         </el-dialog>
         </el-dialog>
+
+        <!-- 转成商机 -->
+        <el-dialog v-model="dialogVisible.newBusinessisible" width="1000" :show-close="false" top="10vh">
+            <template #header="{ close, titleId, titleClass }">
+                <div class="flex justify-between items-center border-b pb-3 dialog-header">
+                    <h4 :id="titleId">{{ allText.businessisText }}</h4>
+                    <div>
+                        <el-button type="primary" @click="transferBusiness()"
+                            :loading="allLoading.businessSaveLading">转为商机</el-button>
+                        <el-button @click="closeVisible('newBusinessisible')">取消</el-button>
+                    </div>
+                </div>
+            </template>
+            <div class="h-[60vh] overflow-y-auto scroll-bar pt-3">
+                <GenerateForm ref="generateFormDataRef" :data="generateFormData" :value="generateFormVal" />
+                <div>相关产品</div>
+                <RelatedProducts ref="relatedProductsRef" :productTableList="productTableList" />
+            </div>
+        </el-dialog>
     </div>
     </div>
 </template>
 </template>
 <script lang="ts" setup>
 <script lang="ts" setup>
 import { ref, reactive, onMounted, onUnmounted, defineExpose, inject, watchEffect } from 'vue'
 import { ref, reactive, onMounted, onUnmounted, defineExpose, inject, watchEffect } from 'vue'
 import { GenerateForm } from '@zmjs/form-design';
 import { GenerateForm } from '@zmjs/form-design';
-import { formatDate, confirmAction } from '@/utils/tools'
-import { GETTEMPLATE, UNDATEFORM, GETPERSONNEL, UNDATECLAIM } from '../../constant'
+import { formatDate, confirmAction, backPath } from '@/utils/tools'
+import { GETTEMPLATE, UNDATEFORM, GETPERSONNEL, UNDATECLAIM, GETTEMPLATETWO } from '../../constant'
 import { get, post } from '@/utils/request'
 import { get, post } from '@/utils/request'
 import { useStore } from '@/store/index'
 import { useStore } from '@/store/index'
 import { ElMessageBox } from 'element-plus';
 import { ElMessageBox } from 'element-plus';
+import RelatedProducts from '@/pages/business/component/relatedProducts.vue'
+import { all } from 'axios';
+import { formatDateTime } from '@/utils/times';
+import { UPDATEINSET } from '@/pages/business/api';
 
 
 interface personnelInterface {
 interface personnelInterface {
     id: string | number,
     id: string | number,
@@ -134,25 +157,59 @@ const generateFormKey = ref(1)
 const allLoading = reactive({
 const allLoading = reactive({
     generateFormLading: false,
     generateFormLading: false,
     saveBtnLoading: false,
     saveBtnLoading: false,
-    clueLoading: false
+    clueLoading: false,
+    businessSaveLading: false,
 })
 })
 const dialogVisible = reactive({
 const dialogVisible = reactive({
     editClueDialogVisible: false,
     editClueDialogVisible: false,
-    clueDialogVisible: false
+    clueDialogVisible: false,
+    newBusinessisible: false
 })
 })
 const allText = reactive({
 const allText = reactive({
     editClueText: '新建线索',
     editClueText: '新建线索',
-    clueText: '认领线索'
+    clueText: '认领线索',
+    businessisText: '转为商机'
 })
 })
 const generateForm: any = ref(null) // 模板
 const generateForm: any = ref(null) // 模板
 const clueTemplate = ref({
 const clueTemplate = ref({
     list: [],
     list: [],
     config: {}
     config: {}
 }) // 线索模板
 }) // 线索模板
+const generateFormData = ref({ // 商机模板
+    config: {},
+    list: []
+})
+const generateFormDataRef = ref<typeof GenerateForm>() // 商机模板dom
+const relatedProductsRef = ref<typeof RelatedProducts>()
+const productTableList = ref([])
+const generateFormVal = ref<any>({})
 const editForm = ref({}) // 编辑表单
 const editForm = ref({}) // 编辑表单
 const transferValue = ref('') // 转移/认领 id
 const transferValue = ref('') // 转移/认领 id
 const transferOptions = ref<personnelInterface[]>([]) // 转移人员列表
 const transferOptions = ref<personnelInterface[]>([]) // 转移人员列表
 
 
+// 转为商机
+function transferBusiness() {
+    generateFormDataRef.value?.getData().then((res: any) => {
+        let productTableListData = relatedProductsRef?.value?.returnData()
+        let newForm = {
+            ...res,
+            expectedTransactionDate: res.expectedTransactionDate ? formatDateTime(new Date(res.expectedTransactionDate)) : '',
+            businessItemProductList: productTableListData ? JSON.stringify(productTableListData) : []
+        }
+        allLoading.businessSaveLading = true
+        post(UPDATEINSET, { ...newForm }).then((_res) => {
+            dialogVisible.newBusinessisible = false
+            globalPopup?.showSuccess('操作成功')
+            backPath()
+        }).finally(() => {
+            allLoading.businessSaveLading = false
+        })
+    }).catch((_err: any) => {
+        console.log(_err)
+        globalPopup?.showError('请填写完整')
+    })
+}
+
 // 转移/认领 线索
 // 转移/认领 线索
 function transferClues() {
 function transferClues() {
     const ids = information.value?.id
     const ids = information.value?.id
@@ -229,8 +286,21 @@ function receiveAssignment(item: any) {
 function showVisible(filed: keyof typeof dialogVisible) {
 function showVisible(filed: keyof typeof dialogVisible) {
     if (filed == 'clueDialogVisible') {
     if (filed == 'clueDialogVisible') {
         transferValue.value = ''
         transferValue.value = ''
+        allText.clueText = '转移线索'
+    } else {
+        allText.clueText = '认领线索'
     }
     }
-    dialogVisible[filed] = true
+
+    if (filed == 'newBusinessisible') {
+        generateFormVal.value.name = information.value.clueName
+    }
+    setTimeout(() => {
+        dialogVisible[filed] = true
+    }, 100)
+}
+
+function closeVisible(filed: keyof typeof dialogVisible) {
+    dialogVisible[filed] = false
 }
 }
 
 
 async function getSystemField() {
 async function getSystemField() {
@@ -252,6 +322,9 @@ onMounted(async () => {
     receiveAssignment(props)
     receiveAssignment(props)
     const res = await get(GETTEMPLATE)
     const res = await get(GETTEMPLATE)
     clueTemplate.value = JSON.parse(res.data[0].config)
     clueTemplate.value = JSON.parse(res.data[0].config)
+    const data = await get(GETTEMPLATETWO)
+    let newData = JSON.parse(data.data[0].config)
+    generateFormData.value = newData
     getSystemField()
     getSystemField()
 });
 });
 </script>
 </script>

+ 85 - 51
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/detail/components/relatedTasks.vue

@@ -3,73 +3,107 @@
         <div class="flex justify-between">
         <div class="flex justify-between">
             <div class="title">相关任务</div>
             <div class="title">相关任务</div>
             <div>
             <div>
-                <el-button type="primary">新建任务</el-button>
+                <el-button type="primary" @click="newTask()">新建任务</el-button>
             </div>
             </div>
         </div>
         </div>
         <div class="flex-1 overflow-auto pt-3">
         <div class="flex-1 overflow-auto pt-3">
             <el-table :data="relatedTaskstable" border style="width: 100%;height: 100%;">
             <el-table :data="relatedTaskstable" border style="width: 100%;height: 100%;">
                 <el-table-column prop="taskName" label="任务名称">
                 <el-table-column prop="taskName" label="任务名称">
                     <template #default="scope">
                     <template #default="scope">
-                        <el-button link type="primary" size="large">{{
+                        <el-button link type="primary" size="large" @click="toTask()">{{
                             scope.row.taskName
                             scope.row.taskName
                         }}</el-button>
                         }}</el-button>
                     </template>
                     </template>
                 </el-table-column>
                 </el-table-column>
-                <el-table-column prop="priority" label="优先级" width="130" />
-                <el-table-column prop="status" label="状态" width="130" />
-                <el-table-column prop="executor" label="执行人" width="130" />
-                <el-table-column prop="startTime" label="开始时间" width="130" />
-                <el-table-column prop="endTime" label="截至时间" width="130" />
+                <el-table-column prop="priorityStr" label="优先级" width="130" />
+                <el-table-column prop="statusStr" label="状态" width="130" />
+                <el-table-column prop="executorNamesStr" label="执行人" width="130" />
+                <el-table-column prop="startTimes" label="开始时间" width="130" />
+                <el-table-column prop="endTimes" label="截至时间" width="130" />
             </el-table>
             </el-table>
         </div>
         </div>
     </div>
     </div>
+
+    <!-- 新建任务 -->
+    <TaskModal :visible="allVisible.taskModalVisible" :edit-form="taskModalForm" :save-loading="taskLoading"
+        @close="closeTaskModal" @submit="submitForm" :title="'新建任务'" :disabled-list="['taskType', 'clueId']" />
 </template>
 </template>
 <script lang="ts" setup>
 <script lang="ts" setup>
-import { ref, reactive, onMounted, onUnmounted, defineExpose, inject } from 'vue'
+import { PRIORITY } from '@/components/TaskModal/api';
+import { STATUS } from '@/pages/tasks/api';
+import { formatDate } from '@/utils/times';
+import { createTaskFromType } from '@/utils/tools';
+import { ref, reactive, onMounted, watchEffect, inject } from 'vue'
+import { useRouter } from "vue-router";
+import TaskModal from '@/components/TaskModal/index.vue'
+import { createTask } from '@/components/TaskModal/taskFunction';
+const globalPopup = inject<GlobalPopup>('globalPopup')
+const emits = defineEmits(['refreshData']);
+
+const props = defineProps<{
+    data: any,
+    information: any
+}>()
+
+const relatedTaskstable = ref([])
+const information = ref<any>({})
+const router = useRouter()
+const taskLoading = ref<saveLoadingType>('1')
+const taskModalForm = ref({}) // 任务弹窗表单
+const allVisible = reactive({
+    taskModalVisible: false
+})
+
+function submitForm(submitData: any, isClose: boolean) { // 任务提交
+    taskLoading.value = '2'
+    createTask(submitData, isClose).then((res) => {
+        const { saveLoading, isClose } = res
+        taskLoading.value = saveLoading
+        allVisible.taskModalVisible = isClose
+        globalPopup?.showSuccess('新增成功')
+        emits('refreshData')
+    }).catch((err) => {
+        const { saveLoading, isClose, message } = err
+        taskLoading.value = saveLoading
+        allVisible.taskModalVisible = isClose
+        globalPopup?.showError(message)
+    })
+}
+
+function newTask() {
+    const { id } = information.value
+    taskModalForm.value = { ...createTaskFromType(3), clueId: id, }
+    allVisible.taskModalVisible = true
+}
+
+function toTask() {
+    router.push({
+        path: `/tasks`
+    })
+}
+
+function closeTaskModal() {
+    allVisible.taskModalVisible = false
+}
+
+// 接收参数赋值
+function receiveAssignment(item: any) {
+    information.value = item.information
+    const dataVal = item.data
+    for (let i in dataVal) {
+        dataVal[i].executorNamesStr = (dataVal[i].executorNames || []).join(','),
+            dataVal[i].startTimes = dataVal[i].startDate ? formatDate(new Date(dataVal[i].startDate)) : '',
+            dataVal[i].endTimes = dataVal[i].endDate ? formatDate(new Date(dataVal[i].endDate)) : '',
+            dataVal[i].priorityStr = PRIORITY.find(item => item.value == dataVal[i].priority)?.label || '',
+            dataVal[i].statusStr = STATUS.find(item => item.value == dataVal[i].status)?.label || ''
+    }
+    relatedTaskstable.value = dataVal
+}
+
+watchEffect(() => {
+    receiveAssignment(props)
+});
 
 
-const relatedTaskstable = ref([{
-    taskName: '任务名称20240316-tempalsbls',
-    priority: '中',
-    status: '进行中',
-    executor: '张三',
-    startTime: '2024-04-01',
-    endTime: '2024-04-01',
-}, {
-    taskName: '任务名称20240316-tempalsbls',
-    priority: '中',
-    status: '进行中',
-    executor: '张三',
-    startTime: '2024-04-01',
-    endTime: '2024-04-01',
-}, {
-    taskName: '任务名称20240316-tempalsbls',
-    priority: '中',
-    status: '进行中',
-    executor: '张三',
-    startTime: '2024-04-01',
-    endTime: '2024-04-01',
-}, {
-    taskName: '任务名称20240316-tempalsbls',
-    priority: '中',
-    status: '进行中',
-    executor: '张三',
-    startTime: '2024-04-01',
-    endTime: '2024-04-01',
-}, {
-    taskName: '任务名称20240316-tempalsbls',
-    priority: '中',
-    status: '进行中',
-    executor: '张三',
-    startTime: '2024-04-01',
-    endTime: '2024-04-01',
-}, {
-    taskName: '任务名称20240316-tempalsbls',
-    priority: '中',
-    status: '进行中',
-    executor: '张三',
-    startTime: '2024-04-01',
-    endTime: '2024-04-01',
-}])
 // 生命周期钩子
 // 生命周期钩子
 onMounted(() => {
 onMounted(() => {
 });
 });

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

@@ -10,7 +10,7 @@
     </div>
     </div>
     <div class="layout pl-3 pr-3 pb-3">
     <div class="layout pl-3 pr-3 pb-3">
       <div class="bg-white w-2/3 shadow-md rounded-md">
       <div class="bg-white w-2/3 shadow-md rounded-md">
-        <RelatedTasks :data="relatedTasks" :information="information" @refreshData="refreshData"></RelatedTasks>
+        <DetailCompinents :data="relatedTasks" :information="information" :formTaskType="3" :disabledList="['taskType', 'clueId']" :filed="'clueId'" @refreshData="refreshData"></DetailCompinents>
       </div>
       </div>
       <div class="bg-white w-1/3 ml-3 shadow-md rounded-md">
       <div class="bg-white w-1/3 ml-3 shadow-md rounded-md">
         <OperationRecord :data="operationRecord" :information="information" @refreshData="refreshData"></OperationRecord>
         <OperationRecord :data="operationRecord" :information="information" @refreshData="refreshData"></OperationRecord>
@@ -22,7 +22,7 @@
 <script lang="ts" setup>
 <script lang="ts" setup>
 import Information from './components/information.vue'
 import Information from './components/information.vue'
 import Attachment from './components/attachment.vue'
 import Attachment from './components/attachment.vue'
-import RelatedTasks from './components/relatedTasks.vue';
+import DetailCompinents from '@/components/detailcompinents/relatedTasks.vue'
 import OperationRecord from './components/operationRecord.vue';
 import OperationRecord from './components/operationRecord.vue';
 import { ref, reactive, onMounted, inject } from "vue";
 import { ref, reactive, onMounted, inject } from "vue";
 import { post, get } from "@/utils/request";
 import { post, get } from "@/utils/request";

+ 63 - 11
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/index.vue

@@ -56,7 +56,7 @@
           <el-button type="primary" @click="batchTransfer()">批量转移</el-button>
           <el-button type="primary" @click="batchTransfer()">批量转移</el-button>
           <el-button type="primary" @click="batchDeletes()">批量删除</el-button>
           <el-button type="primary" @click="batchDeletes()">批量删除</el-button>
           <el-button type="primary" @click="showDeteleClue(true)">回收站</el-button>
           <el-button type="primary" @click="showDeteleClue(true)">回收站</el-button>
-          <el-button type="primary">导入</el-button>
+          <el-button type="primary" @click="dialogVisible.importVisible = true">导入</el-button>
           <el-button type="primary">导出</el-button>
           <el-button type="primary">导出</el-button>
         </div>
         </div>
         <div class="flex-1 w-full overflow-hidden">
         <div class="flex-1 w-full overflow-hidden">
@@ -83,8 +83,7 @@
                 <el-button link type="primary" size="large" @click="editClue(scope.row)">编辑</el-button>
                 <el-button link type="primary" size="large" @click="editClue(scope.row)">编辑</el-button>
                 <!-- <el-button link type="primary" size="large"
                 <!-- <el-button link type="primary" size="large"
                   @click="dialogVisible.taskModalVisible = true">新建任务</el-button> -->
                   @click="dialogVisible.taskModalVisible = true">新建任务</el-button> -->
-                <el-button link type="primary" size="large"
-                  @click="newTask(scope.row)">新建任务</el-button>
+                <el-button link type="primary" size="large" @click="newTask(scope.row)">新建任务</el-button>
                 <el-button link type="danger" size="large" @click.prevent="deleteRow(scope.row)">删除</el-button>
                 <el-button link type="danger" size="large" @click.prevent="deleteRow(scope.row)">删除</el-button>
               </template>
               </template>
             </el-table-column>
             </el-table-column>
@@ -140,21 +139,45 @@
 
 
     <DeteleTables :visibles="dialogVisible.deteleClueDialogVisible" @showDeteleClue="showDeteleClue" />
     <DeteleTables :visibles="dialogVisible.deteleClueDialogVisible" @showDeteleClue="showDeteleClue" />
 
 
-    <TaskModal :visible="dialogVisible.taskModalVisible" :edit-form="taskModalForm" :save-loading="'1'"
+    <TaskModal :visible="dialogVisible.taskModalVisible" :edit-form="taskModalForm" :save-loading="taskLoading"
       @close="closeTaskModal" @submit="submitForm" :title="'新建任务'" :disabled-list="['taskType', 'clueId']" />
       @close="closeTaskModal" @submit="submitForm" :title="'新建任务'" :disabled-list="['taskType', 'clueId']" />
+
+    <!-- 导入线索 -->
+    <el-dialog v-model="dialogVisible.importVisible" width="680" :show-close="false" top="10vh">
+      <template #header="{ close, titleId, titleClass }">
+        <div class="flex justify-between items-center border-b pb-3 dialog-header">
+          <h4 :id="titleId">导入线索</h4>
+          <div class="flex">
+            <el-upload class="upload-demo mr-3" :limit="1" :show-file-list="false" accept=".xlsx"
+              :http-request="importProducts">
+              <el-button type="primary" :loading="allLoading.importLoading">导入</el-button>
+            </el-upload>
+            <el-button @click="dialogVisible.importVisible = false">取消</el-button>
+          </div>
+        </div>
+      </template>
+      <div class="p-8">
+        <div class="ml-4 mr-4">
+          <div class="flex items-center">1、点击下载 <el-link type="primary"
+              @click="downloadTemplate(IMPORMOD, '线索导入模板.xlsx')">线索导入模板.xlsx</el-link></div>
+          <div class="mt-4">2、填写excel文件、线索名称、线索来源必填</div>
+        </div>
+      </div>
+    </el-dialog>
   </div>
   </div>
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
 import { ref, reactive, onMounted, inject } from "vue";
 import { ref, reactive, onMounted, inject } from "vue";
-import { GETSYSFILED, MOD, GETPERSONNEL, GETTABLE, GETTEMPLATE, GETDETAIL, UNDATECLAIM, UNDATEFORM, DELTEROW } from './constant'
-import { getAllListByCode, getFromValue, resetFromValue, getFirstDayOfMonth, getLastDayOfMonth, formatDate, createTaskFromType, confirmAction } from '@/utils/tools'
-import { FormInstance, FormRules, ElMessageBox, ElTable } from 'element-plus'
-import { post, get } from "@/utils/request";
+import { GETSYSFILED, MOD, IMPORMOD, GETPERSONNEL, GETTABLE, GETTEMPLATE, GETDETAIL, UNDATECLAIM, UNDATEFORM, DELTEROW } from './constant'
+import { getAllListByCode, getFromValue, resetFromValue, getFirstDayOfMonth, getLastDayOfMonth, formatDate, createTaskFromType, confirmAction, downloadTemplate } from '@/utils/tools'
+import { FormInstance, FormRules, ElMessageBox, ElTable, UploadRequestOptions } from 'element-plus'
+import { post, get, uploadFile } from "@/utils/request";
 import { useRouter, useRoute } from "vue-router";
 import { useRouter, useRoute } from "vue-router";
 import { GenerateForm } from '@zmjs/form-design';
 import { GenerateForm } from '@zmjs/form-design';
 import TaskModal from '@/components/TaskModal/index.vue'
 import TaskModal from '@/components/TaskModal/index.vue'
 import DeteleTables from "./deteleTables.vue";
 import DeteleTables from "./deteleTables.vue";
+import { createTask } from "@/components/TaskModal/taskFunction";
 
 
 // 定义类型
 // 定义类型
 interface fixedDataInterface {
 interface fixedDataInterface {
@@ -190,16 +213,19 @@ const filterCriteriaForm = reactive<filterCriteriaFormType>({ // 筛选条件for
   pageFrom: 10
   pageFrom: 10
 })
 })
 const generateFormKey = ref(1)
 const generateFormKey = ref(1)
+const taskLoading = ref<saveLoadingType>('1')
 const allLoading = reactive({
 const allLoading = reactive({
   clueTableLading: false,
   clueTableLading: false,
   generateFormLading: false,
   generateFormLading: false,
   clueLoading: false,
   clueLoading: false,
+  importLoading: false,
 })
 })
 const dialogVisible = reactive({
 const dialogVisible = reactive({
   editClueDialogVisible: false,
   editClueDialogVisible: false,
   taskModalVisible: false,
   taskModalVisible: false,
   clueDialogVisible: false,
   clueDialogVisible: false,
-  deteleClueDialogVisible: false
+  deteleClueDialogVisible: false,
+  importVisible: false
 })
 })
 const allText = reactive({
 const allText = reactive({
   editClueText: '新建线索',
   editClueText: '新建线索',
@@ -272,7 +298,18 @@ function closeTaskModal() {
 }
 }
 
 
 function submitForm(submitData: any, isClose: boolean) {
 function submitForm(submitData: any, isClose: boolean) {
-  console.log(submitData, isClose)
+  taskLoading.value = '2'
+  createTask(submitData, isClose).then((res) => {
+    const { saveLoading, isClose } = res
+    taskLoading.value = saveLoading
+    dialogVisible.taskModalVisible = isClose
+    globalPopup?.showSuccess('新增成功')
+  }).catch((err) => {
+    const { saveLoading, isClose, message } = err
+    taskLoading.value = saveLoading
+    dialogVisible.taskModalVisible = isClose
+    globalPopup?.showError(message)
+  })
 }
 }
 
 
 function editClue(item: any) {
 function editClue(item: any) {
@@ -327,6 +364,22 @@ function deleteRow(row: any) {
   })
   })
 }
 }
 
 
+async function importProducts(param: UploadRequestOptions) {
+  allLoading.importLoading = true
+  const formData = new FormData();
+  formData.append('multipartFile', param.file)
+  const res = await uploadFile('导入接口', formData).finally(() => {
+    allLoading.importLoading = false
+  })
+  allLoading.importLoading = false
+  if (res.code == 'ok') {
+    globalPopup?.showSuccess('导入成功' || '')
+    
+    return
+  }
+  globalPopup?.showError(res.msg || '')
+}
+
 function batchTransfer() {
 function batchTransfer() {
   const data = clueTableRef.value && clueTableRef.value.getSelectionRows()
   const data = clueTableRef.value && clueTableRef.value.getSelectionRows()
   if (!data.length) {
   if (!data.length) {
@@ -359,7 +412,6 @@ function batchDeletes() {
 }
 }
 
 
 function toClueTableDetail(row: any) {
 function toClueTableDetail(row: any) {
-  console.log('点击跳转详情')
   router.push({
   router.push({
     path: `${MOD}/detail`,
     path: `${MOD}/detail`,
     query: { id: row.id }
     query: { id: row.id }

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

@@ -28,4 +28,8 @@ type REPEAT_VALUE_TYPE = 0 | 1 | 2 | 3 | 4; //0是每天, 1是每周, 2是每月
 
 
 type componentType = "success" | "info" | "warning" | "error"
 type componentType = "success" | "info" | "warning" | "error"
 
 
-type TaskResponse = { saveLoading: saveLoadingType, isClose: boolean, message?: string }
+type TaskResponse = { saveLoading: saveLoadingType, isClose: boolean, message?: string }
+
+type optionType = { value: number | string, label: string | number }
+
+type sexTYpe = { value: number | string, label: string }

+ 5 - 0
fhKeeper/formulahousekeeper/management-crm/pom.xml

@@ -14,6 +14,11 @@
     <version>3.4.0</version>
     <version>3.4.0</version>
 
 
     <dependencies>
     <dependencies>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <version>2.9.8</version>
+        </dependency>
         <dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
             <artifactId>spring-boot-starter-web</artifactId>

+ 20 - 2
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/controller/BusinessOpportunityController.java

@@ -82,7 +82,25 @@ public class BusinessOpportunityController {
 
 
         }
         }
         return msg;
         return msg;
-    } @RequestMapping("rollBack")
+    }
+
+    @RequestMapping("deleterDelete")
+    public Object deleterDelete(BusinessOpportunity bo) {
+        HttpRespMsg msg = new HttpRespMsg();
+        String ids1 = bo.getIds();
+        List<Integer> ids = new ArrayList<>();
+        if (!ids1.isEmpty()) {
+            for (String id : ids1.split(",")) {
+                ids.add(Integer.parseInt(id));
+            }
+            bOservice.deleterDelete(ids);
+            msg.setMsg("操作成功");
+        } else {
+            msg.setError("请选择线索");
+        }
+        return msg;
+    }
+    @RequestMapping("rollBack")
     public Object rollBack(BusinessOpportunity bo) {
     public Object rollBack(BusinessOpportunity bo) {
         HttpRespMsg msg = new HttpRespMsg();
         HttpRespMsg msg = new HttpRespMsg();
         String ids1 = bo.getIds();
         String ids1 = bo.getIds();
@@ -200,7 +218,7 @@ public class BusinessOpportunityController {
 
 
     @RequestMapping("saveStage")
     @RequestMapping("saveStage")
     public HttpRespMsg insertStage(List<Stage> stages, HttpServletRequest request) {
     public HttpRespMsg insertStage(List<Stage> stages, HttpServletRequest request) {
-        return stageService.changeStage(stages);
+        return stageService.changeStage(stages,request);
     }
     }
     @RequestMapping("getStage")
     @RequestMapping("getStage")
     public HttpRespMsg Stage() {
     public HttpRespMsg Stage() {

+ 17 - 0
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/controller/CustomController.java

@@ -170,6 +170,23 @@ public class CustomController {
         return customService.deleteFile(file, request);
         return customService.deleteFile(file, request);
     }
     }
 
 
+    //删除
+    @RequestMapping("deleterDelete")
+    public Object deleterDelete(Custom custom , HttpServletRequest request) throws Exception {
+        HttpRespMsg msg = new HttpRespMsg();
+        String ids1 = custom.getIds();
+        List<Integer> ids = new ArrayList<>();
+        if (!ids1.isEmpty()) {
+            for (String id : ids1.split(",")) {
+                ids.add(Integer.parseInt(id));
+            }
+            customService.deleterDelete(ids);
+            msg.setMsg("操作成功");
+        } else {
+            msg.setError("请选择线索");
+        }
+        return msg;    }
+
     //重命名
     //重命名
     @RequestMapping("reFileName")
     @RequestMapping("reFileName")
     public Object reFileName(UploadFile uploadFile, HttpServletRequest request) throws Exception {
     public Object reFileName(UploadFile uploadFile, HttpServletRequest request) throws Exception {

+ 4 - 2
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/entity/BusinessOpportunity.java

@@ -5,7 +5,6 @@ import com.baomidou.mybatisplus.extension.activerecord.Model;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableId;
 
 
 import java.math.BigDecimal;
 import java.math.BigDecimal;
-import java.time.LocalDateTime;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableField;
 import java.io.Serializable;
 import java.io.Serializable;
 import java.util.Date;
 import java.util.Date;
@@ -174,8 +173,11 @@ public class BusinessOpportunity extends Model<BusinessOpportunity> {
      */
      */
     @TableField("plate5")
     @TableField("plate5")
     private String plate5;
     private String plate5;
+//    @JsonDeserialize(using = BusinessItemProductListDeserializer.class)
     @TableField(exist = false)
     @TableField(exist = false)
-    private List<BusinessItemProduct> businessItemProductList;
+    private String businessItemProductList;
+    @TableField(exist = false)
+    private List<BusinessItemProduct> businessItemProducts;
     @TableField(exist = false)
     @TableField(exist = false)
     private List<ActionLog> actionLogList;
     private List<ActionLog> actionLogList;
     @TableField(exist = false)
     @TableField(exist = false)

+ 1 - 1
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/mapper/BusinessOpportunityMapper.java

@@ -33,5 +33,5 @@ public interface BusinessOpportunityMapper extends BaseMapper<BusinessOpportunit
 
 
     Map<String, Object> getDataSummary(Integer companyId, String startDate, String endDate, String userId,@Param("list") List<String> targetUserIds);
     Map<String, Object> getDataSummary(Integer companyId, String startDate, String endDate, String userId,@Param("list") List<String> targetUserIds);
 
 
-    Map<String, Object> getDataStage(Integer companyId, String startDate, String endDate, String userId,@Param("list") List<String> targetUserIds);
+    List<Map<String, Object>> getDataStage(Integer companyId, String startDate, String endDate, String userId,@Param("list") List<String> targetUserIds);
 }
 }

+ 3 - 1
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/BusinessOpportunityService.java

@@ -57,5 +57,7 @@ public interface BusinessOpportunityService extends IService<BusinessOpportunity
 
 
     Map<String, Object> getDataSummary(Integer companyId, String startDate, String endDate, String userId,List<String> targetUserIds);
     Map<String, Object> getDataSummary(Integer companyId, String startDate, String endDate, String userId,List<String> targetUserIds);
 
 
-    Map<String, Object> getDataStage(Integer companyId, String startDate, String endDate, String userId,List<String> targetUserIds);
+    List<Map<String, Object>> getDataStage(Integer companyId, String startDate, String endDate, String userId,List<String> targetUserIds);
+
+    void deleterDelete(List<Integer> ids);
 }
 }

+ 2 - 0
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/CustomService.java

@@ -52,4 +52,6 @@ public interface CustomService extends IService<Custom> {
     HttpRespMsg getAllCustom(HttpServletRequest request);
     HttpRespMsg getAllCustom(HttpServletRequest request);
 
 
     Map<String, Object> getDataSummary(Integer companyId,String startDate, String endDate, String userId,@Param("list") List<String> targetUserIds);
     Map<String, Object> getDataSummary(Integer companyId,String startDate, String endDate, String userId,@Param("list") List<String> targetUserIds);
+
+    void deleterDelete(List<Integer> ids);
 }
 }

+ 2 - 1
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/StageService.java

@@ -4,6 +4,7 @@ import com.management.platform.entity.Stage;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.management.platform.util.HttpRespMsg;
 import com.management.platform.util.HttpRespMsg;
 
 
+import javax.servlet.http.HttpServletRequest;
 import java.util.List;
 import java.util.List;
 
 
 /**
 /**
@@ -16,7 +17,7 @@ import java.util.List;
  */
  */
 public interface StageService extends IService<Stage> {
 public interface StageService extends IService<Stage> {
 
 
-    HttpRespMsg changeStage(List<Stage> stages);
+    HttpRespMsg changeStage(List<Stage> stages, HttpServletRequest request);
 
 
     HttpRespMsg getStage();
     HttpRespMsg getStage();
 }
 }

+ 16 - 7
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/BusinessOpportunityServiceImpl.java

@@ -3,9 +3,11 @@ package com.management.platform.service.impl;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.TypeReference;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.management.platform.entity.*;
 import com.management.platform.entity.*;
 import com.management.platform.mapper.*;
 import com.management.platform.mapper.*;
 import com.management.platform.service.BusinessOpportunityService;
 import com.management.platform.service.BusinessOpportunityService;
@@ -77,7 +79,7 @@ public class BusinessOpportunityServiceImpl extends ServiceImpl<BusinessOpportun
         businessOpportunity.setUploadFilePList(uploadFileMapper.selectByInfoList("business",bo.getId()));
         businessOpportunity.setUploadFilePList(uploadFileMapper.selectByInfoList("business",bo.getId()));
         businessOpportunity.setTaskList(taskMapper.selectList(new QueryWrapper<Task>().eq("business_opportunity_id",bo.getId())));
         businessOpportunity.setTaskList(taskMapper.selectList(new QueryWrapper<Task>().eq("business_opportunity_id",bo.getId())));
         List<BusinessItemProduct> businessItemProducts = biMapper.selectListToBoId(bo.getId());
         List<BusinessItemProduct> businessItemProducts = biMapper.selectListToBoId(bo.getId());
-        businessOpportunity.setBusinessItemProductList(businessItemProducts);
+        businessOpportunity.setBusinessItemProducts(businessItemProducts);
         if (businessItemProducts.size() > 0){
         if (businessItemProducts.size() > 0){
             BigDecimal finalPrice = new BigDecimal(0);
             BigDecimal finalPrice = new BigDecimal(0);
             BigDecimal discountedPrice = new BigDecimal(0);
             BigDecimal discountedPrice = new BigDecimal(0);
@@ -123,11 +125,13 @@ public class BusinessOpportunityServiceImpl extends ServiceImpl<BusinessOpportun
         setNull(bo);
         setNull(bo);
         bo.setCreateTime(new Date());
         bo.setCreateTime(new Date());
         bOMapper.insert(bo);
         bOMapper.insert(bo);
+        List<BusinessItemProduct> businessItemProducts = JSONArray.parseArray(bo.getBusinessItemProductList(), BusinessItemProduct.class);
+        System.out.println(businessItemProducts);
         ActionLog actionLog = new ActionLog();
         ActionLog actionLog = new ActionLog();
         actionLog.setUserId(bo.getUserId());
         actionLog.setUserId(bo.getUserId());
         actionLog.setItemId(bo.getId());
         actionLog.setItemId(bo.getId());
         actionLog.setCode("business");
         actionLog.setCode("business");
-        actionLog.setName("创建了线索");
+        actionLog.setName("创建了商机");
         actionLogMapper.insert(actionLog);
         actionLogMapper.insert(actionLog);
     }
     }
 
 
@@ -168,13 +172,13 @@ public class BusinessOpportunityServiceImpl extends ServiceImpl<BusinessOpportun
         for (BusinessOpportunity clue1 : clues) {
         for (BusinessOpportunity clue1 : clues) {
             if (clue1.getInchargerId() == null) {
             if (clue1.getInchargerId() == null) {
                 //认领
                 //认领
-                clueLog.setName("认领了线索");
+                clueLog.setName("认领了商机");
                 clueLog.setUserId(user.getId());
                 clueLog.setUserId(user.getId());
                 bo.setInchargerId(user.getId());
                 bo.setInchargerId(user.getId());
                 actionLogMapper.insert(clueLog);
                 actionLogMapper.insert(clueLog);
             } else {
             } else {
                 //转移
                 //转移
-                clueLog.setName("转移了线索");
+                clueLog.setName("转移了商机");
                 bo.setInchargerId(bo.getInchargerId());
                 bo.setInchargerId(bo.getInchargerId());
                 actionLogMapper.insert(clueLog);
                 actionLogMapper.insert(clueLog);
             }
             }
@@ -216,9 +220,9 @@ public class BusinessOpportunityServiceImpl extends ServiceImpl<BusinessOpportun
         HttpRespMsg msg = new HttpRespMsg();
         HttpRespMsg msg = new HttpRespMsg();
         msg.setMsg("操作成功");
         msg.setMsg("操作成功");
         biMapper.delete(new QueryWrapper<BusinessItemProduct>().eq("business_id",bo.getId()));
         biMapper.delete(new QueryWrapper<BusinessItemProduct>().eq("business_id",bo.getId()));
-        List<BusinessItemProduct> businessItemProductList = bo.getBusinessItemProductList();
+        List<BusinessItemProduct> businessItemProducts = JSONArray.parseArray(bo.getBusinessItemProductList(), BusinessItemProduct.class);
 //        biMapper.saveBatch()
 //        biMapper.saveBatch()
-        for (BusinessItemProduct businessItemProduct : businessItemProductList) {
+        for (BusinessItemProduct businessItemProduct : businessItemProducts) {
             biMapper.insert(businessItemProduct);
             biMapper.insert(businessItemProduct);
         }
         }
         ActionLog al = new ActionLog();
         ActionLog al = new ActionLog();
@@ -367,10 +371,15 @@ public class BusinessOpportunityServiceImpl extends ServiceImpl<BusinessOpportun
     }
     }
 
 
     @Override
     @Override
-    public Map<String, Object> getDataStage(Integer companyId, String startDate, String endDate, String userId, List<String> targetUserIds) {
+    public List<Map<String, Object>> getDataStage(Integer companyId, String startDate, String endDate, String userId, List<String> targetUserIds) {
         return bOMapper.getDataStage(companyId,startDate,endDate,userId,targetUserIds);
         return bOMapper.getDataStage(companyId,startDate,endDate,userId,targetUserIds);
     }
     }
 
 
+    @Override
+    public void deleterDelete(List<Integer> ids) {
+        bOMapper.deleteBatchIds(ids);
+    }
+
     private BusinessOpportunity setNull(BusinessOpportunity bo) {
     private BusinessOpportunity setNull(BusinessOpportunity bo) {
         if (bo.getPlate1() == "") {
         if (bo.getPlate1() == "") {
             bo.setPlate1(null);
             bo.setPlate1(null);

+ 5 - 0
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/CustomServiceImpl.java

@@ -390,6 +390,11 @@ public class CustomServiceImpl extends ServiceImpl<CustomMapper, Custom> impleme
         return customMapper.getDataSummary(companyId,startDate,endDate,userId,targetUserIds);
         return customMapper.getDataSummary(companyId,startDate,endDate,userId,targetUserIds);
     }
     }
 
 
+    @Override
+    public void deleterDelete(List<Integer> ids) {
+        customMapper.deleteBatchIds(ids);
+    }
+
 
 
     private Custom setNull(Custom clue) {
     private Custom setNull(Custom clue) {
         if (clue.getPlate1() == "") {
         if (clue.getPlate1() == "") {

+ 17 - 1
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/SalesOrderServiceImpl.java

@@ -566,13 +566,21 @@ public class SalesOrderServiceImpl extends ServiceImpl<SalesOrderMapper, SalesOr
         int customCount = customService.count(customLambdaQueryWrapper);
         int customCount = customService.count(customLambdaQueryWrapper);
         int contactsCount = contactsService.count(contactsLambdaQueryWrapper);
         int contactsCount = contactsService.count(contactsLambdaQueryWrapper);
         int businessOpportunityCount = businessOpportunityService.count(businessOpportunityLambdaQueryWrapper);
         int businessOpportunityCount = businessOpportunityService.count(businessOpportunityLambdaQueryWrapper);
+        List<BusinessOpportunity> businessOpportunityList = businessOpportunityService.list(businessOpportunityLambdaQueryWrapper);
+        double businessOpportunityPrice = businessOpportunityList.stream().filter(b->!StringUtils.isEmpty(b.getAmountOfMoney())).mapToDouble(b ->Double.valueOf(b.getAmountOfMoney())).sum();
         Integer salesOrderCount = salesOrderMapper.selectCount(salesOrderLambdaQueryWrapper);
         Integer salesOrderCount = salesOrderMapper.selectCount(salesOrderLambdaQueryWrapper);
+        List<SalesOrder> salesOrders = salesOrderMapper.selectList(salesOrderLambdaQueryWrapper);
+        double salesOrdersPrice = salesOrders.stream().mapToDouble(s -> s.getPrice().doubleValue()).sum();
         Integer clueCount = clueMapper.selectCount(clueLambdaQueryWrapper);
         Integer clueCount = clueMapper.selectCount(clueLambdaQueryWrapper);
 
 
         int customCount1 = customService.count(customLambdaQueryWrapper1);
         int customCount1 = customService.count(customLambdaQueryWrapper1);
         int contactsCount1 = contactsService.count(contactsLambdaQueryWrapper1);
         int contactsCount1 = contactsService.count(contactsLambdaQueryWrapper1);
         int businessOpportunityCount1 = businessOpportunityService.count(businessOpportunityLambdaQueryWrapper1);
         int businessOpportunityCount1 = businessOpportunityService.count(businessOpportunityLambdaQueryWrapper1);
+        List<BusinessOpportunity> businessOpportunityList1 = businessOpportunityService.list(businessOpportunityLambdaQueryWrapper1);
+        double businessOpportunityPrice1 = businessOpportunityList1.stream().filter(b->!StringUtils.isEmpty(b.getAmountOfMoney())).mapToDouble(b -> Double.valueOf(b.getAmountOfMoney())).sum();
         Integer salesOrderCount1 = salesOrderMapper.selectCount(salesOrderLambdaQueryWrapper1);
         Integer salesOrderCount1 = salesOrderMapper.selectCount(salesOrderLambdaQueryWrapper1);
+        List<SalesOrder> salesOrders1 = salesOrderMapper.selectList(salesOrderLambdaQueryWrapper1);
+        double salesOrdersPrice1 = salesOrders1.stream().mapToDouble(s -> s.getPrice().doubleValue()).sum();
         Integer clueCount1 = clueMapper.selectCount(clueLambdaQueryWrapper1);
         Integer clueCount1 = clueMapper.selectCount(clueLambdaQueryWrapper1);
         Map<String,Object> customMap=new HashMap<>();
         Map<String,Object> customMap=new HashMap<>();
         customMap.put("customCount",customCount);
         customMap.put("customCount",customCount);
@@ -586,10 +594,18 @@ public class SalesOrderServiceImpl extends ServiceImpl<SalesOrderMapper, SalesOr
         businessOpportunityMap.put("businessOpportunityCount",businessOpportunityCount);
         businessOpportunityMap.put("businessOpportunityCount",businessOpportunityCount);
         businessOpportunityMap.put("businessOpportunityPromote",getPromote(businessOpportunityCount,businessOpportunityCount1));
         businessOpportunityMap.put("businessOpportunityPromote",getPromote(businessOpportunityCount,businessOpportunityCount1));
         resultMap.put("businessOpportunity",businessOpportunityMap);
         resultMap.put("businessOpportunity",businessOpportunityMap);
+        Map<String,Object> businessOpportunityPriceMap=new HashMap<>();
+        businessOpportunityPriceMap.put("businessOpportunityPrice",businessOpportunityPrice);
+        businessOpportunityPriceMap.put("businessOpportunityPromote",getPromote((int)businessOpportunityPrice,(int)businessOpportunityPrice1));
+        resultMap.put("businessOpportunityPrice",businessOpportunityPriceMap);
         Map<String,Object> salesOrderMap=new HashMap<>();
         Map<String,Object> salesOrderMap=new HashMap<>();
         salesOrderMap.put("salesOrderCount",salesOrderCount);
         salesOrderMap.put("salesOrderCount",salesOrderCount);
         salesOrderMap.put("salesOrderPromote",getPromote(salesOrderCount,salesOrderCount1));
         salesOrderMap.put("salesOrderPromote",getPromote(salesOrderCount,salesOrderCount1));
         resultMap.put("salesOrder",salesOrderMap);
         resultMap.put("salesOrder",salesOrderMap);
+        Map<String,Object> salesOrderPriceMap=new HashMap<>();
+        salesOrderPriceMap.put("salesOrdersPrice",salesOrdersPrice);
+        salesOrderPriceMap.put("salesOrderPricePromote",getPromote((int)salesOrdersPrice,(int)salesOrdersPrice1));
+        resultMap.put("salesOrdersPrice",salesOrderPriceMap);
         Map<String,Object> clueMap=new HashMap<>();
         Map<String,Object> clueMap=new HashMap<>();
         clueMap.put("clueCount",clueCount);
         clueMap.put("clueCount",clueCount);
         clueMap.put("cluePromote",getPromote(clueCount,clueCount1));
         clueMap.put("cluePromote",getPromote(clueCount,clueCount1));
@@ -747,7 +763,7 @@ public class SalesOrderServiceImpl extends ServiceImpl<SalesOrderMapper, SalesOr
                     targetUserIds=targetUserIds2;
                     targetUserIds=targetUserIds2;
             }
             }
         }
         }
-        Map<String,Object> dataMap=businessOpportunityService.getDataStage(companyId,startDate,endDate,userId,targetUserIds);
+        List<Map<String,Object>> dataMap=businessOpportunityService.getDataStage(companyId,startDate,endDate,userId,targetUserIds);
         Map<String,Object> resultMap=new HashMap<>();
         Map<String,Object> resultMap=new HashMap<>();
         resultMap.put("dataMap",dataMap);
         resultMap.put("dataMap",dataMap);
         msg.setData(resultMap);
         msg.setData(resultMap);

+ 2 - 1
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/StageServiceImpl.java

@@ -10,6 +10,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.annotation.Transactional;
 
 
+import javax.servlet.http.HttpServletRequest;
 import java.util.List;
 import java.util.List;
 
 
 /**
 /**
@@ -27,7 +28,7 @@ public class StageServiceImpl extends ServiceImpl<StageMapper, Stage> implements
     private StageMapper stageMapper;
     private StageMapper stageMapper;
     @Override
     @Override
     @Transactional(rollbackFor = Exception.class)
     @Transactional(rollbackFor = Exception.class)
-    public HttpRespMsg changeStage(List<Stage> stages) {
+    public HttpRespMsg changeStage(List<Stage> stages, HttpServletRequest request) {
         HttpRespMsg msg = new HttpRespMsg();
         HttpRespMsg msg = new HttpRespMsg();
         int i = 0;
         int i = 0;
         for (Stage stage : stages) {
         for (Stage stage : stages) {

+ 1 - 1
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/service/impl/SysFormServiceImpl.java

@@ -78,7 +78,7 @@ public class SysFormServiceImpl extends ServiceImpl<SysFormMapper, SysForm> impl
         allList.add(heads);
         allList.add(heads);
         String title;
         String title;
         switch (code){
         switch (code){
-            case "Clue":title = company.getCompanyName()+"_线索导入模板";
+            case "Thread":title = company.getCompanyName()+"_线索导入模板";
             break;
             break;
             case "Custom":title = company.getCompanyName()+"_客户导入模板";
             case "Custom":title = company.getCompanyName()+"_客户导入模板";
                 break;
                 break;

+ 31 - 0
fhKeeper/formulahousekeeper/management-crm/src/main/java/com/management/platform/util/BusinessItemProductListDeserializer.java

@@ -0,0 +1,31 @@
+package com.management.platform.util;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.management.platform.entity.BusinessItemProduct;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class BusinessItemProductListDeserializer extends JsonDeserializer<List<BusinessItemProduct>> {
+
+    @Override
+    public List<BusinessItemProduct> deserialize(JsonParser p, DeserializationContext ctxt)
+            throws IOException, JsonProcessingException {
+        ObjectMapper mapper = (ObjectMapper) p.getCodec();
+        JsonNode node = mapper.readTree(p);
+        List<BusinessItemProduct> products = new ArrayList<>();
+        if (node.isArray()) {
+            for (JsonNode jsonNode : node) {
+                BusinessItemProduct product = mapper.treeToValue(jsonNode, BusinessItemProduct.class);
+                products.add(product);
+            }
+        }
+        return products;
+    }
+}

+ 1 - 0
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/controller/ProjectController.java

@@ -1193,6 +1193,7 @@ public class ProjectController {
 
 
     //todo 同步项目相关数据
     //todo 同步项目相关数据
     @RequestMapping("/synchronizationProject")
     @RequestMapping("/synchronizationProject")
+    @LimitRequest(count = 1,time = 1000)
     public HttpRespMsg synchronizationProject(@RequestBody String dataJson){
     public HttpRespMsg synchronizationProject(@RequestBody String dataJson){
         return projectService.synchronizationProject(dataJson);
         return projectService.synchronizationProject(dataJson);
     }
     }

+ 16 - 9
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ProjectServiceImpl.java

@@ -245,6 +245,8 @@ public class ProjectServiceImpl extends ServiceImpl<ProjectMapper, Project> impl
     private CompanyDingdingMapper companyDingdingMapper;
     private CompanyDingdingMapper companyDingdingMapper;
     @Resource
     @Resource
     private CompanyDingdingService companyDingdingService;
     private CompanyDingdingService companyDingdingService;
+    @Resource
+    private TaskExecutorService taskExecutorService;
 
 
     @Value(value = "${upload.path}")
     @Value(value = "${upload.path}")
     private String path;
     private String path;
@@ -13794,18 +13796,21 @@ public class ProjectServiceImpl extends ServiceImpl<ProjectMapper, Project> impl
             resultList=projectMapper.projectExpendProcessList(startDate,endDate,projectId,categoryId,userId,companyId,null,start,size);
             resultList=projectMapper.projectExpendProcessList(startDate,endDate,projectId,categoryId,userId,companyId,null,start,size);
             total=projectMapper.projectExpendProcessListCount(startDate,endDate,projectId,categoryId,userId,companyId,null);
             total=projectMapper.projectExpendProcessListCount(startDate,endDate,projectId,categoryId,userId,companyId,null);
         }
         }
+        List<Integer> projectList = resultList.stream().map(i -> (Integer) i.get("projectId")).distinct().collect(Collectors.toList());
+        projectList.add(-1);
+        List<TaskExecutor> taskExecutorList = taskExecutorService.list(new LambdaQueryWrapper<TaskExecutor>().in(TaskExecutor::getProjectId, projectList));
+        List<String> executorIds = taskExecutorList.stream().map(TaskExecutor::getExecutorId).distinct().collect(Collectors.toList());
+        executorIds.add("-1");
+        List<Report> reportList = reportMapper.selectList(new LambdaQueryWrapper<Report>().eq(Report::getCompanyId,companyId).eq(Report::getState,1).in(Report::getProjectId, projectList).in(Report::getCreatorId, executorIds).between(Report::getCreateDate, startDate, endDate));
         for (Map<String, Object> map : resultList) {
         for (Map<String, Object> map : resultList) {
-            if(StringUtils.isEmpty(String.valueOf(map.get("executorString")))){
-                continue;
-            }
-            String executorString = String.valueOf(map.get("executorString"));
-            List<String> list = ListUtil.convertLongIdsArrayToList(executorString);
             List<Map<String,Object>> itemList=new ArrayList<>();
             List<Map<String,Object>> itemList=new ArrayList<>();
-            for (String str : list) {
-                String[] split = str.split("\\|");
+            List<TaskExecutor> executors = taskExecutorList.stream().filter(t -> t.getProjectId().equals((Integer) map.get("projectId"))).collect(Collectors.toList());
+            for (TaskExecutor taskExecutor : executors) {
+                List<Report> reports = reportList.stream().filter(r -> r.getCreatorId().equals(taskExecutor.getExecutorId())&&r.getProjectId().equals((Integer)map.get("projectId"))).collect(Collectors.toList());
+                double sum = reports.stream().mapToDouble(Report::getWorkingTime).sum();
                 Map<String,Object> item=new HashMap<>();
                 Map<String,Object> item=new HashMap<>();
-                item.put("userName",split[0]);
-                item.put("progress",split[1]);
+                item.put("userName",taskExecutor.getExecutorName());
+                item.put("progress",sum);
                 itemList.add(item);
                 itemList.add(item);
             }
             }
             Map<String, List<Map<String, Object>>> listMapGroup = itemList.stream().collect(Collectors.groupingBy(i -> String.valueOf(i.get("userName"))));
             Map<String, List<Map<String, Object>>> listMapGroup = itemList.stream().collect(Collectors.groupingBy(i -> String.valueOf(i.get("userName"))));
@@ -13823,8 +13828,10 @@ public class ProjectServiceImpl extends ServiceImpl<ProjectMapper, Project> impl
         }
         }
         //处理项目下的负责组长
         //处理项目下的负责组长
         List<Integer> needProjectIds = resultList.stream().map(m -> Integer.valueOf(String.valueOf(m.get("projectId")))).collect(Collectors.toList());
         List<Integer> needProjectIds = resultList.stream().map(m -> Integer.valueOf(String.valueOf(m.get("projectId")))).collect(Collectors.toList());
+        needProjectIds.add(-1);
         List<Participation> participationList = participationMapper.selectList(new LambdaQueryWrapper<Participation>().in(Participation::getProjectId, needProjectIds));
         List<Participation> participationList = participationMapper.selectList(new LambdaQueryWrapper<Participation>().in(Participation::getProjectId, needProjectIds));
         List<String> needUserIds = participationList.stream().map(m -> String.valueOf(m.getUserId())).collect(Collectors.toList());
         List<String> needUserIds = participationList.stream().map(m -> String.valueOf(m.getUserId())).collect(Collectors.toList());
+        needUserIds.add("-1");
         List<User> userList = userMapper.selectList(new LambdaQueryWrapper<User>().in(User::getId, needUserIds));
         List<User> userList = userMapper.selectList(new LambdaQueryWrapper<User>().in(User::getId, needUserIds));
         resultList.forEach(r->{
         resultList.forEach(r->{
             List<Participation> targetParticipationList = participationList.stream().filter(p -> p.getProjectId().equals(Integer.valueOf(String.valueOf(r.get("projectId"))))).collect(Collectors.toList());
             List<Participation> targetParticipationList = participationList.stream().filter(p -> p.getProjectId().equals(Integer.valueOf(String.valueOf(r.get("projectId"))))).collect(Collectors.toList());

+ 2 - 2
fhKeeper/formulahousekeeper/management-platform/src/main/java/com/management/platform/service/impl/ReportServiceImpl.java

@@ -5582,11 +5582,11 @@ public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> impleme
                 if(needCorpWxTranslate){
                 if(needCorpWxTranslate){
                     item.add("$userName="+(map.get("corpwxUserId")==null?"":map.get("corpwxUserId"))+"$");
                     item.add("$userName="+(map.get("corpwxUserId")==null?"":map.get("corpwxUserId"))+"$");
                     item.add(departmentService.exportWxDepartment(dept,departments));
                     item.add(departmentService.exportWxDepartment(dept,departments));
-                    item.add(manager.isPresent()?manager.get().getName():"");
+                    item.add(manager.isPresent()?("$userName="+manager.get().getName()+"$"):"");
                 }else if(dingding!=null&&dingding.getContactNeedTranslate()==1){
                 }else if(dingding!=null&&dingding.getContactNeedTranslate()==1){
                     item.add("$userName="+(map.get("name")==null?"":map.get("name"))+"$");
                     item.add("$userName="+(map.get("name")==null?"":map.get("name"))+"$");
                     item.add(departmentService.exportDdDepartment(dept,departments));
                     item.add(departmentService.exportDdDepartment(dept,departments));
-                    item.add(manager.isPresent()?manager.get().getName():"");
+                    item.add(manager.isPresent()?("$userName="+manager.get().getName()+"$"):"");
                 }else  {
                 }else  {
                     item.add((String) map.get("name"));
                     item.add((String) map.get("name"));
                     item.add(departmentService.getSupDepartment(dept,departments));
                     item.add(departmentService.getSupDepartment(dept,departments));

+ 3 - 4
fhKeeper/formulahousekeeper/management-platform/src/main/resources/mapper/ProjectMapper.xml

@@ -1918,15 +1918,14 @@
     <select id="projectExpendProcessList" resultType="java.util.Map">
     <select id="projectExpendProcessList" resultType="java.util.Map">
         select p.id AS projectId,p.project_name as projectName,DATE_FORMAT(p.`plan_start_date`,'%Y-%m-%d') AS planStartDate,DATE_FORMAT(p.`plan_end_date`,'%Y-%m-%d') AS planEndDate,pc.name as categoryName,p.project_code as projectCode,IFNULL(SUM(te.plan_hours),0) as planHour,
         select p.id AS projectId,p.project_name as projectName,DATE_FORMAT(p.`plan_start_date`,'%Y-%m-%d') AS planStartDate,DATE_FORMAT(p.`plan_end_date`,'%Y-%m-%d') AS planEndDate,pc.name as categoryName,p.project_code as projectCode,IFNULL(SUM(te.plan_hours),0) as planHour,
         IFNULL((select SUM(working_time) from report where project_id=p.id and create_date BETWEEN #{startDate} AND #{endDate} and state=1),0) as realHour, IFNULL((select SUM(cost) from report where project_id=p.id and create_date BETWEEN #{startDate} AND #{endDate} and state=1),0) as realCost,
         IFNULL((select SUM(working_time) from report where project_id=p.id and create_date BETWEEN #{startDate} AND #{endDate} and state=1),0) as realHour, IFNULL((select SUM(cost) from report where project_id=p.id and create_date BETWEEN #{startDate} AND #{endDate} and state=1),0) as realCost,
-        IFNULL(IFNULL(SUM(te.plan_hours),0)-IFNULL((select SUM(working_time) from report where project_id=p.id and create_date BETWEEN #{startDate} AND #{endDate} and state=1),0),0) as residueHour,
-        GROUP_CONCAT(CONCAT_WS('|',te.executor_name,IFNULL((select SUM(working_time) from report where creator_id=te.executor_id and project_id=p.id and create_date BETWEEN #{startDate} AND #{endDate} and state=1),0))) as executorString
+        IFNULL(IFNULL(SUM(te.plan_hours),0)-IFNULL((select SUM(working_time) from report where project_id=p.id and create_date BETWEEN #{startDate} AND #{endDate} and state=1),0),0) as residueHour
         from task_executor te
         from task_executor te
         left join  user u on u.id=te.executor_id
         left join  user u on u.id=te.executor_id
         left join department d on u.department_id=d.department_id
         left join department d on u.department_id=d.department_id
         left join task t on t.id=te.task_id
         left join task t on t.id=te.task_id
         left join project p on p.id=t.project_id
         left join project p on p.id=t.project_id
         left join project_category pc on pc.id=p.category
         left join project_category pc on pc.id=p.category
-        where u.company_id=#{companyId} and te.project_id is not null  and pc.name in ('报价项目','售后报价项目','售后工程项目','工程项目')
+        where u.company_id=#{companyId} and te.project_id is not null  and pc.name not in ('报价项目','售后报价项目','研发项目')
         <if test="projectId!=null">
         <if test="projectId!=null">
             and p.id=#{projectId}
             and p.id=#{projectId}
         </if>
         </if>
@@ -1957,7 +1956,7 @@
         left join task t on t.id=te.task_id
         left join task t on t.id=te.task_id
         left join project p on p.id=t.project_id
         left join project p on p.id=t.project_id
         left join project_category pc on pc.id=p.category
         left join project_category pc on pc.id=p.category
-        where u.company_id=#{companyId} and te.project_id is not null  and pc.name in ('报价项目','售后报价项目','售后工程项目','工程项目')
+        where u.company_id=#{companyId} and te.project_id is not null  and pc.name not in ('报价项目','售后报价项目','研发项目')
         <if test="projectId!=null">
         <if test="projectId!=null">
             and p.id=#{projectId}
             and p.id=#{projectId}
         </if>
         </if>

+ 6 - 4
fhKeeper/formulahousekeeper/timesheet/src/components/vueMultipleDept.vue

@@ -4,8 +4,9 @@
             <div @click.stop="showVisible(true)">
             <div @click.stop="showVisible(true)">
                 <div class="bionicClassText" v-if="selectedDept.length <= 0">全部部门</div>
                 <div class="bionicClassText" v-if="selectedDept.length <= 0">全部部门</div>
                 <div v-else>
                 <div v-else>
-                    <el-tag @click.stop="" type="info" size="small" closable @close="deteleItem(0)">{{ selectedDeptLabel[0]
-                    }}</el-tag>
+                    <el-tag @click.stop="" type="info" size="small" closable @close="deteleItem(0)">
+                        <TranslationOpenDataText type='departmentName' :openid='selectedDeptLabel[0]'></TranslationOpenDataText>
+                    </el-tag>
                     <el-tag @click.stop="" type="info" size="small" v-if="selectedDeptLabel.length > 1">+ {{
                     <el-tag @click.stop="" type="info" size="small" v-if="selectedDeptLabel.length > 1">+ {{
                         selectedDeptLabel.length - 1 }}</el-tag>
                         selectedDeptLabel.length - 1 }}</el-tag>
                 </div>
                 </div>
@@ -26,13 +27,14 @@
                         :filter-node-method="treeDatafilterNode" default-expand-all @check-change="handleCheckChange"
                         :filter-node-method="treeDatafilterNode" default-expand-all @check-change="handleCheckChange"
                         ref="treeDataComtent">
                         ref="treeDataComtent">
                         <span class="custom-tree-node" slot-scope="{ node, data }">
                         <span class="custom-tree-node" slot-scope="{ node, data }">
-                            <span v-if="node.data.children">
+                            <!-- <span v-if="node.data.children">
                                 <TranslationOpenDataText type='departmentName' :openid='node.label'>
                                 <TranslationOpenDataText type='departmentName' :openid='node.label'>
                                 </TranslationOpenDataText>
                                 </TranslationOpenDataText>
                             </span>
                             </span>
                             <span v-else>
                             <span v-else>
                                 <TranslationOpenDataText type='userName' :openid='node.label'></TranslationOpenDataText>
                                 <TranslationOpenDataText type='userName' :openid='node.label'></TranslationOpenDataText>
-                            </span>
+                            </span> -->
+                            <TranslationOpenDataText type='departmentName' :openid='node.label'></TranslationOpenDataText>
                         </span>
                         </span>
                     </el-tree>
                     </el-tree>
                 </div>
                 </div>

+ 2 - 1
fhKeeper/formulahousekeeper/timesheet/src/views/corpreport/list.vue

@@ -3628,6 +3628,7 @@ export default {
         this.getConsumptionSchedule()
         this.getConsumptionSchedule()
       }
       }
       if(this.ins == 25) {
       if(this.ins == 25) {
+        this.page=1
         this.getConsumptionScheduleTwo()
         this.getConsumptionScheduleTwo()
       }
       }
       if(this.ins == 26) {
       if(this.ins == 26) {
@@ -3962,7 +3963,7 @@ export default {
       let dataList = data || []
       let dataList = data || []
       dataList.push({id: 0, name: '未分类'})
       dataList.push({id: 0, name: '未分类'})
       this.projectSortList = dataList
       this.projectSortList = dataList
-      this.projectSortListTwo = dataList.filter((item) => item.name == '报价项目' || item.name == '售后工程项目' || item.name == '售后报价项目'||item.name == '工程项目')
+      this.projectSortListTwo = dataList.filter((item) => item.name != '报价项目' &&  item.name != '售后报价项目' && item.name != '研发项目' && item.name != '未分类')
       this.projectSortId = dataList[0].id
       this.projectSortId = dataList[0].id
       this.projectSortName = dataList[0].name
       this.projectSortName = dataList[0].name
     },
     },