Browse Source

提交客户管家文件

Lijy 1 year ago
parent
commit
926a938269
23 changed files with 1783 additions and 138 deletions
  1. 1 1
      fhKeeper/formulahousekeeper/customerBuler-crm/src/components/TaskModal/index.vue
  2. 8 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/product/api.ts
  3. 76 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/product/component/attachment.vue
  4. 73 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/product/component/information.vue
  5. 58 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/product/component/operationRecord.vue
  6. 93 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/product/component/products.vue
  7. 81 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/product/component/relatedBusiness.vue
  8. 92 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/product/component/relatedProducts.vue
  9. 152 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/product/detail/index.vue
  10. 264 2
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/product/index.vue
  11. 11 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/product/type.d.ts
  12. 2 1
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/system/customForm/index.vue
  13. 9 1
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/constant.ts
  14. 118 45
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/detail/components/attachment.vue
  15. 208 17
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/detail/components/information.vue
  16. 18 35
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/detail/components/operationRecord.vue
  17. 40 5
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/detail/index.vue
  18. 166 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/deteleTables.vue
  19. 222 28
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/index.vue
  20. 2 2
      fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/type.d.ts
  21. 3 1
      fhKeeper/formulahousekeeper/customerBuler-crm/src/type.d.ts
  22. 31 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/utils/times.ts
  23. 55 0
      fhKeeper/formulahousekeeper/customerBuler-crm/src/utils/tools.ts

+ 1 - 1
fhKeeper/formulahousekeeper/customerBuler-crm/src/components/TaskModal/index.vue

