ZaiZai hace 6 meses
padre
commit
27591e99bf

+ 1 - 1
package.json

@@ -18,7 +18,7 @@
         "dayjs": "^1.11.12",
         "echarts": "^5.5.1",
         "element-plus": "2.8.0",
-        "hc-vue3-ui": "^4.1.7",
+        "hc-vue3-ui": "^4.1.8",
         "js-base64": "^3.7.7",
         "js-fast-way": "^0.5.6",
         "js-md5": "^0.8.3",

+ 40 - 0
src/api/modules/exctab/exceltab.js

@@ -254,4 +254,44 @@ export default {
             params: form,
         })
     },
+    //获取excel字段信息
+    async getElementInfoByTabId(form) {
+        return HcApi({
+            url: '/api/blade-manager/linkdatainfo/getElementInfoByTabId',
+            method: 'get',
+            params: form,
+        })
+    },
+    //提取未匹配的元素字段
+    async getUnMatchField(form) {
+        return HcApi({
+            url: '/api/blade-manager/exceltab/getUnMatchField',
+            method: 'get',
+            params: form,
+        })
+    },
+    //删除元素
+    async delExcelElement(form) {
+        return HcApi({
+            url: '/api/blade-manager/linkdatainfo/saveTabColInfo',
+            method: 'post',
+            data: form,
+        })
+    },
+    //保存匹配元素
+    async saveTabColInfo(form) {
+        return HcApi({
+            url: '/api/blade-manager/linkdatainfo/saveTabColInfo2',
+            method: 'post',
+            data: form,
+        })
+    },
+    //添加新元素
+    async submitBatch(form) {
+        return HcApi({
+            url: '/api/blade-manager/wbsFormElement/submit-batch',
+            method: 'post',
+            data: form,
+        })
+    },
 }

+ 3 - 9
src/components/table-form/modules/components.vue

@@ -112,23 +112,17 @@ const isSlots = !!slots.default
     height: 100%;
     .name-placeholder {
         display: block;
+        pointer-events: none;
     }
     .warning-icon {
         display: none;
         color: #101010;
         font-size: 18px;
+        pointer-events: none;
     }
     .slot-content {
         display: none;
-    }
-    &.warnstyle {
-        background: #fe641e;
-        .name-placeholder {
-            display: none;
-        }
-        .warning-icon {
-            display: block;
-        }
+        pointer-events: none;
     }
     &:hover {
         background: #eddac4;

+ 15 - 1
src/components/table-form/table-form.vue

@@ -1,5 +1,9 @@
 <template>
-    <div :id="`table-form-item-${uuid}`" :class="!isTableForm ? 'no-scroll-bar' : ''" class="hc-table-form-data-item h-full w-full">
+    <div
+        :id="`table-form-item-${uuid}`"
+        :class="[!isTableForm ? 'no-scroll-bar' : '', isWarn ? 'is-warn' : '']"
+        class="hc-table-form-data-item h-full w-full"
+    >
         <el-scrollbar class="table-form-item-scrollbar">
             <div :id="`table-form-${uuid}`" class="hc-excel-table-form" />
         </el-scrollbar>
@@ -20,6 +24,7 @@ const props = defineProps({
         type: Object,
         default: () => ({}),
     },
+    isWarn: Boolean,
 })
 
 //事件
@@ -219,5 +224,14 @@ defineExpose({
             user-select: none;
         }
     }
+    &.is-warn .hc-excel-table-form .warnstyle {
+        background: #fe641e;
+        .name-placeholder {
+            display: none;
+        }
+        .warning-icon {
+            display: block;
+        }
+    }
 }
 </style>

+ 325 - 24
src/views/project/list/edit-element.vue

@@ -2,7 +2,7 @@
     <hc-drawer v-model="isShow" ui="hc-project-list-edit-element-drawer" to-id="hc-layout-box" is-close @close="drawerClose">
         <hc-page-split :fold="false" :options="splitOptions">
             <template #left>
