Browse Source

Merge branch 'master' into test-dev

duy 1 month ago
parent
commit
5bb75b8b6a

+ 73 - 0
src/api/modules/systemService/service.js

@@ -0,0 +1,73 @@
+import { HcApi } from '../../request/index'
+
+export default {
+//分页
+    async getPage(form) {
+        return HcApi({
+            url: '/api/blade-manager/serviceplan/page',
+            method: 'get',
+            params: form,
+        })
+    },
+    async getDetail(form) {
+        return HcApi({
+            url: '/api/blade-manager/serviceplan/detail',
+            method: 'get',
+            params: form,
+        })
+    },
+    async save(form) {
+        return HcApi({
+            url: '/api/blade-manager/serviceplan/save',
+            method: 'post',
+            data: form,
+        })
+    },
+    async update(form) {
+        return HcApi({
+            url: '/api/blade-manager/serviceplan/update',
+            method: 'post',
+            data: form,
+        })
+    },
+    async remove(form) {
+        return HcApi({
+            url: '/api/blade-manager/serviceplan/remove',
+            method: 'post',
+            params: form,
+        })
+    },
+    
+    async getServiceHtml(form) {
+        return HcApi({
+            url: '/api/blade-manager/serviceplan/getServiceHtml',
+            method: 'get',
+            params: form,
+        })
+    },
+    async saveServiceData(form) {
+        return HcApi({
+            url: '/api/blade-manager/serviceplan/saveServiceData',
+            method: 'post',
+            data: form,
+        })
+    },
+    async getHtmlBus(form) {
+        return HcApi({
+            url: '/api/blade-manager/serviceplan/get-html-buss-cols',
+            method: 'post',
+            data: form,
+        })
+    },
+    
+    async getServiceBussData(form) {
+        return HcApi({
+            url: '/api/blade-manager/serviceplan/getServiceBussData',
+            method: 'get',
+            params: form,
+        })
+    },
+
+
+
+}

+ 27 - 1
src/components/plugins/table-form/hc-form-upload.vue

@@ -1,6 +1,8 @@
 <template>
     <el-upload
-        v-loading="isLoading" :accept="accept" :action="action" :class="isFocus ? 'is-focus' : ''"
+        v-loading="isLoading"
+        
+        :accept="accept" :action="action" :class="isFocus ? 'is-focus' : ''"
         :disabled="isLoading" :headers="getHeader()" :keyname="isKeyName"
         :on-error="formUploadError"
         :on-progress="uploadprogress" :placeholder="placeholder" :show-file-list="false"
@@ -117,6 +119,30 @@ const handleBlur = () => {
         z-index: -1;
         right: 10px;
         width: 10px;
+    }
+
+}
+</style>
+
+<style lang="scss">
+.hc-upload-table-form{
+      border-radius: 3px;
+    transition: box-shadow 0.3s, background-color 0.3s;
+    &.is-focus, &:hover {
+        background-color: #eddac4;
+        box-shadow: 0 0 0 1.5px var(--el-color-primary) inset;
+    }
+    .el-upload-dragger{
+        height:100%;
+        width: 100%;
+        background-color: transparent;
+
+        padding: 10px;
+        text-align: left;
+
+
+
+
     }
 }
 </style>

+ 157 - 3
src/views/systemService/fromDrawer.vue

@@ -11,6 +11,8 @@
                         hc-btn
                         keys="system-service-plan-save-btn"
                         type="primary"
+                        :loading="saveLoading"
+                        @click="savePlan"
                     >
                         <HcIcon name="save" />
                         保存数据
@@ -32,8 +34,8 @@
                         class="node-card-plan-btn ml-6"
                         hc-btn
                         keys="system-service-plan-send-btn"
-                      
                         type="warning"
+                        @click="sendPlan"
                     >
                         <HcIcon name="send-plane" />
                         发送计划
@@ -73,16 +75,35 @@
                     :html="excelHtmlData"
                     :loading="loading"
                     :pkey="excelIdVal"
+                    :disabled="!isTableForm"
                     @render="tableFormRender"
                 />
             </div>
         </hc-card>
+        <!-- 选择任务人 -->
+        <HcUserModal v-model="isUserModalShow" :data="userData" :datas="dataInfo" @finish="fixedUserFinish" />
     </hc-drawer>
 </template>
 
 <script setup>
-import { ref } from 'vue'
-
+import { ref, watch } from 'vue'
+import { arrToKey, deepClone, getObjVal, isString } from 'js-fast-way'
+import HcUserModal from './hc-tasks-user/user-modal.vue'
+import { useAppStore } from '~src/store'
+import dataApi from '~api/systemService/service'
+//参数
+const props = defineProps({
+    type: {
+        type:[String, Number],
+        default:'',
+    },
+    dataId: {
+        type:[String, Number],
+        default:'',
+    },
+})
+const type = ref(props.type)
+const dataId = ref(props.dataId)
 const isShow = defineModel('modelValue', {
     default: false,
 })
@@ -92,7 +113,87 @@ const excelHtmlData = ref('')
 const loading = ref(false)
 const excelIdVal = ref('')
 const isTableForm = ref(false)
+const useAppState = useAppStore()
+const projectId = ref(useAppState.getProjectId)
+const contractId = ref(useAppState.getContractId)
 
