2 Achegas 797ff3a477 ... 59568f9829

Autor SHA1 Mensaxe Data
  lm 59568f9829 添加操作日志弹窗 hai 4 días
  lm f02bb53c9c 添加赢单申请审核弹窗 hai 5 días

+ 130 - 0
src/baseComponents/PreviewDialog.vue

@@ -0,0 +1,130 @@
+<template>
+<div class="page_dialog">
+    <!-- v-model="previewVisible" -->
+    <!-- append-to-body -->
+    <!-- style="max-height: 90vh;overflow: auto;max-width: 900px;" -->
+    <el-dialog
+        @close="HandleDialogClose"
+        class="preview_dialog el_button"
+        :model-value="props.previewVisible"
+        :width="dialogWidth ? dialogWidth : '80%'"
+        :title="props.title || '附件预览'"
+        :align-center="true"
+    >
+        <div class="custom">
+            <!-- 内部 class="preview_introduce" -->
+            <slot></slot>
+        </div>
+      
+        <template v-if="props.customFooter" #footer>
+            <slot name="close">
+                <el-button @click="HandleDialogClose" class="button_border_gray cancel" >取 消</el-button>
+            </slot>
+            <slot name="footer"></slot>
+            <slot name="confirm">
+                <el-button :disabled="props.disabledConfirm" :loading="props.submitLoading"  @click="props.disabledConfirm ? '' : HandleConfirmCheck()" class="button_background"  >确 定</el-button>
+            </slot>
+        </template>
+    </el-dialog>
+</div>
+</template> 
+
+<script setup lang="ts">
+const props = defineProps({
+    title:{
+        type:String
+    },
+    customFooter:{
+        type:Boolean,
+        required:false
+    },
+    previewVisible:{
+        type:Boolean,
+        required:true
+    },
+    submitLoading:{
+      type:Boolean,
+      default:false,
+    },
+    disabledConfirm:{
+        type:Boolean,
+        default:false
+    },
+    dialogWidth:{
+        type:String,
+    }
+})
+
+const emit = defineEmits(['close','confirm'])
+
+
+const HandleDialogClose = ()=>{
+    emit('close')
+    console.log('弹窗关闭===')
+    // previewVisible.value = false
+}
+
+const HandleConfirmCheck = async () => {
+    emit('confirm')
+}
+
+</script>
+
+
+<style lang="scss" scoped>
+.preview_dialog{
+    overflow: auto;
+    border-radius: 8px;
+    padding: 0;
+}
+:deep(.el-dialog__header) {
+    width: 100%;
+    height: 48px;
+    line-height: 48px;
+    text-indent: 20px;
+    background-color: #f5f7fa;
+    border-bottom: 1px solid #f5f7fa;
+    border-top-right-radius: 8px;
+    border-top-left-radius: 8px;
+    padding: 0 !important;
+    text-align: left;
+    .el-dialog__title {
+    font-weight: 600;
+    font-size: 16px;
+    color: #303133;
+    }
+    button:focus, button:focus-visible {
+    outline: none;
+    }
+}
+//饿了么内容样式覆盖
+:deep(.el-dialog__body) {
+    overflow: auto;
+    font-weight: 400;
+    font-size: 16px;
+    color: #333;
+    padding: 20px;
+    width: 100%;
+    margin: auto;
+
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-start;
+    max-height: calc(90vh - 50px - 66px);
+    
+    .custom{
+        .preview_introduce{
+            background-color: #fafafa;
+            border: solid #e9e9e9 1px !important;
+            padding:10px;
+            margin-bottom: 10px;
+            border-radius: 8px;
+            display: flex;
+            flex-direction: row;
+            justify-content: flex-start;
+            align-items: flex-start;
+        }
+    }
+}
+
+</style>

+ 408 - 13
src/views/customerFollowUp/index.vue

@@ -4,11 +4,12 @@
             <div class="page_search">
                 <div class="search_content">
                     <div class="content_left">
-                        <SearchBlock :searchList="searchList" >
+                        <SearchBlock :searchList="headerSearchList"  @searchChange="HandleBlockSearchChange">
                             <template #rowItem-time>
                                 <el-date-picker