-                <hc-card :title="`【编辑元素】${dataInfo.tableName}`">
+                <hc-card :title="`【编辑元素】${dataInfo.tableName}`" :loading="tableFormLoading">
                     <template #search>
                         <div class="text-13px color-#f0720a">
                             <span>提示:鼠标右键功能:更换匹配元素字段、新增元素字段、删除匹配元素字段、公式配置</span>
@@ -11,13 +11,13 @@
                             <span class="text-green-6">绿色代表匹配成功</span>
                         </div>
                     </template>
-                    <hc-table-form ref="excelRef" :html="excelHtml" @tap="excelClick" />
+                    <hc-table-form ref="excelRef" :html="excelHtml" is-warn @tap="excelClick" />
                 </hc-card>
             </template>
             <hc-card scrollbar>
                 <template #header>
-                    <el-button type="warning">表单调整</el-button>
-                    <el-button type="primary">公式配置</el-button>
+                    <el-button type="warning" @click="formAdjustmentsClick">表单调整</el-button>
+                    <el-button type="primary" @click="formulaConfigClick">公式配置</el-button>
                 </template>
                 <template #extra>
                     <el-button @click="drawerClose">返回上一级</el-button>
@@ -27,15 +27,16 @@
                         <el-input v-model="formModel.colName" placeholder="请点击左侧表单" disabled />
                     </el-form-item>
                     <el-form-item label="将元素替换为:" prop="htmlType">
-                        <el-select v-model="formModel.htmlType" filterable block placeholder="输入元素名称搜索">
-                            <el-option label="个人证书" :value="2" />
-                            <el-option label="企业证书" :value="6" />
+                        <el-select v-model="formModel.htmlType" filterable block placeholder="输入元素名称搜索" @change="allElementChange">
+                            <template v-for="item in allElement" :key="item.id">
+                                <el-option :label="item.eName" :value="item.id" />
+                            </template>
                         </el-select>
                     </el-form-item>
                 </el-form>
                 <div class="action-btn-box mb-40px mt-10px text-center">
-                    <el-button type="primary" @click="savingClick">临时保存</el-button>
-                    <el-button type="danger" style="margin-left: 50px">删除文本</el-button>
+                    <el-button type="primary" :loading="submitLoading" @click="savingClick">临时保存</el-button>
+                    <el-button type="danger" style="margin-left: 50px" :loading="submitLoading" @click="delElement">删除元素</el-button>
                 </div>
                 <div class="hc-edit-element-collapse">
                     <el-collapse v-model="activeNames">
@@ -44,33 +45,69 @@
                                 <div class="hc-collapse-item-header hc-flex">
                                     <div class="title text-green">本次临时保存的元素</div>
                                     <div class="hc-extra-text-box">
-                                        <el-button type="success" size="small" :loading="submitLoading" @click="dialogSubmit">全部提交保存</el-button>
+                                        <el-button type="success" size="small" :loading="submitLoading" @click.stop="dialogSubmit">全部提交保存</el-button>
                                     </div>
                                 </div>
                             </template>
-                            1111
+                            <hc-table ref="formArrRef" :column="tableColumn" :is-index="false" :datas="formDataArr">
+                                <template #action="{ index }">
+                                    <el-link v-loading="submitLoading" type="danger" @click="rowDelClick(index)">删除</el-link>
+                                </template>
+                            </hc-table>
                         </el-collapse-item>
                         <el-collapse-item name="key2">
                             <template #title>
                                 <div class="hc-collapse-item-header hc-flex">
                                     <div class="title text-orange">未进行匹配的元素字段</div>
                                     <div class="hc-extra-text-box">
-                                        <el-button type="warning" size="small">新增元素</el-button>
+                                        <el-button type="warning" size="small" :loading="submitLoading" @click.stop="addElementClick">新增元素</el-button>
                                     </div>
                                 </div>
                             </template>
