ZaiZai 2 年之前
父节点
当前提交
73a401cb6a

+ 24 - 3
src/router/modules/base.js

@@ -85,23 +85,44 @@ export default [
         meta: {title: '征拆协议'},
         component: Layout,
         children: [
+            //征地补偿协议
             {
                 path: '/lar-agree/land',
                 name: 'lar-agree-land',
                 meta: {title: '征地补偿协议'},
-                component: () => import('~src/views/agree/land.vue')
+                component: () => import('~src/views/agree/land/land.vue')
             },
+            {
+                path: '/lar-agree/land/form',
+                name: 'lar-agree-land-form',
+                meta: {title: '征地补偿协议表单'},
+                component: () => import('~src/views/agree/land/form.vue')
+            },
+            //坟地补偿协议
             {
                 path: '/lar-agree/tomb',
                 name: 'lar-agree-tomb',
                 meta: {title: '坟地补偿协议'},
-                component: () => import('~src/views/agree/tomb.vue')
+                component: () => import('~src/views/agree/tomb/tomb.vue')
+            },
+            {
+                path: '/lar-agree/tomb/form',
+                name: 'lar-agree-tomb-form',
+                meta: {title: '坟地补偿协议表单'},
+                component: () => import('~src/views/agree/tomb/form.vue')
             },
+            //专项设施协议
             {
                 path: '/lar-agree/special',
                 name: 'lar-agree-special',
                 meta: {title: '专项设施协议'},
-                component: () => import('~src/views/agree/special.vue')
+                component: () => import('~src/views/agree/special/special.vue')
+            },
+            {
+                path: '/lar-agree/special/form',
+                name: 'lar-agree-special-form',
+                meta: {title: '专项设施协议表单'},
+                component: () => import('~src/views/agree/special/form.vue')
             },
         ],
     },

+ 3 - 0
src/router/modules/token.js

@@ -3,4 +3,7 @@ export default [
     'home',
     'home-index',
     'home-config',
+    'lar-agree-land-form',
+    'lar-agree-tomb-form',
+    'lar-agree-special-form'
 ]

+ 122 - 0
src/views/agree/components/collapse-form.scoped.scss

@@ -0,0 +1,122 @@
+table {
+    width: 100%;
+}
+.data-fill-list-box {
+    position: relative;
+    .hc-collapse-item-header {
+        flex: 1;
+        position: relative;
+        margin-left: 46px;
+        display: flex;
+        align-items: center;
+        .item-title {
+            flex: 1;
+            position: relative;
+            user-select: none;
+            color: #50545E;
+            font-size: 16px;
+            font-weight: 400;
+            cursor: pointer;
+        }
+        .hc-extra-text-box {
+            position: relative;
+            padding-right: 24px;
+        }
+    }
+    .data-fill-list-item-content {
+        position: relative;
+        display: flex;
+        height: calc(100vh - 386px);
+        .data-fill-table-form-box {
+            flex: 1;
+            position: relative;
+            height: 100%;
+            overflow: hidden;
+            border: 8px solid #c4c4c4;
+            &.is-window {
+                border: 0;
+                .hc-window-tip {
+                    position: relative;
+                    height: 100%;
+                    display: flex;
+                    justify-content: center;
+                    align-items: center;
+                    .table-form-no {
+                        position: relative;
+                        img {
+                            width: 380px;
+                        }
+                        .desc {
+                            text-align: center;
+                            font-size: 20px;
+                            color: #aaa;
+                        }
+                    }
+                }
+            }
+        }
+        .hc-window-switch-box {
+            display: flex;
+            align-items: center;
+            position: absolute;
+            top: 14px;
+            right: 260px;
+            .icon-btn-view {
+                padding: 0 18px;
+                height: 34px;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                color: #ffffff;
+                cursor: pointer;
+                user-select: none;
+                border-radius: 80px;
+                box-shadow: 4px 4px 8px 0 rgba(54, 92, 167, 0.15), -3px -2px 8px 0 #ffffff;
+                background: linear-gradient(to right, var(--el-color-primary-light-5), var(--el-color-primary), var(--el-color-primary-dark-2));
+                background-size: 200%;
+                transition: background-position .5s;
+                .icon {
+                    font-size: 16px;
+                }
+                &:hover {
+                    background-position: 100% 0;
+                }
+            }
+        }
+        .data-fill-table-tip-box {
+            width: 240px;
+            position: relative;
+            border-left: 1px solid #E9E9E9;
+            padding: 20px 15px 80px;
+            .tip-title {
+                font-size: 16px;
+                margin-bottom: 10px;
+                display: flex;
+                align-items: center;
+            }
+            .tip-item {
+                margin-bottom: 20px;
+            }
+            .table-tip-foot {
+                position: absolute;
+                bottom: 15px;
+                right: 0;
+                left: 0;
+                display: flex;
+                align-items: center;
+                padding: 0 15px;
+                .tip-left-btn {
+                    flex: 1;
+                    .dow-text {
+                        cursor: pointer;
+                        display: flex;
+                        align-items: center;
+                    }
+                }
+            }
+        }
+    }
+}
+.radio-group-box {
+    text-align: center;
+}

+ 105 - 0
src/views/agree/components/collapse-form.scss

@@ -0,0 +1,105 @@
+//插入特殊字符弹窗的输入框
+.data-fill-list-box .data-fill-table-form-box td,
+.data-fill-list-box .data-fill-table-form-box td .el-input .el-input__wrapper .el-input__inner,
+.el-form-item.special-form-item .el-form-item__content .el-input .el-input__wrapper .el-input__inner {
+    font-family: "hc-eudc", hc-sans, 宋体, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+}
+
+.data-fill-list-box {
+    .el-collapse {
+        --el-collapse-header-height: 60px;
+        border: 0;
+        .el-collapse-item {
+            margin: 0 0 16px;
+            background-color: #f1f5f8;
+            border: 1px solid #E9E9E9;
+            border-radius: 4px;
+        }
+        .el-collapse-item__header {
+            background-color: transparent;
+            font-weight: 400;
+            border-bottom: 0;
+            cursor: default;
+            font-size: 14px;
+            .el-collapse-item__arrow {
+                position: absolute;
+                color: #50545E;
+                cursor: pointer;
+                left: 20px;
+                margin: 0;
+            }
+        }
+        .el-collapse-item.is-active .el-collapse-item__header.is-active {
+            background-color: #E7EEF4;
+        }
+        .el-collapse-item__wrap {
+            background-color: transparent;
+            border-bottom: 0;
+            .el-collapse-item__content {
+                position: relative;
+                padding-bottom: 0;
+                font-size: 14px;
+                color: #50545E;
+                line-height: initial;
+            }
+        }
+    }
+}
+.data-fill-list-box .data-fill-table-form-box .hc-table-form-data-item {
+    padding: 0;
+    background-color: initial;
+}
+
+//复制本表弹窗
+.copy-node-many-box {
+    position: relative;
+    height: 53vh;
+    display: flex;
+    // margin-top: 24px;
+    margin-bottom: -30px;
+    border-top: 1px solid #efeff5;
+    .copy-node-many-tree {
+        position: relative;
+        flex: 1;
+        height: 100%;
+        padding: 20px 20px 20px 0;
+        border-right: 1px solid #efeff5;
+    }
+    .copy-node-many-table {
+        position: relative;
+        flex: 1;
+        height: 100%;
+        padding: 20px 0 20px 20px;
+    }
+}
+
+.copy-node-many-table {
+    position: relative;
+    flex: 1;
+    height: 100%;
+    padding: 20px 0 20px 20px;
+}
+.dialog-table-box {
+    position: relative;
+    flex: 1;
+    height: 100%;
+    padding: 18px;
+    .dialog-search {
+        position: relative;
+        display: flex;
+    }
+    .dialog-table {
+        position: relative;
+        height: calc(100% - 68px);
+        padding: 18px 0;
+    }
+    .dialog-pages {
+        position: relative;
+    }
+}
+.text-blue {
+    color: blue;
+}
+.text-green {
+    color: green
+}

+ 0 - 15
src/views/agree/land.vue

@@ -1,15 +0,0 @@
-<template>
-    <div>222</div>
-</template>
-
-<script setup>
-
-</script>
-
-<style lang="scss" scoped>
-
-</style>
-
-<style lang="scss">
-
-</style>

+ 285 - 0
src/views/agree/land/collapse-form/form-item.vue

@@ -0,0 +1,285 @@
+<template>
+    <HcTableForm ref="tableFormRef"
+                 :cols="colsKeys"
+                 :form="tableFormInfo"
+                 :height="heights"
+                 :html="excelHtml"
+                 :loading="loading"
+                 :pid="activeKey"
+                 :pkey="keyId"
+                 :scroll="scroll"
+                 :width="widths"
+                 @excelBodyTap="excelTableFormClick"
+                 @render="tableFormRender"
+                 @rightTap="tableFormRightTap"
+    />
+</template>
+
+<script setup>
+import {ref, watch, onMounted} from "vue"
+import {useAppStore} from "~src/store";
+import wbsApi from "~api/data-fill/wbs";
+import {deepClone, getArrValue, getObjVal, isString} from "js-fast-way"
+
+//初始
+const props = defineProps({
+    tid: { // 树节点
+        type: [String, Number],
+        default: ''
+    },
+    kid: { // pkeyId
+        type: [String, Number],
+        default: ''
+    },
+    classify: { // 类型
+        type: [String, Number],
+        default: ''
+    },
+    scroll: {
+        type: Boolean,
+        default: true
+    },
+    height: {
+        type: String,
+        default: '100%'
+    },
+    width: {
+        type: String,
+        default: 'auto'
+    },
+    datas: {
+        type: Object,
+        default: () => ({})
+    },
+    nodeName: { // 表单名称
+        type: String,
+        default: ''
+    },
+    pid: { // 折叠ID
+        type: String,
+        default: ''
+    },
+})
+
+//初始变量
+const useAppState = useAppStore()
+const projectId = ref(useAppState.getProjectId)
+const contractId = ref(useAppState.getContractId)
+const keyId = ref(props.kid ? props.kid + '' : '')
+const treeId = ref(props.tid)
+const classify = ref(props.classify)
+const loading = ref(false)
+const changeData = ref(props.datas)
+const nodeNames = ref(props.nodeName)
+
+const heights = ref(props.height)
+const widths = ref(props.width)
+const activeKey = ref(props.pid)
+
+const tableFormRef = ref(null)
+
+//监听
+watch(() => [
+    useAppState.getProjectId,
+    useAppState.getContractId,
+    props.tid,
+    props.kid,
+    props.classify,
+    props.nodeName,
+    props.height,
+    props.width,
+    props.pid,
+], (
+    [project_id, contract_id, tree_id, key_id, cid, nodeName, height, width, pid]
+) => {
+    projectId.value = project_id
+    contractId.value = contract_id
+    treeId.value = tree_id
+    keyId.value = key_id ? key_id + '' : ''
+    classify.value = cid
+    nodeNames.value = nodeName
+    heights.value = height
+    widths.value = width
+    activeKey.value = pid
+})
+
+//深度监听变动的对象数据
+watch(() => [
+    props.datas
+], ([data]) => {
+    changeData.value = data
+    setFormChangeData(data)
+}, {deep: true})
+
+
+//渲染完成
+onMounted(async () => {
+    loading.value = true
+    //获取已填写的数据
+    await getTableFormInfo(keyId.value)
+    //按键key列表
+    await getHtmlBussColsApi(keyId.value)
+    //渲染表单
+    await getExcelHtml(keyId.value)
+    loading.value = false
+})
+
+//事件
+const emit = defineEmits(['rightTap', 'render', 'excelBodyTap'])
+
+//表单被点击
+const excelTableFormClick = (item) => {
+    emit('excelBodyTap', item)
+}
+
+//获取表单初始数据
+const getFormDataInit = () => {
+    return {
+        projectId: projectId.value,
+        contractId: contractId.value,
+        classify: classify.value,
+        pkeyId: keyId.value,
+        nodeId: treeId.value,
+        isRenderForm: false,
+    }
+}
+
+//获取已填写的数据
+const tableFormInfo = ref({})
+const getTableFormInfo = async (pkeyId) => {
+    if (pkeyId) {
+        const {error, code, data} = await wbsApi.getBussDataInfo({
+            pkeyId: pkeyId
+        }, false)
+        const resData = getObjVal(data)
+        if (!error && code === 200 && resData) {
+            tableFormInfo.value = {
+                ...resData,
+                ...getFormDataInit(),
+                ...changeData.value
+            }
+        } else {
+            tableFormInfo.value = {
+                ...getFormDataInit(),
+                ...changeData.value
+            }
+        }
+    } else {
+        tableFormInfo.value = {}
+        window?.$message?.warning('pkeyId为空')
+    }
+}
+
+//获取按键切换输入框的key列表
+const colsKeys = ref([])
+const getHtmlBussColsApi = async (pkeyId) => {
+    if (pkeyId) {
+        const {error, code, data} = await wbsApi.getHtmlBussCols({
+            pkeyId: pkeyId
+        }, false)
+        if (!error && code === 200) {
+            let keys = getArrValue(data);
+            for (let i = 0; i < keys.length; i++) {
+                if (keys[i].length <= 0) {
+                    keys.splice(i, 1)
+                }
+            }
+            colsKeys.value = keys;
+        } else {
+            colsKeys.value = [];
+        }
+    } else {
+        colsKeys.value = []
+    }
+}
+
+//获取模板标签数据
+const excelHtml = ref('')
+const getExcelHtml = async (pkeyId) => {
+    if (pkeyId) {
+        const {error, code, data} = await wbsApi.getExcelHtml({
+            pkeyId: pkeyId
+        }, false)
+        const resData = isString(data) ? data || '' : ''
+        if (!error && code === 200 && resData) {
+            excelHtml.value = resData
+        } else {
+            excelHtml.value = ''
+            tableFormInfo.value.isRenderForm = false
+            window?.$message?.warning('暂无表单')
+        }
+    } else {
+        excelHtml.value = ''
+        tableFormInfo.value.isRenderForm = false
+        window?.$message?.warning('pkeyId为空')
+    }
+}
+
+//渲染完成
+const tableFormRender = (form) => {
+    tableFormInfo.value = form
+    emit('render', form)
+}
+
+//右键点击
+const tableFormRightTap = (item) => {
+    emit('rightTap', item)
+}
+
+//设置数据
+const setFormChangeData = (data) => {
+    const form = deepClone(tableFormInfo.value)
+    tableFormInfo.value = {...form, ...data}
+    //console.log('设置数据', {...form, ...data})
+}
+
+const getFormData = () => {
+    return tableFormInfo.value
+    //return tableFormRef.value?.getFormData()
+}
+
+const setFormData = (data) => {
+    setFormChangeData(data)
+    tableFormRef.value?.setFormData(tableFormInfo.value)
+}
+
+const getRegExpJson = () => {
+    return tableFormRef.value?.getRegExpJson()
+}
+
+const isFormRegExp = async () => {
+    return await tableFormRef.value?.isFormRegExp()
+}
+
+//获取表单名称
+const getNodeName = () => {
+    return nodeNames.value
+}
+
+//按下ctrl键
+const setIsCtrlKey = (data) => {
+    tableFormRef.value?.setIsCtrlKey(data)
+}
+
+//按下复制快捷键
+const setCopyKeyList = (event) => {
+    tableFormRef.value?.setCopyKeyList(event)
+}
+
+//按下粘贴快捷键
+const setPasteKeyList = async (event) => {
+    await tableFormRef.value?.setPasteKeyList(event)
+}
+
+// 暴露出去
+defineExpose({
+    getFormData,
+    setFormData,
+    getRegExpJson,
+    isFormRegExp,
+    getNodeName,
+    setIsCtrlKey,
+    setCopyKeyList,
+    setPasteKeyList
+})
+</script>

+ 1093 - 0
src/views/agree/land/collapse-form/index.vue