-                                    style="height: 28px;flex-grow: 0;"
-                                    v-model="startAndEndTime"
+                                    v-if="listSearchParams.time == 'custom'"
+                                    style="height: 28px;flex-grow: 0;width: 225px;"
+                                    v-model="listSearchParams.startAndEndTime"
                                     type="daterange"
                                     range-separator="至"
                                     start-placeholder="开始日期"
@@ -19,8 +20,11 @@
                         </SearchBlock>
                     </div>
                     <div class="content_right">
-                        <el-button class="delete_button_border_no_bg">赢单申请</el-button>
-                        <el-button class="button_border">操作日志</el-button>
+                        <el-button class="delete_button_border_no_bg" @click="OpenAuditDialog">
+                            赢单申请
+                            <span class="bubble">{{ winnerOrderNum }}</span>
+                        </el-button>
+                        <el-button class="button_border" @click="OpenOperationLogDialog">操作日志</el-button>
                     </div>
                 </div>
             </div>
@@ -33,7 +37,7 @@
                     defaultValue:'',
                     disabled:false
                 }
-            ]"/>
+            ]" @searchChange="HandleBlurSearchChange"/>
             <Table :currentPage="currentPage" :pageSize="pageSize" :totalNum="totalNum" 
                 :tableColumns="tableColumns" :tableData="tableData">
                 <template #num="{currentIndex}">
@@ -52,21 +56,144 @@
             </Table>
 
         </div>
+
+        <!-- 赢单申请审核弹窗 -->
+        <PreviewDialog 
+            dialogWidth="1000px"
+            class="audit_dialog"
+            :previewVisible="auditDialogVisible"  
+            title="审核申请" 
+            :customFooter="true"
+            @close="CloseAuditDialog">
+            <TabSearch :tabList="tabList" :defaultTabKey="defaultAuditStatus" tab-type="box" />
+            <ul class="audit_list_container">
+                <li 
+                    v-for="(item, index) in auditList" 
+                    :key="item.id"
+                    class="audit_card"
+                >
+                    <!-- 1. 顶部信息栏:时间、学校、产品、状态标签 -->
+                    <div class="card_header">
+                        <div class="header_left">
+                            <span class="time_text">{{ item.followUpTime }}</span>
+                            <span class="school_name">{{ item.schoolName }}</span>
+                            <span class="product_name">{{ item.followUpProduct }}</span>
+                            <!-- 使用 el-tag 模拟“赢单”状态 -->
+                            <el-tag type="success" effect="light" size="small" class="status_tag">
+                                {{ item.followUpStatus }}
+                            </el-tag>
+                        </div>
+                        <div class="header_right">
+                            <el-button class="delete_button_border_no_bg">驳回赢单</el-button>
+                            <el-button class="button_border">通过赢单</el-button>
+                        </div>
+                    </div>
+
+                    <!-- 2. 中部人员信息:跟进人、联系人、职务 -->
+                    <div class="card_info">
+                        <span>跟进人:{{ item.followUpUser }}</span>
+                        <span>联系人:{{ item.contactPerson }}</span>
+                        <span>职务:{{ item.jobTitle }}</span>
+                    </div>
+                    <!-- 3. 底部跟进内容 -->
+                    <div class="card_content">
+                        跟进内容:{{ item.followUpContent }}
+                    </div>
+                </li>
+            </ul>
+            <template #close>
+                <el-button @click="CloseAuditDialog" class="button_background" >关闭</el-button>
+            </template>
+            <template #confirm><i></i></template>
+        </PreviewDialog>
+
+        <!-- 操作日志弹窗 -->
+         <PreviewDialog title="操作记录" 
+            class="operation_log_dialog"
+            :customFooter="true"
+            :previewVisible="operationLogDialogVisible" 
+            @close="CloseOperationLogDialog">
+
+
+            <!-- 搜索列表 -->
+            <SearchBlock :searchList="operationLogSearchList" 
+                @searchChange="HandleLogBlockSearchChange">
+                <template #rowItem-time>
+                    <el-date-picker
+                        v-if="logSearchParams.time === 'custom'"
+                        style="height: 28px;flex-grow: 0;width: 225px;"
+                        v-model="logSearchParams.startAndEndTime"
+                        type="daterange"
+                        range-separator="至"
+                        start-placeholder="开始日期"
+                        end-placeholder="结束日期"
+                        size="default"
+                    />
+                </template>
+            </SearchBlock>
+
+            <FormSearchGroup :searchList="[
+                    {
+                        key:'queryStr',
+                        type:'input',
+                        placeholder:'请输入关键字搜索',
+                        hidden:false,
+                        defaultValue:'',
+                        disabled:false
+                    }
+                ]" @searchChange="HandleLogBlurSearchChange"/>
+
+
+            <!-- 日志列表 -->
+            <ul class="log_list_container">
+                <li 
+                    v-for="(item, index) in operationLogList" 
+                    :key="item.id"
+                    class="audit_card"
+                >
+                    <!-- 1. 顶部信息栏:时间、学校、产品、状态标签 -->
+                    <div class="card_header">
+                        <div class="header_left">
+                            <span class="time_text">{{ item.operationTime }}</span>
+                            <span class="school_name">{{ item.operatorName }}</span>
+                            <span class="school_name">{{ item.schoolName }}</span>
+                            <span class="product_name">{{ item.operationType }}</span>
+                        </div>
+                    </div>
+
+                    <!-- 3. 底部跟进内容 -->
+                    <div class="card_content">
+                        操作内容:{{ item.operationContent }}
+                    </div>
+                </li>
+            </ul>
+
+
+            <template #close>
+                <el-button @click="CloseOperationLogDialog" class="button_background" >关闭</el-button>
+            </template>
+            <template #confirm><i></i></template>
+
+         </PreviewDialog>
+
     </div>
 </template>
 
 
 <script setup lang="ts">