-                            1111
+                            <div class="hc-collapse-item-button">
+                                <template v-for="item in matchElement" :key="item.id">
+                                    <el-button type="warning" size="small" plain @click="matchElementClick(item)">{{ item.eName }}</el-button>
+                                </template>
+                                <hc-empty v-if="matchElement.length <= 0" />
+                            </div>
                         </el-collapse-item>
                     </el-collapse>
                 </div>
             </hc-card>
+
+            <!-- 添加新的元素字段 -->
+            <hc-dialog v-model="isAddElementShow" ui="hc-project-list-edit-element-add-element" widths="50rem" is-table title="添加新的元素字段" :padding="false" @close="addElementClose">
+                <template #extra>
+                    <el-button type="danger" size="small" @click="addElementData">新增元素</el-button>
+                </template>
+                <hc-table :column="addElementColumn" :datas="addElementTable" :is-current-row="false">
+                    <template #eName="{ row }">
+                        <hc-table-input v-model="row.eName" type="text" />
+                    </template>
+                    <template #eType="{ row }">
+                        <el-select v-model="row.eType" filterable block>
+                            <template v-for="item in dataType" :key="item.id">
+                                <el-option :label="item.label" :value="item.value" />
+                            </template>
+                        </el-select>
+                    </template>
+                    <template #action="{ index }">
+                        <el-link type="danger" @click="addElementDelClick(index)">删除</el-link>
+                    </template>
+                </hc-table>
+                <template #footer>
+                    <el-button hc-btn @click="addElementClose">取消</el-button>
+                    <el-button hc-btn type="primary" :loading="addElementLoading" @click="addElementSubmit">确定</el-button>
+                </template>
+            </hc-dialog>
         </hc-page-split>
     </hc-drawer>
 </template>
 
 <script setup>
 import { ref, watch } from 'vue'