@@ -0,0 +1,1093 @@
+<template>
+    <div class="data-fill-list-box">
+        <el-collapse v-model="ActiveKey" accordion @change="CollapseChange">
+            <template v-for="(item,index) in listDatas" :key="item?.pkeyId">
+                <el-collapse-item :id="`item-${index}-${item?.pkeyId}`" :name="`item-${index}-${item?.pkeyId}`">
+                    <template #title>
+                        <div class="hc-collapse-item-header">
+                            <div class="text-lg truncate item-title"> {{ item.nodeName }}</div>
+                            <div class="hc-extra-text-box">
+                                <el-button :loading="copyClickLoading" plain type="primary" @click.stop="copyClick(item,index)">复制本表</el-button>
+                            </div>
+                        </div>
+                    </template>
+                    <div class="data-fill-list-item-content">
+                        <TableFormItem v-if="item.isTableRender"
+                                       :ref="(el) => setItemRefs(el, item)"
+                                       :classify="classifys"
+                                       :datas="changeFormDatas(item?.pkeyId, 'collapse')"
+                                       :kid="item?.pkeyId"
+                                       :nodeName="item.nodeName"
+                                       :pid="`table-form-${item?.pkeyId}`"
+                                       :tid="treeId"
+                                       @excelBodyTap="excelTableFormClick($event)"
+                                       @render="tableFormRender($event, item, index)"
+                                       @rightTap="tableFormRightTap($event, index)"
+                        />
+                    </div>
+                </el-collapse-item>
+            </template>
+        </el-collapse>
+    </div>
+</template>
+
+<script setup>
+import {ref, watch, nextTick, onActivated, onDeactivated, onMounted, onUnmounted} from "vue";
+import HTableForm from "~src/plugins/HTableForm"
+import {useAppStore} from "~src/store";
+import wbsApi from "~api/data-fill/wbs"
+import TableFormItem from "./form-item.vue"
+import {
+    getArrValue, getObjValue, getObjVal,
+    isNullES, deepClone, arrIndex, setPosRange
+} from "js-fast-way"
+
+//初始变量
+const useAppState = useAppStore()
+
+//参数
+const props = defineProps({
+    datas: {
+        type: Array,
+        default: () => ([])
+    },
+    classify: {
+        type: [String, Number],
+        default: ''
+    },
+    status: {
+        type: [String, Number],
+        default: ''
+    },
+    primaryKeyId: {
+        type: [String, Number],
+        default: ''
+    },
+    contractId: {
+        type: [String, Number],
+        default: ''
+    },
+    drawType: {
+        type: Boolean,
+        default: false
+    },
+    wbsTempId: {
+        type: [String, Number],
+        default: ''
+    },
+    tenantId: {
+        type: [String, Number],
+        default: ''
+    },
+    wbsType: {
+        type: [String, Number],
+        default: ''
+    },
+})
+
+//全局变量
+const projectId = ref(useAppState.projectId);
+const contract_id = ref(props.contractId)
+const treeId = ref(props.primaryKeyId)
+const classifys = ref(props.classify)
+const wbsTemp_id = ref(props.wbsTempId);
+const tenant_id = ref(props.tenantId);
+const wbs_type = ref(props.wbsType);
+const isStatus = ref(parseInt(props.status))
+const listDatas = ref([])
+const draw_type = ref(props.drawType)
+
+
+//表单变量
+const formDataList = ref([])
+const formKeyIds = ref('')
+const formparentId = ref('')
+
+//处理ref
+const itemRefs = ref([])
+const setItemRefs = (el, {pkeyId}) => {
+    if (el) {
+        let index = arrIndex(itemRefs.value, 'pkeyId', pkeyId)
+        if (index !== -1) {
+            itemRefs.value[index].ref = el
+        } else {
+            itemRefs.value.push({
+                pkeyId: pkeyId,
+                ref: el
+            });
+        }
+    }
+}
+
+//处理表单的ref
+const setSpliceItemRefs = async ({pkeyId}) => {
+    const refs = itemRefs.value
+    let index = arrIndex(refs, 'pkeyId', pkeyId)
+    if (index !== -1) {
+        refs.splice(index, 1)
+        itemRefs.value = refs
+    }
+}
+
+const closeIconArr = [
+    {key: 'reduction', icon: 'picture-in-picture-2', name: '还原到面板内,并自动展开面板'}
+]
+
+//事件
+const emit = defineEmits(['renew', 'offsetTop'])
+
+//组件参数变量
+const apis = ref({
+    dataInfo: wbsApi.getBussDataInfo,
+    bussCols: wbsApi.getHtmlBussCols,
+    excelHtml: wbsApi.getExcelHtml
+})
+
+//深度监听数据
+watch(() => [
+    props.datas,
+], ([datas]) => {
+    setFormDataNum(datas)
+}, {deep: true})
+
+//监听变量值
+watch(() => [
+    useAppState.projectId,
+    props.contractId,
+    props.wbsTempId,
+    props.tenantId,
+    props.wbsType,
+    props.status,
+    props.classify,
+    props.primaryKeyId,
+], ([pid, cid, temp_id, tid, type, status, class_id, tree_id]) => {
+    projectId.value = pid
+    contract_id.value = cid
+    wbsTemp_id.value = temp_id
+    tenant_id.value = tid
+    wbs_type.value = type
+    isStatus.value = parseInt(status)
+    classifys.value = class_id
+    treeId.value = tree_id
+})
+
+//渲染完成
+onMounted(() => {
+    setFormDataNum(props.datas)
+    setTableFormMenu(useAppState.projectInfo)
+    const {offsetHeight} = document.body
+    DragModalHeight.value = offsetHeight - 200
+    setMountOnEventKey()
+})
+
+//处理变动的数据
+const changeFormData = ref({
+    'window': [],
+    'collapse': [],
+})
+const changeFormDatas = (pkeyId, type) => {
+    const changeData = changeFormData.value[type]
+    const index = arrIndex(changeData, 'pkeyId', pkeyId)
+    if (index !== -1) {
+        return changeData[index]
+    } else {
+        return {}
+    }
+}
+
+//设置变动的数据
+const setChangeFormDatas = async (pkeyId, type) => {
+    const refs = await getFormRef(pkeyId)
+    const formData = refs?.getFormData()
+    const changeData = changeFormData.value[type]
+    const index = arrIndex(changeData, 'pkeyId', pkeyId)
+    if (index !== -1) {
+        changeData[index] = formData
+    } else {
+        changeData.push(formData)
+    }
+    changeFormData.value[type] = changeData
+}
+
+//展开事件
+const ActiveKey = ref('')
+const CollapseChange = (name) => {
+    ActiveKey.value = name
+    let index = getCollapseItemIndex(name)
+    if (index > -1) {
+        getOffsetTop(name);
+        const item = listDatas.value[index]
+        formKeyIds.value = setToString(item.pkeyId)
+        formparentId.value = setToString(item.parentId)
+        nextTick(() => {
+            if (!item.isTableRender) {
+                item.isTableRender = true
+            }
+        })
+    } else {
+        getOffsetTop()
+        formKeyIds.value = ''
+        formparentId.value = ''
+    }
+}
+
+//初始设置
+const setFormDataNum = (datas) => {
+    itemRefs.value = []
+    ActiveKey.value = ''
+    let newArr = [];
+    for (let i = 0; i < datas.length; i++) {
+        newArr.push({isCollapseLoad: false})
+    }
+    formDataList.value = newArr
+    listDatas.value = deepClone(datas)
+}
+
+//渲染完成
+const tableFormRender = (form, item, index) => {
+    console.log(form)
+    formDataList.value[index] = form
+    formDataList.value[index].isCollapseLoad = form.isRenderForm
+    item.isTableForm = form.isRenderForm
+}
+
+
+//菜单数据
+const contextMenuRef = ref(null)
+const tableFormMenu = ref([])
+const tableFormItemNode = ref({}) //临时信息
+
+//设置菜单权限数据
+const setTableFormMenu = (info) => {
+    let newArr = [], infos = getObjValue(info)
+    const isOpen = infos['isOpenRandomNumber'] ?? 0
+    if (isOpen === 1 && isStatus.value !== 3) {
+        newArr.push({label: '插入设计值/频率', key: "design"})
+    }
+    newArr.push({label: '插入特殊字符', key: "special"})
+    newArr.push({label: '关联试验数据', key: "test"})
+    newArr.push({label: '关联试验文件', key: "file"})
+    newArr.push({label: '公式参数', key: "formula"})
+    tableFormMenu.value = newArr
+}
+
+//鼠标右键事件
+const tableFormRightTap = ({event, KeyName, startPos, endPos, pkeyId}, index) => {
+    //存储临时信息
+    tableFormItemNode.value = {KeyName, index, startPos, endPos, pkeyId}
+    contextMenuRef.value?.showMenu(event, false) //展开菜单
+}
+
+//鼠标右键菜单被点击
+const handleMenuSelect = ({key}) => {
+    if (key === 'design') {
+        setInitDesignForm()
+        designModalLoading.value = false
+        designModal.value = true
+    } else if (key === 'special') {
+        specialModalShow()
+    } else if (key === 'test') {
+        testModalLoading.value = false
+        testModal.value = true
+    } else if (key === 'file') {
+        fileModalLoading.value = false
+        fileModal.value = true;
+    } else if (key === 'formula') {
+        formulaModalLoading.value = false
+        formulaModal.value = true
+    }
+}
+
+//插入设计值
+const designModal = ref(false)
+const formDesignRef = ref(null)
+const formDesignModel = ref()
+
+//初始设计值/频率表单
+const setInitDesignForm = () => {
+    formDesignModel.value = {
+        type: 1, design: '', size: '',
+        dev: '', key: '', capacity: '',
+        pass: '', pkId: ''
+    }
+}
+
+//设计值频率计算
+const designModalLoading = ref(false)
+const designModalSave = async () => {
+    const {pkeyId, KeyName} = tableFormItemNode.value
+    if (pkeyId) {
+        designModalLoading.value = true
+        //const {design, size} = formDesignModel.value
+        const {error, code, data} = await wbsApi.queryFormulaRange({
+            ...formDesignModel.value,
+            // dev: (!design && !size) ? '±5': '',
+            key: KeyName,
+            pkId: pkeyId,
+        })
+        //处理数据
+        const res = getObjVal(data)
+        if (!error && code === 200 && res) {
+            try {
+                const refs = await getFormRef(pkeyId)
+                const itemFormData = refs?.getFormData()
+                Object.keys(data).forEach(key => {
+                    itemFormData[key] = data[key]
+                })
+                refs?.setFormData(itemFormData)
+            } catch {
+            }
+            designModalLoading.value = false
+            designModal.value = false
+        } else {
+            designModalLoading.value = false
+        }
+    } else {
+        window?.$message?.warning('pkeyId为空')
+    }
+}
+//关闭设计值/频率弹窗
+const closeDesignModal = () => {
+    designModal.value = false
+    setInitDesignForm()
+}
+
+
+//插入特殊字符
+const specialModal = ref(false)
+const specialModalLoading = ref(false)
+const specialModalShow = () => {
+    specialFormValue.value = ''
+    specialModalLoading.value = false
+    specialModal.value = true
+}
+
+//监听特殊符号输入框的内容
+const specialFormValue = ref('')
+const specialDiaolgChange = (val) => {
+    specialFormValue.value = val
+}
+
+//确认插入
+const specialRef = ref(null)
+const specialNodeClick = async () => {
+    specialModalLoading.value = true
+    const itemNode = tableFormItemNode.value
+    const {KeyName, pkeyId} = itemNode
+    try {
+        const refs = await getFormRef(pkeyId)
+        const itemFormData = refs?.getFormData()
+        const {code, val, posVal} = await specialRef.value?.getSpecialNode(itemNode, itemFormData[KeyName])
+        if (code === 200 && val) {
+            itemFormData[KeyName] = val
+            refs?.setFormData(itemFormData)
+            specialModalLoading.value = false
+            specialModal.value = false
+            await nextTick(() => {
+                setPosRange(KeyName, posVal)
+            })
+        } else {
+            specialModalLoading.value = false
+        }
+    } catch (e) {
+        specialModalLoading.value = false
+    }
+}
+
+//关闭插入特殊字符
+const specialModalClose = () => {
+    specialModalLoading.value = false
+    specialModal.value = false
+}
+
+//关联试验数据
+const testModal = ref(false)
+const testModalLoading = ref(false)
+
+//关联试验数据被点击
+const itinsertTableId = ref('')
+const itinsertTreeId = ref('')
+const testTableRowName = ({row, treeId}) => {
+    itinsertTableId.value = row.id
+    itinsertTreeId.value = treeId
+    insertDataLoading.value = false
+    insertDataShow.value = true
+}
+
+//关闭弹窗
+const testModalClose = () => {
+    testModal.value = false
+    testModalLoading.value = false
+}
+
+//选择要插入的实验数据
+const insertDataShow = ref(false);
+const insertDataLoading = ref(false);
+
+//确定关联试验数据数据
+const insertDataRef = ref(null)
+const submitinsertData = async () => {
+    insertDataLoading.value = true
+    const itemNode = tableFormItemNode.value
+    const {KeyName, pkeyId} = itemNode
+    try {
+        const refs = await getFormRef(pkeyId)
+        const itemFormData = refs?.getFormData()
+        const {code, val, posVal} = await insertDataRef.value?.submitinsertData(itemNode, itemFormData[KeyName])
+        if (code === 200 && val) {
+            itemFormData[KeyName] = val
+            refs?.setFormData(itemFormData)
+            insertDataLoading.value = false
+            insertDataShow.value = false
+            testModal.value = false
+            await nextTick(() => {
+                setPosRange(KeyName, posVal)
+            })
+        }
+    } catch {
+        insertDataLoading.value = false
+    }
+}
+
+//取消关联数据
+const cancelinsertData = async () => {
+    insertDataShow.value = false
+    insertDataLoading.value = false
+}
+
+
+//关联试验文件
+const fileModal = ref(false)
+const testFileRefs = ref(null)
+//确认关联文件
+const fileModalLoading = ref(false)
+const savefileModal = async () => {
+    fileModalLoading.value = true
+    await testFileRefs.value?.savefileSubmit()
+    fileModalLoading.value = false
+    fileModal.value = false
+}
+
+//关闭弹窗
+const fileModalClose = () => {
+    fileModal.value = false
+    fileModalLoading.value = false
+}
+
+//公式参数配置
+const formulaModal = ref(false)
+const formulaRefs = ref(null)
+
+//保存
+const formulaModalLoading = ref(false)
+const formulaSaveClick = async () => {
+    formulaModalLoading.value = true
+    await formulaRefs.value?.panelSave()
+    formulaModalLoading.value = false
+    formulaModal.value = false
+}
+
+
+//关闭
+const formulaModalClose = () => {
+    formulaModal.value = false
+    formulaModalLoading.value = false
+}
+
+
+//窗口化
+const DragModalTableForm = ref([])
+const DragModalHeight = ref(600)
+const windowClick = async (item, indexs) => {
+    const list = deepClone(DragModalTableForm.value)
+    let index = arrIndex(list, 'pkeyId', item.pkeyId)
+    if (!item.isWindow) {
+        const formSize = getTableFormSize(item?.pkeyId)
+        const newTableForm = {
+            ...setInitDragModalTableForm(item, indexs),
+            ...formSize
+        }
+        await setChangeFormDatas(item?.pkeyId, 'window')
+        item.isWindow = true
+        //处理表单的ref
+        await setSpliceItemRefs(item)
+        //弹窗表单的排序
+        if (index === -1) {
+            list.push(newTableForm)
+        } else if (index !== list.length - 1) {
+            //检查是否在最上层,不在则置顶,可以解决多次点击时,频繁更改全局状态的问题
+            list.splice(index, 1)
+            list.push(newTableForm)
+        }
+        DragModalTableForm.value = list
+        ActiveKey.value = ''
+    } else {
+        await setChangeFormDatas(item?.pkeyId, 'collapse')
+        //处理表单的ref
+        await setSpliceItemRefs(item)
+        if (index !== -1) {
+            list.splice(index, 1)
+            DragModalTableForm.value = list
+        }
+        item.isWindow = false
+    }
+}
+
+//初始拖动表单的内容
+const setInitDragModalTableForm = (item, index) => {
+    return {
+        projectId: projectId.value,
+        contractId: contract_id.value,
+        wbsTempId: wbsTemp_id.value,
+        tenantId: tenant_id.value,
+        wbsType: wbs_type.value,
+        classify: classifys.value,
+        treeId: treeId.value,
+        pkeyId: item.pkeyId,
+        height: '100%',
+        width: '100%',
+        title: item.nodeName,
+        isShow: true,
+        index: index,
+        item: item
+    }
+}
+
+//关闭窗口
+const TableFormClose = async ({pkeyId, index}, indexs) => {
+    const list = deepClone(DragModalTableForm.value)
+    //取表单的数据
+    await setChangeFormDatas(pkeyId, 'collapse')
+    //关闭窗口
+    list.splice(indexs, 1)
+    DragModalTableForm.value = list
+    listDatas.value[index].isWindow = false
+}
+
+const dragNodeMoreMenu = [
+    {key: 'save', icon: 'save-2', name: '保存'},
+    {key: 'preview', icon: 'eye', name: '预览'},
+]
+
+//还原窗口
+const closeIconTap = async (event, item, indexs) => {
+    const {index, pkeyId} = item
+    let KeyId = `item-${index}-${pkeyId}`
+    await TableFormClose(item, indexs)
+    ActiveKey.value = KeyId
+}
+
+//菜单被点击
+const dragNodeMoreMenuTap = ({key}, items) => {
+    const {item} = items
+    if (key === 'save') {
+        if (item?.isTableForm) {
+            tableFormSaveClick(item, items)
+        } else {
+            window.$message.warning('此表单暂无数据和文件')
+        }
+    } else if (key === 'preview') {
+        if (item['isBussShow'] === 2 || item['isTabPdf'] === 1 || item['pdfUrl'] === '') {
+            window.$message.warning('此表单暂无可预览文件')
+        } else {
+            previewClick(item, items)
+        }
+    }
+}
+
+//删除本表
+const tableFormDelLoading = ref(false)
+const delClick = async ({pkeyId}) => {
+    if (pkeyId) {
+        if (isStatus.value !== 3) {
+            tableFormDelLoading.value = true
+            const {error, code} = await wbsApi.removeBussTabInfo({
+                pkeyid: pkeyId,
+                classify: classifys.value,
+            })
+            tableFormDelLoading.value = false
+            if (!error && code === 200) {
+                window?.$message?.success('操作成功')
+                //判断是否存在窗口,如果存在,就删除窗口
+                delWindowRefs(pkeyId)
+                renewData()
+            }
+        } else {
+            window?.$message?.warning('已上报的资料,不允许删除')
+        }
+    } else {
+        window?.$message?.warning('pkeyId为空')
+    }
+}
+//复制本表相关
+const showcopyModal=ref(false)
+const copyRefs=ref(null)
+const copyModalClose=()=>{
+    // copyModal.value=false
+}
+const CopyModalType=ref('1')
+
+const copySaveClick=async()=>{
+    //本节点复制
+   if(CopyModalType.value==='2'){
+        const {pkeyId, isTableRender, isTableForm} =  copyItems.value
+        if (pkeyId) {
+            if (isStatus.value !== 3) {
+                if (!isTableRender) {
+                    await copeBussTab(pkeyId)
+                } else if (!isTableForm) {
+                    window?.$message?.warning('暂无表单数据')
+                } else if (isTableRender) {
+                    copyClickLoading.value = true
+                    const isSave = await saveExcelBussData(items, null, false)
+                    if (isSave) {
+                        await copeBussTab(pkeyId)
+                    } else {
+                        copyClickLoading.value = false
+                        window?.$message?.warning('复制本表操作失败')
+                    }
+                } else {
+                    window?.$message?.warning(`数据异常了, isRenderTableForm: ${isTableRender}, isTableForm: ${isTableForm}, pkeyIds:${pkeyId}`)
+                }
+            } else {
+                window?.$message?.warning('已上报的资料,不允许复制')
+            }
+        } else {
+            window?.$message?.warning('pkeyId为空')
+    }
+   }else{
+    window?.$message?.warning('暂无相关接口')
+   }
+}
+//复制本表
+const copyClickModalLoading = ref(false)
+const copyClickLoading = ref(false)
+const copyClick = async (items) => {
+    const {pkeyId, isTableRender, isTableForm} = items
+    if (pkeyId) {
+        if (isStatus.value !== 3) {
+            if (!isTableRender) {
+                await copeBussTab(pkeyId)
+            } else if (!isTableForm) {
+                window?.$message?.warning('暂无表单数据')
+            } else if (isTableRender) {
+                copyClickLoading.value = true
+                const isSave = await saveExcelBussData(items, null, false)
+                if (isSave) {
+                    await copeBussTab(pkeyId)
+                } else {
+                    copyClickLoading.value = false
+                    window?.$message?.warning('复制本表操作失败')
+                }
+            } else {
+                window?.$message?.warning(`数据异常了, isRenderTableForm: ${isTableRender}, isTableForm: ${isTableForm}, pkeyIds:${pkeyId}`)
+            }
+        } else {
+            window?.$message?.warning('已上报的资料,不允许复制')
+        }
+    } else {
+        window?.$message?.warning('pkeyId为空')
+    }
+ }
+const copyItems=ref([])
+//跨节点复制弹窗
+// const copyClick =  (items) => {
+//     showcopyModal.value=true
+//     copyItems.value=items
+
+// }
+
+//复制表的请求
+const copeBussTab = async (pkeyId) => {
+    copyClickLoading.value = true
+    const {error, code} = await wbsApi.copeBussTab({
+        pkeyId: pkeyId
+    })
+    copyClickLoading.value = false
+    if (!error && code === 200) {
+        window?.$message?.success('操作成功')
+        renewData()
+    }
+}
+
+//隐藏本表
+const tableFormHideLoading = ref(false)
+const hideClick = async ({pkeyId, isBussShow}) => {
+    if (pkeyId) {
+        if (isStatus.value !== 3) {
+            tableFormHideLoading.value = true
+            const bussShow = isBussShow === 2 ? 1 : 2 //状态(1显示 2隐藏)
+            const {error, code} = await wbsApi.showBussTab({
+                pkeyId: pkeyId,
+                status: bussShow
+            })
+            tableFormHideLoading.value = false
+            if (!error && code === 200) {
+                window?.$message?.success('操作成功')
+                if (bussShow === 2) {
+                    //判断是否存在窗口,如果存在,就删除窗口
+                    delWindowRefs(pkeyId)
+                }
+                renewData()
+            }
+        } else {
+            window?.$message?.warning('已上报的资料,不允许隐藏')
+        }
+    } else {
+        window?.$message?.warning('pkeyId为空')
+    }
+}
+
+//预览本表
+const tableFormPreviewLoading = ref(false)
+const previewClick = async (item, dragItem = null) => {
+    tableFormPreviewLoading.value = true
+    await getBussPdfInfo(item, dragItem)
+    tableFormPreviewLoading.value = false
+}
+
+//上传变量
+const uploadModal = ref(false)
+const fileListData = ref([]);
+const uploadData = ref({})
+//上传附件
+const uploadClick = (items, index) => {
+    const {pkeyId, isTableForm, isTableRender} = items
+    const keyName = `item-${index}-${pkeyId}`
+    if (pkeyId) {
+        if (isStatus.value !== 3 && isTableForm) {
+            uploadModal.value = true
+            uploadData.value = {
+                projectId: projectId.value,
+                contractId: contract_id.value,
+                classify: classifys.value,
+                pkeyId: pkeyId,
+                nodeId: treeId.value
+            }
+            //获取文件列表
+            getBussFileList(pkeyId)
+        } else if (!isTableRender) {
+            CollapseChange(keyName)
+            window?.$message?.warning('请再次点击上传')
+        } else if (!isTableForm) {
+            window?.$message?.warning('暂无表单数据')
+        } else {
+            window?.$message?.warning('已上报的资料,不允许上传')
+        }
+    } else {
+        window?.$message?.warning('pkeyId为空')
+    }
+}
+
+//获取文件列表
+const getBussFileList = async (pkeyId) => {
+    const {error, code, data} = await wbsApi.getBussFileList({
+        pkeyid: pkeyId
+    })
+    if (!error && code === 200) {
+        fileListData.value = getArrValue(data)
+    } else {
+        fileListData.value = []
+    }
+}
+
+//上传文件
+const uploadChange = async ({type}) => {
+    if (type === 'success') {
+        uploadModal.value = false
+        renewData()
+    } else if (type === 'del') {
+        renewData()
+    }
+}
+
+//关闭上传附件窗口
+const uploadModalClose = () => {
+    uploadModal.value = false
+}
+
+//单个保存
+const tableFormSaveLoading = ref(false)
+const tableFormSaveClick = async (item, dragItem = null) => {
+    if (isStatus.value !== 3) {
+        tableFormSaveLoading.value = true
+        const isSave = await saveExcelBussData(item, dragItem)
+        if (isSave) {
+            await getBussPdfInfo(item, dragItem)
+            tableFormSaveLoading.value = false
+            renewData()
+        } else {
+            tableFormSaveLoading.value = false
+        }
+    } else {
+        window?.$message?.warning('已上报的资料,不允许保存。')
+    }
+}
+
+//保存表单数据
+const saveExcelBussData = async ({pkeyId}, dragItem = null, showTip = true) => {
+    setDragModalLoading(dragItem, '保存中...', true)
+    const refs = await getFormRef(pkeyId)
+    const isRegExp = await refs?.isFormRegExp()
+    if (isRegExp) {
+        const formData = refs?.getFormData()
+        const {error, code} = await wbsApi.saveExcelBussData(formData)
+        setDragModalLoading(dragItem)
+        if (!error && code === 200) {
+            if (showTip) {
+                window?.$message?.success('保存成功')
+            }
+            return true
+        } else {
+            return false
+        }
+    } else {
+        setDragModalLoading(dragItem)
+        return false
+    }
+}
+
+//预览PDF
+const getBussPdfInfo = async ({pkeyId}, dragItem = null, showTip = true) => {
+    setDragModalLoading(dragItem, '获取pdf中...', true)
+    const {error, code, data} = await wbsApi.getBussPdfInfo({
+        pkeyId: pkeyId
+    }, false)
+    setDragModalLoading(dragItem)
+    if (!error && code === 200) {
+        if (data) {
+            window.open(data, '_blank')
+        } else if (showTip) {
+            window?.$message?.warning('PDF错误')
+        }
+    } else {
+        if (showTip) {
+            window?.$message?.warning(data.msg || '获取PDF失败')
+        }
+    }
+}
+
+
+//通知数据更新
+const renewData = () => {
+    emit('renew')
+    ActiveKey.value = ''
+}
+
+//设置表单的加载状态
+const setDragModalLoading = (dragItem, text = '保存中...', show = false) => {
+    if (dragItem && show) {
+        dragItem.loading = true
+        dragItem.loadingText = text
+    }
+    if (dragItem && !show) {
+        dragItem.loading = false
+    }
+}
+
+//获取表单的ref
+const getFormRef = async (pkeyId) => {
+    const itemRef = itemRefs.value
+    const index = arrIndex(itemRef, 'pkeyId', pkeyId)
+    return itemRef[index].ref
+}
+
+//删除打开的窗口
+const delWindowRefs = (pkeyId) => {
+    //判断是否存在窗口,如果存在,就删除窗口
+    const list = DragModalTableForm.value
+    const index = arrIndex(list, 'pkeyId', pkeyId)
+    if (index !== -1) {
+        list.splice(index, 1)
+        DragModalTableForm.value = list
+    }
+}
+
+//计算展开高度和滚动位置
+const getOffsetTop = (key = '') => {
+    if (key) {
+        const dom = document.getElementById(key)
+        if (!draw_type.value) {
+            if (dom?.offsetTop >= 583 && key) {
+                emit('offsetTop', dom?.offsetTop - 583)
+            } else {
+                emit('offsetTop', dom?.offsetTop)
+            }
+        } else {
+            if (dom.offsetTop >= 424 && key) {
+                emit('offsetTop', dom?.offsetTop - 424)
+            } else {
+                emit('offsetTop', dom?.offsetTop)
+            }
+        }
+    } else {
+        emit('offsetTop', 0)
+    }
+    ActiveKey.value = key
+}
+
+//获取折叠面板的索引
+const getCollapseItemIndex = (name) => {
+    const keys = name.split('-')
+    if (keys.length > 0) {
+        return keys[1]
+    } else {
+        return -1
+    }
+}
+
+//获取表单的大小
+const getTableFormSize = (pkeyId) => {
+    let formId = `table-form-${pkeyId}`
+    try {
+        const {clientWidth, clientHeight} = document.getElementById(formId).children[0]
+        return {
+            width: (clientWidth + 40) + 'px',
+            height: (clientHeight + 80) + 'px'
+        }
+    } catch {
+        return {
+            width: '100%',
+            height: '100%'
+        }
+    }
+}
+
+//转字符串
+const setToString = (val) => {
+    return val ? val + '' : ''
+}
+
+//表单被点击
+const presentId = ref('')
+const excelTableFormClick = (key) => {
+    presentId.value = key
+}
+
+//缓存被激活时
+onActivated(() => {
+    setMountOnEventKey()
+})
+
+//缓存时被移除
+onDeactivated(() => {
+    HTableForm.unmountEventKey()
+})
+
+//页面被卸载
+onUnmounted(() => {
+    HTableForm.unmountEventKey()
+})
+
+const setMountOnEventKey = () => {
+    HTableForm.setOnEventKey({
+        //按下ctrl键 或 control 键
+        onCtrlDown: async () => {
+            //window.$HcLog('全局按键', '按下ctrl键 或 control 键')
+            const refs = await setOnFuncFormRef()
+            refs?.setIsCtrlKey(true)
+        },
+        //按下复制快捷键
+        onCtrlDownC: async (event) => {
+            //window.$HcLog('全局按键', '按下复制快捷键')
+            const refs = await setOnFuncFormRef()
+            refs?.setCopyKeyList(event)
+        },
+        //按下粘贴快捷键
+        onCtrlDownV: async (event) => {
+            //window.$HcLog('全局按键', '按下粘贴快捷键')
+            const refs = await setOnFuncFormRef()
+            await refs?.setPasteKeyList(event)
+        },
+        //放开ctrl键 或 control 键
+        onCtrlUp: async () => {
+            //window.$HcLog('全局按键', '放开ctrl键 或 control 键')
+            const refs = await setOnFuncFormRef()
+            refs?.setIsCtrlKey(false)
+        },
+    })
+}
+
+//获取表单的ref
+const setOnFuncFormRef = async () => {
+    const pkeyId = presentId.value
+    if (!isNullES(pkeyId)) {
+        return await getFormRef(pkeyId)
+    } else {
+        return;
+    }
+}
+
+
+//获取已渲染的表单
+const getFilterFormData = async () => {
+    const formArr = formDataList.value;
+    return formArr.filter((item) => {
+        return (item.pkeyId ?? '') !== '' && item.isCollapseLoad;
+    })
+}
+
+//获取表单数据
+const getFormData = async () => {
+    const formArr = await getFilterFormData();
+    //获取表单数据
+    let newArr = [];
+    for (let i = 0; i < formArr.length; i++) {
+        const pkeyId = formArr[i].pkeyId
+        const refs = await getFormRef(pkeyId)
+        const form = refs?.getFormData()
+        newArr.push({...form})
+    }
+    console.log('表单数据', newArr)
+    return newArr
+}
+
+//获取表单效验数据
+const getFormRegExpJson = async () => {
+    const formArr = await getFilterFormData();
+    const list = listDatas.value
+    //获取表单数据
+    let formRegExpJson = {};
+    for (let i = 0; i < formArr.length; i++) {
+        const pkeyId = formArr[i].pkeyId
+        const refs = await getFormRef(pkeyId)
+        const regExp = refs?.getRegExpJson()
+        const nodeName = refs?.getNodeName()
+        if (getObjVal(regExp)) {
+            const index = arrIndex(list, 'pkeyId', pkeyId)
+            formRegExpJson[pkeyId] = {
+                ...regExp,
+                itemId: `item-${index}-${pkeyId}`,
+                nodeName: nodeName
+            }
+        }
+    }
+    return formRegExpJson
+}
+
+//获取当前展开项
+const getActiveKey = () => {
+    return ActiveKey.value;
+}
+
+//设置当前展开项
+const setActiveKey = (key) => {
+    return ActiveKey.value = key;
+}
+
+// 暴露出去
+defineExpose({
+    getFormData,
+    getFormRegExpJson,
+    getActiveKey,
+    setActiveKey
+})
+</script>
+
+<style lang="scss" scoped>
+@import "../../components/collapse-form.scoped.scss";
+</style>
+
+<style lang="scss">
+@import "../../components/collapse-form.scss";
+</style>