-import { ref } from 'vue';
+import { computed, ref } from 'vue';
 import SearchBlock from '@/baseComponents/SearchBlock.vue';
-import {searchList} from './searchBlock'
+import {headerSearchList, operationLogSearchList} from './searchBlock'
 import Table from '@/baseComponents/Table.vue';
 import FormSearchGroup from '@/baseComponents/FormSearchGroup.vue';
 
+import PreviewDialog from '@/baseComponents/PreviewDialog.vue';
 import {tableColumns} from './table'
+import TabSearch from '@/baseComponents/TabSearch.vue';
 
-
-// 表格数据有关内容
+/**
+ * 1. 默认列表 表格数据有关内容
+ */
 const tableData = ref([
     {
         id: 1,
@@ -123,11 +250,164 @@ const pageSize = ref(20)
 const totalNum = ref(0)
 
 
+/**
+ * 2.默认列表 筛选项有关数据
+ */
+const listSearchParams = ref({})
+const HandleBlockSearchChange = (params,key)=>{
+    listSearchParams.value = {
+        ...listSearchParams.value,
+        ...params
+    }
+}
+const HandleBlurSearchChange = (params,key)=>{
+    // console.log('blurl--',params)
+    listSearchParams.value = {
+        ...listSearchParams.value,
+        ...params
+    }
+}
 
-// 筛选项有关数据
-const startAndEndTime = ref()
-const searchParams = ref({})
+/**
+ * 3. 赢单申请有关数据
+ */
+const winnerOrderNum = ref(5)
+// 审核弹窗数据
+const auditDialogVisible = ref(false)
+const defaultAuditStatus = ref(0)
 
+const waitAuditNum = ref(0)
+const auditPassNum = ref(0)
+const auditRejectNum = ref(0)
+const tabList = computed(()=>[
+    {
+        label:'待审核',
+        key:0,
+        suffix:waitAuditNum.value,
+    },
+    {
+        label:'已通过',
+        key:1,
+        suffix:auditPassNum.value,
+    },
+    {
+        label:'已驳回',
+        key:2,
+        suffix:auditRejectNum.value,
+    },
+
+])
+const auditList = ref([
+    {
+        id:'1',
+        followUpTime: '26-02-19 19:00',
+        schoolName: '北京师达学校',
+        followUpProduct: '大数据精准教学',
+        followUpStatus: '赢单',
+        followUpUser: '张三',
+        contactPerson: '严谨',
+        jobTitle: '校长',
+        followUpContent: '跟进文案跟进文案跟进文案跟进文案跟进文案北京市东城区葱店西街56号跟进文案跟进文案跟进文案跟进文案跟进文'
+    },
+    {
+        id:'2',
+        followUpTime: '26-02-19 19:00',
+        schoolName: '北京师达学校',
+        followUpProduct: '大数据精准教学',
+        followUpStatus: '赢单',
+        followUpUser: '张三',
+        contactPerson: '严谨',
+        jobTitle: '校长',
+        followUpContent: '跟进文案跟进文案跟进文案跟进文案跟进文案北京市东城区葱店西街56号跟进文案跟进文案跟进文案跟进文案跟进文案北京\n文案跟进文案跟进文案北京市东城区葱店西街56号跟进文案跟进文案跟进文案跟进文案跟进文案北京市东城区葱店西街56号'
+    },
+     {
+        id:'3',
+        followUpTime: '26-02-19 19:00',
+        schoolName: '北京师达学校',
+        followUpProduct: '大数据精准教学',
+        followUpStatus: '赢单',
+        followUpUser: '张三',
+        contactPerson: '严谨',
+        jobTitle: '校长',
+        followUpContent: '跟进文案跟进文案跟进文案跟进文案跟进文案北京市东城区葱店西街56号跟进文案跟进文案跟进文案跟进文案跟进文案北京\n文案跟进文案跟进文案北京市东城区葱店西街56号跟进文案跟进文案跟进文案跟进文案跟进文案北京市东城区葱店西街56号'
+    }
+])
+
+const OpenAuditDialog = ()=>{
+    auditDialogVisible.value = true
+}
+const CloseAuditDialog = ()=>{
+    auditDialogVisible.value = false
+}
+
+/**
+ * 4. 操作日志有关数据
+ */
+const operationLogDialogVisible = ref(false)
+const operationLogList = ref([
+    {
+        id:1,
+        operationTime: '26-02-19 19:00',
+        operatorName: '张安顺',
+        schoolName: '北京师达学校',
+        operationType: '编辑学校信息',
+        operationContent: '学校名称【北京109中学】学段【初中】调整为学校名称【北京一零九中学】学段【九年一贯】。'
+    },
+    {
+        id:2,
+        operationTime: '26-02-19 19:00',
+        operatorName: '张安顺',
+        schoolName: '北京师达学校',
+        operationType: '删除联系人',
+        operationContent: '删除 姓名【张三】职务【校长】手机电话【17820681226】办公电话【17820681226】备注【文案文案】。'
+    },
+    {
+        id:3,
+        operationTime: '26-02-19 19:00',
+        operatorName: '张安顺',
+        schoolName: '北京师达学校',
+        operationType: '新增联系人',
+        operationContent: '新增 姓名【张三】职务【校长】手机电话【17820681226】办公电话【17820681226】备注【文案文案】。'
+    },
+    {
+        id:4,
+        operationTime: '26-02-19 19:00',
+        operatorName: '张安顺',
+        schoolName: '北京师达学校',
+        operationType: '编辑联系人',
+        operationContent: '联系人姓名【张三】职务【校长】手机电话【17820681226】备注【文案文案】调整为 联系人姓名【李四】职务【校长】手机电话【17820681226】备注【文案文案】。'
+    },
+    {
+        id:5,
+        operationTime: '26-02-19 19:00',
+        operatorName: '张安顺',
+        schoolName: '北京师达学校',
+        operationType: '转至公海',
+        operationContent: '将客户转至公海。'
+    }
+]);
+
+//操作日志搜索项
+const logSearchParams = ref({})
+const HandleLogBlockSearchChange = (params,key)=>{
+    logSearchParams.value = {
+        ...logSearchParams.value,
+        ...params
+    }
+}
+const HandleLogBlurSearchChange = (params,key)=>{
+    listSearchParams.value = {
+        ...listSearchParams.value,
+        ...params
+    }
+}
+
+const OpenOperationLogDialog = ()=>{
+    operationLogDialogVisible.value = true
+}
+const CloseOperationLogDialog = ()=>{
+    operationLogDialogVisible.value = false
+}
 
 </script>
 
@@ -140,7 +420,122 @@ const searchParams = ref({})
 .content_right{
     align-self: flex-start;
     flex-wrap: wrap;
+    gap:20px !important;
+
+    .delete_button_border_no_bg{
+        position: relative;
+        .bubble{
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            position: absolute;
+            top:-10px;
+            right: -10px;
+            width: 20px;
+            height: 20px;
+            font-size: 12px;
+            background: #F56C6C;
+            border-radius: 50%;
+            color: white;
+        }
+    }
+}
+
 
+.operation_log_dialog,
+.audit_dialog{
+    :deep(.custom){
+        display: flex;
+        flex-direction: column;
+        gap:16px;
+    }
+
+    :deep(.el-dialog__body){
+        max-height: 500px;
+        min-height: 500px;
+    }
 }
 
+.log_list_container,
+.audit_list_container {
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+
+    .audit_card {
+        border-radius: 8px 8px 8px 8px;
+        border: 1px solid #EBEEF5;
+        display: flex;
+        flex-direction: column;
+        gap:8px;
+
+        /* 顶部布局 */
+        .card_header{
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            gap:10px;
+
+            .header_left {
+                display: flex;
+                align-items: center;
+                gap:8px;
+                font-weight: 600;
+                color: #333333;
+
+                .status_tag {
+                    padding:4px 8px;
+                    font-weight: 400;
+                    font-size: 12px;
+                    background: rgba(46,100,250,0.1);
+                    border-radius: 4px 4px 4px 4px;
+                    color: #2E64FA;
+                }
+            }
+            .header_right{
+                display: flex;
+                gap:16px;
+                .el-button{
+                    margin:0 !important;
+                }
+            }
+        }
+
+        /* 中部布局 */
+        .card_info {
+            display: flex;
+            align-items: center;
+            gap:11px;
+            font-weight: 400;
+            font-size: 12px;
+            color: #333333;
+        }
+
+        /* 底部内容布局 */
+        .card_content {
+            font-weight: 400;
+            font-size: 12px;
+            color: #999999;
+        }
+    }
+}
+
+.audit_list_container{
+    .audit_card{
+        padding: 12px 16px;
+        .header_left {
+            font-size: 14px;
+        }
+    }
+}
+.log_list_container{
+    .audit_card{
+        padding:8px 12px;
+        .header_left{
+            font-size: 12px;
+        }
+    }
+}
+
+
 </style>

+ 54 - 1
src/views/customerFollowUp/searchBlock.ts

@@ -1,6 +1,7 @@
 import { ref } from "vue";
 
-export const searchList = ref<SearchBlockItem[]>([
+// 默认列表头部筛选配置
+export const headerSearchList = ref([
     {
         label: '时间',
         key: 'time',
@@ -121,4 +122,56 @@ export const searchList = ref<SearchBlockItem[]>([
         ],
         defaultValue: 'all'
     }
+]);
+
+
+
+
+// 操作日志筛选配置
+export const operationLogSearchList = ref([
+    {
+        label: '时间',
+        key: 'time',
+        type: 'single',
+        options:[
+            { label: '全部', value: 'all' },
+            { label: '今天', value: 'today' },
+            { label: '昨天', value: 'yesterday' },
+            { label: '近3天', value: 'last3Days' },
+            { label: '近1周', value: 'last1Week' },
+            { label: '近1月', value: 'last1Month' },
+            { label: '自定义', value: 'custom' }
+        ],
+        defaultValue: 'all'
+    },
+    {
+        label: '小组',
+        key: 'group',
+        type: 'single',
+        options: [
+            { label: '全部', value: 'all' },
+            { label: '分组1', value: 'group1' },
+            { label: '分组2', value: 'group2' },
+            { label: '分组3', value: 'group3' },
+            { label: '分组4', value: 'group4' },
+            { label: '分组5', value: 'group5' },
+            { label: '分组6', value: 'group6' },
+        ],
+        defaultValue: 'all'
+    },
+    {
+        label: '人员',
+        key: 'personnel',
+        type: 'single',
+        options: [
+            { label: '全部', value: 'all' },
+            { label: '马丁', value: 'martin' },
+            { label: '王诗雅', value: 'wangshiya' },
+            { label: '王梓木', value: 'wangzimu' },
+            { label: '殷思源', value: 'yinsiyuan' },
+            { label: '付文杰', value: 'fuwenjie' },
+            { label: '李润曦', value: 'lirunxi' },
+        ],
+        defaultValue: 'all'
+    },
 ]);

+ 1 - 1
src/views/userPrivilegeManagement/permissionManage/table.ts

@@ -28,7 +28,7 @@ export const userTableColumns = ref([
 export const tableColumns = ref([
     {
         name: 'region', // 对应“项目组”列,通常作为行头
-        label: '项目组',
+        label: '项目组(分组名称)',
         width: '80',
         fixed: 'left', // 建议固定首列,防止横向滚动时看不清区域
         align: 'center',