-import { getArrValue, getObjValue, isNullES } from 'js-fast-way'
+import { arrIndex, deepClone, formValidate, getArrValue, getObjValue, isNullES } from 'js-fast-way'
+import { getDictionaryData } from '~uti/tools'
 import excelApi from '~api/exctab/exceltab'
 
 const props = defineProps({
@@ -85,7 +122,7 @@ const props = defineProps({
 })
 
 //事件
-const emit = defineEmits(['close'])
+const emit = defineEmits(['close', 'toPage'])
 
 //双向绑定
 const isShow = defineModel('modelValue', {
@@ -111,22 +148,68 @@ const splitOptions = { sizes: [70, 30], snapOffset: 0, minSize: [300, 300] }
 //处理相关数据
 const excelRef = ref(null)
 const excelHtml = ref('')
+const tableFormLoading = ref(true)
 const getDataApi = async () => {
     const { pkeyId, excelId } = getObjValue(dataInfo.value)
     if (isNullES(pkeyId) || isNullES(excelId)) {
+        tableFormLoading.value = false
         window?.$message.warning('表单值异常,请联系管理员')
         return
     }
+    tableFormLoading.value = true
     const { data } = await excelApi.getExcelHtml({ pkeyId })
     excelHtml.value = data || ''
+    await getElementInfoByTabId()
+    await getUnMatchField()
+    getDataType().then()
+    tableFormLoading.value = false
+}
+
+//获取全部元素
+const allElement = ref([])
+const getElementInfoByTabId = async () => {
+    const { initTableId } = getObjValue(dataInfo.value)
+    const { data } = await excelApi.getElementInfoByTabId({
+        tabId: initTableId,
+    })
+    allElement.value = getArrValue(data)
+}
+const allElementChange = () => {
+    const { htmlType } = formModel.value
+    let info = getObjValue(allElement.value.find((item) => item.id === htmlType))
+    formModel.value.htmlName = info.eName
+}
+
+//提取未匹配的元素字段
+const matchElement = ref([])
+const getUnMatchField = async () => {
+    const { pkeyId, initTableId } = getObjValue(dataInfo.value)
+    const { data } = await excelApi.getUnMatchField({
+        pkeyId: pkeyId,
+        tabId: initTableId,
+    })
+    matchElement.value = getArrValue(data)
+}
+
+//未匹配的元素
+const matchElementClick = ({ id, eName }) => {
+    if (submitLoading.value) return
+    formModel.value.htmlType = id
+    formModel.value.htmlName = eName
+}
+
+//元素数据类型
+const dataType = ref([])
+const getDataType = async () => {
+    dataType.value = await getDictionaryData('data_type')
 }
 
 //基础表单
 const formRef = ref(null)
 const formModel = ref({})
 const formRules = {
-    colName: { required: true, trigger: 'change', message: '请先获取元素坐标' },
-    htmlType: { required: true, trigger: 'change', message: '请先寻找将元素替换为' },
+    colName: { required: true, trigger: 'blur', message: '请先获取元素坐标' },
+    htmlType: { required: true, trigger: 'blur', message: '请先寻找将元素替换为' },
 }
 
 //元素被点击
@@ -138,14 +221,21 @@ const excelClick = async (item) => {
     }
     const data = await getDomAttribute(item)
     const arr = getArrValue(formDataArr.value)
-    console.log(data)
-    /*formModel.value = {
+    const index = arrIndex(arr, 'key', data.key)
+    const colName = data.name || data.text || data.key
+    const htmlType = index !== -1 ? arr[index].htmlType : ''
+    const htmlName = index !== -1 ? arr[index].htmlName : ''
+    const type = index !== -1 ? arr[index].type : ''
+    formModel.value = {
         tabId: pkeyId,
-        colName: data.name || data.key,
+        colName: colName,
+        key: data.key,
         tdIndex: data.td,
         trIndex: data.tr,
-        htmlType: '',
-    }*/
+        htmlType: htmlType,
+        htmlName: htmlName,
+        type: type,
+    }
 }
 
 //获取元素相关信息
@@ -171,10 +261,62 @@ const getAttribute = async (dom, key) => {
     }
 }
 
+//临时保存表格
+const formArrRef = ref(null)
+const tableColumn = [
+    { key: 'colName', name: '元素坐标', minWidth: 120 },
+    { key: 'key', name: '元素位置', minWidth: 120 },
+    { key: 'htmlName', name: '匹配元素', minWidth: 120 },
+    { key: 'tdIndex', name: 'td', align: 'center', width: 60 },
+    { key: 'trIndex', name: 'tr', align: 'center', width: 60 },
+    { key: 'action', name: '操作', align: 'center', width: 70, fixed: 'right' },
+]
+
 //临时保存
 const formDataArr = ref([])
-const savingClick = () => {
+const savingClick = async () => {
+    const res = await formValidate(formRef.value)
+    if (!res) return
+    const form = deepClone(formModel.value)
+    const arr = formDataArr.value
+    const index = arrIndex(arr, 'key', form.key)
+    const obj = { ...form, type: '' }
+    if (index !== -1) {
+        arr[index] = obj
+    } else {
+        arr.push(obj)
+    }
+    formModel.value = {}
+    window?.$message.success('临时保存成功,记得点击下方的全部提交保存')
+}
 
+//删除元素
+const delElement = () => {
+    const form = deepClone(formModel.value)
+    if (isNullES(form.tdIndex) || isNullES(form.trIndex)) {
+        window?.$message.warning('请先在左侧表单,点击要删除的元素')
+        return
+    }
+    const arr = formDataArr.value
+    const index = arrIndex(arr, 'key', form.key)
+    const obj = {
+        ...form,
+        htmlType: null,
+        htmlName: '删除元素',
+        type: 'del',
+    }
+    if (index !== -1) {
+        arr[index] = obj
+    } else {
+        arr.push(obj)
+    }
+    formModel.value = {}
+    window?.$message.success('当前操作临时保存,记得点击下方的全部提交保存')
+}
+
+//删除临时保存
+const rowDelClick = (index) => {
+    formDataArr.value.splice(index, 1)
 }
 
 //折叠面板
@@ -183,12 +325,157 @@ const activeNames = ref(['key2'])
 //保存数据
 const submitLoading = ref(false)
 const dialogSubmit = async () => {
-    console.log(formModel.value)
+    const arr = deepClone(formDataArr.value)
+    if (arr.length <= 0) {
+        window?.$message.warning('请先添加要操作的元素')
+        return
+    }
+    //数据分类
+    submitLoading.value = true
+    let matchArr = [], delArr = []
+    for (let i = 0; i < arr.length; i++) {
+        if (arr[i].type === 'del') {
+            delArr.push({
+                ...arr[i],
+                colName: '/',
+                htmlName: null,
+                type: null,
+            })
+        } else {
+            matchArr.push({
+                ...arr[i],
+                colName: '',
+                htmlName: null,
+                type: null,
+            })
+        }
+    }
+    await matchElementApi(matchArr)
+    await delElementApi(delArr)
+    window?.$message.success('操作完成')
+    clearFormData()
+    await getDataApi()
+    submitLoading.value = false
+}
+
+//匹配元素
+const matchElementApi = async (data) => {
+    if (data.length <= 0) return
+    await excelApi.saveTabColInfo(data)
+}
+
+//删除元素
+const delElementApi = async (arr, batchSize = 3) => {
+    if (arr.length <= 0) return
+    const totalBatches = Math.ceil(arr.length / batchSize)
+    let totalSuccess = 0, totalFail = 0
+    for (let i = 0; i < arr.length; i += batchSize) {
+        const batch = arr.slice(i, i + batchSize)
+        const currentBatch = Math.floor(i / batchSize) + 1
+        console.log(`正在处理第 ${currentBatch}/${totalBatches} 批`)
+
+        const results = await Promise.all(batch.map(async element => {
+            try {
+                await excelApi.delExcelElement(element)
+                return { element, success: true }
+            } catch (error) {
+                console.error(`删除元素 ${element} 时发生错误:`, error)
+                return { element, success: false, error }
+            }
+        }))
+        const batchSuccess = results.filter(r => r.success).length
+        const batchFail = results.filter(r => !r.success).length
+        totalSuccess += batchSuccess
+        totalFail += batchFail
+
+        console.log(`第 ${currentBatch} 批处理结果: 成功 ${batchSuccess}, 失败 ${batchFail}`)
+    }
+    console.log(`总计处理结果: 成功 ${totalSuccess}, 失败 ${totalFail}`)
+}
+
+//新增元素
+const isAddElementShow = ref(false)
+const addElementClick = () => {
+    addElementTable.value = [{ eName: '', eType: null }]
+    addElementLoading.value = false
+    isAddElementShow.value = true
+}
+
+//表格数据
+const addElementColumn = [
+    { key: 'eName', name: '清表元素名称' },
+    { key: 'eType', name: '元素数据类型' },
+    { key: 'action', name: '操作', align: 'center', width: 70, fixed: 'right' },
+]
+const addElementTable = ref([])
+
+//新增数据
+const addElementData = () => {
+    addElementTable.value.push({ eName: '', eType: null })
+}
+
+//删除
+const addElementDelClick = (index) => {
+    addElementTable.value.splice(index, 1)
+}
+
+//提交新增
+const addElementLoading = ref(false)
+const addElementSubmit = async () => {
+    const arr = addElementTable.value
+    if (arr.length <= 0) {
+        window?.$message.warning('请先添加元素字段')
+        return
+    }
+    const isValid = arr.every(val => val.eName && val.eType)
+    if (!isValid) {
+        window?.$message.warning('请先完善数据')
+        return
+    }
+    const { pid } = getObjValue(editData.value)
+    const { pkeyId, initTableName } = getObjValue(dataInfo.value)
+    const { isRes } = await excelApi.submitBatch({
+        projectId: pid,
+        initTableName,
+        id: pkeyId,
+        listData: arr,
+    })
+    if (!isRes) return
+    window?.$message.success('操作完成')
+    addElementClose()
+    await getElementInfoByTabId()
+    await getUnMatchField()
+}
+
+//关闭新增元素
+const addElementClose = () => {
+    isAddElementShow.value = false
+    addElementLoading.value = false
+    addElementTable.value = []
+}
+
+//表单调整
+const formAdjustmentsClick = () => {
+    drawerClose()
+    emit('toPage', 'adjustment')
+}
+
+//公式配置
+const formulaConfigClick = () => {
+    drawerClose()
+    emit('toPage', 'formula')
+}
+
+//清空数据
+const clearFormData = () => {
+    formModel.value = {}
+    formDataArr.value = []
 }
 
 //关闭抽屉
 const drawerClose = () => {
     isShow.value = false
+    clearFormData()
     emit('close')
 }
 </script>
@@ -236,5 +523,19 @@ const drawerClose = () => {
             }
         }
     }