+ 67 - 0
src/views/agree/land/form.vue

@@ -0,0 +1,67 @@
+<template>
+    <HcCard actionUi="text-center">
+        <el-scrollbar ref="listItemScrollRef">
+            <CollapseForm ref="ListItemsRef"
+                          :classify="0"
+                          :contractId="0"
+                          :datas="[]"
+                          :primaryKeyId="0"
+                          :status="0"
+                          v-if="false"
+            />
+            <HcStatus text="暂无表单"/>
+        </el-scrollbar>
+        <template #action>
+            <!--el-button size="large" type="info" hc-btn @click="goBackClick">
+                <HcIcon name="arrow-go-back"/>
+                <span>取消并返回</span>
+            </el-button-->
+            <el-button size="large" type="primary" hc-btn>
+                <HcIcon name="check-double"/>
+                <span>提交保存</span>
+            </el-button>
+            <el-button size="large" type="primary" hc-btn>
+                <HcIcon name="draft"/>
+                <span>一键生成协议</span>
+            </el-button>
+            <el-button size="large" type="warning" hc-btn>
+                <HcIcon name="eye"/>
+                <span>预览</span>
+            </el-button>
+            <el-button size="large" type="success" hc-btn>
+                <HcIcon name="upload"/>
+                <span>上传附件协议</span>
+            </el-button>
+        </template>
+    </HcCard>
+</template>
+
+<script setup>
+import {onActivated, ref} from "vue";
+import {useRoute, useRouter} from 'vue-router'
+import CollapseForm from "./collapse-form/index.vue"
+
+const router = useRouter()
+const useRoutes = useRoute()
+
+//初始变量
+//const dataType = ref(useRoutes?.query?.type ?? 'view')
+//const dataId = ref(useRoutes?.query?.id ?? '')
+
+//缓存页面被激活时
+onActivated(() => {
+    //dataType.value = useRoutes?.query?.type ?? 'view'
+    //dataId.value = useRoutes?.query?.id ?? ''
+})
+
+const listItemScrollRef = ref(null)
+
+//返回
+const goBackClick = () => {
+    router.back()
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 126 - 0
src/views/agree/land/land.vue

@@ -0,0 +1,126 @@
+<template>
+    <HcPageLayout>
+        <template #left>
+            <div class="hc-layout-tree-box">
+                <el-scrollbar>
+                    <HcTreeData @nodeTap="treeNodeTap"/>
+                </el-scrollbar>
+            </div>
+        </template>
+        <HcCard>
+            <template #header>
+                <div class="w-52">
+                    <el-input v-model="searchForm.queryValue" clearable placeholder="请输入名称进行查询" size="large"/>
+                </div>
+                <div class="ml-4">
+                    <el-button type="primary" @click="searchClick" size="large">
+                        <HcIcon name="search-2"/>
+                        <span>搜索</span>
+                    </el-button>
+                </div>
+            </template>
+            <template #extra>
+                <el-button size="large" type="primary" hc-btn @click="addRowClick">
+                    <HcIcon name="add"/>
+                    <span>新增</span>
+                </el-button>
+                <el-button size="large" type="warning" hc-btn>
+                    <HcIcon name="add"/>
+                    <span>打印</span>
+                </el-button>
+                <el-button size="large" type="danger" hc-btn>
+                    <HcIcon name="delete-bin"/>
+                    <span>删除</span>
+                </el-button>
+            </template>
+            <HcTable :column="tableColumn" :datas="tableData" :loading="tableLoading" isCheck @selection-change="tableSelectionChange">
+                <template #action="{row,index}">
+                    <el-button size="small" type="primary" @click="editRowClick(row)">修改</el-button>
+                    <el-button size="small" type="warning">查看</el-button>
+                    <el-button size="small" type="danger">删除</el-button>
+                </template>
+            </HcTable>
+            <template #action>
+                <HcPages :pages="searchForm" @change="pageChange"/>
+            </template>
+        </HcCard>
+    </HcPageLayout>
+</template>
+
+<script setup>
+import {ref} from "vue";
+import {useRouter} from 'vue-router'
+
+const router = useRouter()
+
+//树节点被点击
+const treeNodeTap = ({node, data}) => {
+
+}
+
+//搜索表单
+const searchForm = ref({
+    projectType: null, queryValue: null, startTime: null, endTime: null,
+    current: 1, size: 20, total: 0
+})
+
+//搜索
+const searchClick = () => {
+    searchForm.value.current = 1;
+    getTableData()
+}
+
+//分页被点击
+const pageChange = ({current, size}) => {
+    searchForm.value.current = current
+    searchForm.value.size = size
+    getTableData()
+}
+
+//获取数据
+const tableLoading = ref(false)
+const tableColumn = [
+    {key: 'key1', name: '协议编号'},
+    {key: 'key2', name: '协议书名称'},
+    {key: 'key3', name: '地类补偿金额'},
+    {key: 'key4', name: '青苗及附着物补偿金额'},
+    {key: 'key5', name: '补偿总金额'},
+    {key: 'action', name: '操作', width: '190', align: 'center'},
+]
+const tableData = ref([
+    {id: 1, key1: 'xxxx', key2: 'xxxx', key3: '65632'},
+    {id: 2, key1: 'xxxx', key2: 'xxxx', key3: '65632'},
+    {id: 3, key1: 'xxxx', key2: 'xxxx', key3: '65632'},
+    {id: 4, key1: 'xxxx', key2: 'xxxx', key3: '65632'},
+])
+const getTableData = () => {
+
+}
+
+//多选事件
+const tableSelectionChange = (rows) => {
+    console.log(rows)
+}
+
+//新增
+const addRowClick = () => {
+    router.push({
+        name: 'lar-agree-land-form'
+    })
+}
+
+//编辑
+const editRowClick = (row) => {
+    router.push({
+        name: 'lar-agree-land-form'
+    })
+}
+</script>
+
+<style lang="scss" scoped>
+.hc-layout-tree-box {
+    position: relative;
+    height: 100%;
+    padding: 18px;
+}
+</style>

+ 0 - 15
src/views/agree/special.vue

@@ -1,15 +0,0 @@
-<template>
-    <div>222</div>
-</template>
-
-<script setup>
-
-</script>
-
-<style lang="scss" scoped>
-
-</style>
-
-<style lang="scss">
-
-</style>

+ 285 - 0
src/views/agree/special/collapse-form/form-item.vue

@@ -0,0 +1,285 @@
+<template>
+    <HcTableForm ref="tableFormRef"
+                 :cols="colsKeys"
+                 :form="tableFormInfo"
+                 :height="heights"
+                 :html="excelHtml"
+                 :loading="loading"
+                 :pid="activeKey"
+                 :pkey="keyId"
+                 :scroll="scroll"
+                 :width="widths"
+                 @excelBodyTap="excelTableFormClick"
+                 @render="tableFormRender"
+                 @rightTap="tableFormRightTap"
+    />
+</template>
+
+<script setup>
+import {ref, watch, onMounted} from "vue"
+import {useAppStore} from "~src/store";
+import wbsApi from "~api/data-fill/wbs";
+import {deepClone, getArrValue, getObjVal, isString} from "js-fast-way"
+
+//初始
+const props = defineProps({
+    tid: { // 树节点
+        type: [String, Number],
+        default: ''
+    },
+    kid: { // pkeyId
+        type: [String, Number],
+        default: ''
+    },
+    classify: { // 类型
+        type: [String, Number],
+        default: ''
+    },
+    scroll: {
+        type: Boolean,
+        default: true
+    },
+    height: {
+        type: String,
+        default: '100%'
+    },
+    width: {
+        type: String,
+        default: 'auto'
+    },
+    datas: {
+        type: Object,
+        default: () => ({})
+    },
+    nodeName: { // 表单名称
+        type: String,
+        default: ''
+    },
+    pid: { // 折叠ID
+        type: String,
+        default: ''
+    },
+})
+
+//初始变量
+const useAppState = useAppStore()
+const projectId = ref(useAppState.getProjectId)
+const contractId = ref(useAppState.getContractId)
+const keyId = ref(props.kid ? props.kid + '' : '')
+const treeId = ref(props.tid)
+const classify = ref(props.classify)
+const loading = ref(false)
+const changeData = ref(props.datas)
+const nodeNames = ref(props.nodeName)
+
+const heights = ref(props.height)
+const widths = ref(props.width)
+const activeKey = ref(props.pid)
+
+const tableFormRef = ref(null)
+
+//监听
+watch(() => [
+    useAppState.getProjectId,
+    useAppState.getContractId,
+    props.tid,
+    props.kid,
+    props.classify,
+    props.nodeName,
+    props.height,
+    props.width,
+    props.pid,
+], (
+    [project_id, contract_id, tree_id, key_id, cid, nodeName, height, width, pid]
+) => {
+    projectId.value = project_id
+    contractId.value = contract_id
+    treeId.value = tree_id
+    keyId.value = key_id ? key_id + '' : ''
+    classify.value = cid
+    nodeNames.value = nodeName
+    heights.value = height
+    widths.value = width
+    activeKey.value = pid
+})
+
+//深度监听变动的对象数据
+watch(() => [
+    props.datas
+], ([data]) => {
+    changeData.value = data
+    setFormChangeData(data)
+}, {deep: true})
+
+
+//渲染完成
+onMounted(async () => {
+    loading.value = true
+    //获取已填写的数据
+    await getTableFormInfo(keyId.value)
+    //按键key列表
+    await getHtmlBussColsApi(keyId.value)
+    //渲染表单
+    await getExcelHtml(keyId.value)
+    loading.value = false
+})
+
+//事件
+const emit = defineEmits(['rightTap', 'render', 'excelBodyTap'])
+
+//表单被点击
+const excelTableFormClick = (item) => {
+    emit('excelBodyTap', item)
+}
+
+//获取表单初始数据
+const getFormDataInit = () => {
+    return {
+        projectId: projectId.value,
+        contractId: contractId.value,
+        classify: classify.value,
+        pkeyId: keyId.value,
+        nodeId: treeId.value,
+        isRenderForm: false,
+    }
+}
+
+//获取已填写的数据
+const tableFormInfo = ref({})
+const getTableFormInfo = async (pkeyId) => {
+    if (pkeyId) {
+        const {error, code, data} = await wbsApi.getBussDataInfo({
+            pkeyId: pkeyId
+        }, false)
+        const resData = getObjVal(data)
+        if (!error && code === 200 && resData) {
+            tableFormInfo.value = {
+                ...resData,
+                ...getFormDataInit(),
+                ...changeData.value
+            }
+        } else {
+            tableFormInfo.value = {
+                ...getFormDataInit(),
+                ...changeData.value
+            }
+        }
+    } else {
+        tableFormInfo.value = {}
+        window?.$message?.warning('pkeyId为空')
+    }
+}
+
+//获取按键切换输入框的key列表
+const colsKeys = ref([])
+const getHtmlBussColsApi = async (pkeyId) => {
+    if (pkeyId) {
+        const {error, code, data} = await wbsApi.getHtmlBussCols({
+            pkeyId: pkeyId
+        }, false)
+        if (!error && code === 200) {
+            let keys = getArrValue(data);
+            for (let i = 0; i < keys.length; i++) {
+                if (keys[i].length <= 0) {
+                    keys.splice(i, 1)
+                }
+            }
+            colsKeys.value = keys;
+        } else {
+            colsKeys.value = [];
+        }
+    } else {
+        colsKeys.value = []
+    }
+}
+
+//获取模板标签数据
+const excelHtml = ref('')
+const getExcelHtml = async (pkeyId) => {
+    if (pkeyId) {
+        const {error, code, data} = await wbsApi.getExcelHtml({
+            pkeyId: pkeyId
+        }, false)
+        const resData = isString(data) ? data || '' : ''
+        if (!error && code === 200 && resData) {
+            excelHtml.value = resData
+        } else {
+            excelHtml.value = ''
+            tableFormInfo.value.isRenderForm = false
+            window?.$message?.warning('暂无表单')
+        }
+    } else {
+        excelHtml.value = ''
+        tableFormInfo.value.isRenderForm = false
+        window?.$message?.warning('pkeyId为空')
+    }
+}
+
+//渲染完成
+const tableFormRender = (form) => {
+    tableFormInfo.value = form
+    emit('render', form)
+}
+
+//右键点击
+const tableFormRightTap = (item) => {
+    emit('rightTap', item)
+}
+
+//设置数据
+const setFormChangeData = (data) => {
+    const form = deepClone(tableFormInfo.value)
+    tableFormInfo.value = {...form, ...data}
+    //console.log('设置数据', {...form, ...data})
+}
+
+const getFormData = () => {
+    return tableFormInfo.value
+    //return tableFormRef.value?.getFormData()
+}
+
+const setFormData = (data) => {
+    setFormChangeData(data)
+    tableFormRef.value?.setFormData(tableFormInfo.value)
+}
+
+const getRegExpJson = () => {
+    return tableFormRef.value?.getRegExpJson()
+}
+
+const isFormRegExp = async () => {
+    return await tableFormRef.value?.isFormRegExp()
+}
+
+//获取表单名称
+const getNodeName = () => {
+    return nodeNames.value
+}
+
+//按下ctrl键
+const setIsCtrlKey = (data) => {
+    tableFormRef.value?.setIsCtrlKey(data)
+}
+
+//按下复制快捷键
+const setCopyKeyList = (event) => {
+    tableFormRef.value?.setCopyKeyList(event)
+}
+
+//按下粘贴快捷键
+const setPasteKeyList = async (event) => {
+    await tableFormRef.value?.setPasteKeyList(event)
+}
+
+// 暴露出去
+defineExpose({
+    getFormData,
+    setFormData,
+    getRegExpJson,
+    isFormRegExp,
+    getNodeName,
+    setIsCtrlKey,
+    setCopyKeyList,
+    setPasteKeyList
+})
+</script>

+ 1093 - 0
src/views/agree/special/collapse-form/index.vue

@@ -0,0 +1,1093 @@
+<template>
+    <div class="data-fill-list-box">
+        <el-collapse v-model="ActiveKey" accordion @change="CollapseChange">
+            <template v-for="(item,index) in listDatas" :key="item?.pkeyId">
+                <el-collapse-item :id="`item-${index}-${item?.pkeyId}`" :name="`item-${index}-${item?.pkeyId}`">
+                    <template #title>
+                        <div class="hc-collapse-item-header">
+                            <div class="text-lg truncate item-title"> {{ item.nodeName }}</div>
+                            <div class="hc-extra-text-box">
+                                <el-button :loading="copyClickLoading" plain type="primary" @click.stop="copyClick(item,index)">复制本表</el-button>
+                            </div>
+                        </div>
+                    </template>
+                    <div class="data-fill-list-item-content">
+                        <TableFormItem v-if="item.isTableRender"
+                                       :ref="(el) => setItemRefs(el, item)"
+                                       :classify="classifys"
+                                       :datas="changeFormDatas(item?.pkeyId, 'collapse')"
+                                       :kid="item?.pkeyId"
+                                       :nodeName="item.nodeName"
+                                       :pid="`table-form-${item?.pkeyId}`"
+                                       :tid="treeId"
+                                       @excelBodyTap="excelTableFormClick($event)"
+                                       @render="tableFormRender($event, item, index)"
+                                       @rightTap="tableFormRightTap($event, index)"
+                        />
+                    </div>
+                </el-collapse-item>
+            </template>
+        </el-collapse>
+    </div>
+</template>
+
+<script setup>
+import {ref, watch, nextTick, onActivated, onDeactivated, onMounted, onUnmounted} from "vue";
+import HTableForm from "~src/plugins/HTableForm"
+import {useAppStore} from "~src/store";
+import wbsApi from "~api/data-fill/wbs"
+import TableFormItem from "./form-item.vue"
+import {
+    getArrValue, getObjValue, getObjVal,
+    isNullES, deepClone, arrIndex, setPosRange
+} from "js-fast-way"
+
+//初始变量
+const useAppState = useAppStore()
+
+//参数
+const props = defineProps({
+    datas: {
+        type: Array,
+        default: () => ([])
+    },
+    classify: {
+        type: [String, Number],
+        default: ''
+    },
+    status: {
+        type: [String, Number],
+        default: ''
+    },
+    primaryKeyId: {
+        type: [String, Number],
+        default: ''
+    },
+    contractId: {
+        type: [String, Number],
+        default: ''
+    },
+    drawType: {
+        type: Boolean,
+        default: false
+    },
+    wbsTempId: {
+        type: [String, Number],
+        default: ''
+    },
+    tenantId: {
+        type: [String, Number],
+        default: ''
+    },
+    wbsType: {
+        type: [String, Number],
+        default: ''
+    },
+})
+
+//全局变量
+const projectId = ref(useAppState.projectId);
+const contract_id = ref(props.contractId)
+const treeId = ref(props.primaryKeyId)
+const classifys = ref(props.classify)
+const wbsTemp_id = ref(props.wbsTempId);
+const tenant_id = ref(props.tenantId);
+const wbs_type = ref(props.wbsType);
+const isStatus = ref(parseInt(props.status))
+const listDatas = ref([])
+const draw_type = ref(props.drawType)
+
+
+//表单变量
+const formDataList = ref([])
+const formKeyIds = ref('')
+const formparentId = ref('')
+
+//处理ref
+const itemRefs = ref([])
+const setItemRefs = (el, {pkeyId}) => {
+    if (el) {
+        let index = arrIndex(itemRefs.value, 'pkeyId', pkeyId)
+        if (index !== -1) {
+            itemRefs.value[index].ref = el
+        } else {
+            itemRefs.value.push({
+                pkeyId: pkeyId,
+                ref: el
+            });
+        }
+    }
+}
+
+//处理表单的ref
+const setSpliceItemRefs = async ({pkeyId}) => {
+    const refs = itemRefs.value
+    let index = arrIndex(refs, 'pkeyId', pkeyId)
+    if (index !== -1) {
+        refs.splice(index, 1)
+        itemRefs.value = refs
+    }
+}
+
+const closeIconArr = [
+    {key: 'reduction', icon: 'picture-in-picture-2', name: '还原到面板内,并自动展开面板'}
+]
+
+//事件
+const emit = defineEmits(['renew', 'offsetTop'])
+
+//组件参数变量
+const apis = ref({
+    dataInfo: wbsApi.getBussDataInfo,
+    bussCols: wbsApi.getHtmlBussCols,
+    excelHtml: wbsApi.getExcelHtml
+})
+
+//深度监听数据
+watch(() => [
+    props.datas,
+], ([datas]) => {
+    setFormDataNum(datas)
+}, {deep: true})
+
+//监听变量值
+watch(() => [
+    useAppState.projectId,
+    props.contractId,
+    props.wbsTempId,
+    props.tenantId,
+    props.wbsType,
+    props.status,
+    props.classify,
+    props.primaryKeyId,
+], ([pid, cid, temp_id, tid, type, status, class_id, tree_id]) => {
+    projectId.value = pid
+    contract_id.value = cid
+    wbsTemp_id.value = temp_id
+    tenant_id.value = tid
+    wbs_type.value = type
+    isStatus.value = parseInt(status)
+    classifys.value = class_id
+    treeId.value = tree_id
+})
+
+//渲染完成
+onMounted(() => {
+    setFormDataNum(props.datas)
+    setTableFormMenu(useAppState.projectInfo)
+    const {offsetHeight} = document.body
+    DragModalHeight.value = offsetHeight - 200
+    setMountOnEventKey()
+})
+
+//处理变动的数据
+const changeFormData = ref({
+    'window': [],
+    'collapse': [],
+})
+const changeFormDatas = (pkeyId, type) => {
+    const changeData = changeFormData.value[type]
+    const index = arrIndex(changeData, 'pkeyId', pkeyId)
+    if (index !== -1) {
+        return changeData[index]
+    } else {
+        return {}
+    }
+}
+
+//设置变动的数据
+const setChangeFormDatas = async (pkeyId, type) => {
+    const refs = await getFormRef(pkeyId)
+    const formData = refs?.getFormData()
+    const changeData = changeFormData.value[type]
+    const index = arrIndex(changeData, 'pkeyId', pkeyId)
+    if (index !== -1) {
+        changeData[index] = formData
+    } else {
+        changeData.push(formData)
+    }
+    changeFormData.value[type] = changeData
+}
+
+//展开事件
+const ActiveKey = ref('')
+const CollapseChange = (name) => {
+    ActiveKey.value = name
+    let index = getCollapseItemIndex(name)
+    if (index > -1) {
+        getOffsetTop(name);
+        const item = listDatas.value[index]
+        formKeyIds.value = setToString(item.pkeyId)
+        formparentId.value = setToString(item.parentId)
+        nextTick(() => {
+            if (!item.isTableRender) {
+                item.isTableRender = true
+            }
+        })
+    } else {
+        getOffsetTop()
+        formKeyIds.value = ''
+        formparentId.value = ''
+    }
+}
+
+//初始设置
+const setFormDataNum = (datas) => {
+    itemRefs.value = []
+    ActiveKey.value = ''
+    let newArr = [];
+    for (let i = 0; i < datas.length; i++) {
+        newArr.push({isCollapseLoad: false})
+    }
+    formDataList.value = newArr
+    listDatas.value = deepClone(datas)
+}
+
+//渲染完成
+const tableFormRender = (form, item, index) => {
+    console.log(form)
+    formDataList.value[index] = form
+    formDataList.value[index].isCollapseLoad = form.isRenderForm
+    item.isTableForm = form.isRenderForm
+}
+
+
+//菜单数据
+const contextMenuRef = ref(null)
+const tableFormMenu = ref([])
+const tableFormItemNode = ref({}) //临时信息
+
+//设置菜单权限数据
+const setTableFormMenu = (info) => {
+    let newArr = [], infos = getObjValue(info)
+    const isOpen = infos['isOpenRandomNumber'] ?? 0
+    if (isOpen === 1 && isStatus.value !== 3) {
+        newArr.push({label: '插入设计值/频率', key: "design"})
+    }
+    newArr.push({label: '插入特殊字符', key: "special"})
+    newArr.push({label: '关联试验数据', key: "test"})
+    newArr.push({label: '关联试验文件', key: "file"})
+    newArr.push({label: '公式参数', key: "formula"})
+    tableFormMenu.value = newArr
+}
+
+//鼠标右键事件
+const tableFormRightTap = ({event, KeyName, startPos, endPos, pkeyId}, index) => {
+    //存储临时信息
+    tableFormItemNode.value = {KeyName, index, startPos, endPos, pkeyId}
+    contextMenuRef.value?.showMenu(event, false) //展开菜单
+}
+
+//鼠标右键菜单被点击
+const handleMenuSelect = ({key}) => {
+    if (key === 'design') {
+        setInitDesignForm()
+        designModalLoading.value = false
+        designModal.value = true
+    } else if (key === 'special') {
+        specialModalShow()
+    } else if (key === 'test') {
+        testModalLoading.value = false
+        testModal.value = true
+    } else if (key === 'file') {
+        fileModalLoading.value = false
+        fileModal.value = true;
+    } else if (key === 'formula') {
+        formulaModalLoading.value = false
+        formulaModal.value = true
+    }
+}
+
+//插入设计值
+const designModal = ref(false)
+const formDesignRef = ref(null)
+const formDesignModel = ref()
+
+//初始设计值/频率表单
+const setInitDesignForm = () => {
+    formDesignModel.value = {
+        type: 1, design: '', size: '',
+        dev: '', key: '', capacity: '',
+        pass: '', pkId: ''
+    }
+}
+
+//设计值频率计算
+const designModalLoading = ref(false)
+const designModalSave = async () => {
+    const {pkeyId, KeyName} = tableFormItemNode.value
+    if (pkeyId) {
+        designModalLoading.value = true
+        //const {design, size} = formDesignModel.value
+        const {error, code, data} = await wbsApi.queryFormulaRange({
+            ...formDesignModel.value,
+            // dev: (!design && !size) ? '±5': '',
+            key: KeyName,
+            pkId: pkeyId,
+        })
+        //处理数据
+        const res = getObjVal(data)
+        if (!error && code === 200 && res) {
+            try {
+                const refs = await getFormRef(pkeyId)
+                const itemFormData = refs?.getFormData()
+                Object.keys(data).forEach(key => {
+                    itemFormData[key] = data[key]
+                })
+                refs?.setFormData(itemFormData)
+            } catch {
+            }
+            designModalLoading.value = false
+            designModal.value = false
+        } else {
+            designModalLoading.value = false
+        }
+    } else {
+        window?.$message?.warning('pkeyId为空')
+    }
+}
+//关闭设计值/频率弹窗
+const closeDesignModal = () => {
+    designModal.value = false
+    setInitDesignForm()
+}
+
+
+//插入特殊字符
+const specialModal = ref(false)
+const specialModalLoading = ref(false)
+const specialModalShow = () => {
+    specialFormValue.value = ''
+    specialModalLoading.value = false
+    specialModal.value = true
+}
+
+//监听特殊符号输入框的内容
+const specialFormValue = ref('')
+const specialDiaolgChange = (val) => {
+    specialFormValue.value = val
+}
+
+//确认插入
+const specialRef = ref(null)
+const specialNodeClick = async () => {
+    specialModalLoading.value = true
+    const itemNode = tableFormItemNode.value
+    const {KeyName, pkeyId} = itemNode
+    try {
+        const refs = await getFormRef(pkeyId)
+        const itemFormData = refs?.getFormData()
+        const {code, val, posVal} = await specialRef.value?.getSpecialNode(itemNode, itemFormData[KeyName])
+        if (code === 200 && val) {
+            itemFormData[KeyName] = val
+            refs?.setFormData(itemFormData)
+            specialModalLoading.value = false
+            specialModal.value = false
+            await nextTick(() => {
+                setPosRange(KeyName, posVal)
+            })
+        } else {
+            specialModalLoading.value = false
+        }
+    } catch (e) {
+        specialModalLoading.value = false
+    }
+}
+
+//关闭插入特殊字符
+const specialModalClose = () => {
+    specialModalLoading.value = false
+    specialModal.value = false
+}
+
+//关联试验数据
+const testModal = ref(false)
+const testModalLoading = ref(false)
+
+//关联试验数据被点击
+const itinsertTableId = ref('')
+const itinsertTreeId = ref('')
+const testTableRowName = ({row, treeId}) => {
+    itinsertTableId.value = row.id
+    itinsertTreeId.value = treeId
+    insertDataLoading.value = false
+    insertDataShow.value = true
+}
+
+//关闭弹窗
+const testModalClose = () => {
+    testModal.value = false
+    testModalLoading.value = false
+}
+
+//选择要插入的实验数据
+const insertDataShow = ref(false);
+const insertDataLoading = ref(false);
+
+//确定关联试验数据数据
+const insertDataRef = ref(null)
+const submitinsertData = async () => {
+    insertDataLoading.value = true
+    const itemNode = tableFormItemNode.value
+    const {KeyName, pkeyId} = itemNode
+    try {
+        const refs = await getFormRef(pkeyId)
+        const itemFormData = refs?.getFormData()
+        const {code, val, posVal} = await insertDataRef.value?.submitinsertData(itemNode, itemFormData[KeyName])
+        if (code === 200 && val) {
+            itemFormData[KeyName] = val
+            refs?.setFormData(itemFormData)
+            insertDataLoading.value = false
+            insertDataShow.value = false
+            testModal.value = false
+            await nextTick(() => {
+                setPosRange(KeyName, posVal)
+            })
+        }
+    } catch {
+        insertDataLoading.value = false
+    }
+}
+
+//取消关联数据
+const cancelinsertData = async () => {
+    insertDataShow.value = false
+    insertDataLoading.value = false
+}
+
+
+//关联试验文件
+const fileModal = ref(false)
+const testFileRefs = ref(null)
+//确认关联文件
+const fileModalLoading = ref(false)
+const savefileModal = async () => {
+    fileModalLoading.value = true
+    await testFileRefs.value?.savefileSubmit()
+    fileModalLoading.value = false
+    fileModal.value = false
+}
+
+//关闭弹窗
+const fileModalClose = () => {
+    fileModal.value = false
+    fileModalLoading.value = false
+}
+
+//公式参数配置
+const formulaModal = ref(false)
+const formulaRefs = ref(null)
+
+//保存
+const formulaModalLoading = ref(false)
+const formulaSaveClick = async () => {
+    formulaModalLoading.value = true
+    await formulaRefs.value?.panelSave()
+    formulaModalLoading.value = false
+    formulaModal.value = false
+}
+
+
+//关闭
+const formulaModalClose = () => {
+    formulaModal.value = false
+    formulaModalLoading.value = false
+}
+
+
+//窗口化
+const DragModalTableForm = ref([])
+const DragModalHeight = ref(600)
+const windowClick = async (item, indexs) => {
+    const list = deepClone(DragModalTableForm.value)
+    let index = arrIndex(list, 'pkeyId', item.pkeyId)
+    if (!item.isWindow) {
+        const formSize = getTableFormSize(item?.pkeyId)
+        const newTableForm = {
+            ...setInitDragModalTableForm(item, indexs),
+            ...formSize
+        }
+        await setChangeFormDatas(item?.pkeyId, 'window')
+        item.isWindow = true
+        //处理表单的ref
+        await setSpliceItemRefs(item)
+        //弹窗表单的排序
+        if (index === -1) {
+            list.push(newTableForm)
+        } else if (index !== list.length - 1) {
+            //检查是否在最上层,不在则置顶,可以解决多次点击时,频繁更改全局状态的问题
+            list.splice(index, 1)
+            list.push(newTableForm)
+        }
+        DragModalTableForm.value = list
+        ActiveKey.value = ''
+    } else {
+        await setChangeFormDatas(item?.pkeyId, 'collapse')
+        //处理表单的ref
+        await setSpliceItemRefs(item)
+        if (index !== -1) {
+            list.splice(index, 1)
+            DragModalTableForm.value = list
+        }
+        item.isWindow = false
+    }
+}
+
+//初始拖动表单的内容
+const setInitDragModalTableForm = (item, index) => {
+    return {
+        projectId: projectId.value,
+        contractId: contract_id.value,
+        wbsTempId: wbsTemp_id.value,
+        tenantId: tenant_id.value,
+        wbsType: wbs_type.value,
+        classify: classifys.value,
+        treeId: treeId.value,
+        pkeyId: item.pkeyId,
+        height: '100%',
+        width: '100%',
+        title: item.nodeName,
+        isShow: true,
+        index: index,
+        item: item
+    }
+}
+
+//关闭窗口
+const TableFormClose = async ({pkeyId, index}, indexs) => {
+    const list = deepClone(DragModalTableForm.value)
+    //取表单的数据
+    await setChangeFormDatas(pkeyId, 'collapse')
+    //关闭窗口
+    list.splice(indexs, 1)
+    DragModalTableForm.value = list
+    listDatas.value[index].isWindow = false
+}
+
+const dragNodeMoreMenu = [
+    {key: 'save', icon: 'save-2', name: '保存'},
+    {key: 'preview', icon: 'eye', name: '预览'},
+]
+
+//还原窗口
+const closeIconTap = async (event, item, indexs) => {
+    const {index, pkeyId} = item
+    let KeyId = `item-${index}-${pkeyId}`
+    await TableFormClose(item, indexs)
+    ActiveKey.value = KeyId
+}
+
+//菜单被点击
+const dragNodeMoreMenuTap = ({key}, items) => {
+    const {item} = items
+    if (key === 'save') {
+        if (item?.isTableForm) {
+            tableFormSaveClick(item, items)
+        } else {
+            window.$message.warning('此表单暂无数据和文件')
+        }
+    } else if (key === 'preview') {
+        if (item['isBussShow'] === 2 || item['isTabPdf'] === 1 || item['pdfUrl'] === '') {
+            window.$message.warning('此表单暂无可预览文件')
+        } else {
+            previewClick(item, items)
+        }
+    }
+}
+
+//删除本表
+const tableFormDelLoading = ref(false)
+const delClick = async ({pkeyId}) => {
+    if (pkeyId) {
+        if (isStatus.value !== 3) {
+            tableFormDelLoading.value = true
+            const {error, code} = await wbsApi.removeBussTabInfo({
+                pkeyid: pkeyId,
+                classify: classifys.value,
+            })
+            tableFormDelLoading.value = false
+            if (!error && code === 200) {
+                window?.$message?.success('操作成功')
+                //判断是否存在窗口,如果存在,就删除窗口
+                delWindowRefs(pkeyId)
+                renewData()
+            }
+        } else {
+            window?.$message?.warning('已上报的资料,不允许删除')
+        }
+    } else {
+        window?.$message?.warning('pkeyId为空')
+    }
+}
+//复制本表相关
+const showcopyModal=ref(false)
+const copyRefs=ref(null)
+const copyModalClose=()=>{
+    // copyModal.value=false
+}
+const CopyModalType=ref('1')
+
+const copySaveClick=async()=>{
+    //本节点复制
+   if(CopyModalType.value==='2'){
+        const {pkeyId, isTableRender, isTableForm} =  copyItems.value
+        if (pkeyId) {
+            if (isStatus.value !== 3) {
+                if (!isTableRender) {
+                    await copeBussTab(pkeyId)
+                } else if (!isTableForm) {
+                    window?.$message?.warning('暂无表单数据')
+                } else if (isTableRender) {
+                    copyClickLoading.value = true
+                    const isSave = await saveExcelBussData(items, null, false)
+                    if (isSave) {
+                        await copeBussTab(pkeyId)
+                    } else {
+                        copyClickLoading.value = false
+                        window?.$message?.warning('复制本表操作失败')
+                    }
+                } else {
+                    window?.$message?.warning(`数据异常了, isRenderTableForm: ${isTableRender}, isTableForm: ${isTableForm}, pkeyIds:${pkeyId}`)
+                }
+            } else {
+                window?.$message?.warning('已上报的资料,不允许复制')
+            }
+        } else {
+            window?.$message?.warning('pkeyId为空')
+    }
+   }else{
+    window?.$message?.warning('暂无相关接口')
+   }
+}
+//复制本表
+const copyClickModalLoading = ref(false)
+const copyClickLoading = ref(false)
+const copyClick = async (items) => {
+    const {pkeyId, isTableRender, isTableForm} = items
+    if (pkeyId) {
+        if (isStatus.value !== 3) {
+            if (!isTableRender) {
+                await copeBussTab(pkeyId)
+            } else if (!isTableForm) {
+                window?.$message?.warning('暂无表单数据')
+            } else if (isTableRender) {
+                copyClickLoading.value = true
+                const isSave = await saveExcelBussData(items, null, false)
+                if (isSave) {
+                    await copeBussTab(pkeyId)
+                } else {
+                    copyClickLoading.value = false
+                    window?.$message?.warning('复制本表操作失败')
+                }
+            } else {
+                window?.$message?.warning(`数据异常了, isRenderTableForm: ${isTableRender}, isTableForm: ${isTableForm}, pkeyIds:${pkeyId}`)
+            }
+        } else {
+            window?.$message?.warning('已上报的资料,不允许复制')
+        }
+    } else {
+        window?.$message?.warning('pkeyId为空')
+    }
+ }
+const copyItems=ref([])
+//跨节点复制弹窗
+// const copyClick =  (items) => {
+//     showcopyModal.value=true
+//     copyItems.value=items
+
+// }
+
+//复制表的请求
+const copeBussTab = async (pkeyId) => {
+    copyClickLoading.value = true
+    const {error, code} = await wbsApi.copeBussTab({
+        pkeyId: pkeyId
+    })
+    copyClickLoading.value = false
+    if (!error && code === 200) {
+        window?.$message?.success('操作成功')
+        renewData()
+    }
+}
+
+//隐藏本表
+const tableFormHideLoading = ref(false)
+const hideClick = async ({pkeyId, isBussShow}) => {
+    if (pkeyId) {
+        if (isStatus.value !== 3) {
+            tableFormHideLoading.value = true
+            const bussShow = isBussShow === 2 ? 1 : 2 //状态(1显示 2隐藏)
+            const {error, code} = await wbsApi.showBussTab({
+                pkeyId: pkeyId,
+                status: bussShow
+            })
+            tableFormHideLoading.value = false
+            if (!error && code === 200) {
+                window?.$message?.success('操作成功')
+                if (bussShow === 2) {
+                    //判断是否存在窗口,如果存在,就删除窗口
+                    delWindowRefs(pkeyId)
+                }
+                renewData()
+            }
+        } else {
+            window?.$message?.warning('已上报的资料,不允许隐藏')
+        }
+    } else {
+        window?.$message?.warning('pkeyId为空')
+    }
+}
+
+//预览本表
+const tableFormPreviewLoading = ref(false)
+const previewClick = async (item, dragItem = null) => {
+    tableFormPreviewLoading.value = true
+    await getBussPdfInfo(item, dragItem)
+    tableFormPreviewLoading.value = false
+}
+
+//上传变量
+const uploadModal = ref(false)
+const fileListData = ref([]);
+const uploadData = ref({})
+//上传附件
+const uploadClick = (items, index) => {
+    const {pkeyId, isTableForm, isTableRender} = items
+    const keyName = `item-${index}-${pkeyId}`
+    if (pkeyId) {
+        if (isStatus.value !== 3 && isTableForm) {
+            uploadModal.value = true
+            uploadData.value = {
+                projectId: projectId.value,
+                contractId: contract_id.value,
+                classify: classifys.value,
+                pkeyId: pkeyId,
+                nodeId: treeId.value
+            }
+            //获取文件列表
+            getBussFileList(pkeyId)
+        } else if (!isTableRender) {
+            CollapseChange(keyName)
+            window?.$message?.warning('请再次点击上传')
+        } else if (!isTableForm) {
+            window?.$message?.warning('暂无表单数据')
+        } else {
+            window?.$message?.warning('已上报的资料,不允许上传')
+        }
+    } else {
+        window?.$message?.warning('pkeyId为空')
+    }
+}
+
+//获取文件列表
+const getBussFileList = async (pkeyId) => {
+    const {error, code, data} = await wbsApi.getBussFileList({
+        pkeyid: pkeyId
+    })
+    if (!error && code === 200) {
+        fileListData.value = getArrValue(data)
+    } else {
+        fileListData.value = []
+    }
+}
+
+//上传文件
+const uploadChange = async ({type}) => {
+    if (type === 'success') {
+        uploadModal.value = false
+        renewData()
+    } else if (type === 'del') {
+        renewData()
+    }
+}
+
+//关闭上传附件窗口
+const uploadModalClose = () => {
+    uploadModal.value = false
+}
+
+//单个保存
+const tableFormSaveLoading = ref(false)
+const tableFormSaveClick = async (item, dragItem = null) => {
+    if (isStatus.value !== 3) {
+        tableFormSaveLoading.value = true
+        const isSave = await saveExcelBussData(item, dragItem)
+        if (isSave) {
+            await getBussPdfInfo(item, dragItem)
+            tableFormSaveLoading.value = false
+            renewData()
+        } else {
+            tableFormSaveLoading.value = false
+        }
+    } else {
+        window?.$message?.warning('已上报的资料,不允许保存。')
+    }
+}
+
+//保存表单数据
+const saveExcelBussData = async ({pkeyId}, dragItem = null, showTip = true) => {
+    setDragModalLoading(dragItem, '保存中...', true)
+    const refs = await getFormRef(pkeyId)
+    const isRegExp = await refs?.isFormRegExp()
+    if (isRegExp) {
+        const formData = refs?.getFormData()
+        const {error, code} = await wbsApi.saveExcelBussData(formData)
+        setDragModalLoading(dragItem)
+        if (!error && code === 200) {
+            if (showTip) {
+                window?.$message?.success('保存成功')
+            }
+            return true
+        } else {
+            return false
+        }
+    } else {
+        setDragModalLoading(dragItem)
+        return false
+    }
+}
+
+//预览PDF
+const getBussPdfInfo = async ({pkeyId}, dragItem = null, showTip = true) => {
+    setDragModalLoading(dragItem, '获取pdf中...', true)
+    const {error, code, data} = await wbsApi.getBussPdfInfo({
+        pkeyId: pkeyId
+    }, false)
+    setDragModalLoading(dragItem)
+    if (!error && code === 200) {
+        if (data) {
+            window.open(data, '_blank')
+        } else if (showTip) {
+            window?.$message?.warning('PDF错误')
+        }
+    } else {
+        if (showTip) {
+            window?.$message?.warning(data.msg || '获取PDF失败')
+        }
+    }
+}
+
+
+//通知数据更新
+const renewData = () => {
+    emit('renew')
+    ActiveKey.value = ''
+}
+
+//设置表单的加载状态
+const setDragModalLoading = (dragItem, text = '保存中...', show = false) => {
+    if (dragItem && show) {
+        dragItem.loading = true
+        dragItem.loadingText = text
+    }
+    if (dragItem && !show) {
+        dragItem.loading = false
+    }
+}
+
+//获取表单的ref
+const getFormRef = async (pkeyId) => {
+    const itemRef = itemRefs.value
+    const index = arrIndex(itemRef, 'pkeyId', pkeyId)
+    return itemRef[index].ref
+}
+
+//删除打开的窗口
+const delWindowRefs = (pkeyId) => {
+    //判断是否存在窗口,如果存在,就删除窗口
+    const list = DragModalTableForm.value
+    const index = arrIndex(list, 'pkeyId', pkeyId)
+    if (index !== -1) {
+        list.splice(index, 1)
+        DragModalTableForm.value = list
+    }
+}
+
+//计算展开高度和滚动位置
+const getOffsetTop = (key = '') => {
+    if (key) {
+        const dom = document.getElementById(key)
+        if (!draw_type.value) {
+            if (dom?.offsetTop >= 583 && key) {
+                emit('offsetTop', dom?.offsetTop - 583)
+            } else {
+                emit('offsetTop', dom?.offsetTop)
+            }
+        } else {
+            if (dom.offsetTop >= 424 && key) {
+                emit('offsetTop', dom?.offsetTop - 424)
+            } else {
+                emit('offsetTop', dom?.offsetTop)
+            }
+        }
+    } else {
+        emit('offsetTop', 0)
+    }
+    ActiveKey.value = key
+}
+
+//获取折叠面板的索引
+const getCollapseItemIndex = (name) => {
+    const keys = name.split('-')
+    if (keys.length > 0) {
+        return keys[1]
+    } else {
+        return -1
+    }
+}
+
+//获取表单的大小
+const getTableFormSize = (pkeyId) => {
+    let formId = `table-form-${pkeyId}`
+    try {
+        const {clientWidth, clientHeight} = document.getElementById(formId).children[0]
+        return {
+            width: (clientWidth + 40) + 'px',
+            height: (clientHeight + 80) + 'px'
+        }
+    } catch {
+        return {
+            width: '100%',
+            height: '100%'
+        }
+    }
+}
+
+//转字符串
+const setToString = (val) => {
+    return val ? val + '' : ''
+}
+
+//表单被点击
+const presentId = ref('')
+const excelTableFormClick = (key) => {
+    presentId.value = key
+}
+
+//缓存被激活时
+onActivated(() => {
+    setMountOnEventKey()
+})
+
+//缓存时被移除
+onDeactivated(() => {
+    HTableForm.unmountEventKey()
+})
+
+//页面被卸载
+onUnmounted(() => {
+    HTableForm.unmountEventKey()
+})
+
+const setMountOnEventKey = () => {
+    HTableForm.setOnEventKey({
+        //按下ctrl键 或 control 键
+        onCtrlDown: async () => {
+            //window.$HcLog('全局按键', '按下ctrl键 或 control 键')
+            const refs = await setOnFuncFormRef()
+            refs?.setIsCtrlKey(true)
+        },
+        //按下复制快捷键
+        onCtrlDownC: async (event) => {
+            //window.$HcLog('全局按键', '按下复制快捷键')
+            const refs = await setOnFuncFormRef()
+            refs?.setCopyKeyList(event)
+        },
+        //按下粘贴快捷键
+        onCtrlDownV: async (event) => {
+            //window.$HcLog('全局按键', '按下粘贴快捷键')
+            const refs = await setOnFuncFormRef()
+            await refs?.setPasteKeyList(event)
+        },
+        //放开ctrl键 或 control 键
+        onCtrlUp: async () => {
+            //window.$HcLog('全局按键', '放开ctrl键 或 control 键')
+            const refs = await setOnFuncFormRef()
+            refs?.setIsCtrlKey(false)
+        },
+    })
+}
+
+//获取表单的ref
+const setOnFuncFormRef = async () => {
+    const pkeyId = presentId.value
+    if (!isNullES(pkeyId)) {
+        return await getFormRef(pkeyId)
+    } else {
+        return;
+    }
+}
+
+
+//获取已渲染的表单
+const getFilterFormData = async () => {
+    const formArr = formDataList.value;
+    return formArr.filter((item) => {
+        return (item.pkeyId ?? '') !== '' && item.isCollapseLoad;
+    })
+}
+
+//获取表单数据
+const getFormData = async () => {
+    const formArr = await getFilterFormData();
+    //获取表单数据
+    let newArr = [];
+    for (let i = 0; i < formArr.length; i++) {
+        const pkeyId = formArr[i].pkeyId
+        const refs = await getFormRef(pkeyId)
+        const form = refs?.getFormData()
+        newArr.push({...form})
+    }
+    console.log('表单数据', newArr)
+    return newArr
+}
+
+//获取表单效验数据
+const getFormRegExpJson = async () => {
+    const formArr = await getFilterFormData();
+    const list = listDatas.value
+    //获取表单数据
+    let formRegExpJson = {};
+    for (let i = 0; i < formArr.length; i++) {
+        const pkeyId = formArr[i].pkeyId
+        const refs = await getFormRef(pkeyId)
+        const regExp = refs?.getRegExpJson()
+        const nodeName = refs?.getNodeName()
+        if (getObjVal(regExp)) {
+            const index = arrIndex(list, 'pkeyId', pkeyId)
+            formRegExpJson[pkeyId] = {
+                ...regExp,
+                itemId: `item-${index}-${pkeyId}`,
+                nodeName: nodeName
+            }
+        }
+    }
+    return formRegExpJson
+}
+
+//获取当前展开项
+const getActiveKey = () => {
+    return ActiveKey.value;
+}
+
+//设置当前展开项
+const setActiveKey = (key) => {
+    return ActiveKey.value = key;
+}
+
+// 暴露出去
+defineExpose({
+    getFormData,
+    getFormRegExpJson,
+    getActiveKey,
+    setActiveKey
+})
+</script>
+
+<style lang="scss" scoped>
+@import "../../components/collapse-form.scoped.scss";
+</style>
+
+<style lang="scss">
+@import "../../components/collapse-form.scss";
+</style>

+ 67 - 0
src/views/agree/special/form.vue

@@ -0,0 +1,67 @@
+<template>
+    <HcCard actionUi="text-center">
+        <el-scrollbar ref="listItemScrollRef">
+            <CollapseForm ref="ListItemsRef"
+                          :classify="0"
+                          :contractId="0"
+                          :datas="[]"
+                          :primaryKeyId="0"
+                          :status="0"
+                          v-if="false"
+            />
+            <HcStatus text="暂无表单"/>
+        </el-scrollbar>
+        <template #action>
+            <!--el-button size="large" type="info" hc-btn @click="goBackClick">
+                <HcIcon name="arrow-go-back"/>
+                <span>取消并返回</span>
+            </el-button-->
+            <el-button size="large" type="primary" hc-btn>
+                <HcIcon name="check-double"/>
+                <span>提交保存</span>
+            </el-button>
+            <el-button size="large" type="primary" hc-btn>
+                <HcIcon name="draft"/>
+                <span>一键生成协议</span>
+            </el-button>
+            <el-button size="large" type="warning" hc-btn>
+                <HcIcon name="eye"/>
+                <span>预览</span>
+            </el-button>
+            <el-button size="large" type="success" hc-btn>
+                <HcIcon name="upload"/>
+                <span>上传附件协议</span>
+            </el-button>
+        </template>
+    </HcCard>
+</template>
+
+<script setup>
+import {onActivated, ref} from "vue";
+import {useRoute, useRouter} from 'vue-router'
+import CollapseForm from "./collapse-form/index.vue"
+
+const router = useRouter()
+const useRoutes = useRoute()
+
+//初始变量
+//const dataType = ref(useRoutes?.query?.type ?? 'view')
+//const dataId = ref(useRoutes?.query?.id ?? '')
+
+//缓存页面被激活时
+onActivated(() => {
+    //dataType.value = useRoutes?.query?.type ?? 'view'
+    //dataId.value = useRoutes?.query?.id ?? ''
+})
+
+const listItemScrollRef = ref(null)
+
+//返回
+const goBackClick = () => {
+    router.back()
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 126 - 0
src/views/agree/special/special.vue

@@ -0,0 +1,126 @@
+<template>
+    <HcPageLayout>
+        <template #left>
+            <div class="hc-layout-tree-box">
+                <el-scrollbar>
+                    <HcTreeData @nodeTap="treeNodeTap"/>
+                </el-scrollbar>
+            </div>
+        </template>
+        <HcCard>
+            <template #header>
+                <div class="w-52">
+                    <el-input v-model="searchForm.queryValue" clearable placeholder="请输入名称进行查询" size="large"/>
+                </div>
+                <div class="ml-4">
+                    <el-button type="primary" @click="searchClick" size="large">
+                        <HcIcon name="search-2"/>
+                        <span>搜索</span>
+                    </el-button>
+                </div>
+            </template>
+            <template #extra>
+                <el-button size="large" type="primary" hc-btn @click="addRowClick">
+                    <HcIcon name="add"/>
+                    <span>新增</span>
+                </el-button>
+                <el-button size="large" type="warning" hc-btn>
+                    <HcIcon name="add"/>
+                    <span>打印</span>
+                </el-button>
+                <el-button size="large" type="danger" hc-btn>
+                    <HcIcon name="delete-bin"/>
+                    <span>删除</span>
+                </el-button>
+            </template>
+            <HcTable :column="tableColumn" :datas="tableData" :loading="tableLoading" isCheck @selection-change="tableSelectionChange">
+                <template #action="{row,index}">
+                    <el-button size="small" type="primary" @click="editRowClick(row)">修改</el-button>
+                    <el-button size="small" type="warning">查看</el-button>
+                    <el-button size="small" type="danger">删除</el-button>
+                </template>
+            </HcTable>
+            <template #action>
+                <HcPages :pages="searchForm" @change="pageChange"/>
+            </template>
+        </HcCard>
+    </HcPageLayout>
+</template>
+
+<script setup>
+import {ref} from "vue";
+import {useRouter} from 'vue-router'
+
+const router = useRouter()
+
+//树节点被点击
+const treeNodeTap = ({node, data}) => {
+
+}
+
+//搜索表单
+const searchForm = ref({
+    projectType: null, queryValue: null, startTime: null, endTime: null,
+    current: 1, size: 20, total: 0
+})
+
+//搜索
+const searchClick = () => {
+    searchForm.value.current = 1;
+    getTableData()
+}
+
+//分页被点击
+const pageChange = ({current, size}) => {
+    searchForm.value.current = current
+    searchForm.value.size = size
+    getTableData()
+}
+
+//获取数据
+const tableLoading = ref(false)
+const tableColumn = [
+    {key: 'key1', name: '协议编号'},
+    {key: 'key2', name: '协议书名称'},
+    {key: 'key3', name: '地类补偿金额'},
+    {key: 'key4', name: '青苗及附着物补偿金额'},
+    {key: 'key5', name: '补偿总金额'},
+    {key: 'action', name: '操作', width: '190', align: 'center'},
+]
+const tableData = ref([
+    {id: 1, key1: 'xxxx', key2: 'xxxx', key3: '65632'},
+    {id: 2, key1: 'xxxx', key2: 'xxxx', key3: '65632'},
+    {id: 3, key1: 'xxxx', key2: 'xxxx', key3: '65632'},
+    {id: 4, key1: 'xxxx', key2: 'xxxx', key3: '65632'},
+])
+const getTableData = () => {
+
+}
+
+//多选事件
+const tableSelectionChange = (rows) => {
+    console.log(rows)
+}
+
+//新增
+const addRowClick = () => {
+    router.push({
+        name: 'lar-agree-special-form'
+    })
+}
+
+//编辑
+const editRowClick = (row) => {
+    router.push({
+        name: 'lar-agree-special-form'
+    })
+}
+</script>
+
+<style lang="scss" scoped>
+.hc-layout-tree-box {
+    position: relative;
+    height: 100%;
+    padding: 18px;
+}
+</style>

+ 0 - 15
src/views/agree/tomb.vue

@@ -1,15 +0,0 @@
-<template>
-    <div>222</div>
-</template>
-
-<script setup>
-
-</script>
-
-<style lang="scss" scoped>
-
-</style>
-
-<style lang="scss">
-
-</style>

+ 285 - 0
src/views/agree/tomb/collapse-form/form-item.vue

@@ -0,0 +1,285 @@
+<template>
+    <HcTableForm ref="tableFormRef"
+                 :cols="colsKeys"
+                 :form="tableFormInfo"
+                 :height="heights"
+                 :html="excelHtml"
+                 :loading="loading"
+                 :pid="activeKey"
+                 :pkey="keyId"
+                 :scroll="scroll"
+                 :width="widths"
+                 @excelBodyTap="excelTableFormClick"
+                 @render="tableFormRender"
+                 @rightTap="tableFormRightTap"
+    />
+</template>
+
+<script setup>
+import {ref, watch, onMounted} from "vue"
+import {useAppStore} from "~src/store";
+import wbsApi from "~api/data-fill/wbs";
+import {deepClone, getArrValue, getObjVal, isString} from "js-fast-way"
+
+//初始
+const props = defineProps({
+    tid: { // 树节点
+        type: [String, Number],
+        default: ''
+    },
+    kid: { // pkeyId
+        type: [String, Number],
+        default: ''
+    },
+    classify: { // 类型
+        type: [String, Number],
+        default: ''
+    },
+    scroll: {
+        type: Boolean,
+        default: true
+    },
+    height: {
+        type: String,
+        default: '100%'
+    },
+    width: {
+        type: String,
+        default: 'auto'
+    },
+    datas: {
+        type: Object,
+        default: () => ({})
+    },
+    nodeName: { // 表单名称
+        type: String,
+        default: ''
+    },
+    pid: { // 折叠ID
+        type: String,
+        default: ''
+    },
+})
+
+//初始变量
+const useAppState = useAppStore()
+const projectId = ref(useAppState.getProjectId)
+const contractId = ref(useAppState.getContractId)
+const keyId = ref(props.kid ? props.kid + '' : '')
+const treeId = ref(props.tid)
+const classify = ref(props.classify)
+const loading = ref(false)
+const changeData = ref(props.datas)
+const nodeNames = ref(props.nodeName)
+
+const heights = ref(props.height)
+const widths = ref(props.width)
+const activeKey = ref(props.pid)
+
+const tableFormRef = ref(null)
+
+//监听
+watch(() => [
+    useAppState.getProjectId,
+    useAppState.getContractId,
+    props.tid,
+    props.kid,
+    props.classify,
+    props.nodeName,
+    props.height,
+    props.width,
+    props.pid,
+], (
+    [project_id, contract_id, tree_id, key_id, cid, nodeName, height, width, pid]
+) => {
+    projectId.value = project_id
+    contractId.value = contract_id
+    treeId.value = tree_id
+    keyId.value = key_id ? key_id + '' : ''
+    classify.value = cid
+    nodeNames.value = nodeName
+    heights.value = height
+    widths.value = width
+    activeKey.value = pid
+})
+
+//深度监听变动的对象数据
+watch(() => [
+    props.datas
+], ([data]) => {
+    changeData.value = data
+    setFormChangeData(data)
+}, {deep: true})
+
+
+//渲染完成
+onMounted(async () => {
+    loading.value = true
+    //获取已填写的数据
+    await getTableFormInfo(keyId.value)
+    //按键key列表
+    await getHtmlBussColsApi(keyId.value)
+    //渲染表单
+    await getExcelHtml(keyId.value)
+    loading.value = false
+})
+
+//事件
+const emit = defineEmits(['rightTap', 'render', 'excelBodyTap'])
+
+//表单被点击
+const excelTableFormClick = (item) => {
+    emit('excelBodyTap', item)
+}
+
+//获取表单初始数据
+const getFormDataInit = () => {
+    return {
+        projectId: projectId.value,
+        contractId: contractId.value,
+        classify: classify.value,
+        pkeyId: keyId.value,
+        nodeId: treeId.value,
+        isRenderForm: false,
+    }
+}
+
+//获取已填写的数据
+const tableFormInfo = ref({})
+const getTableFormInfo = async (pkeyId) => {
+    if (pkeyId) {
+        const {error, code, data} = await wbsApi.getBussDataInfo({
+            pkeyId: pkeyId
+        }, false)
+        const resData = getObjVal(data)
+        if (!error && code === 200 && resData) {
+            tableFormInfo.value = {
+                ...resData,
+                ...getFormDataInit(),
+                ...changeData.value
+            }
+        } else {
+            tableFormInfo.value = {
+                ...getFormDataInit(),
+                ...changeData.value
+            }
+        }
+    } else {
+        tableFormInfo.value = {}
+        window?.$message?.warning('pkeyId为空')
+    }
+}
+
+//获取按键切换输入框的key列表
+const colsKeys = ref([])
+const getHtmlBussColsApi = async (pkeyId) => {
+    if (pkeyId) {
+        const {error, code, data} = await wbsApi.getHtmlBussCols({
+            pkeyId: pkeyId
+        }, false)
+        if (!error && code === 200) {
+            let keys = getArrValue(data);
+            for (let i = 0; i < keys.length; i++) {
+                if (keys[i].length <= 0) {
+                    keys.splice(i, 1)
+                }
+            }
+            colsKeys.value = keys;
+        } else {
+            colsKeys.value = [];
+        }
+    } else {
+        colsKeys.value = []
+    }
+}
+
+//获取模板标签数据
+const excelHtml = ref('')
+const getExcelHtml = async (pkeyId) => {
+    if (pkeyId) {
+        const {error, code, data} = await wbsApi.getExcelHtml({
+            pkeyId: pkeyId
+        }, false)
+        const resData = isString(data) ? data || '' : ''
+        if (!error && code === 200 && resData) {
+            excelHtml.value = resData
+        } else {
+            excelHtml.value = ''
+            tableFormInfo.value.isRenderForm = false
+            window?.$message?.warning('暂无表单')
+        }
+    } else {
+        excelHtml.value = ''
+        tableFormInfo.value.isRenderForm = false
+        window?.$message?.warning('pkeyId为空')
+    }
+}
+
+//渲染完成
+const tableFormRender = (form) => {
+    tableFormInfo.value = form
+    emit('render', form)
+}
+
+//右键点击
+const tableFormRightTap = (item) => {
+    emit('rightTap', item)
+}
+
+//设置数据
+const setFormChangeData = (data) => {
+    const form = deepClone(tableFormInfo.value)
+    tableFormInfo.value = {...form, ...data}
+    //console.log('设置数据', {...form, ...data})
+}
+
+const getFormData = () => {
+    return tableFormInfo.value
+    //return tableFormRef.value?.getFormData()
+}
+
+const setFormData = (data) => {
+    setFormChangeData(data)
+    tableFormRef.value?.setFormData(tableFormInfo.value)
+}
+
+const getRegExpJson = () => {
+    return tableFormRef.value?.getRegExpJson()
+}
+
+const isFormRegExp = async () => {
+    return await tableFormRef.value?.isFormRegExp()
+}
+
+//获取表单名称
+const getNodeName = () => {
+    return nodeNames.value
+}
+
+//按下ctrl键
+const setIsCtrlKey = (data) => {
+    tableFormRef.value?.setIsCtrlKey(data)
+}
+
+//按下复制快捷键
+const setCopyKeyList = (event) => {
+    tableFormRef.value?.setCopyKeyList(event)
+}
+
+//按下粘贴快捷键
+const setPasteKeyList = async (event) => {
+    await tableFormRef.value?.setPasteKeyList(event)
+}
+
+// 暴露出去
+defineExpose({
+    getFormData,
+    setFormData,
+    getRegExpJson,
+    isFormRegExp,
+    getNodeName,
+    setIsCtrlKey,
+    setCopyKeyList,
+    setPasteKeyList
+})
+</script>

+ 1093 - 0
src/views/agree/tomb/collapse-form/index.vue

@@ -0,0 +1,1093 @@
+<template>
+    <div class="data-fill-list-box">
+        <el-collapse v-model="ActiveKey" accordion @change="CollapseChange">
+            <template v-for="(item,index) in listDatas" :key="item?.pkeyId">
+                <el-collapse-item :id="`item-${index}-${item?.pkeyId}`" :name="`item-${index}-${item?.pkeyId}`">
+                    <template #title>
+                        <div class="hc-collapse-item-header">
+                            <div class="text-lg truncate item-title"> {{ item.nodeName }}</div>
+                            <div class="hc-extra-text-box">
+                                <el-button :loading="copyClickLoading" plain type="primary" @click.stop="copyClick(item,index)">复制本表</el-button>
+                            </div>
+                        </div>
+                    </template>
+                    <div class="data-fill-list-item-content">
+                        <TableFormItem v-if="item.isTableRender"
+                                       :ref="(el) => setItemRefs(el, item)"
+                                       :classify="classifys"
+                                       :datas="changeFormDatas(item?.pkeyId, 'collapse')"
+                                       :kid="item?.pkeyId"
+                                       :nodeName="item.nodeName"
+                                       :pid="`table-form-${item?.pkeyId}`"
+                                       :tid="treeId"
+                                       @excelBodyTap="excelTableFormClick($event)"
+                                       @render="tableFormRender($event, item, index)"
+                                       @rightTap="tableFormRightTap($event, index)"
+                        />
+                    </div>
+                </el-collapse-item>
+            </template>
+        </el-collapse>
+    </div>
+</template>
+
+<script setup>
+import {ref, watch, nextTick, onActivated, onDeactivated, onMounted, onUnmounted} from "vue";
+import HTableForm from "~src/plugins/HTableForm"
+import {useAppStore} from "~src/store";
+import wbsApi from "~api/data-fill/wbs"
+import TableFormItem from "./form-item.vue"
+import {
+    getArrValue, getObjValue, getObjVal,
+    isNullES, deepClone, arrIndex, setPosRange
+} from "js-fast-way"
+
+//初始变量
+const useAppState = useAppStore()
+
+//参数
+const props = defineProps({
+    datas: {
+        type: Array,
+        default: () => ([])
+    },
+    classify: {
+        type: [String, Number],
+        default: ''
+    },
+    status: {
+        type: [String, Number],
+        default: ''
+    },
+    primaryKeyId: {
+        type: [String, Number],
+        default: ''
+    },
+    contractId: {
+        type: [String, Number],
+        default: ''
+    },
+    drawType: {
+        type: Boolean,
+        default: false
+    },
+    wbsTempId: {
+        type: [String, Number],
+        default: ''
+    },
+    tenantId: {
+        type: [String, Number],
+        default: ''
+    },
+    wbsType: {
+        type: [String, Number],
+        default: ''
+    },
+})
+
+//全局变量
+const projectId = ref(useAppState.projectId);
+const contract_id = ref(props.contractId)
+const treeId = ref(props.primaryKeyId)
+const classifys = ref(props.classify)
+const wbsTemp_id = ref(props.wbsTempId);
+const tenant_id = ref(props.tenantId);
+const wbs_type = ref(props.wbsType);
+const isStatus = ref(parseInt(props.status))
+const listDatas = ref([])
+const draw_type = ref(props.drawType)
+
+
+//表单变量
+const formDataList = ref([])
+const formKeyIds = ref('')
+const formparentId = ref('')
+
+//处理ref
+const itemRefs = ref([])
+const setItemRefs = (el, {pkeyId}) => {
+    if (el) {
+        let index = arrIndex(itemRefs.value, 'pkeyId', pkeyId)
+        if (index !== -1) {
+            itemRefs.value[index].ref = el
+        } else {
+            itemRefs.value.push({
+                pkeyId: pkeyId,
+                ref: el
+            });
+        }
+    }
+}
+
+//处理表单的ref
+const setSpliceItemRefs = async ({pkeyId}) => {
+    const refs = itemRefs.value
+    let index = arrIndex(refs, 'pkeyId', pkeyId)
+    if (index !== -1) {
+        refs.splice(index, 1)
+        itemRefs.value = refs
+    }
+}
+
+const closeIconArr = [
+    {key: 'reduction', icon: 'picture-in-picture-2', name: '还原到面板内,并自动展开面板'}
+]
+
+//事件
+const emit = defineEmits(['renew', 'offsetTop'])
+
+//组件参数变量
+const apis = ref({
+    dataInfo: wbsApi.getBussDataInfo,
+    bussCols: wbsApi.getHtmlBussCols,
+    excelHtml: wbsApi.getExcelHtml
+})
+
+//深度监听数据
+watch(() => [
+    props.datas,
+], ([datas]) => {
+    setFormDataNum(datas)
+}, {deep: true})
+
+//监听变量值
+watch(() => [
+    useAppState.projectId,
+    props.contractId,
+    props.wbsTempId,
+    props.tenantId,
+    props.wbsType,
+    props.status,
+    props.classify,
+    props.primaryKeyId,
+], ([pid, cid, temp_id, tid, type, status, class_id, tree_id]) => {
+    projectId.value = pid
+    contract_id.value = cid
+    wbsTemp_id.value = temp_id
+    tenant_id.value = tid
+    wbs_type.value = type
+    isStatus.value = parseInt(status)
+    classifys.value = class_id
+    treeId.value = tree_id
+})
+
+//渲染完成
+onMounted(() => {
+    setFormDataNum(props.datas)
+    setTableFormMenu(useAppState.projectInfo)
+    const {offsetHeight} = document.body
+    DragModalHeight.value = offsetHeight - 200
+    setMountOnEventKey()
+})
+
+//处理变动的数据
+const changeFormData = ref({
+    'window': [],
+    'collapse': [],
+})
+const changeFormDatas = (pkeyId, type) => {
+    const changeData = changeFormData.value[type]
+    const index = arrIndex(changeData, 'pkeyId', pkeyId)
+    if (index !== -1) {
+        return changeData[index]
+    } else {
+        return {}
+    }
+}
+
+//设置变动的数据
+const setChangeFormDatas = async (pkeyId, type) => {
+    const refs = await getFormRef(pkeyId)
+    const formData = refs?.getFormData()
+    const changeData = changeFormData.value[type]
+    const index = arrIndex(changeData, 'pkeyId', pkeyId)
+    if (index !== -1) {
+        changeData[index] = formData
+    } else {
+        changeData.push(formData)
+    }
+    changeFormData.value[type] = changeData
+}
+
+//展开事件
+const ActiveKey = ref('')
+const CollapseChange = (name) => {
+    ActiveKey.value = name
+    let index = getCollapseItemIndex(name)
+    if (index > -1) {
+        getOffsetTop(name);
+        const item = listDatas.value[index]
+        formKeyIds.value = setToString(item.pkeyId)
+        formparentId.value = setToString(item.parentId)
+        nextTick(() => {
+            if (!item.isTableRender) {
+                item.isTableRender = true
+            }
+        })
+    } else {
+        getOffsetTop()
+        formKeyIds.value = ''
+        formparentId.value = ''
+    }
+}
+
+//初始设置
+const setFormDataNum = (datas) => {
+    itemRefs.value = []
+    ActiveKey.value = ''
+    let newArr = [];
+    for (let i = 0; i < datas.length; i++) {
+        newArr.push({isCollapseLoad: false})
+    }
+    formDataList.value = newArr
+    listDatas.value = deepClone(datas)
+}
+
+//渲染完成
+const tableFormRender = (form, item, index) => {
+    console.log(form)
+    formDataList.value[index] = form
+    formDataList.value[index].isCollapseLoad = form.isRenderForm
+    item.isTableForm = form.isRenderForm
+}
+
+
+//菜单数据
+const contextMenuRef = ref(null)
+const tableFormMenu = ref([])
+const tableFormItemNode = ref({}) //临时信息
+
+//设置菜单权限数据
+const setTableFormMenu = (info) => {
+    let newArr = [], infos = getObjValue(info)
+    const isOpen = infos['isOpenRandomNumber'] ?? 0
+    if (isOpen === 1 && isStatus.value !== 3) {
+        newArr.push({label: '插入设计值/频率', key: "design"})
+    }
+    newArr.push({label: '插入特殊字符', key: "special"})
+    newArr.push({label: '关联试验数据', key: "test"})
+    newArr.push({label: '关联试验文件', key: "file"})
+    newArr.push({label: '公式参数', key: "formula"})
+    tableFormMenu.value = newArr
+}
+
+//鼠标右键事件
+const tableFormRightTap = ({event, KeyName, startPos, endPos, pkeyId}, index) => {
+    //存储临时信息
+    tableFormItemNode.value = {KeyName, index, startPos, endPos, pkeyId}
+    contextMenuRef.value?.showMenu(event, false) //展开菜单
+}
+
+//鼠标右键菜单被点击
+const handleMenuSelect = ({key}) => {
+    if (key === 'design') {
+        setInitDesignForm()
+        designModalLoading.value = false
+        designModal.value = true
+    } else if (key === 'special') {
+        specialModalShow()
+    } else if (key === 'test') {
+        testModalLoading.value = false
+        testModal.value = true
+    } else if (key === 'file') {
+        fileModalLoading.value = false
+        fileModal.value = true;
+    } else if (key === 'formula') {
+        formulaModalLoading.value = false
+        formulaModal.value = true
+    }
+}
+
+//插入设计值
+const designModal = ref(false)
+const formDesignRef = ref(null)
+const formDesignModel = ref()
+
+//初始设计值/频率表单
+const setInitDesignForm = () => {
+    formDesignModel.value = {
+        type: 1, design: '', size: '',
+        dev: '', key: '', capacity: '',
+        pass: '', pkId: ''
+    }
+}
+
+//设计值频率计算
+const designModalLoading = ref(false)
+const designModalSave = async () => {
+    const {pkeyId, KeyName} = tableFormItemNode.value
+    if (pkeyId) {
+        designModalLoading.value = true
+        //const {design, size} = formDesignModel.value
+        const {error, code, data} = await wbsApi.queryFormulaRange({
+            ...formDesignModel.value,
+            // dev: (!design && !size) ? '±5': '',
+            key: KeyName,
+            pkId: pkeyId,
+        })
+        //处理数据
+        const res = getObjVal(data)
+        if (!error && code === 200 && res) {
+            try {
+                const refs = await getFormRef(pkeyId)
+                const itemFormData = refs?.getFormData()
+                Object.keys(data).forEach(key => {
+                    itemFormData[key] = data[key]
+                })
+                refs?.setFormData(itemFormData)
+            } catch {
+            }
+            designModalLoading.value = false
+            designModal.value = false
+        } else {
+            designModalLoading.value = false
+        }
+    } else {
+        window?.$message?.warning('pkeyId为空')
+    }
+}
+//关闭设计值/频率弹窗
+const closeDesignModal = () => {
+    designModal.value = false
+    setInitDesignForm()
+}
+
+
+//插入特殊字符
+const specialModal = ref(false)
+const specialModalLoading = ref(false)
+const specialModalShow = () => {
+    specialFormValue.value = ''
+    specialModalLoading.value = false
+    specialModal.value = true
+}
+
+//监听特殊符号输入框的内容
+const specialFormValue = ref('')
+const specialDiaolgChange = (val) => {
+    specialFormValue.value = val
+}
+
+//确认插入
+const specialRef = ref(null)
+const specialNodeClick = async () => {
+    specialModalLoading.value = true
+    const itemNode = tableFormItemNode.value
+    const {KeyName, pkeyId} = itemNode
+    try {
+        const refs = await getFormRef(pkeyId)
+        const itemFormData = refs?.getFormData()
+        const {code, val, posVal} = await specialRef.value?.getSpecialNode(itemNode, itemFormData[KeyName])
+        if (code === 200 && val) {
+            itemFormData[KeyName] = val
+            refs?.setFormData(itemFormData)
+            specialModalLoading.value = false
+            specialModal.value = false
+            await nextTick(() => {
+                setPosRange(KeyName, posVal)
+            })
+        } else {
+            specialModalLoading.value = false
+        }
+    } catch (e) {
+        specialModalLoading.value = false
+    }
+}
+
+//关闭插入特殊字符
+const specialModalClose = () => {
+    specialModalLoading.value = false
+    specialModal.value = false
+}
+
+//关联试验数据
+const testModal = ref(false)
+const testModalLoading = ref(false)
+
+//关联试验数据被点击
+const itinsertTableId = ref('')
+const itinsertTreeId = ref('')
+const testTableRowName = ({row, treeId}) => {
+    itinsertTableId.value = row.id
+    itinsertTreeId.value = treeId
+    insertDataLoading.value = false
+    insertDataShow.value = true
+}
+
+//关闭弹窗
+const testModalClose = () => {
+    testModal.value = false
+    testModalLoading.value = false
+}
+
+//选择要插入的实验数据
+const insertDataShow = ref(false);
+const insertDataLoading = ref(false);
+
+//确定关联试验数据数据
+const insertDataRef = ref(null)
+const submitinsertData = async () => {
+    insertDataLoading.value = true
+    const itemNode = tableFormItemNode.value
+    const {KeyName, pkeyId} = itemNode
+    try {
+        const refs = await getFormRef(pkeyId)
+        const itemFormData = refs?.getFormData()
+        const {code, val, posVal} = await insertDataRef.value?.submitinsertData(itemNode, itemFormData[KeyName])
+        if (code === 200 && val) {
+            itemFormData[KeyName] = val
+            refs?.setFormData(itemFormData)
+            insertDataLoading.value = false
+            insertDataShow.value = false
+            testModal.value = false
+            await nextTick(() => {
+                setPosRange(KeyName, posVal)
+            })
+        }
+    } catch {
+        insertDataLoading.value = false
+    }
+}
+
+//取消关联数据
+const cancelinsertData = async () => {
+    insertDataShow.value = false
+    insertDataLoading.value = false
+}
+
+
+//关联试验文件
+const fileModal = ref(false)
+const testFileRefs = ref(null)
+//确认关联文件
+const fileModalLoading = ref(false)
+const savefileModal = async () => {
+    fileModalLoading.value = true
+    await testFileRefs.value?.savefileSubmit()
+    fileModalLoading.value = false
+    fileModal.value = false
+}
+
+//关闭弹窗
+const fileModalClose = () => {
+    fileModal.value = false
+    fileModalLoading.value = false
+}
+
+//公式参数配置
+const formulaModal = ref(false)
+const formulaRefs = ref(null)
+
+//保存
+const formulaModalLoading = ref(false)
+const formulaSaveClick = async () => {
+    formulaModalLoading.value = true
+    await formulaRefs.value?.panelSave()
+    formulaModalLoading.value = false
+    formulaModal.value = false
+}
+
+
+//关闭
+const formulaModalClose = () => {
+    formulaModal.value = false
+    formulaModalLoading.value = false
+}
+
+
+//窗口化
+const DragModalTableForm = ref([])
+const DragModalHeight = ref(600)
+const windowClick = async (item, indexs) => {
+    const list = deepClone(DragModalTableForm.value)
+    let index = arrIndex(list, 'pkeyId', item.pkeyId)
+    if (!item.isWindow) {
+        const formSize = getTableFormSize(item?.pkeyId)
+        const newTableForm = {
+            ...setInitDragModalTableForm(item, indexs),
+            ...formSize
+        }
+        await setChangeFormDatas(item?.pkeyId, 'window')
+        item.isWindow = true
+        //处理表单的ref
+        await setSpliceItemRefs(item)
+        //弹窗表单的排序
+        if (index === -1) {
+            list.push(newTableForm)
+        } else if (index !== list.length - 1) {
+            //检查是否在最上层,不在则置顶,可以解决多次点击时,频繁更改全局状态的问题
+            list.splice(index, 1)
+            list.push(newTableForm)
+        }
+        DragModalTableForm.value = list
+        ActiveKey.value = ''
+    } else {
+        await setChangeFormDatas(item?.pkeyId, 'collapse')
+        //处理表单的ref
+        await setSpliceItemRefs(item)
+        if (index !== -1) {
+            list.splice(index, 1)
+            DragModalTableForm.value = list
+        }
+        item.isWindow = false
+    }
+}
+
+//初始拖动表单的内容
+const setInitDragModalTableForm = (item, index) => {
+    return {
+        projectId: projectId.value,
+        contractId: contract_id.value,
+        wbsTempId: wbsTemp_id.value,
+        tenantId: tenant_id.value,
+        wbsType: wbs_type.value,
+        classify: classifys.value,
+        treeId: treeId.value,
+        pkeyId: item.pkeyId,
+        height: '100%',
+        width: '100%',
+        title: item.nodeName,
+        isShow: true,
+        index: index,
+        item: item
+    }
+}
+
+//关闭窗口
+const TableFormClose = async ({pkeyId, index}, indexs) => {
+    const list = deepClone(DragModalTableForm.value)
+    //取表单的数据
+    await setChangeFormDatas(pkeyId, 'collapse')
+    //关闭窗口
+    list.splice(indexs, 1)
+    DragModalTableForm.value = list
+    listDatas.value[index].isWindow = false
+}
+
+const dragNodeMoreMenu = [
+    {key: 'save', icon: 'save-2', name: '保存'},
+    {key: 'preview', icon: 'eye', name: '预览'},
+]
+
+//还原窗口
+const closeIconTap = async (event, item, indexs) => {
+    const {index, pkeyId} = item
+    let KeyId = `item-${index}-${pkeyId}`
+    await TableFormClose(item, indexs)
+    ActiveKey.value = KeyId
+}
+
+//菜单被点击
+const dragNodeMoreMenuTap = ({key}, items) => {
+    const {item} = items
+    if (key === 'save') {
+        if (item?.isTableForm) {
+            tableFormSaveClick(item, items)
+        } else {
+            window.$message.warning('此表单暂无数据和文件')
+        }
+    } else if (key === 'preview') {
+        if (item['isBussShow'] === 2 || item['isTabPdf'] === 1 || item['pdfUrl'] === '') {
+            window.$message.warning('此表单暂无可预览文件')
+        } else {
+            previewClick(item, items)
+        }
+    }
+}
+
+//删除本表
+const tableFormDelLoading = ref(false)
+const delClick = async ({pkeyId}) => {
+    if (pkeyId) {
+        if (isStatus.value !== 3) {
+            tableFormDelLoading.value = true
+            const {error, code} = await wbsApi.removeBussTabInfo({
+                pkeyid: pkeyId,
+                classify: classifys.value,
+            })
+            tableFormDelLoading.value = false
+            if (!error && code === 200) {
+                window?.$message?.success('操作成功')
+                //判断是否存在窗口,如果存在,就删除窗口
+                delWindowRefs(pkeyId)
+                renewData()
+            }
+        } else {
+            window?.$message?.warning('已上报的资料,不允许删除')
+        }
+    } else {
+        window?.$message?.warning('pkeyId为空')
+    }
+}
+//复制本表相关
+const showcopyModal=ref(false)
+const copyRefs=ref(null)
+const copyModalClose=()=>{
+    // copyModal.value=false
+}
+const CopyModalType=ref('1')
+
+const copySaveClick=async()=>{
+    //本节点复制
+   if(CopyModalType.value==='2'){
+        const {pkeyId, isTableRender, isTableForm} =  copyItems.value
+        if (pkeyId) {
+            if (isStatus.value !== 3) {
+                if (!isTableRender) {
+                    await copeBussTab(pkeyId)
+                } else if (!isTableForm) {
+                    window?.$message?.warning('暂无表单数据')
+                } else if (isTableRender) {
+                    copyClickLoading.value = true
+                    const isSave = await saveExcelBussData(items, null, false)
+                    if (isSave) {
+                        await copeBussTab(pkeyId)
+                    } else {
+                        copyClickLoading.value = false
+                        window?.$message?.warning('复制本表操作失败')
+                    }
+                } else {
+                    window?.$message?.warning(`数据异常了, isRenderTableForm: ${isTableRender}, isTableForm: ${isTableForm}, pkeyIds:${pkeyId}`)
+                }
+            } else {
+                window?.$message?.warning('已上报的资料,不允许复制')
+            }
+        } else {
+            window?.$message?.warning('pkeyId为空')
+    }
+   }else{
+    window?.$message?.warning('暂无相关接口')
+   }
+}
+//复制本表
+const copyClickModalLoading = ref(false)
+const copyClickLoading = ref(false)
+const copyClick = async (items) => {
+    const {pkeyId, isTableRender, isTableForm} = items
+    if (pkeyId) {
+        if (isStatus.value !== 3) {
+            if (!isTableRender) {
+                await copeBussTab(pkeyId)
+            } else if (!isTableForm) {
+                window?.$message?.warning('暂无表单数据')
+            } else if (isTableRender) {
+                copyClickLoading.value = true
+                const isSave = await saveExcelBussData(items, null, false)
+                if (isSave) {
+                    await copeBussTab(pkeyId)
+                } else {
+                    copyClickLoading.value = false
+                    window?.$message?.warning('复制本表操作失败')
+                }
+            } else {
+                window?.$message?.warning(`数据异常了, isRenderTableForm: ${isTableRender}, isTableForm: ${isTableForm}, pkeyIds:${pkeyId}`)
+            }
+        } else {
+            window?.$message?.warning('已上报的资料,不允许复制')
+        }
+    } else {
+        window?.$message?.warning('pkeyId为空')
+    }
+ }
+const copyItems=ref([])
+//跨节点复制弹窗
+// const copyClick =  (items) => {
+//     showcopyModal.value=true
+//     copyItems.value=items
+
+// }
+
+//复制表的请求
+const copeBussTab = async (pkeyId) => {
+    copyClickLoading.value = true
+    const {error, code} = await wbsApi.copeBussTab({
+        pkeyId: pkeyId
+    })
+    copyClickLoading.value = false
+    if (!error && code === 200) {
+        window?.$message?.success('操作成功')
+        renewData()
+    }
+}
+
+//隐藏本表
+const tableFormHideLoading = ref(false)
+const hideClick = async ({pkeyId, isBussShow}) => {
+    if (pkeyId) {
+        if (isStatus.value !== 3) {
+            tableFormHideLoading.value = true
+            const bussShow = isBussShow === 2 ? 1 : 2 //状态(1显示 2隐藏)
+            const {error, code} = await wbsApi.showBussTab({
+                pkeyId: pkeyId,
+                status: bussShow
+            })
+            tableFormHideLoading.value = false
+            if (!error && code === 200) {
+                window?.$message?.success('操作成功')
+                if (bussShow === 2) {
+                    //判断是否存在窗口,如果存在,就删除窗口
+                    delWindowRefs(pkeyId)
+                }
+                renewData()
+            }
+        } else {
+            window?.$message?.warning('已上报的资料,不允许隐藏')
+        }
+    } else {
+        window?.$message?.warning('pkeyId为空')
+    }
+}
+
+//预览本表
+const tableFormPreviewLoading = ref(false)
+const previewClick = async (item, dragItem = null) => {
+    tableFormPreviewLoading.value = true
+    await getBussPdfInfo(item, dragItem)
+    tableFormPreviewLoading.value = false
+}
+
+//上传变量
+const uploadModal = ref(false)
+const fileListData = ref([]);
+const uploadData = ref({})
+//上传附件
+const uploadClick = (items, index) => {
+    const {pkeyId, isTableForm, isTableRender} = items
+    const keyName = `item-${index}-${pkeyId}`
+    if (pkeyId) {
+        if (isStatus.value !== 3 && isTableForm) {
+            uploadModal.value = true
+            uploadData.value = {
+                projectId: projectId.value,
+                contractId: contract_id.value,
+                classify: classifys.value,
+                pkeyId: pkeyId,
+                nodeId: treeId.value
+            }
+            //获取文件列表
+            getBussFileList(pkeyId)
+        } else if (!isTableRender) {
+            CollapseChange(keyName)
+            window?.$message?.warning('请再次点击上传')
+        } else if (!isTableForm) {
+            window?.$message?.warning('暂无表单数据')
+        } else {
+            window?.$message?.warning('已上报的资料,不允许上传')
+        }
+    } else {
+        window?.$message?.warning('pkeyId为空')
+    }
+}
+
+//获取文件列表
+const getBussFileList = async (pkeyId) => {
+    const {error, code, data} = await wbsApi.getBussFileList({
+        pkeyid: pkeyId
+    })
+    if (!error && code === 200) {
+        fileListData.value = getArrValue(data)
+    } else {
+        fileListData.value = []
+    }
+}
+
+//上传文件
+const uploadChange = async ({type}) => {
+    if (type === 'success') {
+        uploadModal.value = false
+        renewData()
+    } else if (type === 'del') {
+        renewData()
+    }
+}
+
+//关闭上传附件窗口
+const uploadModalClose = () => {
+    uploadModal.value = false
+}
+
+//单个保存
+const tableFormSaveLoading = ref(false)
+const tableFormSaveClick = async (item, dragItem = null) => {
+    if (isStatus.value !== 3) {
+        tableFormSaveLoading.value = true
+        const isSave = await saveExcelBussData(item, dragItem)
+        if (isSave) {
+            await getBussPdfInfo(item, dragItem)
+            tableFormSaveLoading.value = false
+            renewData()
+        } else {
+            tableFormSaveLoading.value = false
+        }
+    } else {
+        window?.$message?.warning('已上报的资料,不允许保存。')
+    }
+}
+
+//保存表单数据
+const saveExcelBussData = async ({pkeyId}, dragItem = null, showTip = true) => {
+    setDragModalLoading(dragItem, '保存中...', true)
+    const refs = await getFormRef(pkeyId)
+    const isRegExp = await refs?.isFormRegExp()
+    if (isRegExp) {
+        const formData = refs?.getFormData()
+        const {error, code} = await wbsApi.saveExcelBussData(formData)
+        setDragModalLoading(dragItem)
+        if (!error && code === 200) {
+            if (showTip) {
+                window?.$message?.success('保存成功')
+            }
+            return true
+        } else {
+            return false
+        }
+    } else {
+        setDragModalLoading(dragItem)
+        return false
+    }
+}
+
+//预览PDF
+const getBussPdfInfo = async ({pkeyId}, dragItem = null, showTip = true) => {
+    setDragModalLoading(dragItem, '获取pdf中...', true)
+    const {error, code, data} = await wbsApi.getBussPdfInfo({
+        pkeyId: pkeyId
+    }, false)
+    setDragModalLoading(dragItem)
+    if (!error && code === 200) {
+        if (data) {
+            window.open(data, '_blank')
+        } else if (showTip) {
+            window?.$message?.warning('PDF错误')
+        }
+    } else {
+        if (showTip) {
+            window?.$message?.warning(data.msg || '获取PDF失败')
+        }
+    }
+}
+
+
+//通知数据更新
+const renewData = () => {
+    emit('renew')
+    ActiveKey.value = ''
+}
+
+//设置表单的加载状态
+const setDragModalLoading = (dragItem, text = '保存中...', show = false) => {
+    if (dragItem && show) {
+        dragItem.loading = true
+        dragItem.loadingText = text
+    }
+    if (dragItem && !show) {
+        dragItem.loading = false
+    }
+}
+
+//获取表单的ref
+const getFormRef = async (pkeyId) => {
+    const itemRef = itemRefs.value
+    const index = arrIndex(itemRef, 'pkeyId', pkeyId)
+    return itemRef[index].ref
+}
+
+//删除打开的窗口
+const delWindowRefs = (pkeyId) => {
+    //判断是否存在窗口,如果存在,就删除窗口
+    const list = DragModalTableForm.value
+    const index = arrIndex(list, 'pkeyId', pkeyId)
+    if (index !== -1) {
+        list.splice(index, 1)
+        DragModalTableForm.value = list
+    }
+}
+
+//计算展开高度和滚动位置
+const getOffsetTop = (key = '') => {
+    if (key) {
+        const dom = document.getElementById(key)
+        if (!draw_type.value) {
+            if (dom?.offsetTop >= 583 && key) {
+                emit('offsetTop', dom?.offsetTop - 583)
+            } else {
+                emit('offsetTop', dom?.offsetTop)
+            }
+        } else {
+            if (dom.offsetTop >= 424 && key) {
+                emit('offsetTop', dom?.offsetTop - 424)
+            } else {
+                emit('offsetTop', dom?.offsetTop)
+            }
+        }
+    } else {
+        emit('offsetTop', 0)
+    }
+    ActiveKey.value = key
+}
+
+//获取折叠面板的索引
+const getCollapseItemIndex = (name) => {
+    const keys = name.split('-')
+    if (keys.length > 0) {
+        return keys[1]
+    } else {
+        return -1
+    }
+}
+
+//获取表单的大小
+const getTableFormSize = (pkeyId) => {
+    let formId = `table-form-${pkeyId}`
+    try {
+        const {clientWidth, clientHeight} = document.getElementById(formId).children[0]
+        return {
+            width: (clientWidth + 40) + 'px',
+            height: (clientHeight + 80) + 'px'
+        }
+    } catch {
+        return {
+            width: '100%',
+            height: '100%'
+        }
+    }
+}
+
+//转字符串
+const setToString = (val) => {
+    return val ? val + '' : ''
+}
+
+//表单被点击
+const presentId = ref('')
+const excelTableFormClick = (key) => {
+    presentId.value = key
+}
+
+//缓存被激活时
+onActivated(() => {
+    setMountOnEventKey()
+})
+
+//缓存时被移除
+onDeactivated(() => {
+    HTableForm.unmountEventKey()
+})
+
+//页面被卸载
+onUnmounted(() => {
+    HTableForm.unmountEventKey()
+})
+
+const setMountOnEventKey = () => {
+    HTableForm.setOnEventKey({
+        //按下ctrl键 或 control 键
+        onCtrlDown: async () => {
+            //window.$HcLog('全局按键', '按下ctrl键 或 control 键')
+            const refs = await setOnFuncFormRef()
+            refs?.setIsCtrlKey(true)
+        },
+        //按下复制快捷键
+        onCtrlDownC: async (event) => {
+            //window.$HcLog('全局按键', '按下复制快捷键')
+            const refs = await setOnFuncFormRef()
+            refs?.setCopyKeyList(event)
+        },
+        //按下粘贴快捷键
+        onCtrlDownV: async (event) => {
+            //window.$HcLog('全局按键', '按下粘贴快捷键')
+            const refs = await setOnFuncFormRef()
+            await refs?.setPasteKeyList(event)
+        },
+        //放开ctrl键 或 control 键
+        onCtrlUp: async () => {
+            //window.$HcLog('全局按键', '放开ctrl键 或 control 键')
+            const refs = await setOnFuncFormRef()
+            refs?.setIsCtrlKey(false)
+        },
+    })
+}
+
+//获取表单的ref
+const setOnFuncFormRef = async () => {
+    const pkeyId = presentId.value
+    if (!isNullES(pkeyId)) {
+        return await getFormRef(pkeyId)
+    } else {
+        return;
+    }
+}
+
+
+//获取已渲染的表单
+const getFilterFormData = async () => {
+    const formArr = formDataList.value;
+    return formArr.filter((item) => {
+        return (item.pkeyId ?? '') !== '' && item.isCollapseLoad;
+    })
+}
+
+//获取表单数据
+const getFormData = async () => {
+    const formArr = await getFilterFormData();
+    //获取表单数据
+    let newArr = [];
+    for (let i = 0; i < formArr.length; i++) {
+        const pkeyId = formArr[i].pkeyId
+        const refs = await getFormRef(pkeyId)
+        const form = refs?.getFormData()
+        newArr.push({...form})
+    }
+    console.log('表单数据', newArr)
+    return newArr
+}
+
+//获取表单效验数据
+const getFormRegExpJson = async () => {
+    const formArr = await getFilterFormData();
+    const list = listDatas.value
+    //获取表单数据
+    let formRegExpJson = {};
+    for (let i = 0; i < formArr.length; i++) {
+        const pkeyId = formArr[i].pkeyId
+        const refs = await getFormRef(pkeyId)
+        const regExp = refs?.getRegExpJson()
+        const nodeName = refs?.getNodeName()
+        if (getObjVal(regExp)) {
+            const index = arrIndex(list, 'pkeyId', pkeyId)
+            formRegExpJson[pkeyId] = {
+                ...regExp,
+                itemId: `item-${index}-${pkeyId}`,
+                nodeName: nodeName
+            }
+        }
+    }
+    return formRegExpJson
+}
+
+//获取当前展开项
+const getActiveKey = () => {
+    return ActiveKey.value;
+}
+
+//设置当前展开项
+const setActiveKey = (key) => {
+    return ActiveKey.value = key;
+}
+
+// 暴露出去
+defineExpose({
+    getFormData,
+    getFormRegExpJson,
+    getActiveKey,
+    setActiveKey
+})
+</script>
+
+<style lang="scss" scoped>
+@import "../../components/collapse-form.scoped.scss";
+</style>
+
+<style lang="scss">
+@import "../../components/collapse-form.scss";
+</style>

+ 67 - 0
src/views/agree/tomb/form.vue

@@ -0,0 +1,67 @@
+<template>
+    <HcCard actionUi="text-center">
+        <el-scrollbar ref="listItemScrollRef">
+            <CollapseForm ref="ListItemsRef"
+                          :classify="0"
+                          :contractId="0"
+                          :datas="[]"
+                          :primaryKeyId="0"
+                          :status="0"
+                          v-if="false"
+            />
+            <HcStatus text="暂无表单"/>
+        </el-scrollbar>
+        <template #action>
+            <!--el-button size="large" type="info" hc-btn @click="goBackClick">
+                <HcIcon name="arrow-go-back"/>
+                <span>取消并返回</span>
+            </el-button-->
+            <el-button size="large" type="primary" hc-btn>
+                <HcIcon name="check-double"/>
+                <span>提交保存</span>
+            </el-button>
+            <el-button size="large" type="primary" hc-btn>
+                <HcIcon name="draft"/>
+                <span>一键生成协议</span>
+            </el-button>
+            <el-button size="large" type="warning" hc-btn>
+                <HcIcon name="eye"/>
+                <span>预览</span>
+            </el-button>
+            <el-button size="large" type="success" hc-btn>
+                <HcIcon name="upload"/>
+                <span>上传附件协议</span>
+            </el-button>
+        </template>
+    </HcCard>
+</template>
+
+<script setup>
+import {onActivated, ref} from "vue";
+import {useRoute, useRouter} from 'vue-router'
+import CollapseForm from "./collapse-form/index.vue"
+
+const router = useRouter()
+const useRoutes = useRoute()
+
+//初始变量
+//const dataType = ref(useRoutes?.query?.type ?? 'view')
+//const dataId = ref(useRoutes?.query?.id ?? '')
+
+//缓存页面被激活时
+onActivated(() => {
+    //dataType.value = useRoutes?.query?.type ?? 'view'
+    //dataId.value = useRoutes?.query?.id ?? ''
+})
+
+const listItemScrollRef = ref(null)
+
+//返回
+const goBackClick = () => {
+    router.back()
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 125 - 0
src/views/agree/tomb/tomb.vue

@@ -0,0 +1,125 @@
+<template>
+    <HcPageLayout>
+        <template #left>
+            <div class="hc-layout-tree-box">
+                <el-scrollbar>
+                    <HcTreeData @nodeTap="treeNodeTap"/>
+                </el-scrollbar>
+            </div>
+        </template>
+        <HcCard>
+            <template #header>
+                <div class="w-52">
+                    <el-input v-model="searchForm.queryValue" clearable placeholder="请输入名称进行查询" size="large"/>
+                </div>
+                <div class="ml-4">
+                    <el-button type="primary" @click="searchClick" size="large">
+                        <HcIcon name="search-2"/>
+                        <span>搜索</span>
+                    </el-button>
+                </div>
+            </template>
+            <template #extra>
+                <el-button size="large" type="primary" hc-btn @click="addRowClick">
+                    <HcIcon name="add"/>
+                    <span>新增</span>
+                </el-button>
+                <el-button size="large" type="warning" hc-btn>
+                    <HcIcon name="add"/>
+                    <span>打印</span>
+                </el-button>
+                <el-button size="large" type="danger" hc-btn>
+                    <HcIcon name="delete-bin"/>
+                    <span>删除</span>
+                </el-button>
+            </template>
+            <HcTable :column="tableColumn" :datas="tableData" :loading="tableLoading" isCheck @selection-change="tableSelectionChange">
+                <template #action="{row,index}">
+                    <el-button size="small" type="primary" @click="editRowClick(row)">修改</el-button>
+                    <el-button size="small" type="warning">查看</el-button>
+                    <el-button size="small" type="danger">删除</el-button>
+                </template>
+            </HcTable>
+            <template #action>
+                <HcPages :pages="searchForm" @change="pageChange"/>
+            </template>
+        </HcCard>
+    </HcPageLayout>
+</template>
+
+<script setup>
+import {ref} from "vue";
+import {useRouter} from 'vue-router'
+
+const router = useRouter()
+
+//树节点被点击
+const treeNodeTap = ({node, data}) => {
+
+}
+
+//搜索表单
+const searchForm = ref({
+    projectType: null, queryValue: null, startTime: null, endTime: null,
+    current: 1, size: 20, total: 0
+})
+
+//搜索
+const searchClick = () => {
+    searchForm.value.current = 1;
+    getTableData()
+}
+
+//分页被点击
+const pageChange = ({current, size}) => {
+    searchForm.value.current = current
+    searchForm.value.size = size
+    getTableData()
+}
+
+//获取数据
+const tableLoading = ref(false)
+const tableColumn = [
+    {key: 'key1', name: '协议编号'},
+    {key: 'key2', name: '协议书名称'},
+    {key: 'key3', name: '补偿总金额'},
+    {key: 'key4', name: '备注'},
+    {key: 'action', name: '操作', width: '190', align: 'center'},
+]
+const tableData = ref([
+    {id: 1, key1: 'xxxx', key2: 'xxxx', key3: '65632'},
+    {id: 2, key1: 'xxxx', key2: 'xxxx', key3: '65632'},
+    {id: 3, key1: 'xxxx', key2: 'xxxx', key3: '65632'},
+    {id: 4, key1: 'xxxx', key2: 'xxxx', key3: '65632'},
+])
+const getTableData = () => {
+
+}
+
+//多选事件
+const tableSelectionChange = (rows) => {
+    console.log(rows)
+}
+
+//新增
+const addRowClick = () => {
+    router.push({
+        name: 'lar-agree-tomb-form'
+    })
+}
+
+//编辑
+const editRowClick = (row) => {
+    router.push({
+        name: 'lar-agree-tomb-form'
+    })
+}
+</script>
+
+<style lang="scss" scoped>
+.hc-layout-tree-box {
+    position: relative;
+    height: 100%;
+    padding: 18px;
+}
+</style>