+watch(isShow, (val) => {
+    if (val) {
+            if (dataId.value) getAddLogBusinessData()
+    }
+})
+//监听
+watch(() => [
+    props.type,
+    props.dataId,
+], ([val, id]) => {
+    type.value = val
+    dataId.value = id
+
+    if (val === 1) {
+        excelIdVal.value = '1937773223861026820'
+    } else {
+        excelIdVal.value = '1937773223861026822'
+    }
+    
+     if (val) getExcelHtml()
+    
+})
+
+//获取模板标签数据
+
+const getExcelHtml = async () => {
+   loading.value = true
+        //获取数据
+        const { error, code, data } = await dataApi.getServiceHtml({
+            contractId: contractId.value || '',
+             pkeyId:  excelIdVal.value,
+            projectId: projectId.value || '',
+        }, false)
+         loading.value = false
+        //处理数据
+        const resData = isString(data) ? data || '' : ''
+        
+        if (!error && code === 200 && resData) {
+          
+            excelHtmlData.value = resData
+        } else {
+            excelHtmlData.value = ''
+            isTableForm.value = false
+           
+        }
+   
+}
+//获取填写数据
+const getAddLogBusinessData = async () => {
+
+    const { data } = await dataApi.getServiceBussData({
+        id: dataId.value,
+       pkeyId: excelIdVal.value,
+    
+    }, false)
+    //设置默认数据
+    let formArrData = getObjVal(data)
+    const defaultData = setFormDefaultData(formArrData)
+    //   tableFormData.value = defaultData
+          if (getObjVal(defaultData)) {
+            tableFormData.value = defaultData
+        } else {
+            tableFormData.value = {}
+        }
+       tableFormRef.value?.setExcelHtml()
+}
+//设置表单默认数据
+const setFormDefaultData = (formInfo = {}) => {
+    return {
+      
+        ...formInfo,
+        projectId: projectId.value,
+        contractId: contractId.value,
+        pkeyId: excelIdVal.value,
+      
+    }
+}
 //渲染表单完成
 const tableFormRender = (form) => {
     isTableForm.value = form.isRenderForm
@@ -100,10 +201,63 @@ const tableFormRender = (form) => {
 const goBack = () => {
     isShow.value = false
 }
+const sendPlan = ()=>{
+    isUserModalShow.value = true
+
+}
+const isUserModalShow = ref(false)
+const userData = ref([])
+const dataInfo = ref({
+    projectId:projectId.value,
+    contractId:contractId.value,
+})
+const fixedUserFinish = (data) => {
+    const res = deepClone(data)
+    console.log(res, 'res')
+    userData.value = res
+    const userIds = arrToKey(res, 'userId', ',')
+    let userIdsArr = userIds.split(',')
+ 
+}
+//保存数据
+const saveLoading = ref(false)
+const savePlan = async ()=>{
+    console.log( tableFormData.value, 'tableFormData.value')
+    
+    let objres = {
+        ...tableFormData.value,
+        projectId: projectId.value,
+        contractId: contractId.value,
+        pkeyId: excelIdVal.value,
+       
+    }
+    saveLoading.value = true
+      const { error, code, msg, data } = await dataApi.saveServiceData(
+            objres
+         
+        , false)
+      saveLoading.value = false
+        if (!error && code === 200) {
+         
+            window?.$message?.success(msg)
+           
+        } else {
+            saveLoading.value = false
+          
+          
+        }
+}
 </script>
 
 <style scoped lang="scss">
 .node-card-plan-btn{
     color:white;
 }
+.hc-table-form-box{
+
+    height: 100%;
+    overflow-y: auto;
+    overflow-x: auto;
+ 
+}
 </style>

+ 213 - 0
src/views/systemService/hc-tasks-user/index.scss

@@ -0,0 +1,213 @@
+.hc-report-tasks-user-box {
+    position: relative;
+    padding: 0 12px;
+    cursor: pointer;
+    min-height: 40px;
+    border: 1px solid #e0e0e6;
+    border-radius: 4px;
+    .tag-user-list {
+        position: relative;
+        display: flex;
+        align-items: center;
+        flex-flow: row wrap;
+        min-height: inherit;
+        .tasks-placeholder {
+            color: #a9abb2;
+            font-size: 14px;
+        }
+        .arrow-icon-tag {
+            position: relative;
+            color: #a9abb2;
+            font-size: 18px;
+            margin: 0 8px;
+        }
+    }
+}
+
+//选择任务人弹窗
+.el-overlay-dialog .el-dialog.hc-report-tasks-user-modal {
+    height: 650px;
+
+    .el-dialog__body .hc-new-main-body_content {
+        padding: 0;
+        height: 100%;
+        display: flex;
+    }
+    .card-div-no, .card-empty-no {
+        position: relative;
+        border-left: 1px solid;
+        border-color: #EEEEEE;
+        background: #e8e8e8;
+    }
+    .card-div-no .hc-empty-box .hc-empty-body .hc-empty-title,
+    .card-empty-no .hc-empty-box .hc-empty-body .hc-empty-title {
+        color: #777777;
+    }
+    .card-empty-no {
+        height: 100%;
+        border-left: 0;
+    }
+    .card-empty-no .hc-empty-box .hc-empty-body .hc-empty-assets {
+        display: none;
+    }
+    .card-div-2 {
+        position: relative;
+        border-left: 1px solid;
+        border-color: #EEEEEE;
+    }
+    .card-div-3 {
+        position: relative;
+        border-left: 1px solid;
+        border-color: #EEEEEE;
+    }
+    .card-div-4 {
+        position: relative;
+        border-left: 1px solid;
+        border-color: #EEEEEE;
+    }
+    .card-div-5 {
+        position: relative;
+        border-left: 1px solid;
+        border-right: 1px solid;
+        border-color: #EEEEEE;
+    }
+    //卡片
+    .el-card.hc-card-box.hc-new-card-box {
+        box-shadow: none;
+        --el-card-border-radius: 0;
+        --el-card-bg-color: transparent;
+        .el-scrollbar__bar.is-vertical {
+            right: -8px;
+        }
+    }
+    //角色类型
+    .hc-tasks-user-role-item {
+        position: relative;
+        color: #1F222A;
+        background: white;
+        cursor: pointer;
+        padding: 6px 10px;
+        border-radius: 3px;
+        transition: all .2s;
+        display: flex;
+        align-items: center;
+        i {
+            font-size: 18px;
+            display: inline-block;
+        }
+        &:hover {
+            color: #3A85E9;
+            background: #f2f3f5;
+        }
+        &.cur {
+            color: #3A85E9;
+            background: #EDF3FF;
+        }
+    }
+    //人员列表
+    .hc-tasks-user-sign-pfx-box {
+        position: relative;
+        padding-right: 8px;
+        height: 100%;
+        .el-scrollbar__bar.is-vertical {
+            right: 2px;
+        }
+    }
+    .hc-sign-pfx-file-item {
+        position: relative;
+        background: #f7f7f7;
+        .hc-tasks-user-letter {
+            position: relative;
+            font-weight: bold;
+            padding: 6px 6px;
+            border-bottom: 1px solid #eee;
+        }
+        .hc-tasks-user-item {
+            position: relative;
+            color: #1F222A;
+            background: white;
+            cursor: pointer;
+            padding: 6px 6px;
+            border-radius: 3px;
+            transition: all .2s;
+            display: flex;
+            align-items: center;
+            i {
+                font-size: 16px;
+                display: inline-block;
+            }
+            &:hover {
+                color: #3A85E9;
+                background: #f2f3f5;
+            }
+            &.cur {
+                color: #3A85E9;
+                background: #EDF3FF;
+            }
+        }
+    }
+    //字母索引
+    .hc-tasks-user-letter-index {
+        position: absolute;
+        right: -8px;
+        top: 0;
+        bottom: 0;
+        width: 13px;
+        display: flex;
+        align-items: center;
+        flex-wrap: wrap;
+        color: #5f5e5e;
+        .item {
+            position: relative;
+            flex: 13px;
+            width: 13px;
+            height: 13px;
+            font-size: 12px;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            cursor: pointer;
+            transition: all .2s;
+            &:hover {
+                color: #0a84ff;
+            }
+        }
+    }
+    //已选择列表
+    .hc-tasks-user-cur-box {
+        position: relative;
+        .hc-tasks-user-item {
+            position: relative;
+            margin-top: 18px;
+        }
+        .hc-tasks-user-item:first-child {
+            margin-top: 0;
+        }
+        &.type-1 {
+            .hc-tasks-user-item + .hc-tasks-user-item {
+                &::before{
+                    content: "";
+                    position: absolute;
+                    background: #BBBBBB;
+                    top: -18px;
+                    height: 18px;
+                    width: 1px;
+                    left: 18px;
+                }
+            }
+        }
+        .el-tag {
+            --el-tag-bg-color: #D7E6FB;
+            --el-tag-border-color: #AECCEE;
+            --el-tag-hover-color: var(--el-color-primary);
+            .el-tag__content {
+                display: flex;
+                align-items: center;
+                i {
+                    font-size: 14px;
+                    display: inline-block;
+                }
+            }
+        }
+    }
+}

+ 266 - 0
src/views/systemService/hc-tasks-user/index.vue

@@ -0,0 +1,266 @@
+<template>
+    <div :class="ui" class="hc-report-tasks-user-box">
+        <div class="tag-user-list" @click="userShowModal">
+            <template v-for="(item, index) in userData" :key="index">
+                <el-tag>{{ item.userName }}</el-tag>
+                <hc-icon v-if="(userData.length - 1) > index" name="arrow-right" ui="arrow-icon-tag" />
+            </template>
+            <div v-if="userData.length <= 0" class="tasks-placeholder">
+                <span v-if="!isChangePopele"> 点击这里选择任务人</span>
+                <span v-else> 点击这里选择整改人</span>
+            </div>
+        </div>
+        <!-- 选择任务人 -->
+        <HcUserModal v-model="isUserModalShow" :data="userData" :datas="dataInfo" :fixed-flow-link-type-val="fixedFlowLinkTypeVal" @finish="fixedUserFinish" />
+    </div>
+</template>
+
+<script setup>
+import { ref, watch } from 'vue'
+import { arrToKey, deepClone, getObjValue } from 'js-fast-way'
+import HcUserModal from './user-modal.vue'
+import { checkCustomFlowUserIsEVisaPermissions, checkCustomFlowUserIsEVisaPermissions3, checkCustomFlowUserIsEVisaPermissionsquery } from '~api/other'
+
+//参数
+const props = defineProps({
+    ui: {
+        type: String,
+        default: '',
+    },
+    data: {
+        type: Object,
+        default: () => ({}),
+    },
+  
+    //选中的用户数组
+    users: {
+        type: Array,
+        default: () => ([]),
+    },
+    projectId: {
+        type: [String, Number],
+        default: '',
+    },
+    contractId: {
+        type: [String, Number],
+        default: '',
+    },
+    type: { //first,log,wbs
+        type: [String, Number],
+        default: '',
+    },
+    typeData: {
+        type: [String, Number, Array, Object],
+        default: '',
+    },
+    classifyType: {
+        type: [String, Number],
+        default: '',
+    },
+    tableOwner: {
+        type: [String, Number],
+        default: '',
+    },
+    nodeId: {
+        type: [String, Number],
+        default: '', //选中节点nodeid
+    },
+    infoIds:{
+        type: [String, Number],
+        default: '', //上报任务ID
+    },
+    disabled:{
+        type:Boolean,
+        default:false,
+    },
+    isChangePopele:{
+        type:Boolean,
+        default:false, //显示整改人还是任务人
+    },
+    fixedFlowLinkTypeVal: {
+        type: [String, Number],
+        default: '', //流程审批1,平行审批2
+    },
+})
+
+
+
+
+//事件
+const emit = defineEmits(['change'])
+const projectId = ref(props.projectId)
+const contractId = ref(props.contractId)
+const isTypes = ref(props.type)
+const typeDatas = ref(props.typeData)
+const classifyType = ref(props.classifyType)
+const tableOwner = ref(props.tableOwner)
+const nodeId = ref(props.nodeId)
+const infoIds = ref(props.infoIds)
+const disabled = ref(props.disabled)
+const isChangePopele = ref(props.isChangePopele)
+const fixedFlowLinkTypeVal = ref(props.fixedFlowLinkTypeVal)
+//监听
+watch(() => [
+    props.users,
+    props.projectId,
+    props.contractId,
+    props.type,
+    props.typeData,
+    props.classifyType,
+    props.tableOwner,
+    props.nodeId,
+    props.infoIds,
+    props.disabled,
+    props.isChangePopele,
+    props.fixedFlowLinkTypeVal,
+], ([users, pid, cid, type, data, cla, tab, noid, infoid, disa, isChan, typeVal]) => {
+    userData.value = users
+    projectId.value = pid
+    contractId.value = cid
+    isTypes.value = type
+    typeDatas.value = data
+
+    classifyType.value = cla
+    tableOwner.value = tab
+    nodeId.value = noid
+    infoIds.value = infoid
+    disabled.value = disa
+    isChangePopele.value = isChan
+    fixedFlowLinkTypeVal.value = typeVal
+})
+//监听基础数据
+const dataInfo = ref(props.data)
+watch(() => props.data, (data) => {
+    dataInfo.value = getObjValue(data)
+}, { deep: true, immediate: true })
+
+//展开弹窗
+const isUserModalShow = ref(false)
+const userShowModal = () => {
+    isUserModalShow.value = true
+}
+
+//选择完成
+const userData = ref(props.users)
+const fixedUserFinish = (data) => {
+    const res = deepClone(data)
+    userData.value = res
+    const userIds = arrToKey(res, 'userId', ',')
+    let userIdsArr = userIds.split(',')
+    sureSignUserClick(userIdsArr, userData.value )
+}
+
+//确认选择
+const sureSignUserLoading = ref(false)
+const sureSignUserClick = (userIds, userData) => {
+    let type = isTypes.value, flowJson = {}
+    
+
+    if (userIds.length > 0) {
+        sureSignUserLoading.value = true
+        //判断类型
+        if (type === 'first') {
+            flowJson['firstId'] = typeDatas.value
+        } else if (type === 'log') {
+            // flowJson['theLogPrimaryKeyId'] = typeDatas.value
+            flowJson['theLogPrimaryKeyId'] = nodeId.value
+        } else if (type === 'wbs') {
+            flowJson['privatePKeyId'] = typeDatas.value
+        } else if (type === 'query') {
+            flowJson['privatePKeyId'] = typeDatas.value
+        }
+  
+        //效验人员
+        if (type === 'wbs') {
+            getCheckCustomFlowUserIsEVisaPermissions(flowJson, userIds)
+        } else if (type === 'first' || type === 'log' ) {
+            getCheckCustomFlowUserIsEVisaPermissions3(flowJson, userIds)
+        } else if (type === 'query') {
+            getCheckCustomFlowUserIsEVisaPermissionsquery(flowJson, userIds)
+        } else {
+        
+            sureSignUserLoading.value = false
+            emit('change', userData)
+        }
+    } else {
+        window.$message?.warning('请先选择任务人员,或点击取消')
+    }
+}
+//检查所选的流程环节处理人是否具有审批权限(三大填报页、日志列表的批量上报、首件列表的批量上报)
+const getCheckCustomFlowUserIsEVisaPermissions = async (flowJson, newUserId) => {
+    console.log( dataInfo.value, ' dataInfo.value')
+    
+    const { error, code, data, msg, success } = await checkCustomFlowUserIsEVisaPermissions({
+        projectId: dataInfo.value.projectId,
+        contractId:  dataInfo.value.contractId,
+        customFlowUserList: newUserId,
+        ...flowJson,
+        classifyType:classifyType.value,
+        tableOwner:tableOwner.value,
+        nodeId:nodeId.value,
+
+    })
+    //处理数据
+    sureSignUserLoading.value = false
+    
+    if (!error && code === 200 && data === true) {
+       
+        emit('change', userData.value)
+        isUserModalShow.value = false
+    } else {
+        // window.$message.error(msg)
+        userData.value = []
+        emit('change', [])
+    }
+}
+//资料查询页面
+const getCheckCustomFlowUserIsEVisaPermissionsquery = async (flowJson, newUserId) => {
+    const { error, code, data, msg } = await checkCustomFlowUserIsEVisaPermissionsquery({
+        projectId: projectId.value,
+        contractId: contractId.value,
+        customFlowUserList: newUserId,
+        ...flowJson,
+        nodeId:nodeId.value,
+        classifyType:classifyType.value,
+        tableOwner:tableOwner.value,
+        infoIds:infoIds.value,
+    })
+    //处理数据
+    sureSignUserLoading.value = false
+    if (!error && code === 200 && data === true) {
+        isUserModalShow.value = false
+        emit('change', userData.value)
+    } else {
+        // window.$message.error(msg)
+        userData.value = []
+        emit('change', [])
+    }
+}
+//日志和首件页面
+const getCheckCustomFlowUserIsEVisaPermissions3 = async (flowJson, newUserId) => {
+    const { error, code, data, msg } = await checkCustomFlowUserIsEVisaPermissions3({
+        projectId: projectId.value,
+        contractId: contractId.value,
+        customFlowUserList: newUserId,
+        ...flowJson,
+        // nodeId:nodeId.value,
+        classifyType:classifyType.value,
+        tableOwner:tableOwner.value,
+        infoIds:infoIds.value,
+    })
+    //处理数据
+    sureSignUserLoading.value = false
+    if (!error && code === 200 && data === true) {
+        isUserModalShow.value = false
+        emit('change', userData.value)
+    } else {
+        // window.$message.error(msg)
+        userData.value = []
+         emit('change', [])
+    }
+}
+</script>
+
+<style lang="scss">
+@import './index.scss';
+</style>

+ 80 - 0
src/views/systemService/hc-tasks-user/sort-modal.vue

@@ -0,0 +1,80 @@
+<template>
+    <hc-dialog v-model="isShow" ui="hc-tasks-user-sort-modal" widths="600px" title="调整任务人顺序" @close="modalClose">
+        <hc-table
+            ref="tableRef" :column="tableColumn" :datas="tableData" ui="hc-tasks-user-sort-drop-table"
+            is-row-drop quick-sort is-sort @row-drop="rowDropTap" @row-sort="rowSortTap"
+        />
+        <template #footer>
+            <el-button @click="modalClose">取消</el-button>
+            <el-button type="primary" @click="confirmClick">确定</el-button>
+        </template>
+    </hc-dialog>
+</template>
+
+<script setup>
+import { nextTick, ref, watch } from 'vue'
+import { deepClone, getArrValue } from 'js-fast-way'
+
+const props = defineProps({
+    data: {
+        type: Array,
+        default: () => ([]),
+    },
+})
+
+const emit = defineEmits(['finish', 'close'])
+
+//双向绑定
+const isShow = defineModel('modelValue', {
+    default: false,
+})
+
+//监听数据
+const userData = ref([])
+watch(() => props.data, (data) => {
+    const res = getArrValue(data)
+    userData.value = deepClone(res)
+}, { deep: true, immediate: true })
+
+watch(isShow, (val) => {
+    if (val) setInitData()
+})
+
+//表格
+const tableRef = ref(null)
+const tableColumn = [{ key: 'userName', name: '名称' }]
+const tableData = ref([])
+
+//初始化
+const setInitData = async () => {
+    await nextTick()
+    tableData.value = getArrValue(userData.value)
+}
+
+// 行拖拽
+const rowDropTap = async (rows) => {
+    tableData.value = []
+    await nextTick()
+    tableData.value = rows
+}
+
+// 点击排序
+const rowSortTap = async (rows) => {
+    tableData.value = []
+    await nextTick()
+    tableData.value = rows
+}
+
+//确定选择
+const confirmClick = async () => {
+    const list = deepClone(tableData.value)
+    emit('finish', list)
+    modalClose()
+}
+
+//关闭窗口
+const modalClose = () => {
+    isShow.value = false
+    emit('close')
+}
+</script>

+ 148 - 0
src/views/systemService/hc-tasks-user/style.scss

@@ -0,0 +1,148 @@
+.hc-tasks-user {
+    position: relative;
+    .tasks-user-box {
+        position: relative;
+        border: 1px solid #e0e0e6;
+        border-radius: 4px;
+        padding: 0 12px;
+        cursor: pointer;
+        min-height: 40px;
+        background-color: white;
+        .tag-user-list {
+            position: relative;
+            display: flex;
+            align-items: center;
+            flex-flow: row wrap;
+            min-height: inherit;
+            .tasks-placeholder {
+                color: #a9abb2;
+                font-size: 14px;
+            }
+            .arrow-icon-tag {
+                position: relative;
+                color: #a9abb2;
+                font-size: 18px;
+                margin: 0 8px;
+            }
+        }
+    }
+}
+
+.hc-tasks-user-modal-content-box {
+    position: relative;
+    display: flex;
+    height: 460px;
+    .tree-box {
+        flex: 1;
+        user-select: none;
+        position: relative;
+        padding: 20px;
+        overflow: hidden;
+        border-right: 1px solid #EEEEEE;
+    }
+    .user-box {
+        flex: 2;
+        position: relative;
+        display: flex;
+        flex-direction: column;
+        .y-user-list-box, .s-user-list-box {
+            position: relative;
+            overflow: hidden;
+            display: flex;
+            flex-direction: column;
+            .title-box {
+                position: relative;
+                padding: 2px 24px;
+                display: flex;
+                align-items: center;
+                border-bottom: 1px solid #EEEEEE;
+                background-color: #F8F8F8;
+                color: #838791;
+                .title {
+                    flex: auto;
+                }
+            }
+            .user-list {
+                position: relative;
+                overflow: hidden;
+                padding: 0 24px;
+                .user-item {
+                    position: relative;
+                    padding: 4px 0;
+                }
+                .user-item + .user-item {
+                    border-top: 1px dashed #EEEEEE;
+                }
+            }
+        }
+        .y-user-list-box {
+            flex: 1;
+            .user-list {
+                flex: 1;
+            }
+        }
+        .s-user-list-box {
+            position: relative;
+            border-top: 1px solid #EEEEEE;
+            .user-list {
+                height: 6rem;
+            }
+        }
+    }
+}
+
+.sort-node-body-box.list-group {
+    position: relative;
+    min-height: 20px;
+    border: 1px solid #EEEEEE;
+    .list-group-item {
+        position: relative;
+        display: flex;
+        align-items: center;
+        padding: 6px 15px;
+        cursor: move;
+        transition: background 0.2s;
+        .index-box {
+            position: relative;
+            width: 50px;
+        }
+        .title-box {
+            position: relative;
+            padding-right: 24px;
+            flex: 1;
+        }
+        .icon-box {
+            position: relative;
+            font-size: 18px;
+            display: flex;
+            align-items: center;
+            .icon {
+                cursor: pointer;
+                display: flex;
+                align-items: center;
+            }
+        }
+        &:first-child .icon-box i:last-child,
+        &:last-child .icon-box i:first-child {
+            cursor: default;
+            color: #aaaaaa;
+        }
+        &:hover {
+            background: var(--el-color-primary-light-9);
+        }
+    }
+    .list-group-item + .list-group-item {
+        border-top: 1px solid #EEEEEE;
+    }
+    &.header {
+        border-bottom: 0;
+        .list-group-item {
+            cursor: default;
+            padding: 8px 15px;
+            background-color: #F8F8F8;
+            .index-box, .title-box, .icon-box {
+                font-size: 14px;
+            }
+        }
+    }
+}

+ 364 - 0
src/views/systemService/hc-tasks-user/user-modal.vue

@@ -0,0 +1,364 @@
+<template>
+    <hc-dialog v-model="isShow" ui="hc-report-tasks-user-modal" widths="960px" title="选择任务人" @close="modalClose">
+        <div class="cards-wrapper">
+            <div class="card-div-2 h-full w-235px">
+                <hc-card scrollbar title="角色类型" :loading="signUserLoading">
+                    <template v-for="item in signUserList" :key="item.roleId">
+                        <div class="hc-tasks-user-role-item" :class="{ cur: roleItem.roleId === item.roleId }" @click="roleItemClick(item)">
+                            {{ item.roleName }}
+                        </div>
+                    </template>
+                </hc-card>
+            </div>
+            <div v-if="roleItem.roleId" class="card-div-3 h-full w-265px">
+                <hc-card v-if="positionList.length > 0" scrollbar>
+                    <template #header>
+                        <hc-search-input v-model="positionKey" placeholder="岗位搜索" icon="" @search="positionSearch" />
+                    </template>
+                    <template v-for="item in positionList" :key="item.roleId">
+                        <div class="hc-tasks-user-role-item" :class="{ cur: positionItem.roleId === item.roleId }" @click="positionItemClick(item)">
+                            <i class="i-ph-user-list-light mr-5px" />
+                            <span>{{ item.roleName }}</span>
+                        </div>
+                    </template>
+                </hc-card>
+                <div v-else class="card-empty-no">
+                    <hc-empty :src="HcLoadSvg" title="请先选择角色" />
+                </div>
+            </div>
+            <div class="card-div-4 h-full w-235px">
+                <hc-card v-if="signPfxFileList.length > 0" :loading="signPfxFileListLoading">
+                    <template #header>
+                        <hc-search-input v-model="signUserKey" placeholder="人员搜索" icon="" @search="signUserSearch" />
+                    </template>
+                    <div class="hc-tasks-user-sign-pfx-box">
+                        <el-scrollbar ref="scrollRef">
+                            <template v-for="item in signPfxFileList" :key="item.name">
+                                <div v-if="item.data.length > 0" :id="`hc-sign-pfx-file-item-${item.name}`" class="hc-sign-pfx-file-item">
+                                    <div class="hc-tasks-user-letter">{{ item.name }}</div>
+                                    <template v-for="(items, index) in item.data" :key="index">
+                                        <div class="hc-tasks-user-item" @click="signUserItemClick(items)">
+                                            <i class="i-iconoir-user mr-5px" />
+                                            <span>{{ items.certificateUserName }}</span>
+                                        </div>
+                                    </template>
+                                </div>
+                            </template>
+                        </el-scrollbar>
+                    </div>
+                    <div class="hc-tasks-user-letter-index">
+                        <div v-for="item in alphabet" :key="item" class="item" @click="alphabetClick(item)">{{ item }}</div>
+                    </div>
+                </hc-card>
+                <div v-else class="card-empty-no">
+                    <hc-empty :src="HcLoadSvg" title="请先选择岗位" />
+                </div>
+            </div>
+            <div class="card-div-5 h-full w-205px">
+                <hc-card v-if="userData.length > 0" scrollbar>
+                    <template #header>
+                        <span>已选择{{ userData.length || 0 }}人</span>
+                    </template>
+                    <template #extra>
+                        <el-button type="warning" size="small" @click="fixedUserSortClick">调整排序</el-button>
+                    </template>
+                    <div class="hc-tasks-user-cur-box" :class="`type-${fixedFlowLinkTypeVal}`">
+                        <template v-for="(item, index) in userData" :key="index">
+                            <div class="hc-tasks-user-item">
+                                <el-tag closable @close="fixedItemUserListDel(index)">
+                                    <i class="i-ri-user-3-fill mr-5px" />
+                                    <span>{{ item.userName }}</span>
+                                </el-tag>
+                            </div>
+                        </template>
+                    </div>
+                </hc-card>
+                <div v-else class="card-empty-no">
+                    <hc-empty :src="HcLoadSvg" title="请先选择人员" />
+                </div>
+            </div>
+        </div>
+        <template #footer>
+            <el-button @click="modalClose">取消</el-button>
+            <el-button type="primary" :loading="confirmLoading" @click="confirmClick">确定</el-button>
+        </template>
+    </hc-dialog>
+    <!-- 任务人排序 -->
+    <HcSortModal v-model="isUserSort" :data="userSortData" @finish="userSortFinish" />
+</template>
+
+<script setup>
+import { nextTick, ref, watch } from 'vue'
+import { pinyin } from 'pinyin-pro'
+import { deepClone, getArrValue, getObjValue, isNullES } from 'js-fast-way'
+import HcLoadSvg from '~src/assets/view/load.svg'
+import HcSortModal from './sort-modal.vue'
+import mainApi from '~api/tasks/flow'
+
+const props = defineProps({
+    data: {
+        type: Array,
+        default: () => ([]),
+    },
+    datas: {
+        type: Object,
+        default: () => ({}),
+    },
+    fixedFlowLinkTypeVal: {
+        type: [String, Number],
+        default: '', //流程审批1,平行审批2
+    },
+})
+
+const emit = defineEmits(['finish', 'close'])
+const fixedFlowLinkTypeVal = ref(props.fixedFlowLinkTypeVal)
+//双向绑定
+const isShow = defineModel('modelValue', {
+    default: false,
+})
+
+//监听参数
+const dataInfo = ref(props.datas)
+watch(() => props.datas, (data) => {
+    dataInfo.value = getObjValue(data)
+}, { deep: true, immediate: true })
+watch(() => props.fixedFlowLinkTypeVal, (typeVal) => {
+    fixedFlowLinkTypeVal.value = typeVal
+}, { deep: true, immediate: true })
+//监听数据
+const userData = ref([])
+watch(() => props.data, (data) => {
+    const res = getArrValue(data)
+    userData.value = deepClone(res)
+}, { deep: true, immediate: true })
+
+watch(isShow, (val) => {
+    if (val) setInitData()
+})
+
+//初始化
+const setInitData = async () => {
+    await nextTick()
+    await getAllRoleList()
+    await getsignPfxFileList()
+    console.log(userData.value)
+}
+const signPfxFileListLoading = ref(false)
+const getsignPfxFileList = async () => {
+    signPfxFileListLoading.value = true
+    const { contractId } = getObjValue(dataInfo.value)
+    const { data } = await mainApi.findAllUserAndRoleList({ contractId, roleId:roleItem.value?.roleId || '' })
+    let arr = getObjValue(data)
+    setSignPfxUser(arr['userList'])
+    signPfxFileListLoading.value = false
+
+}
+
+//角色列表
+const signUserLoading = ref(false)
+const signUserList = ref([])
+const getAllRoleList = async () => {
+    signUserLoading.value = true
+    const { contractId } = getObjValue(dataInfo.value)
+    const { data } = await mainApi.queryAllRoleList({ contractId })
+    signUserList.value = getArrValue(data)
+    signUserLoading.value = false
+}
+
+//角色被点击
+const roleItem = ref({})
+const roleItemClick = async(item) => {
+
+    
+    //roleItem.value = item
+    //const arr = getArrValue(item.childRoleList)
+   // positionList.value = deepClone(arr)
+   if (roleItem.value.roleId === item.roleId) {
+ 
+    
+        // 如果点击的是已选中的角色,则清空选择
+        roleItem.value = {}
+        positionList.value = []
+        positionItem.value = {}
+        roleItem.value = {}
+        await getsignPfxFileList()
+        return
+    }
+    roleItem.value = item
+
+    const arr = getArrValue(item.childRoleList)
+    positionList.value = deepClone(arr)
+    setSignPfxUser(item.signPfxFileList)
+}
+
+//岗位搜索
+const positionKey = ref('')
+const positionList = ref([])
+const positionSearch = () => {
+    const key = positionKey.value
+    const list = getArrValue(roleItem.value?.childRoleList)
+    const arr = deepClone(list)
+    if (isNullES(key)) {
+        positionList.value = arr
+        return
+    }
+    positionList.value = arr.filter(({ roleName }) => roleName.toLowerCase().includes(key.toLowerCase()))
+}
+
+//岗位被点击
+const positionItem = ref({})
+const positionItemClick = async(item) => {
+
+    // positionItem.value = item
+    // setSignPfxUser(item.signPfxFileList)
+    if (positionItem.value.roleId === item.roleId) {
+        // 如果点击的是已选中的岗位,则清空选择并显示当前角色下所有岗位的所有人员
+        positionItem.value = {}
+        // 调用API获取当前角色下所有人员
+        await getsignPfxFileList()
+        return
+    }
+    positionItem.value = item
+    setSignPfxUser(item.signPfxFileList)
+}
+
+//设置任务人数据
+const alphabet = Array.from({ length: 26 }, (_, i) => String.fromCharCode(65 + i))
+const setSignPfxUser = (data) => {
+    const list = deepClone(data)
+    const arr = getArrValue(list)
+    arr.forEach(item => {
+        item.letter = getFirstLetter(item.certificateUserName)
+    })
+    signPfxFileData.value = deepClone(arr)
+    signPfxFileList.value = alphabet.map(letter => ({
+        name: letter,
+        data: arr.filter(item => item.letter === letter),
+    }))
+}
+
+//中文转姓氏拼音
+const getFirstLetter = (name) => {
+    return pinyin(name.charAt(0), { pattern: 'first', toneType: 'none', surname: 'head' }).charAt(0).toUpperCase()
+}
+
+//搜索任务人
+const signPfxFileData = ref([])
+const signUserKey = ref('')
+const signPfxFileList = ref([])
+const signUserSearch = () => {
+    const key = signUserKey.value
+    const arr = deepClone(signPfxFileData.value)
+    if (isNullES(key)) {
+        setSignPfxUser(arr)
+        return
+    }
+    // 判断是否为全英文
+    const isAllEnglish = /^[A-Za-z]+$/.test(key)
+    const letterName = getFirstLetter(key)
+    //搜索筛选
+    const filteredData = arr.filter(({ certificateUserName, letter }) => {
+        if (isAllEnglish) {
+            // 如果是英文,判断首字母是否一致
+            return letter.toLowerCase().includes(letterName.toLowerCase())
+        } else {
+            // 如果是中文或其他字符,进行精准搜索
+            return certificateUserName.toLowerCase().includes(key.toLowerCase())
+        }
+    })
+    signPfxFileList.value = alphabet.map(letter => ({
+        name: letter,
+        data: filteredData.filter(item => item.letter === letter),
+    }))
+}
+
+//滚动到相关位置
+const scrollRef = ref(null)
+const alphabetClick = (key) => {
+    const ids = `hc-sign-pfx-file-item-${key}`
+    const dom = document.getElementById(ids)
+    if (isNullES(dom)) return
+    scrollRef.value?.setScrollTop(dom.offsetTop - 20)
+}
+
+//任务人被点击
+const signUserItemClick = ({ certificateUserId, certificateUserName }) => {
+    userData.value.push({ userId: certificateUserId, userName: certificateUserName })
+}
+
+//删除选择的任务人
+const fixedItemUserListDel = (index) => {
+    userData.value.splice(index, 1)
+}
+
+//任务人排序
+const isUserSort = ref(false)
+const userSortData = ref([])
+const fixedUserSortClick = () => {
+    const arr = deepClone(userData.value)
+    if (arr.length <= 0) {
+        window.$message.warning('请先添加任务人')
+        return
+    }
+    userSortData.value = arr
+    isUserSort.value = true
+}
+
+//任务人排序完成
+const userSortFinish = (data) => {
+    userData.value = getArrValue(data)
+}
+
+//确定选择
+const confirmLoading = ref(false)
+const confirmClick = async () => {
+    // const list = deepClone(userData.value)
+    const list = deepClone(userData.value).filter((user, index, self) =>
+        index === self.findIndex((u) => u.userId === user.userId),
+    )
+    if (list.length <= 0) {
+        window.$message.warning('请先添加任务人')
+        return
+    }
+    emit('finish', list)
+    modalClose()
+}
+
+//关闭窗口
+const modalClose = () => {
+    isShow.value = false
+    emit('close')
+}
+</script>
+
+<style scoped>
+.cards-wrapper {
+    display: flex;
+    gap: 12px;
+    width: 100%;
+    height: 100%;
+}
+
+.card-div-2,
+.card-div-3,
+.card-div-4,
+.card-div-5 {
+    height: 100%;
+}
+
+.card-div-2 {
+    width: 235px;
+    flex-shrink: 0;
+}
+
+.card-div-3 {
+    width: 255px;
+    flex-shrink: 0;
+}
+
+.card-div-4,
+.card-div-5 {
+    flex: 1;
+    min-width: 205px;
+}
+</style>

+ 73 - 27
src/views/systemService/plan.vue

@@ -2,7 +2,7 @@
     <hc-card id="node-card-plan">
         <template #header>
             <div class="w-32">
-                <el-select v-model="searchForm.key1" clearable block placeholder="填写类型">
+                <el-select v-model="searchForm.fileInType" clearable block placeholder="填写类型" @change="getTableData">
                     <el-option v-for="item in fillType" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" />
                 </el-select>
             </div>
@@ -10,44 +10,48 @@
                 <HcDatePicker :dates="betweenTime" clearable @change="betweenTimeUpdate" />
             </div>
             <div class="ml-3 w-32">
-                <el-select v-model="searchForm.key2" clearable block placeholder="状态">
+                <el-select v-model="searchForm.status" clearable block placeholder="状态 " @change="getTableData">
                     <el-option v-for="item in tasksStatus" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" />
                 </el-select>
             </div>
             <div class="ml-3 w-32">
-                <el-select v-model="searchForm.key3" clearable block placeholder="编写人">
+                <el-select v-model="searchForm.createUser" clearable block placeholder="编写人" @change="getTableData">
                     <el-option v-for="item in preparedList" :key="item.id" :label="item.name" :value="item.id" />
                 </el-select>
             </div>
             <div class="ml-3 w-32">
-                <el-select v-model="searchForm.key4" clearable block placeholder="发送人">
+                <el-select v-model="searchForm.sendUser" placeholder="发送人" clearable multiple block @change="getTableData">
                     <el-option v-for="item in postList" :key="item.batch" :label="item.name" :value="item.id" />
                 </el-select>
             </div>
         </template>
         <template #extra>
-            <el-button type="primary" @click="createMonthPlan">
+            <el-button type="primary" @click="createMonthPlan(1)">
                 <HcIcon name="add" />
                 <span>创建月度计划</span>
             </el-button>
-            <el-button type="primary">
+            <el-button type="primary" @click="createMonthPlan(2)">
                 <HcIcon name="add" />
                 <span>创建服务计划</span>
             </el-button>
         </template>
     
 
-        <hc-table :column="tableColumn" :datas="tableData">
-            <template #key3="{ row }">
+        <hc-table :column="tableColumn" :datas="tableData" :loading="tableLoad ">
+            <template #fileInType="{ row }">
+                <span>{{ row.fileInType === 1 ? '月度服务计划' : '服务完成确认单' }}</span>
+            </template>
+            
+            <template #statusValue="{ row }">
                 <el-tag
-                    v-if="row?.key3"
-                    :type="`${row.key3 === '已计划' ? 'success' : row.key3 === '计划中' ? 'info' : 'warning'}`" class="mx-1" effect="dark"
+                    v-if="row?.statusValue"
+                    :type="`${row.statusValue === '已计划' ? 'success' : row.statusValue === '计划中' ? 'info' : 'warning'}`" class="mx-1" effect="dark"
                 >
-                    {{ row.key3 }}
+                    {{ row.statusValue }}
                 </el-tag>
             </template>
             <template #action="{ row }">
-                <el-link type="primary">编辑</el-link>
+                <el-link type="primary" @click="editRow(row)">编辑</el-link>
                 <el-link type="success">查看</el-link>
                 <el-link type="warning">删除</el-link>
             </template>
@@ -56,14 +60,23 @@
         <template #action>
             <HcPages :pages="searchForm" @change="pageChange" />
         </template>
+        <fromDrawer v-model="isShowForm" :type="typeVal" :data-id="dataId" />
     </hc-card>
-    <fromDrawer v-model="isShowForm" />
 </template>
 
 <script setup>
-import { nextTick, ref, watch } from 'vue'
+import { nextTick, onMounted, ref } from 'vue'
 import fromDrawer from './fromDrawer.vue'
+import dataApi from '~api/systemService/service'
+import { useAppStore } from '~src/store'
+import { arrToId, getArrValue } from 'js-fast-way'
 
+onMounted(()=>{
+        getTableData()
+})
+const store = useAppStore()
+const projectId = ref(store.getProjectId)
+const contractId = ref(store.getContractId)
 //搜索表单
 const searchForm = ref({
     key1: null, ke2: null, key3: null, key4: null, startTimeValue: null, endTimeValue: null,
@@ -74,9 +87,10 @@ const fillType = ref([
     { dictKey: '2', dictValue: '服务完成确认单' },
 ])
 const tasksStatus = ref([
-    { dictKey: '1', dictValue: '待审核' },
-    { dictKey: '2', dictValue: '已审核' },
-    { dictKey: '3', dictValue: '退回' },
+    { dictKey: '1', dictValue: '计划中' },
+    { dictKey: '2', dictValue: '协同中-甲方' },
+    { dictKey: '3', dictValue: '协同中-系统' },
+    { dictKey: '4', dictValue: '已计划' },
 ])
 const preparedList = ref([
     { id: 1, name: '张三' },
@@ -93,19 +107,21 @@ const postList = ref([
 const betweenTime = ref(null)
 const betweenTimeUpdate = ({ val, arr }) => {
     betweenTime.value = arr
-    searchForm.value.startTimeValue = val['start']
-    searchForm.value.endTimeValue = val['end']
+    searchForm.value.planStartTime = val['start']
+    searchForm.value.planEndTime = val['end']
+    getTableData()
+
 }
 const searchClick = ()=>{
 
 }
 const tableColumn = [
-    { key: 'key1', name: '服务完成确认单' },
-    { key: 'key2', name: '计划时间' },
-    { key: 'key3', name: '状态' },
-    { key: 'key4', name: '编写人' },
-    { key: 'key5', name: '创建时间' },
-    { key: 'key6', name: '发送人员' },
+    { key: 'fileInType', name: '填写类型' },
+    { key: 'planTime', name: '计划时间' },
+    { key: 'statusValue', name: '状态' },
+    { key: 'createUser', name: '编写人' },
+    { key: 'createTime', name: '创建时间' },
+    { key: 'sendUser', name: '发送人员' },
     { key: 'action', name: '操作', width:150 },
 ]
 const tableData = ref([
@@ -117,10 +133,40 @@ const tableData = ref([
 const pageChange = ({ current, size }) => {
     searchForm.value.current = current
     searchForm.value.size = size
-    // getTableData()
+    getTableData()
+}
+
+const tableLoad = ref(false)
+const getTableData = async () => {
+    tableLoad.value = true
+    const { error, code, data } = await dataApi.getPage({
+        projectId: projectId.value,
+        contractId: contractId.value,
+        ...searchForm.value,
+       
+    
+    })
+    //处理数据
+    tableLoad.value = false
+    if (!error && code === 200) {
+        tableData.value = getArrValue(data['records'])
+      
+    } else {
+        tableData.value = []
+      
+    }
 }
 const isShowForm = ref(false)
-const createMonthPlan = ()=>{
+const typeVal = ref('')
+const createMonthPlan = (type)=>{
+     dataId.value = ''
+    isShowForm.value = true
+    typeVal.value = type
+}
+const dataId = ref(null)
+const editRow = (row)=>{
     isShowForm.value = true
+    typeVal.value = row.fileInType
+    dataId.value = row.id
 }
 </script>    

+ 3 - 3
yarn.lock

@@ -2724,9 +2724,9 @@ postcss@^8.4.47:
     source-map-js "^1.2.1"
 
 postcss@^8.4.48:
-  version "8.5.5"
-  resolved "http://219.151.181.73:9000/postcss/-/postcss-8.5.5.tgz#04de7797f6911fb1c96550e96616d08681537ef3"
-  integrity sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==
+  version "8.5.6"
+  resolved "http://219.151.181.73:9000/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c"
+  integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==
   dependencies:
     nanoid "^3.3.11"
     picocolors "^1.1.1"