iZaiZaiA 2 жил өмнө
parent
commit
cb229fe3a4

+ 23 - 4
src/global/components/hc-drawer/index.vue

@@ -1,8 +1,8 @@
 <template>
     <Suspense v-if="isBody">
         <Teleport :to="`#${toId}`">
-            <el-drawer :custom-class="`hc-drawer-box ${ui}`" v-model="isShow" :with-header="false" :direction="direction" :size="size" destroy-on-close @closed="drawerClosed">
-                <HcCard :title="title" :extraText="extraText" :actionSize="actionSize" :scrollbar="scrollbar">
+            <el-drawer ref="drawerRef" :modal-class="uis" :custom-class="`hc-drawer-box ${ui}`" v-model="isShow" :with-header="false" :direction="direction" :size="size" destroy-on-close @closed="drawerClosed">
+                <HcCard :title="title" :extraText="extraText" :actionSize="actionSize" :scrollbar="scrollbar" v-if="isCard">
                     <template #header v-if="isSlotHeader">
                         <slot name='header'/>
                     </template>
@@ -17,6 +17,7 @@
                     </template>
                     <slot></slot>
                 </HcCard>
+                <slot v-if="!isCard"></slot>
             </el-drawer>
         </Teleport>
     </Suspense>
@@ -25,6 +26,10 @@
 <script setup>
 import {ref, nextTick, watch, useSlots} from "vue";
 const props = defineProps({
+    uis: {
+        type: String,
+        default: ''
+    },
     ui: {
         type: String,
         default: ''
@@ -48,7 +53,7 @@ const props = defineProps({
     },
     scrollbar: {
         type: Boolean,
-        default: true
+        default: false
     },
     extraText: {
         type: [String,Number],
@@ -56,12 +61,16 @@ const props = defineProps({
     },
     actionSize: {
         type: [String,Number],
-        default: 'df'
+        default: 'lg'
     },
     size: {
         type: [String,Number],
         default: '100%'
     },
+    isCard: {
+        type: Boolean,
+        default: true
+    },
 })
 
 //变量
@@ -88,6 +97,7 @@ const isSlotExtra = ref(!!slots.extra);
 const isSlotAction = ref(!!slots.action);
 const isSlotSearchBar = ref(!!slots.search);
 
+const drawerRef = ref(null)
 const emit = defineEmits(['close'])
 
 //关闭
@@ -95,6 +105,15 @@ const drawerClosed = () => {
     isShow.value = false
     emit('close', false)
 }
+
+const handleClose = () => {
+    drawerRef.value?.handleClose()
+}
+
+// 暴露出去
+defineExpose({
+    handleClose
+})
 </script>
 
 <style lang="scss">

+ 6 - 3
src/global/components/hc-new-switch/index.vue

@@ -40,7 +40,7 @@ const switchClick = (item) => {
     isolation: isolate;
     position: relative;
     overflow: hidden;
-    height: 32px;
+    height: 40px;
     font-size: 14px;
     background: #f1f5f8;
     border-radius: 40px;
@@ -50,9 +50,12 @@ const switchClick = (item) => {
     box-shadow: 4px 4px 8px 0 rgba(54,92,167,0.15) inset, -4px -4px 8px 0 #ffffff inset;
     .switch-bg {
         color: #838791;
-        padding: 2px 12px;
+        padding: 0 14px;
         border-radius: 80px;
-        min-height: 24px;
+        height: 30px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
         &.dots {
             color: #ffffff;
             background: var(--el-color-primary);

+ 38 - 0
src/styles/other/first-item.scss

@@ -53,3 +53,41 @@
         margin-left: 24px;
     }
 }
+
+.hc-first-item-node-layout {
+    .node-content {
+        flex: 1;
+        display: flex;
+        position: relative;
+        margin-bottom: 24px;
+        height: calc(100% - 105px);
+        .node-form {
+            flex: 1;
+            padding: 24px;
+            margin-right: 24px;
+            position: relative;
+            background: #f1f5f8;
+            border-radius: 10px;
+            box-shadow: -2px 0px 10px 0px rgba(32,37,50,0.03), 0px 10px 21px 20px rgba(32,37,50,0.03);
+        }
+        .node-file {
+            width: 440px;
+            position: relative;
+            padding: 24px 20px;
+            background: #f1f5f8;
+            border-radius: 10px;
+            box-shadow: -2px 0px 10px 0px rgba(32,37,50,0.03), 0px 10px 21px 20px rgba(32,37,50,0.03);
+        }
+    }
+    .node-action {
+        position: relative;
+        height: 80px;
+        background: #f1f5f8;
+        border-radius: 10px;
+        display: flex;
+        align-items: center;
+        padding: 20px 24px;
+        box-shadow: -2px 0px 10px 0 rgba(32,37,50,0.03), 0 -10px 21px 3px rgba(32,37,50,0.03);
+        overflow: hidden;
+    }
+}

+ 1 - 1
src/views/data-fill/components/HcUpload.vue

@@ -57,7 +57,7 @@ const emit = defineEmits(['change'])
 //上传前
 const beforeFileNum = ref(0)
 const beforeUpload = async (file) => {
-    if (isSize(file?.size,30)) {
+    if (isSize(file?.size,60)) {
         beforeFileNum.value ++;
         return true;
     } else {

+ 1 - 3
src/views/ledger/components/table-form.vue

@@ -234,9 +234,7 @@ const getBussDataInfo = (index = 0) => {
     const info = getObjValue(formLog[index])
     formLogIndex.value = index
     if (getObjNullValue(info)) {
-        const newInfo = HTableForm.setPickerKey(info)
-        console.log(newInfo)
-        tableFormData.value = newInfo
+        tableFormData.value = HTableForm.setPickerKey(info)
     } else {
         tableFormData.value = {}
     }

+ 106 - 0
src/views/other/components/HcUpload.vue

@@ -0,0 +1,106 @@
+<template>
+    <el-upload class="hc-upload-border first-item-upload" drag :action="action" :headers="getTokenHeader()" :data="uploadData" :accept="accept" :file-list="fileListData" multiple :disabled="uploadDisabled"
+               :on-preview="uploadPreview" :on-success="uploadSuccess" :on-exceed="uploadExceed" :on-error="uploadError" :before-upload="beforeUpload"
+               :on-progress="uploadPreview">
+        <div class="hc-upload-loading" v-loading="uploadDisabled" element-loading-text="上传中...">
+            <HcIcon name="backup" ui="text-5xl mt-4"/>
+            <div class="el-upload__text">拖动文件到这里 或 <em>点击这里选择文件</em> 并上传</div>
+        </div>
+        <template #tip>
+            <div class="el-upload__tip" style="font-size: 14px;">允许格式:jpg/png/pdf/excel/word, 文件大小 小于 60MB</div>
+        </template>
+    </el-upload>
+</template>
+
+<script setup>
+import {ref,watch,onMounted} from "vue";
+import {getTokenHeader} from '~src/api/request/header';
+import {isSize} from "vue-utils-plus"
+const props = defineProps({
+    fileList: {
+        type: Array,
+        default: () => ([])
+    },
+    datas: {
+        type: Object,
+        default: () => ({})
+    },
+})
+
+//变量
+const uploadData = ref(props.datas)
+const fileListData = ref(props.fileList);
+const action = '/api/blade-manager/first/add-first-buss-file';
+const accept = 'image/png,image/jpg,image/jpeg,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel,application/pdf,.doc,.docx,application/msword';
+const uploadDisabled = ref(false)
+
+//监听
+watch(() => [
+    props.fileList,
+    props.datas,
+], ([fileList, datas]) => {
+    uploadData.value = datas
+    fileListData.value = fileList
+})
+
+//渲染完成
+onMounted(()=> {
+    beforeFileNum.value = 0
+    finishFileNum.value = 0
+    errorFileNum.value = 0
+})
+
+//事件
+const emit = defineEmits(['finish'])
+
+//上传前
+const beforeFileNum = ref(0)
+const beforeUpload = async (file) => {
+    if (isSize(file?.size,60)) {
+        beforeFileNum.value ++;
+        return true;
+    } else {
+        window?.$message?.warning('文件大小, 不能过60M!');
+        return false;
+    }
+}
+
+//超出限制时
+const uploadExceed = () => {
+    window?.$message?.warning('请上传 jpg/png/pdf/excel/word 的文件,文件大小 不超过60M');
+}
+
+//上传中
+const uploadprogress = () => {
+    uploadDisabled.value = true
+}
+
+//上传完成
+const finishFileNum = ref(0)
+const uploadSuccess = () => {
+    finishFileNum.value ++;
+    if (beforeFileNum.value === finishFileNum.value) {
+        uploadDisabled.value = false
+        emit('finish', {type: 'success'})
+    }
+}
+
+//上传失败
+const errorFileNum = ref(0)
+const uploadError = () => {
+    errorFileNum.value ++;
+    window?.$message?.error('上传失败');
+    const num = finishFileNum.value + errorFileNum.value;
+    if (beforeFileNum.value === num) {
+        uploadDisabled.value = false
+        emit('finish', {type: 'error'})
+    }
+}
+
+//预览
+const uploadPreview = ({url}) => {
+    if (url) {
+        window.open(url, '_blank')
+    }
+}
+</script>

+ 396 - 1
src/views/other/components/first-item-form.vue

@@ -1,9 +1,369 @@
 <template>
-    <div>首件工程上报</div>
+    <n-divider dashed title-placement="left">
+        <span class="text-hover" @click="goToBack()">首件工程</span>
+        <span class="ml-2">/</span>
+        <span class="ml-2">上报首件</span>
+    </n-divider>
+    <div class="hc-layout-box">
+        <div class="hc-layout-content-box">
+            <n-card class="hc-card-overflow-p-box" :segmented="{content: true}">
+                <div class="table-form-box">
+                    <div :id="`table-form-${contractId}`"></div>
+                </div>
+            </n-card>
+        </div>
+        <div class="hc-layout-card-box">
+            <n-card class="hc-card-overflow-p-box" :segmented="{content: true}">
+                <template #header>
+                    <div class="hc-card-header flex items-center">
+                        <div>上传总结报告:</div>
+                        <div class="ml-2">
+                            <n-upload :action="action" :headers="getTokenHeader()" :data="upData" :accept="accept" :disabled="!pkeyId"
+                                      :show-file-list="false" @before-upload="beforeUpload" @finish="uploadFinish">
+                                <n-button type="primary" class="px-4" :loading="uploadLoading">选择文件</n-button>
+                            </n-upload>
+                        </div>
+                        <div class="ml-5 text-purple" v-if="pkeyId">{{fileName}}</div>
+                        <div class="ml-5 text-purple" v-else>表单异常,暂时无法使用上传</div>
+                    </div>
+                </template>
+                <n-data-table :columns="tableColumns" :data="tableData" :row-key="row => row.id" :pagination="false" :single-line="false" striped/>
+                <template #action>
+                    <div class="text-center">
+                        <n-button class="px-5" @click="goToBack">取消</n-button>
+                        <n-button type="primary" class="px-5 ml-6" :disabled="!pkeyId" @click="saveBussData">保存</n-button>
+                        <n-button type="primary" strong secondary class="px-5 ml-6" :disabled="!pkeyId" @click="bussPdfInfo">预览</n-button>
+                        <n-button type="primary" strong secondary class="px-5 ml-6" :disabled="!pkeyId" @click="reportModalClick">上报</n-button>
+                    </div>
+                </template>
+            </n-card>
+        </div>
+    </div>
+    <!--上报审批-->
+    <HcReportModal  title="上报审批" url="informationWriteQuery/taskOne" :show="showReportModal" :projectId="projectId" :contractId="contractId"
+                    :taskName="reportTaskName" :ids="reportIds" :addition="reportAddition" @hide="showReportModal= false" @finish="showReportFinish"/>
 </template>
 
 <script setup>
+import {h, onMounted, ref, watch} from 'vue'
+import {createApp} from "vue/dist/vue.esm-bundler.js";
+import {getTokenHeader} from '~src/api/request/header';
+import HcReportModal from "~com/reportModal/index.vue"
+import firstItemApi from '~api/other/first-item';
+import {isObject,isObjNull} from "~src/utils/lib/isApp";
+import {ElButton,ElTooltip,ElInput,ElDatePicker,ElUpload,ElInputNumber,ElTimePicker,ElSelect,ElOption,ElRadioGroup,ElRadio,ElCheckbox,ElCheckboxGroup} from 'element-plus'
 
+//参数
+const props = defineProps({
+    rows: {
+        type: Array,
+        default: () => ([])
+    },
+    wbsId: {
+        type: [String,Number],
+        default: ''
+    },
+    relation: {
+        type: [String,Number],
+        default: ''
+    },
+    projectId: {
+        type: [String,Number],
+        default: ''
+    },
+    contractId: {
+        type: [String,Number],
+        default: ''
+    },
+})
+
+//初始变量
+const primaryKeyId = ref(props.wbsId)
+const relation = ref(props.relation)
+const projectId = ref(props.projectId)
+const contractId = ref(props.contractId)
+//监听
+watch(() => [
+    props.rows,
+    props.wbsId,
+    props.relation,
+    props.projectId,
+    props.contractId
+], ([rows,wid,rid,pid,cid]) => {
+    tableData.value = rows
+    primaryKeyId.value = wid
+    relation.value = rid
+    projectId.value = pid
+    contractId.value = cid
+    if (cid) getExcelHtmlData()
+})
+
+//渲染完成
+onMounted(() => {
+    if (contractId.value) {
+        getExcelHtmlData()
+    }
+})
+
+//表格表单渲染
+const pkeyId = ref(null)
+const tableFormData = ref({})
+const getExcelHtmlData = () => {
+    pkeyId.value = null
+    const cid = contractId.value
+    firstItemApi.getFirstExcelHtml({
+        contractId: cid
+    }).then(({data}) => {
+        const temp = data?.data?.data
+        if (!isObjNull(data.data) && temp) {
+            pkeyId.value = data?.data?.id ?? null
+            HTableForm(temp,tableFormData.value,cid)
+            //getBussDataInfo(data?.data?.id)
+        } else {
+            window?.$message?.warning(data.msg || '暂无表单')
+        }
+    })
+}
+
+//渲染表格表单
+const HTableForm = (templateData, tableForm, cid) => {
+    const app = createApp({
+        data() {
+            return {
+                getTokenHeader: getTokenHeader(),
+                formData: tableForm,
+                formUploadLoading: false,
+            }
+        },
+        template: templateData,
+        components: {ElButton,ElTooltip,ElInput,ElDatePicker,ElUpload,ElInputNumber,ElTimePicker,ElSelect,ElOption,ElRadioGroup,ElRadio,ElCheckbox,ElCheckboxGroup},
+        watch: {
+            tableForm: {
+                handler(obj) {
+                    this.formData = obj
+                },
+                deep: true
+            },
+            formData: {
+                handler(obj) {
+                    tableFormData.value = obj
+                },
+                deep: true
+            },
+        },
+        methods: {
+            RightClick(a,b,c,d,e,f,event) {
+                event.preventDefault();
+            },
+            getInformation() {},
+            datePickerChange(val,key) {
+                this.formData[key] = val
+            },
+            //上传进度
+            uploadprogress() {
+                this.formUploadLoading = true
+            },
+            //上传完成
+            formUploadSuccess(res,key) {
+                this.formUploadLoading = false
+                if (res.code === 200) {
+                    this.formData[key] = res.data?.link || ''
+                }
+            },
+            //上传失败
+            formUploadError() {
+                this.formUploadLoading = false
+            },
+            //格式错误
+            formUploadExceed() {
+                this.formUploadLoading = false
+            },
+            //删除上传的文件
+            delTableFormFile(key) {
+                this.formData[key] = ''
+            },
+        }
+    })
+    app.mount(`#table-form-${cid}`)
+}
+
+//获取表单数据
+const getBussDataInfo = async (pkeyId) => {
+    if (pkeyId) {
+        const { data } = await firstItemApi.getFirstBussDataInfo({pkeyId: pkeyId + ''})
+        const valRes = isObject(data?.data) ? data?.data : {}
+        if (!isObjNull(valRes)) {
+            const pickerKey = valRes['pickerKey'] || ''
+            const pickerKeys = pickerKey.split(',')
+            for (let i = 0; i < pickerKeys.length; i++) {
+                const val = valRes[pickerKeys[i]] || ''
+                if (val) {
+                    const data = val.replace(/'/g,'"');
+                    valRes[pickerKeys[i]] = JSON.parse(data)
+                } else {
+                    valRes[pickerKeys[i]] = []
+                }
+            }
+            //有数据,关联数据
+            tableFormData.value = valRes
+        } else {
+            tableFormData.value = {}
+        }
+    } else {
+        tableFormData.value = {}
+    }
+}
+
+//表格表头
+const tableData = ref(props.rows)
+const tableColumns = [
+    {title: '文件名称', key: 'name'},
+    {title: "操作", key: "actions", width: 80, align: 'center',
+        render(_, index) {
+            return h('span', {
+                class: 'text-red',
+                onClick: () => tableDelButton(index)
+            }, {
+                default: () => '删除'
+            })
+        }
+    }
+]
+
+//删除文件
+const tableDelButton = (index) => {
+    window?.$dialog?.warning({
+        title: "删除提醒",
+        content: "确定删除该文件吗?",
+        positiveText: "确定删除",
+        negativeText: "取消",
+        onPositiveClick: () => {
+            tableData.value.splice(index, 1)
+        }
+    });
+}
+
+//文件上传
+const fileName = ref('')
+const upData = ref({})
+const action = '/api/blade-manager/first/add-first-buss-file';
+const accept = 'image/png,image/jpg,image/jpeg,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel,application/pdf,.doc,.docx,application/msword';
+
+//上传前
+const uploadLoading = ref(false)
+const beforeUpload = ({file}) => {
+    let maxTrillion = 60;
+    let fileSizeData = file?.file?.size
+    let maxSize = maxTrillion * 1024 * 1024
+    if (fileSizeData > maxSize) {
+        window.$message?.warning(`文件大小,不能过${maxTrillion}M!`);
+        return false;
+    } else {
+        fileName.value = file.name
+        upData.value = {pkeyId: pkeyId.value}
+        uploadLoading.value = true
+        return true;
+    }
+}
+//上传完成
+const uploadFinish = ({event}) => {
+    uploadLoading.value = false
+    let res = JSON.parse(event?.target?.response);
+    if (res.code === 200) {
+        window.$message?.success('上传成功');
+    } else {
+        window.$message?.error('上传失败');
+    }
+}
+
+//填报数据保存
+const saveBussData = async () => {
+    const res = await saveExcelBussData(pkeyId.value + '')
+    if (res) {
+        getBussPdfInfo(pkeyId.value + '')
+    }
+}
+
+//pdf预览
+const bussPdfInfo = () => {
+    getBussPdfInfo(pkeyId.value + '')
+}
+
+//保存请求
+const saveExcelBussData = async (pkeyId) => {
+    const {data} = await firstItemApi.saveBussData({
+        ...tableFormData.value,
+        projectId: projectId.value,
+        contractId: contractId.value,
+        firstNodeId: primaryKeyId.value,
+        pkeyId: pkeyId,
+        classify: '1',
+        isFirst: 1
+    })
+    if(data && data.code === 200) {
+        window?.$message?.success('保存成功')
+        return true
+    } else {
+        window?.$message?.warning(data.msg || '保存失败')
+        return false
+    }
+}
+
+//预览PDF请求
+const getBussPdfInfo = (pkeyId) => {
+    const liunkIds = tableData.value.map((obj) => {
+        return obj?.id;
+    }).join(",")
+    //发起请求
+    firstItemApi.getFirstBussPdfInfo({
+        pkeyId: pkeyId,
+        liunkIds: liunkIds
+    }).then(({data}) => {
+        if(data.code === 200 && data?.data) {
+            window.open(data?.data,'_blank')
+        } else {
+            window.$message?.warning('暂无PDF')
+        }
+    })
+}
+
+
+//事件
+const emit = defineEmits(['ToClose'])
+
+//批量上报
+const showReportModal = ref(false)
+const reportIds = ref('')
+const reportTaskName = ref('')
+const reportAddition = ref({})
+const reportModalClick = () => {
+    const rows = tableData.value
+    if (rows.length > 0) {
+        reportIds.value = rows.map((obj) => {
+            return obj?.id;
+        }).join(",")
+        reportTaskName.value = rows.length > 1?`${rows[0].name}等${rows.length}个文件`:rows[0].name
+        reportAddition.value = {
+            classify: 1,
+            isFirst: 1,
+            primaryKeyId: primaryKeyId.value,
+            contractIdRelation: relation.value ?? contractId.value,
+        }
+        showReportModal.value = true
+    } else {
+        window.$message?.warning('暂无相关数据')
+    }
+}
+
+//上报完成
+const showReportFinish = () => {
+    showReportModal.value = false
+}
+
+//关闭
+const goToBack = () => {
+    emit('ToClose')
+}
 </script>
 
 <style lang="scss" scoped>
@@ -16,6 +376,19 @@
         overflow: auto;
         position: relative;
         padding: 0 24px 15px 20px;
+        .table-form-box {
+            position: relative;
+            flex: 1;
+            overflow: auto;
+            height: 100%;
+            .hc-no-table-form {
+                position: relative;
+                height: 100%;
+                display: flex;
+                justify-content: center;
+                align-items: center;
+            }
+        }
     }
     .hc-layout-card-box {
         flex: 1;
@@ -29,3 +402,25 @@
     font-weight: initial;
 }
 </style>
+<style lang="scss">
+//设置表单样式
+.table-form-box {
+    td {
+        padding: 6px;
+        font-family: "EUDC", 宋体, v-sans, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !important;
+        .el-input {
+            background-color: #ffffff !important;
+            border-radius: 3px;
+            .el-input__wrapper {
+                background-color: inherit;
+            }
+            .el-input__wrapper.is-focus, .el-input__wrapper:hover {
+                box-shadow: 0 0 0 1.5px var(--el-input-focus-border-color) inset;
+                background-color: #eddac4;
+            }
+            //公式 #dcdcdc
+            //焦点 #eddac4
+        }
+    }
+}
+</style>

+ 150 - 7
src/views/other/first-item.vue

@@ -1,6 +1,6 @@
 <template>
-    <div class="hc-layout-box">
-        <div class="hc-layout-left-box" :style="'width:' + leftWidth + 'px;'">
+    <div class="hc-layout-box" id="first-item-node-layout-target">
+        <div class="hc-layout-left-box" :style="'width:' + leftWidth + 'px;'" v-show="!isFirstReportDrawer">
             <div class="hc-project-box">
                 <div class="hc-project-icon-box">
                     <HcIcon name="stack"/>
@@ -18,7 +18,7 @@
             <!--左右拖动-->
             <div class="horizontal-drag-line" @mousedown="onmousedown"/>
         </div>
-        <div class="hc-layout-content-box first-item">
+        <div class="hc-layout-content-box first-item" v-show="!isFirstReportDrawer">
             <HcCard :scrollbar="false" actionSize="lg">
                 <template #header>
                     <HcTooltip keys="other-first-item-report" v-if="tabTypeKey === 'mark'">
@@ -73,7 +73,6 @@
                         </el-button>
                     </div>
                 </template>
-
                 <HcTable ref="tableListRef" :column="tableListColumn" :datas="tableListData" :loading="tableLoading" isCheck @selection-change="tableSelectionChange">
                     <template #name="{row}">
                         <span class="text-link" @click="tableRowName(row)">{{row?.name}}</span>
@@ -100,16 +99,60 @@
                 </template>
             </HcCard>
         </div>
+
+        <!--上报首件-->
+        <HcDrawer :show="isFirstReportDrawer" :isCard="false" uis="hc-first-item-node-layout" to-id="first-item-node-layout-target" @close="FirstReportDrawerClose">
+            <div class="node-content">
+                <div class="node-form">
+                    <el-scrollbar v-if="contractId && isTableForm">
+                        <div class="hc-excel-table-form-view" :id="`table-form-${contractId}`"></div>
+                    </el-scrollbar>
+                    <HcStatus :desc="statusDesc" v-else/>
+                </div>
+                <div class="node-file">
+                    <div class="title">上传总结报告</div>
+                    <div class="ml-2">
+                        <HcUpload :fileList="fileListData" :datas="uploadData" @finish='uploadChange'/>
+                    </div>
+                </div>
+            </div>
+            <div class="node-action">
+                <HcTooltip keys="wbs_save">
+                    <el-button type="primary" hc-btn>
+                        <HcIcon name="save"/>
+                        <span>保存</span>
+                    </el-button>
+                </HcTooltip>
+                <HcTooltip keys="wbs_report">
+                    <el-button hc-btn>
+                        <HcIcon name="send-plane-2"/>
+                        <span>上报</span>
+                    </el-button>
+                </HcTooltip>
+                <HcTooltip keys="wbs_preview">
+                    <el-button hc-btn>
+                        <HcIcon name="eye"/>
+                        <span>预览</span>
+                    </el-button>
+                </HcTooltip>
+                <el-button hc-btn @click="FirstReportDrawerClose">
+                    <HcIcon name="close"/>
+                    <span>关闭</span>
+                </el-button>
+            </div>
+        </HcDrawer>
     </div>
 </template>
 
 <script setup>
-import {onMounted, ref, watch} from 'vue'
+import {nextTick, onMounted, ref, watch} from 'vue'
 import {useAppStore} from "~src/store";
 import {useRouter, useRoute} from 'vue-router'
 import WbsTree from "./components/WbsTree.vue"
+import HcUpload from "./components/HcUpload.vue"
 import {getStoreData, setStoreData} from '~src/utils/storage'
-import {getArrValue, deepClone} from "vue-utils-plus"
+import {getArrValue, isString, getObjValue, getObjNullValue} from "vue-utils-plus"
+import HTableForm from "~src/plugins/HTableForm"
 import {getReportNumber} from "~api/other";
 import firstApi from '~api/other/first-item';
 import tasksApi from '~api/tasks/data';
@@ -176,7 +219,6 @@ const nodeWbsElTreeClick = ({data, keys}) => {
     searchClick()
 }
 
-
 //获取流程状态
 const processStatus = ref([])
 const firstTaskStatus = async () => {
@@ -317,10 +359,93 @@ const tableRowName = (row) => {
 }
 
 //上报首件
+const isFirstReportDrawer = ref(false)
 const firstReportClick = () => {
+    isFirstReportDrawer.value = true
+    getFirstExcelHtml()
+}
+const FirstReportDrawerClose = () => {
+    isFirstReportDrawer.value = false
+}
 
+//获取表单
+const statusDesc = ref('')
+const isTableForm = ref(false)
+const getFirstExcelHtml = async () => {
+    const cid = contractId.value;
+    const { error, code, data } = await firstApi.getFirstExcelHtml({
+        contractId: contractId.value || ''
+    }, false)
+    //处理数据
+    const temp = isString(data?.data) ? data?.data || '' : ''
+    if (!error && code === 200 && temp) {
+        let pkeyId = data?.data?.id ?? null
+        setHTableForm(temp, cid, tableFormData.value)
+        getFirstBussDataInfo(pkeyId)
+    } else {
+        isTableForm.value = false
+        statusDesc.value = '暂无表单'
+        window?.$message?.warning('暂无表单')
+    }
 }
 
+//渲染表单
+const tableFormApp = ref(null)
+const setHTableForm = (resData, cid, info) => {
+    //先卸载
+    if (tableFormApp.value) {
+        tableFormApp.value?.unmount()
+    }
+    if (resData) {
+        isTableForm.value = true
+        nextTick(() => {
+            tableFormApp.value = HTableForm.createForm({
+                template: resData,
+                tableForm: info,
+                appId: `#table-form-${cid}`
+            })
+        })
+    } else {
+        isTableForm.value = false
+        statusDesc.value = '暂无表单'
+        window?.$message?.warning('暂无表单')
+    }
+}
+
+//获取回显数据
+const tableFormData = ref({})
+const getFirstBussDataInfo = async (pkeyId) => {
+    if (pkeyId) {
+        const { data } = await firstApi.getFirstBussDataInfo({
+            contractId: contractId.value || '',
+            pkeyId: pkeyId + ''
+        }, false)
+        const info = getObjValue(data)
+        if (getObjNullValue(info)) {
+            tableFormData.value = HTableForm.setPickerKey(info)
+        } else {
+            tableFormData.value = {}
+        }
+    } else {
+        tableFormData.value = {}
+    }
+}
+
+//上传变量
+const fileListData = ref([]);
+const uploadData = ref({})
+
+//上传文件
+const uploadChange = async ({type}) => {
+    if(type === 'success') {
+        //renewData()
+    } else if (type === 'del') {
+        //renewData()
+    }
+}
+
+
+
 //上报审批
 const reportModalClick = () => {
 
@@ -353,3 +478,21 @@ const onmousedown = () => {
 <style lang="scss" scoped>
 @import "../../styles/other/first-item.scss";
 </style>
+
+<style lang="scss">
+.hc-first-item-node-layout.el-overlay {
+    position: absolute;
+    background-color: transparent;
+    margin: -24px;
+    height: revert;
+    .hc-drawer-box.el-drawer {
+        --el-drawer-bg-color: transparent;
+        .el-drawer__body {
+            padding: 24px;
+            display: flex;
+            flex-direction: column;
+            overflow: hidden;
+        }
+    }
+}
+</style>