+    .hc-collapse-item-button {
+        position: relative;
+        .el-button {
+            margin-right: 10px;
+            margin-bottom: 5px;
+        }
+        .el-button + .el-button {
+            margin-left: 0;
+        }
+    }
+}
+.el-overlay-dialog .el-dialog.hc-new-dialog.hc-project-list-edit-element-add-element {
+    height: calc(100% - 300px);
+    --el-dialog-margin-top: 150px;
 }
 </style>

+ 16 - 1
src/views/project/list/wbs-tree.vue

@@ -121,7 +121,7 @@
         <!-- 关联清表 -->
         <HcAssociationList v-model="isAssociationShow" :info="associationInfo" @change="getInfoTableData" />
         <!-- 编辑元素 -->
-        <HcEditElement v-model="isEditElementShow" :info="editElementInfo" :data="editElementData" />
+        <HcEditElement v-model="isEditElementShow" :info="editElementInfo" :data="editElementData" @to-page="editElementToPage" />
         <!-- 调整表单 -->
         <HcAdjustExcel v-model="isAdjustExcelShow" :info="adjustExcelInfo" />
     </hc-drawer>
@@ -657,6 +657,21 @@ const adjustExcelClick = async (row) => {
     isAdjustExcelShow.value = true
 }
 
+//编辑元素里的跳转页面
+const editElementToPage = async (name) => {
+    const row = deepClone(editElementInfo.value)
+    //表单调整
+    if (name === 'adjustment') {
+        adjustExcelInfo.value = deepClone(row)
+        await nextTick()
+        isAdjustExcelShow.value = true
+    }
+    //公式配置
+    if (name === 'formula') {
+        console.log('还没做')
+    }
+}
+
 //离开了当前页面
 onDeactivated(() => {
     isAdjustExcelShow.value = false

+ 4 - 4
yarn.lock

@@ -2017,10 +2017,10 @@ has-flag@^4.0.0:
   resolved "http://39.108.216.210:9000/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
   integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
 
-hc-vue3-ui@^4.1.7:
-  version "4.1.7"
-  resolved "http://39.108.216.210:9000/hc-vue3-ui/-/hc-vue3-ui-4.1.7.tgz#b9f7e6b2ba272e9d8574466535924ba9cbae5eb8"
-  integrity sha512-rIKsZcsg627trBP5g24lgyy2u1tr+q4n73xZcUF8fVHg/G1rp9DD6NtzWnpeHhfbnc8EZV2ti3numxMBbKXrkA==
+hc-vue3-ui@^4.1.8:
+  version "4.1.8"
+  resolved "http://39.108.216.210:9000/hc-vue3-ui/-/hc-vue3-ui-4.1.8.tgz#d3435cd91933eda5541ff37147e4a8d8bd320dc5"
+  integrity sha512-CO4DrbA+rKWw7Hu25/OeK34Qm74x6wHM060GAHMiaR93+4RD6lad7rPM7yH4ykscW1bl9O8PiAEo2Id8Y/71rQ==
   dependencies:
     axios "^1.7.4"
     dayjs "^1.11.12"