@@ -161,7 +161,7 @@ watch(() => props.editForm, (val) => {
   changeTaskType(form.value.taskType)
   changeTaskType(form.value.taskType)
   contactValueData.value = [{ label: '联系人1', value: 1 }, { label: '联系人2', value: 2 }];
   contactValueData.value = [{ label: '联系人1', value: 1 }, { label: '联系人2', value: 2 }];
   executorValueData.value = [{ label: '执行人1', value: 1 }, { label: '执行人2', value: 2 }];
   executorValueData.value = [{ label: '执行人1', value: 1 }, { label: '执行人2', value: 2 }];
-
+  
 })
 })
 const rules = ref({
 const rules = ref({
   taskName: [
   taskName: [

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

@@ -0,0 +1,8 @@
+export const MOD = '/product'
+export const prefix = '/product'
+export const GETSYSFILED = '/sys-dict/getListByCode'
+export const GETPERSONNEL = '/user/getSimpleActiveUserList'
+export const GETTEMPLATE = `/sys-form/getListByCode${MOD}`
+export const GETTABLELIST = `${prefix}/list`
+export const ADDPRODUCT = `${prefix}/addOrUpdate`
+export const ALLDETELE = `${prefix}/delete`

+ 76 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/product/component/attachment.vue

@@ -0,0 +1,76 @@
+<template>
+    <div class="attachment 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">上传</el-button>
+            </div>
+        </div>
+        <div class="flex-1 overflow-auto pt-3">
+            <el-table :data="attachmenttable" border style="width: 100%;height: 200px;">
+                <el-table-column prop="fileName" label="附件名称" width="180" />
+                <el-table-column prop="fileSize" label="附件大小" width="120" />
+                <el-table-column prop="uploader" label="上传人" width="120" />
+                <el-table-column prop="uploadTime" label="上传时间" width="180" />
+                <el-table-column label="操作" width="180" fixed="right">
+                    <template #default="scope">
+                        <el-button link type="primary" size="large">下载</el-button>
+                        <el-button link type="primary" size="large">重命名</el-button>
+                        <el-button link type="danger" size="large">删除</el-button>
+                    </template>
+                </el-table-column>
+            </el-table>
+        </div>
+    </div>
+</template>
+<script lang="ts" setup>
+import { ref, reactive, onMounted, onUnmounted, defineExpose, inject } from 'vue'
+
+const attachmenttable = ref([{
+    fileName: '文件附件',
+    fileSize: '3.1MB',
+    uploader: '张三',
+    uploadTime: '2024-04-01',
+}, {
+    fileName: '文件附件',
+    fileSize: '3.1MB',
+    uploader: '张三',
+    uploadTime: '2024-04-01',
+}, {
+    fileName: '文件附件',
+    fileSize: '3.1MB',
+    uploader: '张三',
+    uploadTime: '2024-04-01',
+}, {
+    fileName: '文件附件',
+    fileSize: '3.1MB',
+    uploader: '张三',
+    uploadTime: '2024-04-01',
+}, {
+    fileName: '文件附件',
+    fileSize: '3.1MB',
+    uploader: '张三',
+    uploadTime: '2024-04-01',
+}, {
+    fileName: '文件附件',
+    fileSize: '3.1MB',
+    uploader: '张三',
+    uploadTime: '2024-04-01',
+}, {
+    fileName: '文件附件',
+    fileSize: '3.1MB',
+    uploader: '张三',
+    uploadTime: '2024-04-01',
+}])
+// 生命周期钩子
+onMounted(() => {
+});
+</script>
+<style scoped lang="scss">
+.attachment {
+    .title {
+        font-size: 18px;
+        color: #000
+    }
+}
+</style>

File diff suppressed because it is too large
+ 73 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/product/component/information.vue


+ 58 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/product/component/operationRecord.vue

@@ -0,0 +1,58 @@
+<template>
+    <div class="operationRecord pl-4 pr-4 pt-3 pb-3 h-full flex flex-col">
+        <div class="flex justify-between">
+            <div class="title">操作记录</div>
+        </div>
+        <div class="flex-1 overflow-auto pt-5">
+            <el-table :data="operationRecordtable" border style="width: 100%;height: 278px;">
+                <el-table-column prop="operatingTime" label="操作时间" width="140" />
+                <el-table-column prop="operator" label="操作人" width="120" />
+                <el-table-column prop="operationContent" label="操作内容" />
+            </el-table>
+        </div>
+    </div>
+</template>
+<script lang="ts" setup>
+import { ref, reactive, onMounted, onUnmounted, defineExpose, inject } from 'vue'
+
+const operationRecordtable = ref([{
+    operationContent: '转移线索',
+    operator: '张三',
+    operatingTime: '2024-04-01',
+}, {
+    operationContent: '转移线索',
+    operator: '张三',
+    operatingTime: '2024-04-01',
+}, {
+    operationContent: '转移线索',
+    operator: '张三',
+    operatingTime: '2024-04-01',
+}, {
+    operationContent: '转移线索',
+    operator: '张三',
+    operatingTime: '2024-04-01',
+}, {
+    operationContent: '转移线索',
+    operator: '张三',
+    operatingTime: '2024-04-01',
+}, {
+    operationContent: '转移线索',
+    operator: '张三',
+    operatingTime: '2024-04-01',
+}, {
+    operationContent: '转移线索',
+    operator: '张三',
+    operatingTime: '2024-04-01',
+},])
+// 生命周期钩子
+onMounted(() => {
+});
+</script>
+<style scoped lang="scss">
+.operationRecord {
+    .title {
+        font-size: 18px;
+        color: #000
+    }
+}
+</style>

+ 93 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/product/component/products.vue

@@ -0,0 +1,93 @@
+<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">编辑产品</el-button>
+            </div>
+        </div>
+        <div class="flex-1 overflow-auto pt-3">
+            <el-table :data="relatedTaskstable" border style="width: 100%;height: 300px;">
+                <el-table-column label="序号" width="80">
+                    <template #default="scope">
+                        {{ scope.$index + 1 }}
+                    </template>
+                </el-table-column>
+                <el-table-column prop="taskName" label="产品名称">
+                    <template #default="scope">
+                        <el-button link type="primary" size="large">{{
+                            scope.row.taskName
+                        }}</el-button>
+                    </template>
+                </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="endTime" label="售价" width="130" />
+                <el-table-column prop="endTime" label="数量" width="130" />
+                <el-table-column prop="endTime" label="折扣(%)" width="130" />
+                <el-table-column prop="endTime" label="合计" width="130" />
+            </el-table>
+        </div>
+    </div>
+</template>
+<script lang="ts" setup>
+import { ref, reactive, onMounted, onUnmounted, defineExpose, inject } from 'vue'
+
+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(() => {
+});
+</script>
+<style scoped lang="scss">
+.relatedTasks {
+    .title {
+        font-size: 18px;
+        color: #000
+    }
+}
+</style>

+ 81 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/product/component/relatedBusiness.vue

@@ -0,0 +1,81 @@
+<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>
+        <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">{{
+                            scope.row.taskName
+                        }}</el-button>
+                    </template>
+                </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>
+        </div>
+    </div>
+</template>
+<script lang="ts" setup>
+import { ref, reactive, onMounted, onUnmounted, defineExpose, inject } from 'vue'
+
+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(() => {
+});
+</script>
+<style scoped lang="scss">
+.relatedTasks {
+    .title {
+        font-size: 18px;
+        color: #000
+    }
+}
+</style>

+ 92 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/product/component/relatedProducts.vue

@@ -0,0 +1,92 @@
+<template>
+    <div>
+        <el-table ref="productTableRef" :data="productTable" border :row-class-name="tableRowClassName"
+            @row-click="tableRowItem" style="width: 100%;height: 200px">
+            <el-table-column label="序号" width="80">
+                <template #default="scope">
+                    <span>{{ scope.$index + 1 }}</span>
+                </template>
+            </el-table-column>
+            <el-table-column prop="productName" label="产品名称" width="180">
+                <template #default="scope">
+                    <el-select v-model="productTable[scope.$index].productName" placeholder="请选择"
+                        v-if="productTableIndex == scope.$index" clearable @clear="clearTableItem(scope.$index)"
+                        @change="selectChange(scope.$index, productTable[scope.$index].productName)">
+                        <el-option v-for="item in productArrar" :key="item.id" :label="item.productName" :value="item.id" />
+                    </el-select>
+                    <span v-else>{{ productTable[scope.$index].productName }}</span>
+                </template>
+            </el-table-column>
+            <el-table-column prop="productType" label="产品类型" width="180"></el-table-column>
+            <el-table-column prop="unit" label="单位" width="80"></el-table-column>
+            <el-table-column prop="price" label="标准价格" width="120"></el-table-column>
+            <el-table-column prop="stock" label="库存" width="80"></el-table-column>
+            <el-table-column prop="sellingPrice" label="售价" width="180">
+                <template #default="scope">
+                    <el-input-number v-model="productTable[scope.$index].sellingPrice" class="mx-4" :min="0" :max="100000000" controls-position="right" v-if="productTableIndex == scope.$index" />
+                    <span v-else>{{ productTable[scope.$index].sellingPrice }}</span>
+                </template>
+            </el-table-column>
+            <el-table-column prop="quantity" label="数量" width="180">
+                <template #default="scope">
+                    <el-input-number v-model="productTable[scope.$index].quantity" class="mx-4" :min="0" :max="100000000" controls-position="right" v-if="productTableIndex == scope.$index" />
+                    <span v-else>{{ productTable[scope.$index].quantity }}</span>
+                </template>
+            </el-table-column>
+            <el-table-column prop="discount" label="折扣(%)" width="180">
+                <template #default="scope">
+                    <el-input-number v-model="productTable[scope.$index].discount" class="mx-4" :min="0" :max="100" controls-position="right" v-if="productTableIndex == scope.$index" />
+                    <span v-else>{{ productTable[scope.$index].discount }}</span>
+                </template>
+            </el-table-column>
+            <el-table-column prop="total" label="合计" width="180"></el-table-column>
+            <el-table-column label="操作" fixed="right" width="120">
+                <template #default="scope">
+                    <el-button link type="primary" size="large" @click.stop="addTableItem(scope.$index)">添加</el-button>
+                    <el-button link type="danger" size="large" v-if="productTable.length > 1"
+                        @click.stop="deteleTableItem(scope.$index)">删除</el-button>
+                </template>
+            </el-table-column>
+        </el-table>
+    </div>
+</template>
+  
+<script lang="ts" setup>
+import { ref, reactive, onMounted, inject } from "vue";
+
+const productTable: any = ref([{}])
+const productTableIndex = ref(0) // 可以编辑索引
+const productArrar = ref([
+    { id: 1, productName: '产品1', productType: '类别1', unit: '台', price: '1122', stock: '100', sellingPrice: 0, quantity: 0, discount: 0, total: '' },
+    { id: 2, productName: '产品2', productType: '类别2', unit: '台', price: '2211', stock: '300', sellingPrice: 0, quantity: 0, discount: 0, total: '' },
+])
+
+function selectChange(index: number, val: number | string) {
+    let newObj = productArrar.value.find((item: any) => item.id == val)
+    console.log(newObj)
+    productTable.value.splice(index, 1, newObj)
+}
+
+function tableRowItem(row: any) {
+    productTableIndex.value = row.index
+}
+
+function tableRowClassName({ row, rowIndex, }: { row: any, rowIndex: number }) {
+    row.index = rowIndex
+    return ''
+}
+
+function addTableItem(index: number) {
+    productTable.value.splice(index + 1, 0, {})
+}
+
+function clearTableItem(index: number) {
+    productTable.value.splice(index, 0, {})
+}
+
+function deteleTableItem(index: number) {
+    productTable.value.splice(index, 1)
+}
+</script>
+  
+<style lang="scss" scoped></style>

+ 152 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/product/detail/index.vue

@@ -0,0 +1,152 @@
+<template>
+  <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="icon mr-4">
+        <el-link :underline="false" @click="backPath()">
+          <el-icon class="el-icon--right"><icon-view /></el-icon> 返回产品列表
+        </el-link>
+      </div>
+      <div class="mr-8">
+        <el-select v-model="value" placeholder="请选择" style="width: 150px">
+          <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </div>
+    </div>
+    <!-- 内容 -->
+    <div class="flex-1 flex flex-col overflow-y-auto overflow-x-hidden scroll-bar">
+      <div class="w-full h-auto flex justify-between">
+        <div class="bg-white shadow-md rounded-md" style="width: 46%;">
+          <Information />
+        </div>
+        <div class="bg-white ml-2 shadow-md rounded-md flex-1">
+          <Attachment />
+        </div>
+      </div>
+
+      <div class="w-full h-auto flex justify-between mt-2">
+        <div class="bg-white shadow-md rounded-md" style="width: 65%;">
+          <RelatedBusiness />
+        </div>
+        <div class="bg-white ml-2 shadow-md rounded-md flex-1">
+          <OperationRecord />
+        </div>
+      </div>
+
+      <div class="w-full h-auto flex justify-between mt-2">
+        <div class="bg-white shadow-md rounded-md w-full">
+          <Products />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+  
+<script lang="ts" setup>
+import { ref, reactive, onMounted, inject } from "vue";
+import type { FormInstance, FormRules } from 'element-plus'
+import { Edit, ArrowLeft as IconView } from '@element-plus/icons-vue'
+import { backPath } from '../../../utils/tools'
+
+import Information from '../component/information.vue'
+import Attachment from '../component/attachment.vue'
+import RelatedBusiness from '../component/relatedBusiness.vue';
+import OperationRecord from '../component/operationRecord.vue';
+import Products from '../component/products.vue';
+
+const value = 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',
+  },
+]
+
+
+function handleScroll(event: any) { // 滚表横向滚动
+  if (event.deltaY) {
+    event.preventDefault();
+    const element = event.currentTarget;
+    element.scrollLeft += event.deltaY;
+  }
+}
+</script>
+  
+<style lang="scss" scoped>
+.businessDetail {
+  .icon {
+    .el-link {
+      color: #0052CC;
+    }
+  }
+
+  .text {
+    .el-link {
+      color: #fff;
+      font-size: 14px;
+    }
+  }
+
+  .backDarkBlue {
+    background-color: #0052CC;
+    color: #fff;
+  }
+
+  .backGray {
+    background-color: #F4F5F7;
+    color: #000;
+  }
+
+  .startStep {
+    clip-path: polygon(0% 0%,
+        90% 0%,
+        100% 50%,
+        90% 100%,
+        0% 100%);
+  }
+
+  .nextStep {
+    clip-path: polygon(0% 0%,
+        90% 0%,
+        100% 50%,
+        90% 100%,
+        0% 100%,
+        10% 50%);
+  }
+
+  .endStep {
+
+    clip-path: polygon(0% 0%,
+        100% 0%,
+        100% 100%,
+        0% 100%,
+        10% 50%);
+  }
+
+  .itemPing {
+    padding-top: 4px;
+    padding-bottom: 4px;
+  }
+
+  .selectClas >>> .el-select__wrapper {
+    background-color: none !important;
+    box-shadow: none !important;
+  }
+}
+</style>

+ 264 - 2
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/product/index.vue

@@ -1,11 +1,273 @@
 <template>
 <template>
-  <div>
-    product
+  <div class="h-full flex">
+    <div class="p-5 w-80 pr-0">
+      <div class="bg-white w-full h-full shadow-md rounded-md flex flex-col">
+        <div class="flex-1 p-3 overflow-y-auto">
+          <el-form :model="filterProductForm" label-width="70px" style="max-width: 600px">
+            <el-form-item label="产品编号">
+              <el-input v-model="filterProductForm.productCode" clearable placeholder="请输入"></el-input>
+            </el-form-item>
+            <el-form-item label="产品名称">
+              <el-input v-model="filterProductForm.productName" clearable placeholder="请输入"></el-input>
+            </el-form-item>
+            <el-form-item label="产品类别">
+              <el-select v-model="filterProductForm.type" placeholder="请选择" clearable>
+                <el-option v-for="item in fixedData.ProductType" :key="item.id" :label="item.name" :value="item.id" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="负责人">
+              <el-select v-model="filterProductForm.userId" placeholder="请选择">
+                <el-option v-for="item in fixedData.Personnel" :key="item.id" :label="item.name" :value="item.id" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="创建时间">
+              <el-date-picker v-model="filterProductForm.startTime" type="date" placeholder="请选择" :clearable="false"
+                format="YYYY-MM-DD" value-format="YYYY-MM-DD" />
+            </el-form-item>
+            <el-form-item label="">
+              <el-date-picker v-model="filterProductForm.endTime" type="date" placeholder="请选择" :clearable="false"
+                format="YYYY-MM-DD" value-format="YYYY-MM-DD" />
+            </el-form-item>
+          </el-form>
+        </div>
+        <div class="w-full flex p-3 shadow-[0_-3px_5px_0px_rgba(0,0,0,0.2)]">
+          <El-button class="w-full" @click="resetFilter()">重置</El-Button>
+          <El-button type="primary" class="w-full" @click="getProductTableList()">搜索</El-Button>
+        </div>
+      </div>
+    </div>
+    <div class="flex-1 p-5 overflow-auto">
+      <div class="bg-white w-full h-full p-3 shadow-md rounded-md flex flex-col">
+        <div class="flex justify-end pb-3">
+          <el-button type="primary" @click="editProduct(true)">新建产品</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>
+        </div>
+        <div class="flex-1 w-full overflow-hidden">
+          <el-table ref="productTableRef" :data="productTableList" border v-loading="allLoading.productTableLading"
+            style="width: 100%;height: 100%;">
+            <el-table-column type="selection" width="55" />
+            <el-table-column prop="productCode" label="产品编号" width="180"></el-table-column>
+            <el-table-column prop="productName" label="产品名称" width="180">
+              <template #default="scope">
+                <el-button link type="primary" size="large" @click="toProductDetail(scope.row)">{{
+                  scope.row.productName
+                }}</el-button>
+              </template>
+            </el-table-column>
+            <el-table-column prop="type" label="产品类别" width="180"></el-table-column>
+            <el-table-column prop="unit" label="单位" width="180"></el-table-column>
+            <el-table-column prop="price" label="标准价格" width="180"></el-table-column>
+            <el-table-column prop="inventory" label="库存" width="180"></el-table-column>
+            <el-table-column prop="status" label="状态" width="180"></el-table-column>
+            <el-table-column prop="inchargerId" label="负责人" width="190"></el-table-column>
+            <el-table-column prop="creatorId" label="创建人" width="180"></el-table-column>
+            <el-table-column prop="createTime" label="创建时间" width="180"></el-table-column>
+            <el-table-column label="操作" fixed="right" width="200">
+              <template #default="scope">
+                <el-button link type="primary" size="large">编辑</el-button>
+                <el-button link type="danger" size="large" @click.stop="deteleRow([scope.row])">删除</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="filterProductForm.pageSize"
+            @size-change="handleSizeChange" @current-change="handleCurrentChange" :total="productTableTotal" :hide-on-single-page="true" />
+        </div>
+      </div>
+    </div>
+
+    <!-- 弹窗 -->
+    <el-dialog v-model="dialogVisible.editProductVisible" 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.editClueText }}</h4>
+          <div>
+            <el-button type="primary" @click="saveProductRow(true)" v-loading="allLoading.saveLoading">保存并新建</el-button>
+            <el-button type="primary" @click="saveProductRow(false)" v-loading="allLoading.saveLoading">保存</el-button>
+            <el-button @click="dialogVisible.editProductVisible = false">取消</el-button>
+          </div>
+        </div>
+      </template>
+      <div class="h-[60vh] overflow-y-auto scroll-bar pt-3" v-loading="allLoading.generateFormLading">
+        <div class="ml-4 mr-4">
+          <GenerateForm ref="generateForm" :data="productTemplate" :value="genereditForm" :key="generateFormKey" />
+        </div>
+      </div>
+    </el-dialog>
+
   </div>
   </div>
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
+import { ref, reactive, onMounted, inject } from "vue";
+import { GETSYSFILED, MOD, GETPERSONNEL, GETTEMPLATE, GETTABLELIST, ADDPRODUCT, ALLDETELE } from './api'
+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 { useRouter, useRoute } from "vue-router";
+import { GenerateForm } from '@zmjs/form-design';
+
+const router = useRouter()
+const globalPopup = inject<GlobalPopup>('globalPopup')
+const filterProductForm = reactive<filterProductFormType>({ // 筛选条件form
+  userId: '',
+  productName: '',
+  productCode: '',
+  startTime: getFirstDayOfMonth(new Date()),
+  endTime: formatDate(new Date()),
+  pageIndex: 1,
+  pageSize: 10,
+  id: '',
+  type: '',
+})
+const allLoading = reactive({
+  productTableLading: false,
+  generateFormLading: false,
+  saveLoading: false,
+})
+const dialogVisible = reactive({
+  editProductVisible: false,
+  taskModalVisible: false,
+  clueDialogVisible: false,
+  deteleClueDialogVisible: false
+})
+const allText = reactive({
+  editClueText: '新建产品',
+})
+const fixedData = reactive({
+  ProductType: [] as fixedDataInterface[],
+  Personnel: [] as personnelInterface[]
+})
+const productTableList = ref([]) // 产品table数据
+const productTableTotal = ref(0) // 产品 table 数据总数
+const productTableRef = ref<InstanceType<typeof ElTable>>() // 产品table dom
+const generateForm: any = ref(null) // 模板
+const productTemplate = ref({
+  list: [],
+  config: {}
+}) // 产品模板
+const genereditForm = ref({}) // 编辑表单
+const generateFormKey = ref(1)
+
+// 方法定义
+function deteleRow(items: any[]) {
+  let ids = items.map(item => item.id).join(',')
+  let str = items.map(item => item.productName).join(',')
+  confirmAction(`确定${items.length > 1 ? '批量删除' : '删除'}【${str}】线索吗?`).then(() => {
+    post(ALLDETELE, { ids: ids }).then(res => {
+      if (res.code != 'ok') {
+        globalPopup?.showError(res.msg)
+        return
+      }
+      globalPopup?.showSuccess('删除成功')
+      getProductTableList()
+    })
+  })
+}
+
+async function saveProductRow(flag: boolean) {
+  const data = await generateForm.value.getData()
+  let newData = { ...genereditForm.value, ...data }
+  allLoading.saveLoading = true
+  post(ADDPRODUCT, { ...newData }).then((res) => {
+    console.log(res)
+    if(res.code != 'ok') {
+      globalPopup?.showError(res.msg)
+      return
+    }
+    globalPopup?.showSuccess('保存成功')
+    if (!flag) {
+      genereditForm.value = {}
+      generateForm.value && generateForm.value.reset()
+      generateFormKey.value++
+    }
+    dialogVisible.editProductVisible = flag
+  }).catch(() => {
+    dialogVisible.editProductVisible = flag
+  }).finally(() => {
+    allLoading.saveLoading = false
+  })
+}
+
+function editProduct(_flag: boolean) {
+  dialogVisible.editProductVisible = true
+  allLoading.generateFormLading = true
+  setTimeout(() => {
+    generateForm.value && generateForm.value.reset()
+    generateFormKey.value++
+    allLoading.generateFormLading = false
+  }, 1000)
+}
+
+function getProductTableList() {
+  allLoading.productTableLading = true
+  let valueForm = getFromValue(filterProductForm)
+  post(GETTABLELIST, { ...valueForm }).then((res) => {
+    if(res.code == 'ok') {
+      const { record, total } = res.data
+      productTableList.value = record
+      productTableTotal.value = total
+    }
+  }).finally(() => {
+    allLoading.productTableLading = false
+  })
+}
+
+function handleSizeChange(val: number) {
+  filterProductForm.pageIndex = 1
+  filterProductForm.pageSize = val
+  getProductTableList()
+}
+
+function handleCurrentChange(val: number) {
+  filterProductForm.pageIndex = val
+  getProductTableList()
+}
+
+function resetFilter() {
+  let newResetForm = resetFromValue(filterProductForm, { startTime: getFirstDayOfMonth(new Date()), endTime: formatDate(new Date()), pageIndex: 1, pageFrom: 10 })
+  Object.assign(filterProductForm, newResetForm)
+  getProductTableList()
+}
+
+function toProductDetail(row: any) {
+  router.push({ 
+    path: `${MOD}/detail`, 
+    query: { id: row.id } 
+  })
+}
+
+async function getSystemField() {
+  const systemField = getAllListByCode(['产品类型'])
+  for (let i in systemField) {
+    const { data } = await get(`${GETSYSFILED}?code=${systemField[i]}`)
+    for (let key of Object.keys(fixedData)) {
+      if (systemField[i] == key) {
+        Object.assign(fixedData, { [key]: data })
+      }
+    }
+  }
+
+  const { data } = await post(GETPERSONNEL, {})
+  fixedData.Personnel = data.map((item: any) => {
+    const { id, name, phone, jobNumber } = item
+    return {
+      id, name, phone, jobNumber
+    }
+  })
+
+  const res = await get(GETTEMPLATE)
+  productTemplate.value = JSON.parse(res.data[0].config)
+}
 
 
+onMounted(() => {
+  getSystemField()
+  getProductTableList()
+})
 </script>
 </script>
 
 
 <style lang="scss" scoped></style>
 <style lang="scss" scoped></style>

+ 11 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/product/type.d.ts

@@ -0,0 +1,11 @@
+interface filterProductFormType {
+    userId: string | number,
+    productName: string,
+    productCode: string,
+    pageIndex: number,
+    pageSize: number,
+    type: string | number,
+    id: string | number,
+    startTime: string,
+    endTime: string
+}

+ 2 - 1
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/system/customForm/index.vue

@@ -177,7 +177,8 @@ function handleClose(done: any) {
 function setData() {
 function setData() {
   // let data = JSON.parse(localStorage.getItem('threadDataJson') || '')
   // let data = JSON.parse(localStorage.getItem('threadDataJson') || '')
   // let data = JSON.parse(localStorage.getItem('business') || '')
   // let data = JSON.parse(localStorage.getItem('business') || '')
-  let data = JSON.parse(localStorage.getItem('tasks') || '')
+  // let data = JSON.parse(localStorage.getItem('tasks') || '')
+  let data = JSON.parse(localStorage.getItem('product') || '')
   data.list.forEach((element: any) => {
   data.list.forEach((element: any) => {
     element.allDisable = true
     element.allDisable = true
   });
   });

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

@@ -2,8 +2,16 @@ export const MOD = '/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 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`
 export const UNDATEFORM = `${prefix}/insertAndUpdate`
 export const UNDATEFORM = `${prefix}/insertAndUpdate`
-export const DELTEROW = `${prefix}/delete`
+export const DELTEROW = `${prefix}/delete`
+export const DETELEFILE = `${prefix}/deleteFile`
+export const DOWNFILE = `${prefix}/downFile`
+export const REFIENAME = `${prefix}/reFileName`
+export const UPLOADFILE = `${prefix}/uploadFile`
+export const DEYELWCLUE = `${prefix}/listDeleterClue`
+export const DETERDETELE = `${prefix}/deleterDelete`
+export const ROLLBACK = `${prefix}/rollback`

+ 118 - 45
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/detail/components/attachment.vue

@@ -3,67 +3,140 @@
         <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-upload ref="uploadRef" :http-request="httpUploadFile" :limit="1" :show-file-list="false"
+                    element-loading-text="正在上传">
+                    <template #trigger>
+                        <el-button type="primary">上传</el-button>
+                    </template>
+                </el-upload>
             </div>
             </div>
         </div>
         </div>
         <div class="flex-1 overflow-auto pt-3">
         <div class="flex-1 overflow-auto pt-3">
             <el-table :data="attachmenttable" border style="width: 100%;height: 100%;">
             <el-table :data="attachmenttable" border style="width: 100%;height: 100%;">
-                <el-table-column prop="fileName" label="附件名称" width="180" />
-                <el-table-column prop="fileSize" label="附件大小" width="120" />
-                <el-table-column prop="uploader" label="上传人" width="120" />
-                <el-table-column prop="uploadTime" label="上传时间" width="180" />
+                <el-table-column prop="name" label="附件名称" width="200" />
+                <el-table-column prop="size" label="附件大小" width="120" />
+                <el-table-column prop="userName" label="上传人" width="120" />
+                <el-table-column prop="createTime" label="上传时间" width="180" />
                 <el-table-column label="操作" width="180" fixed="right">
                 <el-table-column label="操作" width="180" fixed="right">
                     <template #default="scope">
                     <template #default="scope">
-                        <el-button link type="primary" size="large">下载</el-button>
-                        <el-button link type="primary" size="large">重命名</el-button>
-                        <el-button link type="danger" size="large">删除</el-button>
+                        <el-button link type="primary" size="large" @click="fileDownload(scope.row)">下载</el-button>
+                        <el-button link type="primary" size="large" @click="showVisible(scope.row)">重命名</el-button>
+                        <el-button link type="danger" size="large" @click="deteleFile(scope.row)">删除</el-button>
                     </template>
                     </template>
                 </el-table-column>
                 </el-table-column>
             </el-table>
             </el-table>
         </div>
         </div>
+
+        <!-- 弹窗 -->
+        <el-dialog v-model="renameDialogVisible" width="800" :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="saveEditClue()">保存</el-button>
+                        <el-button @click="renameDialogVisible = false">取消</el-button>
+                    </div>
+                </div>
+            </template>
+            <div class="pt-3">
+                <el-input v-model.trim="renameVal" style="width: 100%" class="pb-3" clearable />
+            </div>
+        </el-dialog>
     </div>
     </div>
 </template>
 </template>
 <script lang="ts" setup>
 <script lang="ts" setup>
-import { ref, reactive, onMounted, onUnmounted, defineExpose, inject } from 'vue'
+import { ElMessageBox, UploadRequestOptions } from 'element-plus';
+import { ref, watch, onMounted, inject, defineEmits, watchEffect } from 'vue'
+import { uploadFile, post } from '@/utils/request'
+import { formatDate, downloadFile, confirmAction } from '@/utils/tools'
+import { DETELEFILE, DOWNFILE, REFIENAME, UPLOADFILE } from '../../constant';
+
+const globalPopup = inject<GlobalPopup>('globalPopup')
+const emits = defineEmits(['refreshData']);
+const attachmenttable = ref([])
+const information: any = ref({})
+const renameDialogVisible = ref(false)
+const renameVal = ref('')
+const props = defineProps<{
+    data: any,
+    information: any
+}>()
+
+// 下载文件
+function fileDownload(item: any) {
+    const id = item.id
+    post(DOWNFILE, { id }).then((res) => {
+        downloadFile(res, item.name)
+    }).catch((err) => {
+        downloadFile(err, item.name)
+    })
+}
+
+// 删除文件
+function deteleFile(item: any) {
+    const id = item.id
+    confirmAction(`确定删除【${item.name}】文件吗?`).then(() => {
+        post(DETELEFILE, { id }).then((_res) => {
+            globalPopup?.showSuccess('删除成功')
+            emits('refreshData')
+        })
+    })
+}
+
+// 保存重命名
+function saveEditClue() {
+    if (!renameVal.value) {
+        globalPopup?.showWarning('请输入文件名称')
+        return
+    }
+    const clueId = information.value.id
+    post(REFIENAME, { name: renameVal.value, id: clueId }).then((res) => {
+        if (res.code == 'ok') {
+            renameDialogVisible.value = false
+            globalPopup?.showSuccess(res.msg || '')
+            emits('refreshData')
+        }
+    }).catch((_err) => { })
+}
+
+// 显示弹窗
+function showVisible(item: any) {
+    renameVal.value = JSON.parse(JSON.stringify(item.name))
+    renameDialogVisible.value = true
+}
+
+// 上传附件
+async function httpUploadFile(param: UploadRequestOptions) {
+    const clueId = information.value.id
+    const formData = new FormData();
+    formData.append('file', param.file)
+    formData.append('id', clueId)
+    const res = await uploadFile(UPLOADFILE, formData)
+    if (res.code == 'ok') {
+        globalPopup?.showSuccess(res.msg || '')
+        emits('refreshData');
+        return
+    }
+    globalPopup?.showError(res.msg || '')
+    return res
+}
+
+// 接收参数赋值
+function receiveAssignment(item: any) {
+    item.data.forEach((item: any) => {
+        item.createTime = formatDate(new Date(item.createTime))
+    });
+    attachmenttable.value = item.data
+    information.value = item.information
+}
+
+watchEffect(() => {
+    receiveAssignment(props)
+});
 
 
-const attachmenttable = ref([{
-    fileName: '文件附件',
-    fileSize: '3.1MB',
-    uploader: '张三',
-    uploadTime: '2024-04-01',
-}, {
-    fileName: '文件附件',
-    fileSize: '3.1MB',
-    uploader: '张三',
-    uploadTime: '2024-04-01',
-}, {
-    fileName: '文件附件',
-    fileSize: '3.1MB',
-    uploader: '张三',
-    uploadTime: '2024-04-01',
-}, {
-    fileName: '文件附件',
-    fileSize: '3.1MB',
-    uploader: '张三',
-    uploadTime: '2024-04-01',
-}, {
-    fileName: '文件附件',
-    fileSize: '3.1MB',
-    uploader: '张三',
-    uploadTime: '2024-04-01',
-}, {
-    fileName: '文件附件',
-    fileSize: '3.1MB',
-    uploader: '张三',
-    uploadTime: '2024-04-01',
-}, {
-    fileName: '文件附件',
-    fileSize: '3.1MB',
-    uploader: '张三',
-    uploadTime: '2024-04-01',
-}])
 // 生命周期钩子
 // 生命周期钩子
 onMounted(() => {
 onMounted(() => {
+    receiveAssignment(props)
 });
 });
 </script>
 </script>
 <style scoped lang="scss">
 <style scoped lang="scss">

File diff suppressed because it is too large
+ 208 - 17
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/detail/components/information.vue


+ 18 - 35
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/detail/components/operationRecord.vue

@@ -5,48 +5,31 @@
         </div>
         </div>
         <div class="flex-1 overflow-auto pt-5">
         <div class="flex-1 overflow-auto pt-5">
             <el-table :data="operationRecordtable" border style="width: 100%;height: 100%;">
             <el-table :data="operationRecordtable" border style="width: 100%;height: 100%;">
-                <el-table-column prop="operatingTime" label="操作时间" width="140" />
-                <el-table-column prop="operator" label="操作人" width="120" />
-                <el-table-column prop="operationContent" label="操作内容" />
+                <el-table-column prop="creatTime" label="操作时间" width="140" />
+                <el-table-column prop="userName" label="操作人" width="120" />
+                <el-table-column prop="name" label="操作内容" />
             </el-table>
             </el-table>
         </div>
         </div>
     </div>
     </div>
 </template>
 </template>
 <script lang="ts" setup>
 <script lang="ts" setup>
-import { ref, reactive, onMounted, onUnmounted, defineExpose, inject } from 'vue'
+import { formatDate } from '@/utils/tools';
+import { ref, onMounted, watchEffect } from 'vue'
 
 
-const operationRecordtable = ref([{
-    operationContent: '转移线索',
-    operator: '张三',
-    operatingTime: '2024-04-01',
-}, {
-    operationContent: '转移线索',
-    operator: '张三',
-    operatingTime: '2024-04-01',
-}, {
-    operationContent: '转移线索',
-    operator: '张三',
-    operatingTime: '2024-04-01',
-}, {
-    operationContent: '转移线索',
-    operator: '张三',
-    operatingTime: '2024-04-01',
-}, {
-    operationContent: '转移线索',
-    operator: '张三',
-    operatingTime: '2024-04-01',
-}, {
-    operationContent: '转移线索',
-    operator: '张三',
-    operatingTime: '2024-04-01',
-}, {
-    operationContent: '转移线索',
-    operator: '张三',
-    operatingTime: '2024-04-01',
-},])
-// 生命周期钩子
-onMounted(() => {
+const props = defineProps<{
+    data: any
+}>()
+
+const operationRecordtable = ref([])
+
+watchEffect(() => {
+    props.data.forEach((item: any) => {
+        item.creatTime = formatDate(new Date(item.creatTime))
+    })
+    operationRecordtable.value = props.data
 });
 });
+// 生命周期钩子
+onMounted(() => {});
 </script>
 </script>
 <style scoped lang="scss">
 <style scoped lang="scss">
 .operationRecord {
 .operationRecord {

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

@@ -2,35 +2,70 @@
   <div class="h-full threadDetail">
   <div class="h-full threadDetail">
     <div class="layout p-3">
     <div class="layout p-3">
       <div class="bg-white w-1/2 shadow-md rounded-md">
       <div class="bg-white w-1/2 shadow-md rounded-md">
-        <Information></Information>
+        <Information :data="information" @refreshData="refreshData"></Information>
       </div>
       </div>
       <div class="bg-white w-1/2 ml-3 shadow-md rounded-md">
       <div class="bg-white w-1/2 ml-3 shadow-md rounded-md">
-        <Attachment></Attachment>
+        <Attachment :data="attachment" :information="information" @refreshData="refreshData"></Attachment>
       </div>
       </div>
     </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></RelatedTasks>
+        <RelatedTasks :data="relatedTasks" :information="information" @refreshData="refreshData"></RelatedTasks>
       </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></OperationRecord>
+        <OperationRecord :data="operationRecord" :information="information" @refreshData="refreshData"></OperationRecord>
       </div>
       </div>
     </div>
     </div>
   </div>
   </div>
 </template>
 </template>
   
   
 <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 RelatedTasks from './components/relatedTasks.vue';
 import OperationRecord from './components/operationRecord.vue';
 import OperationRecord from './components/operationRecord.vue';
+import { ref, reactive, onMounted, inject } from "vue";
+import { post, get } from "@/utils/request";
+import { useRouter, useRoute } from "vue-router";
+import { GETDETAIL } from "../constant"
+
+const route = useRoute()
+
+const globalPopup = inject<GlobalPopup>('globalPopup')
+const addressParameters: any = ref(0) // 地址上的参数
+const information = ref({}) // 基本信息
+const attachment = ref([]) // 附件
+const relatedTasks = ref([]) // 相关任务
+const operationRecord = ref([]) // 操作记录
+
+function getDetail() {
+  const id = addressParameters.value
+  post(GETDETAIL, { id }).then((res) => {
+    const { clueLogList, files, taskList } = res.data
+    information.value = res.data
+    attachment.value = files || []
+    relatedTasks.value = taskList || []
+    operationRecord.value = clueLogList || []
+  })
+}
+
+function refreshData() {
+  getDetail()
+}
+
+onMounted(() => {
+  const { id } = route.query
+  addressParameters.value = id
+  getDetail()
+
+})
 </script>
 </script>
   
   
 <style lang="scss" scoped>
 <style lang="scss" scoped>
 .threadDetail {
 .threadDetail {
   display: flex;
   display: flex;
   flex-direction: column;
   flex-direction: column;
+
   .layout {
   .layout {
     width: 100%;
     width: 100%;
     height: 50%;
     height: 50%;

+ 166 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/deteleTables.vue

@@ -0,0 +1,166 @@
+<template>
+    <el-dialog v-model="deteleClueDialogVisible" 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="batchRestore()" v-loading="allLoading.batchRecoveryLoading">批量恢复</el-button>
+                    <el-button type="primary" @click="batchDeletes()"
+                        v-loading="allLoading.batchDeteleLoading">批量删除</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="clueTableRef" :data="deteleClueTable" border v-loading="allLoading.tableLoading"
+                    style="width: 100%;height: 100%;">
+                    <el-table-column type="selection" width="55" />
+                    <el-table-column prop="clueName" label="线索名称" width="180"></el-table-column>
+                    <el-table-column prop="clueSourceValue" label="线索来源" width="180"></el-table-column>
+                    <el-table-column prop="phone" label="电话号码" width="180"></el-table-column>
+                    <el-table-column prop="email" label="邮箱" width="180"></el-table-column>
+                    <el-table-column prop="customerIndustryValue" label="客户行业" width="180"></el-table-column>
+                    <el-table-column prop="customerLevelValue" label="客户级别" width="180"></el-table-column>
+                    <el-table-column prop="inchargerName" label="负责人" width="190"></el-table-column>
+                    <el-table-column prop="createName" label="创建人" width="180"></el-table-column>
+                    <el-table-column prop="createTime" label="创建时间" width="180"></el-table-column>
+                    <el-table-column label="操作" fixed="right" width="120">
+                        <template #default="scope">
+                            <el-button link type="primary" size="large" @click="restoreItemRow([scope.row])">恢复</el-button>
+                            <el-button link type="danger" size="large" @click="deteItemRow([scope.row])">删除</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="clueTotalTable"
+                    :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 { DETERDETELE, DEYELWCLUE, ROLLBACK } from './constant';
+import { ElTable } from 'element-plus';
+import { confirmAction } from '@/utils/tools';
+
+const emits = defineEmits(['showDeteleClue']);
+const globalPopup = inject<GlobalPopup>('globalPopup')
+const deteleClueTable = ref([])
+const deteleClueDialogVisible = ref(false)
+const clueTotalTable = ref(0)
+const allLoading = reactive({
+    batchRecoveryLoading: false,
+    batchDeteleLoading: false,
+    tableLoading: false
+})
+
+const tableForm = reactive({
+    pageIndex: 1,
+    pageFrom: 10
+})
+
+const clueTableRef = ref<InstanceType<typeof ElTable>>() // 线索table dom
+
+const props = defineProps<{
+    visibles: boolean
+}>()
+
+watch(() => props.visibles, (newVal) => {
+    deteleClueDialogVisible.value = newVal
+    if (newVal) {
+        getTableList()
+    }
+})
+
+function batchRestore() {
+    const data = clueTableRef.value && clueTableRef.value.getSelectionRows()
+    if (!data.length) {
+        globalPopup?.showWarning('请选择需要恢复的数据')
+        return
+    }
+    restoreItemRow(data)
+}
+
+function batchDeletes() {
+    const data = clueTableRef.value && clueTableRef.value.getSelectionRows()
+    if (!data.length) {
+        globalPopup?.showWarning('请选择需要删除的数据')
+        return
+    }
+    deteItemRow(data)
+}
+
+function deteItemRow(items: any[]) {
+    const ids = items.map((item: any) => item.id).join(',')
+    const str = items.map((item: any) => item.clueName).join(',')
+    confirmAction(`确定${items.length > 1 ? '批量删除这些' : '删除'}【${str}】线索吗?`, '', 'warning').then(() => {
+    post(DETERDETELE, { ids }).then(res => {
+      if (res.code != 'ok') {
+        globalPopup?.showError(res.msg)
+        return
+      }
+      globalPopup?.showSuccess('删除成功')
+      getTableList()
+    })
+  })
+}
+
+function restoreItemRow(items: any[]) {
+    const ids = items.map((item: any) => item.id).join(',')
+    const str = items.map((item: any) => item.clueName).join(',')
+    confirmAction(`确定${items.length > 1 ? '批量恢复这些' : '恢复'}【${str}】线索吗?`, '', 'warning').then(() => {
+    post(ROLLBACK, { ids }).then(res => {
+      if (res.code != 'ok') {
+        globalPopup?.showError(res.msg)
+        return
+      }
+      globalPopup?.showSuccess('恢复成功')
+      getTableList()
+    })
+  })
+}
+
+function getTableList() {
+    allLoading.tableLoading = true
+    post(DEYELWCLUE, { ...tableForm }).then((res) => {
+        if (res.code == 'ok') {
+            const { data, total } = res.data
+            deteleClueTable.value = data
+            clueTotalTable.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('showDeteleClue', false)
+}
+
+function beForeCancel(done: () => void) {
+    emits('showDeteleClue', false)
+    done()
+}
+
+onMounted(() => {
+
+})
+
+</script>
+<style lang="scss" scoped></style>

+ 222 - 28
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/thread/index.vue

@@ -8,7 +8,7 @@
               <el-input v-model="filterCriteriaForm.clueName" clearable placeholder="请输入"></el-input>
               <el-input v-model="filterCriteriaForm.clueName" clearable placeholder="请输入"></el-input>
             </el-form-item>
             </el-form-item>
             <el-form-item label="线索来源">
             <el-form-item label="线索来源">
-              <el-select v-model="filterCriteriaForm.clueSourceId" placeholder="请选择">
+              <el-select v-model="filterCriteriaForm.clueSourceId" placeholder="请选择" clearable>
                 <el-option v-for="item in fixedData.ClueSources" :key="item.id" :label="item.name" :value="item.id" />
                 <el-option v-for="item in fixedData.ClueSources" :key="item.id" :label="item.name" :value="item.id" />
               </el-select>
               </el-select>
             </el-form-item>
             </el-form-item>
@@ -52,10 +52,10 @@
     <div class="flex-1 p-5 overflow-auto">
     <div class="flex-1 p-5 overflow-auto">
       <div class="bg-white w-full h-full p-3 shadow-md rounded-md flex flex-col">
       <div class="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">新建线索</el-button>
+          <el-button type="primary" @click="editClue(false)">新建线索</el-button>
           <el-button type="primary" @click="batchTransfer()">批量转移</el-button>
           <el-button type="primary" @click="batchTransfer()">批量转移</el-button>
-          <el-button type="primary" @clicl="batchDelete()">批量删除</el-button>
-          <el-button type="primary">回收站</el-button>
+          <el-button type="primary" @click="batchDeletes()">批量删除</el-button>
+          <el-button type="primary" @click="showDeteleClue(true)">回收站</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>
         </div>
         </div>
@@ -65,44 +65,94 @@
             <el-table-column type="selection" width="55" />
             <el-table-column type="selection" width="55" />
             <el-table-column prop="clueName" label="线索名称" width="180">
             <el-table-column prop="clueName" label="线索名称" width="180">
               <template #default="scope">
               <template #default="scope">
-                <el-button link type="primary" size="large" @click.prevent="toClueTableDetail(scope.row)">{{ scope.row.clueName
+                <el-button link type="primary" size="large" @click.prevent="toClueTableDetail(scope.row)">{{
+                  scope.row.clueName
                 }}</el-button>
                 }}</el-button>
               </template>
               </template>
             </el-table-column>
             </el-table-column>
-            <el-table-column prop="clueSourceId" label="线索来源" width="180"></el-table-column>
+            <el-table-column prop="clueSourceValue" label="线索来源" width="180"></el-table-column>
             <el-table-column prop="phone" label="电话号码" width="180"></el-table-column>
             <el-table-column prop="phone" label="电话号码" width="180"></el-table-column>
             <el-table-column prop="email" label="邮箱" width="180"></el-table-column>
             <el-table-column prop="email" label="邮箱" width="180"></el-table-column>
-            <el-table-column prop="customerIndustryId" label="客户行业" width="180"></el-table-column>
-            <el-table-column prop="customerLevelId" label="客户级别" width="180"></el-table-column>
-            <el-table-column prop="inchargerId" label="负责人" width="190"></el-table-column>
+            <el-table-column prop="customerIndustryValue" label="客户行业" width="180"></el-table-column>
+            <el-table-column prop="customerLevelValue" label="客户级别" width="180"></el-table-column>
+            <el-table-column prop="inchargerName" label="负责人" width="190"></el-table-column>
             <el-table-column prop="createName" label="创建人" width="180"></el-table-column>
             <el-table-column prop="createName" label="创建人" width="180"></el-table-column>
             <el-table-column prop="createTime" label="创建时间" width="180"></el-table-column>
             <el-table-column 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">编辑</el-button>
-                <el-button link type="primary" size="large">新建任务</el-button>
-                <el-button link type="danger" size="large" @click.prevent="deleteRow(scope.$index)">删除</el-button>
+                <el-button link type="primary" size="large" @click="editClue(scope.row)">编辑</el-button>
+                <el-button link type="primary" size="large"
+                  @click="dialogVisible.taskModalVisible = true">新建任务</el-button>
+                <el-button link type="danger" size="large" @click.prevent="deleteRow(scope.row)">删除</el-button>
               </template>
               </template>
             </el-table-column>
             </el-table-column>
           </el-table>
           </el-table>
         </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="clueTotalTable" :hide-on-single-page="true" />
+          <el-pagination layout="total, prev, pager, next, sizes" :page-size="filterCriteriaForm.pageFrom"
+            @size-change="handleSizeChange" @current-change="handleCurrentChange" :total="clueTotalTable"
+            :hide-on-single-page="true" />
         </div>
         </div>
       </div>
       </div>
     </div>
     </div>
 
 
     <!-- 弹窗 -->
     <!-- 弹窗 -->
-    
+    <el-dialog v-model="dialogVisible.editClueDialogVisible" 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.editClueText }}</h4>
+          <div>
+            <el-button type="primary" @click="saveEditClue(true)">保存并新建</el-button>
+            <el-button type="primary" @click="saveEditClue(false)">保存</el-button>
+            <el-button @click="dialogVisible.editClueDialogVisible = false">取消</el-button>
+          </div>
+        </div>
+      </template>
+      <div class="h-[60vh] overflow-y-auto scroll-bar pt-3" v-loading="allLoading.generateFormLading">
+        <div class="ml-4 mr-4">
+          <GenerateForm ref="generateForm" :data="clueTemplate" :value="editForm" :key="generateFormKey" />
+        </div>
+      </div>
+    </el-dialog>
+
+    <el-dialog v-model="dialogVisible.clueDialogVisible" 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.clueText }}</h4>
+          <div>
+            <el-button type="primary" v-loading="allLoading.clueLoading" @click="transferClues()">转移</el-button>
+            <el-button @click="dialogVisible.clueDialogVisible = 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="transferForm.transferValue" 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>
+
+    <DeteleTables :visibles="dialogVisible.deteleClueDialogVisible" @showDeteleClue="showDeteleClue" />
+
+    <TaskModal :visible="dialogVisible.taskModalVisible" :edit-form="createTaskFromType(3)" :save-loading="'1'"
+      @close="closeTaskModal" @submit="submitForm" />
   </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, GETDETAIL, UNDATECLAIM, UNDATEFORM, DELTEROW } from './constant'
-import { getAllListByCode, getFromValue, resetFromValue, getFirstDayOfMonth, getLastDayOfMonth, formatDate } from '@/utils/tools'
+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 { post, get } from "@/utils/request";
 import { useRouter, useRoute } from "vue-router";
 import { useRouter, useRoute } from "vue-router";
+import { GenerateForm } from '@zmjs/form-design';
+import TaskModal from '@/components/TaskModal/index.vue'
+import DeteleTables from "./deteleTables.vue";
 
 
 // 定义类型
 // 定义类型
 interface fixedDataInterface {
 interface fixedDataInterface {
@@ -137,8 +187,21 @@ const filterCriteriaForm = reactive<filterCriteriaFormType>({ // 筛选条件for
   pageIndex: 1,
   pageIndex: 1,
   pageFrom: 10
   pageFrom: 10
 })
 })
+const generateFormKey = ref(1)
 const allLoading = reactive({
 const allLoading = reactive({
   clueTableLading: false,
   clueTableLading: false,
+  generateFormLading: false,
+  clueLoading: false,
+})
+const dialogVisible = reactive({
+  editClueDialogVisible: false,
+  taskModalVisible: false,
+  clueDialogVisible: false,
+  deteleClueDialogVisible: false
+})
+const allText = reactive({
+  editClueText: '新建线索',
+  clueText: '转移线索'
 })
 })
 const fixedData = reactive({
 const fixedData = reactive({
   ClueSources: [] as fixedDataInterface[],
   ClueSources: [] as fixedDataInterface[],
@@ -146,10 +209,45 @@ const fixedData = reactive({
   CustomLevel: [] as fixedDataInterface[],
   CustomLevel: [] as fixedDataInterface[],
   Personnel: [] as personnelInterface[]
   Personnel: [] as personnelInterface[]
 })
 })
-const clueTable = ref([{ clueName: '线索名称', clueSourceId: '线索来源', id: 123456789 }]) // 线索table数据
+const clueTable = ref([]) // 线索table数据
 const clueTotalTable = ref(0) // 线索 table 数据总数
 const clueTotalTable = ref(0) // 线索 table 数据总数
+const clueTableRef = ref<InstanceType<typeof ElTable>>() // 线索table dom
+const generateForm: any = ref(null) // 模板
+const clueTemplate = ref({
+  list: [],
+  config: {}
+}) // 线索模板
+const editForm = ref({}) // 编辑表单
+
+// 批量变量
+const transferForm = reactive({
+  transferValue: '',
+  batchTransferIds: '',
+})
+
 
 
 // 定义方法
 // 定义方法
+function showDeteleClue(flag: boolean) {
+  dialogVisible.deteleClueDialogVisible = flag
+}
+
+function transferClues() {
+  const { transferValue, batchTransferIds } = transferForm
+  const ids = batchTransferIds
+  const inchargerId = transferValue
+  if (!inchargerId) {
+    globalPopup?.showWarning('请选择转移的人员')
+    return
+  }
+  post(UNDATECLAIM, { ids, inchargerId }).then((res) => {
+    if (res.code == 'ok') {
+      globalPopup?.showSuccess('批量转移成功')
+      dialogVisible.clueDialogVisible = false
+      getClueTable()
+    }
+  })
+}
+
 function searchTable() {
 function searchTable() {
   getClueTable()
   getClueTable()
 }
 }
@@ -160,23 +258,102 @@ function resetTable() {
   getClueTable()
   getClueTable()
 }
 }
 
 
-function deleteRow(_row: any) {
-  console.log('点击了删除')
+function closeTaskModal() {
+  dialogVisible.taskModalVisible = false
+}
+
+function submitForm(submitData: any, isClose: boolean) {
+  console.log(submitData, isClose)
+}
+
+function editClue(item: any) {
+  dialogVisible.editClueDialogVisible = true
+  allLoading.generateFormLading = true
+  setTimeout(() => {
+    generateForm.value && generateForm.value.reset()
+    generateFormKey.value++
+    allLoading.generateFormLading = false
+  }, 1000);
+  if (!item) {
+    allText.editClueText = '新建线索'
+    editForm.value = {}
+    return
+  }
+  editForm.value = JSON.parse(JSON.stringify(item))
+  allText.editClueText = '编辑线索'
+}
+
+async function saveEditClue(flag: boolean) {
+  const data = await generateForm.value.getData()
+  let newData = { ...editForm.value, ...data }
+  delete newData.createTime
+  post(UNDATEFORM, { ...newData }).then((res) => {
+    if (res.code != 'ok') {
+      globalPopup?.showError(res.msg)
+      return
+    }
+    globalPopup?.showSuccess('保存成功')
+    if (!flag) {
+      editForm.value = {}
+      generateForm.value && generateForm.value.reset()
+      generateFormKey.value++
+    }
+    dialogVisible.editClueDialogVisible = flag
+    getClueTable()
+  }).catch((_err) => {
+    console.log(_err)
+  })
+}
+
+function deleteRow(row: any) {
+  confirmAction(`确定删除【${row.clueName}】线索吗?`).then(() => {
+    post(DELTEROW, { ids: row.id }).then(res => {
+      if (res.code != 'ok') {
+        globalPopup?.showError(res.msg)
+        return
+      }
+      globalPopup?.showSuccess('删除成功')
+      getClueTable()
+    })
+  })
 }
 }
 
 
 function batchTransfer() {
 function batchTransfer() {
-  console.log('点击了批量转移')
+  const data = clueTableRef.value && clueTableRef.value.getSelectionRows()
+  if (!data.length) {
+    globalPopup?.showWarning('请选择数据')
+    return
+  }
+  transferForm.batchTransferIds = data.map((item: any) => item.id).join(',')
+  dialogVisible.clueDialogVisible = true
 }
 }
 
 
-function batchDelete() {
-  console.log('批量删除')
+function batchDeletes() {
+  const data = clueTableRef.value && clueTableRef.value.getSelectionRows()
+  if (!data.length) {
+    globalPopup?.showWarning('请选择数据')
+    return
+  }
+  const str = data.map((item: any) => item.clueName).join(',')
+  const ids = data.map((item: any) => item.id).join(',')
+
+  confirmAction(`确定批量删除这些【${str}】线索吗?`, '', 'warning').then(() => {
+    post(DELTEROW, { ids }).then(res => {
+      if (res.code != 'ok') {
+        globalPopup?.showError(res.msg)
+        return
+      }
+      globalPopup?.showSuccess('删除成功')
+      getClueTable()
+    })
+  })
 }
 }
 
 
 function toClueTableDetail(row: any) {
 function toClueTableDetail(row: any) {
   console.log('点击跳转详情')
   console.log('点击跳转详情')
-  router.push({ 
-    path: `${MOD}/detail`, 
-    query: { id: row.id } 
+  router.push({
+    path: `${MOD}/detail`,
+    query: { id: row.id }
   })
   })
 }
 }
 
 
@@ -184,15 +361,29 @@ function getClueTable() {
   allLoading.clueTableLading = true
   allLoading.clueTableLading = true
   let valueForm = getFromValue(filterCriteriaForm)
   let valueForm = getFromValue(filterCriteriaForm)
   post(GETTABLE, { ...valueForm }).then((res: any) => {
   post(GETTABLE, { ...valueForm }).then((res: any) => {
-    const { total, data } = res
-    clueTable.value = data
-    clueTotalTable.value = total
+    let { data } = res
+    for (let i in data.data) {
+      data.data[i].createTime = formatDate(new Date(data.data[i].createTime))
+    }
+    clueTable.value = data.data
+    clueTotalTable.value = data.total
     allLoading.clueTableLading = false
     allLoading.clueTableLading = false
   }).catch((_error) => {
   }).catch((_error) => {
     allLoading.clueTableLading = false
     allLoading.clueTableLading = false
   })
   })
 }
 }
 
 
+function handleSizeChange(val: number) {
+  filterCriteriaForm.pageIndex = 1
+  filterCriteriaForm.pageFrom = val
+  getClueTable()
+}
+
+function handleCurrentChange(val: number) {
+  filterCriteriaForm.pageIndex = val
+  getClueTable()
+}
+
 async function getSystemField() {
 async function getSystemField() {
   const systemField = getAllListByCode(['线索来源', '客户行业', '客户级别'])
   const systemField = getAllListByCode(['线索来源', '客户行业', '客户级别'])
   for (let i in systemField) {
   for (let i in systemField) {
@@ -211,6 +402,9 @@ async function getSystemField() {
       id, name, phone, jobNumber
       id, name, phone, jobNumber
     }
     }
   })
   })
+
+  const res = await get(GETTEMPLATE)
+  clueTemplate.value = JSON.parse(res.data[0].config)
 }
 }
 
 
 onMounted(() => {
 onMounted(() => {

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

@@ -8,6 +8,6 @@ interface filterCriteriaFormType { // 线索筛选条件类型
   inchargerId: string | number,
   inchargerId: string | number,
   startTime: string | number,
   startTime: string | number,
   endTime: string | number,
   endTime: string | number,
-  pageIndex: string | number,
-  pageFrom: string | number
+  pageIndex: number,
+  pageFrom: number
 }
 }

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

@@ -24,4 +24,6 @@ type ListByCodeType = (
 type saveLoadingType = "1" | "2" | "3" | "4"; //1是没有保存, 2是正在保存, 3是保存成功, 4是保存失败
 type saveLoadingType = "1" | "2" | "3" | "4"; //1是没有保存, 2是正在保存, 3是保存成功, 4是保存失败
 
 
 type TASK_VALUE_TYPE = 0 | 1 | 2 | 3; //0是客户, 1是商机, 2是销售订单 ,3是线索
 type TASK_VALUE_TYPE = 0 | 1 | 2 | 3; //0是客户, 1是商机, 2是销售订单 ,3是线索
-type REPEAT_VALUE_TYPE = 0 | 1 | 2 | 3 | 4; //0是每天, 1是每周, 2是每月, 3是自定义周期, 4是自定义日期
+type REPEAT_VALUE_TYPE = 0 | 1 | 2 | 3 | 4; //0是每天, 1是每周, 2是每月, 3是自定义周期, 4是自定义日期
+
+type componentType = "success" | "info" | "warning" | "error"

+ 31 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/utils/times.ts

@@ -0,0 +1,31 @@
+/**
+ * 获取当月第一天
+ * @param date 日期 new Date()
+ * @returns
+ */
+export function getFirstDayOfMonth(date: Date) {
+  const firstDay = new Date(date.getFullYear(), date.getMonth(), 1);
+  return formatDate(firstDay);
+}
+
+/**
+ * 获取当月最后一天
+ * @param date 日期 new Date()
+ * @returns
+ */
+export function getLastDayOfMonth(date: Date) {
+  const nextMonth = new Date(date.getFullYear(), date.getMonth() + 1, 0);
+  return formatDate(nextMonth);
+}
+
+/**
+ * 将 Date 对象格式化为 "YYYY-MM-DD" 的形式
+ * @param date 日期 new Date()
+ * @returns
+ */
+export function formatDate(date: Date) {
+  const year = date.getFullYear();
+  const month = (1 + date.getMonth()).toString().padStart(2, "0");
+  const day = date.getDate().toString().padStart(2, "0");
+  return `${year}-${month}-${day}`;
+}

+ 55 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/utils/tools.ts

@@ -1,3 +1,5 @@
+import { defalutModalForm } from '@/components/TaskModal/api'
+import { ElMessageBox } from 'element-plus';
 /**
 /**
  * 判断值是否为空
  * 判断值是否为空
  * @param value 值
  * @param value 值
@@ -139,6 +141,59 @@ export function formatDate(date: Date) {
   return `${year}-${month}-${day}`;  
   return `${year}-${month}-${day}`;  
 }  
 }  
 
 
+/**
+ * 获取创建任务的 form
+ * @param taskType 任务类型
+ * @returns form
+ */
+export function createTaskFromType(taskType: TASK_VALUE_TYPE) {
+  return {
+    ...defalutModalForm,
+    taskType
+  }
+}
+
+/**
+ * 下载文件
+ * @param dataFile 接口返回的数据
+ * @param fileName 文件名称
+ */
+export function downloadFile(dataFile: any, fileName: string) {
+  const data = dataFile;
+  const blob = new Blob([data]);
+  const url = window.URL.createObjectURL(blob);
+  const a = document.createElement('a');
+  a.href = url;
+  a.download = fileName;
+  document.body.appendChild(a);
+  a.click();
+  window.URL.revokeObjectURL(url);
+  document.body.removeChild(a);
+};
+
+/**
+ * 消息弹窗框
+ * @param message 消息弹窗框提示
+ * @param title 消息弹窗框标题
+ * @param type type 类型
+ * @param options 消息弹窗框其他配置
+ * @returns promise
+ */
+export function confirmAction(message: string, title = '', type: componentType = 'warning', options = {}) {  
+  return new Promise<void>((resolve, reject) => {  
+    ElMessageBox.confirm(message, title, {  
+      ...{  
+        confirmButtonText: '确定',  
+        cancelButtonText: '取消',  
+        type: type,  
+      },  
+      ...options,  
+    })  
+      .then(() => resolve())  
+      .catch(() => reject());  
+  });  
+}
+
 /**
 /**
  * 返回上一级
  * 返回上一级
  */
  */