|
|
@@ -1,5 +1,7 @@
|
|
|
<template>
|
|
|
<CustomDialog
|
|
|
+ overflow
|
|
|
+ destroy-on-close
|
|
|
:model-value="modelValue"
|
|
|
title="费用计算"
|
|
|
width="600px"
|
|
|
@@ -36,7 +38,7 @@
|
|
|
<el-table-column label="说明" prop="description"/>
|
|
|
</el-table>
|
|
|
<div class="button-box" v-if="enterDataList.length > 0">
|
|
|
- <el-button type="primary" @click="handleCalcFee">费用计算</el-button>
|
|
|
+ <el-button type="primary" @click="handleCalcFee" :disabled="!templateReady" :loading="!templateReady">费用计算</el-button>
|
|
|
</div>
|
|
|
<div v-else class="empty-text">
|
|
|
暂无输入项
|
|
|
@@ -77,11 +79,11 @@
|
|
|
项目检验费用
|
|
|
</div>
|
|
|
<el-form ref="feeFormRef" :model="feeForm" :rules="feeFormRules" label-width="160px">
|
|
|
- <el-form-item label="自动计算总费用:" prop="" v-if="!isBatch">
|
|
|
+ <el-form-item label="自动计算总费用:" prop="">
|
|
|
{{ isManualInput ? '项目未设置计算规则,请手动填入实际费用' : feeForm.totalCost }}
|
|
|
</el-form-item>
|
|
|
<el-form-item label="历史收费:" prop="">
|
|
|
- {{ templateInfo.fee || 0 }}
|
|
|
+ {{ oldFee || 0 }}
|
|
|
</el-form-item>
|
|
|
<el-form-item label="实际费用:" prop="actualFee">
|
|
|
<el-input-number v-model="feeForm.actualFee" :controls="false" :precision="2" :min="0"
|
|
|
@@ -94,10 +96,10 @@
|
|
|
<el-button type="default" @click="handleClose">取消</el-button>
|
|
|
</template>
|
|
|
</CustomDialog>
|
|
|
- <!-- 费用计算模板:不在页面显示 -->
|
|
|
+ <!-- 费用计算模板:弹窗关闭时销毁,避免 DOM 残留 -->
|
|
|
<Teleport to="body">
|
|
|
<Designer
|
|
|
- v-if="!isManualInput"
|
|
|
+ v-if="!isManualInput && modelValue"
|
|
|
id="gc-designer-container"
|
|
|
ref="spreadDesignerRef"
|
|
|
@designer-initialized="handleDesignerInit"
|
|
|
@@ -105,16 +107,11 @@
|
|
|
</Teleport>
|
|
|
</template>
|
|
|
<script setup>
|
|
|
-import {nextTick} from 'vue'
|
|
|
-import {ElMessage} from 'element-plus'
|
|
|
+import { nextTick } from 'vue'
|
|
|
+import { ElMessage } from 'element-plus'
|
|
|
import CustomDialog from '@/components/CustomDialog/index.vue'
|
|
|
-import {BoilerTaskOrderApi} from '@/api/pressure2/boilertaskorder'
|
|
|
-import {buildFileUrl} from '@/utils'
|
|
|
-import axios from 'axios'
|
|
|
-import {is} from '@/utils/is'
|
|
|
|
|
|
// 导入葡萄城相关依赖
|
|
|
-//import '@grapecity/spread-sheets-resources-zh'
|
|
|
import '@grapecity-software/spread-sheets-designer-resources-cn'
|
|
|
import '@grapecity-software/spread-sheets/styles/gc.spread.sheets.excel2013white.css'
|
|
|
import '@grapecity-software/spread-sheets-designer/styles/gc.spread.sheets.designer.min.css'
|
|
|
@@ -128,30 +125,32 @@ import '@grapecity-software/spread-sheets-tablesheet'
|
|
|
import '@grapecity-software/spread-sheets-designer'
|
|
|
import Designer from '@grapecity-software/spread-sheets-designer-vue'
|
|
|
import GC from "@/components/SpreadDesigner/tools/gc";
|
|
|
-import {BoilerConnectRecordReportApi} from "@/api/pressure2/boilerconnectrecordreport";
|
|
|
-import {DynamicTbFeeColApi} from "@/api/pressure2/dynamictbfeecol";
|
|
|
+import { BoilerConnectRecordReportApi } from "@/api/pressure2/boilerconnectrecordreport";
|
|
|
+import { DynamicTbFeeColApi } from "@/api/pressure2/dynamictbfeecol";
|
|
|
|
|
|
const props = defineProps({
|
|
|
modelValue: {
|
|
|
type: Boolean,
|
|
|
required: true
|
|
|
},
|
|
|
+ // 项目ID(对应检验项目的 connectId / templateId)
|
|
|
projectId: {
|
|
|
type: String,
|
|
|
required: true
|
|
|
},
|
|
|
- oldAmount: {
|
|
|
+ // 历史费用
|
|
|
+ oldFee: {
|
|
|
type: Number,
|
|
|
+ default: 0
|
|
|
},
|
|
|
- newAmount: {
|
|
|
- type: Number,
|
|
|
- },
|
|
|
- feeCalculateJson:{
|
|
|
- type:Object
|
|
|
- },
|
|
|
+ // 已有的费用计算 JSON 字符串(用于回显之前保存的计算参数)
|
|
|
+ feeCalculateJsonStr: {
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ }
|
|
|
})
|
|
|
|
|
|
-const emit = defineEmits(['update:modelValue', 'refresh', 'save'])
|
|
|
+const emit = defineEmits(['update:modelValue', 'save'])
|
|
|
|
|
|
const loading = ref(true)
|
|
|
const enterFormRef = ref(null)
|
|
|
@@ -169,16 +168,24 @@ const enterFormRules = ref({})
|
|
|
const outputFormRules = ref({})
|
|
|
const feeFormRules = ref({})
|
|
|
|
|
|
-
|
|
|
const enterDataList = ref([])
|
|
|
-
|
|
|
const outputDataList = ref([])
|
|
|
|
|
|
-const isReport = ref(false)
|
|
|
-
|
|
|
let designer = null
|
|
|
const spreadDesignerRef = ref(null)
|
|
|
-// 费用模板初始化
|
|
|
+const templateReady = ref(false)
|
|
|
+
|
|
|
+// 解析已有的费用计算 JSON
|
|
|
+const feeCalculateJson = computed(() => {
|
|
|
+ if (!props.feeCalculateJsonStr) return {}
|
|
|
+ try {
|
|
|
+ return JSON.parse(props.feeCalculateJsonStr)
|
|
|
+ } catch (e) {
|
|
|
+ return {}
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 费用模板初始化(Designer 就绪时回调)
|
|
|
const handleDesignerInit = async (instance) => {
|
|
|
designer = instance
|
|
|
|
|
|
@@ -192,62 +199,101 @@ const handleDesignerInit = async (instance) => {
|
|
|
)
|
|
|
designModeCommand.execute(designer)
|
|
|
|
|
|
- await handleGetPressureReportTemplateInfo()
|
|
|
- // 加载费用计算模板
|
|
|
+ // 等待模板数据加载完成后再渲染公式模板
|
|
|
+ await waitForTemplateData()
|
|
|
await handleRenderFormulaTemplate()
|
|
|
+
|
|
|
+ // 获取费用计算字段
|
|
|
+ await handleQueryCheckItemCalcPreFillField()
|
|
|
+ templateReady.value = true
|
|
|
+
|
|
|
+ // 已有历史输入值 → 打开弹窗时自动触发一次计算
|
|
|
+ if (enterDataList.value.length > 0) {
|
|
|
+ nextTick(() => handleCalcFee())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 等待 fetchTemplateData 加载完成(handleGetTemplateInfo 可能还没返回)
|
|
|
+const waitForTemplateData = () => {
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ const check = () => {
|
|
|
+ // templateDataLoaded 在 handleGetTemplateInfo 完成后设为 true
|
|
|
+ if (templateDataLoaded.value) {
|
|
|
+ resolve()
|
|
|
+ } else {
|
|
|
+ setTimeout(check, 100)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ check()
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
// 获取费用模板信息
|
|
|
const fetchTemplateData = ref({})
|
|
|
-const combineTemplateInfo = computed(() => ({...props.templateInfo, ...fetchTemplateData.value}))
|
|
|
-const feeCalculateJson = computed(() => !props.templateInfo.feeCalculateJson ? {} : JSON.parse(props.templateInfo.feeCalculateJson))
|
|
|
-
|
|
|
-// 是否手动录入 true是,false否
|
|
|
-const isManualInput = computed(() => combineTemplateInfo.value.feeCalcType === '1')
|
|
|
-const handleGetPressureReportTemplateInfo = async () => {
|
|
|
- const byTemplateId = await BoilerConnectRecordReportApi.getByTemplateId({
|
|
|
- templateId: props.projectId,
|
|
|
- type: 'project'
|
|
|
- });
|
|
|
- if (byTemplateId.length !== 1) {
|
|
|
- ElMessage.error('获取费用模板信息失败')
|
|
|
+const templateDataLoaded = ref(false)
|
|
|
+
|
|
|
+// 费用计算类型:'1' = 手动录入, feeCalcType 为空 或 feeFileUrl 为空 = 走手动模式
|
|
|
+const isManualInput = computed(() =>
|
|
|
+ fetchTemplateData.value.feeCalcType === '1' ||
|
|
|
+ !fetchTemplateData.value.feeCalcType ||
|
|
|
+ !fetchTemplateData.value.feeFileUrl
|
|
|
+)
|
|
|
+
|
|
|
+const handleGetTemplateInfo = async () => {
|
|
|
+ try {
|
|
|
+ const byTemplateId = await BoilerConnectRecordReportApi.getByTemplateId({
|
|
|
+ templateId: props.projectId,
|
|
|
+ type: 'project'
|
|
|
+ });
|
|
|
+ if (byTemplateId.length !== 1) {
|
|
|
+ ElMessage.error('获取费用模板信息失败')
|
|
|
+ }
|
|
|
+ fetchTemplateData.value = byTemplateId[0] || {}
|
|
|
+
|
|
|
+ // 手动录入模式 或 未配置费用计算模板:需手动填入费用,不走 Designer
|
|
|
+ if (isManualInput.value) {
|
|
|
+ // 优先用已有计算值,其次用项目本身的 fee
|
|
|
+ const projectFee = fetchTemplateData.value.fee
|
|
|
+ feeForm.value = {
|
|
|
+ actualFee: feeCalculateJson.value['actualFee'] || feeCalculateJson.value['总费用'] || projectFee || 0,
|
|
|
+ totalCost: feeCalculateJson.value['totalCost'] || feeCalculateJson.value['总费用'] || projectFee || 0
|
|
|
+ }
|
|
|
+ loading.value = false
|
|
|
+ templateReady.value = true
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取费用模板信息失败:', error)
|
|
|
+ } finally {
|
|
|
+ templateDataLoaded.value = true
|
|
|
}
|
|
|
- fetchTemplateData.value = byTemplateId[0]
|
|
|
}
|
|
|
|
|
|
// 渲染费用计算模板
|
|
|
const handleRenderFormulaTemplate = async () => {
|
|
|
const spread = designer?.getWorkbook()
|
|
|
- if (isManualInput.value){
|
|
|
+ if (isManualInput.value) {
|
|
|
return
|
|
|
}
|
|
|
- if (!combineTemplateInfo.value.feeFileUrl) {
|
|
|
- console.warn('公式模板URL不存在')
|
|
|
- ElMessage.warning('费用计算模板未配置,请联系管理员')
|
|
|
+ if (!fetchTemplateData.value.feeFileUrl) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
- const newFileUrl = (combineTemplateInfo.value.feeFileUrl || '').startsWith('http')
|
|
|
- ? combineTemplateInfo.value.feeFileUrl
|
|
|
- : import.meta.env.VITE_FILE_URL + '/' + combineTemplateInfo.value.feeFileUrl
|
|
|
+ const fileUrl = fetchTemplateData.value.feeFileUrl
|
|
|
+ const newFileUrl = fileUrl.startsWith('http')
|
|
|
+ ? fileUrl
|
|
|
+ : import.meta.env.VITE_FILE_URL + '/' + fileUrl
|
|
|
|
|
|
- // 直接使用 fetch 获取 JSON 数据
|
|
|
const response = await fetch(newFileUrl)
|
|
|
if (!response.ok) {
|
|
|
throw new Error(`加载报表失败: ${response.status} ${response.statusText}`)
|
|
|
}
|
|
|
|
|
|
- // 不严格检查 content-type,尝试直接解析 JSON
|
|
|
const text = await response.text()
|
|
|
-
|
|
|
try {
|
|
|
const json = JSON.parse(text)
|
|
|
- // 使用 fromJSON 方法加载 JSON 数据
|
|
|
spread.fromJSON(json)
|
|
|
- // 让活动工作表的单元格全部失焦
|
|
|
spread.getActiveSheet().setActiveCell(null);
|
|
|
- ElMessage.success('费用计算模板加载成功')
|
|
|
} catch (jsonError) {
|
|
|
console.error('JSON 解析失败:', jsonError)
|
|
|
throw new Error('报表文件格式错误:不是有效的 JSON 格式')
|
|
|
@@ -268,51 +314,43 @@ const handleQueryCheckItemCalcPreFillField = async () => {
|
|
|
|
|
|
allCalcPreFillField.value = fields || []
|
|
|
|
|
|
- // 保存所有的输入项 (colType为INPUT或NUMBER的为输入项)
|
|
|
+ // 保存所有的输入项 (colType为INPUT或NUMBER的为输入项),回显已有值
|
|
|
enterDataList.value = (fields || []).filter(field => field.colType === 'INPUT' || field.colType === 'NUMBER').map(field => ({
|
|
|
inputText: feeCalculateJson.value[field.code] || field.defaultValue || '',
|
|
|
...field
|
|
|
}))
|
|
|
|
|
|
- // 保存所有的输出项 (colType为TOTAL或OUTPUT的为输出项)
|
|
|
+ // 保存所有的输出项 (colType为TOTAL或OUTPUT的为输出项),回显已有值
|
|
|
outputDataList.value = JSON.parse(JSON.stringify((fields || []).filter(field => field.colType === 'TOTAL' || field.colType === 'OUTPUT').map(field => ({
|
|
|
inputText: feeCalculateJson.value[field.code] || '',
|
|
|
...field
|
|
|
}))))
|
|
|
|
|
|
- // 获取所有需要field的code(包含输入项和输出项等所有字段),转成{[code]: ''}格式,set到费用模板中
|
|
|
+ // 获取所有field的code,转成{[code]: ''}格式,用于set到费用模板中
|
|
|
allCalcPreFillFieldKeyValue.value = Object.fromEntries(allCalcPreFillField.value.map(item => ([
|
|
|
item.code,
|
|
|
feeCalculateJson.value[item.code] || item.defaultValue || ''
|
|
|
])))
|
|
|
-
|
|
|
} catch (error) {
|
|
|
console.error('获取费用计算字段报错了', error)
|
|
|
} finally {
|
|
|
loading.value = false
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
onMounted(() => {
|
|
|
- // 获取费用计算字段
|
|
|
- if (!isManualInput.value){
|
|
|
- handleQueryCheckItemCalcPreFillField()
|
|
|
- }
|
|
|
- if (combineTemplateInfo.value.feeCalcType === '1'){
|
|
|
- feeForm.value = {
|
|
|
- actualFee: feeCalculateJson.value['actualFee'] || feeCalculateJson.value['总费用'] || 0,
|
|
|
- totalCost: feeCalculateJson.value['totalCost'] || feeCalculateJson.value['总费用'] || 0
|
|
|
- }
|
|
|
- }else {
|
|
|
- console.log(combineTemplateInfo)
|
|
|
- feeForm.value = {
|
|
|
- actualFee: combineTemplateInfo.value.fee,
|
|
|
- totalCost: combineTemplateInfo.value.fee
|
|
|
- }
|
|
|
- }
|
|
|
+ // 获取费用模板元数据(feeCalcType、feeFileUrl 等)
|
|
|
+ // 手动模式的 feeForm 会在 handleGetTemplateInfo 中设置
|
|
|
+ handleGetTemplateInfo()
|
|
|
})
|
|
|
|
|
|
// 自动计算费用
|
|
|
const handleCalcFee = () => {
|
|
|
+ if (!templateReady.value) {
|
|
|
+ ElMessage.warning('费用计算模板正在加载中,请稍后再试')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
if (!designer || !spreadDesignerRef.value) {
|
|
|
console.error('SpreadDesigner 未初始化')
|
|
|
ElMessage.error('费用计算模板未初始化,请刷新页面重试')
|
|
|
@@ -326,7 +364,7 @@ const handleCalcFee = () => {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- // 如果 bindingPathSchema 不存在,使用默认值
|
|
|
+ // 构建绑定路径模式
|
|
|
let bindingPathSchema = {
|
|
|
"dataFields": allCalcPreFillField.value.map(item => ({
|
|
|
"name": item.code,
|
|
|
@@ -338,7 +376,6 @@ const handleCalcFee = () => {
|
|
|
// 确保输入数据是数字类型
|
|
|
const entryData = Object.fromEntries(enterDataList.value.map(item => {
|
|
|
let value = item.inputText
|
|
|
- // 对于数值类型的字段,确保转换为数字
|
|
|
if (item.colType === 'NUMBER' || item.valType === 'NUMBER') {
|
|
|
value = Number(value) || '0'
|
|
|
}
|
|
|
@@ -360,36 +397,26 @@ const handleCalcFee = () => {
|
|
|
}
|
|
|
|
|
|
console.log('计算数据源:', combineDataSource)
|
|
|
- console.log('绑定路径模式:', bindingPathSchema)
|
|
|
|
|
|
- // 使用葡萄城标准 API 计算费用
|
|
|
try {
|
|
|
console.log('开始计算费用...')
|
|
|
|
|
|
- // 获取 Spread 工作簿
|
|
|
const spread = designer.getWorkbook()
|
|
|
if (!spread) {
|
|
|
throw new Error('无法获取 Spread 工作簿')
|
|
|
}
|
|
|
|
|
|
- // 获取活动工作表
|
|
|
const sheet = spread.getActiveSheet()
|
|
|
if (!sheet) {
|
|
|
throw new Error('无法获取活动工作表')
|
|
|
}
|
|
|
|
|
|
- // 确保模板已经加载
|
|
|
- if (!combineTemplateInfo.value.feeFileUrl) {
|
|
|
- throw new Error('费用计算模板未配置')
|
|
|
- }
|
|
|
-
|
|
|
- // 准备符合葡萄城要求的数据源
|
|
|
console.log('数据源:', combineDataSource)
|
|
|
try {
|
|
|
- sheet.setDataSource( new GC.Spread.Sheets.Bindings.CellBindingSource(combineDataSource))
|
|
|
+ sheet.setDataSource(new GC.Spread.Sheets.Bindings.CellBindingSource(combineDataSource))
|
|
|
console.log('设置数据源成功')
|
|
|
} catch (e) {
|
|
|
- ElMessage.error('设置数据源失败,尝试其他方法:', e.message)
|
|
|
+ ElMessage.error('设置数据源失败: ' + e.message)
|
|
|
}
|
|
|
|
|
|
// 强制重新计算
|
|
|
@@ -398,24 +425,16 @@ const handleCalcFee = () => {
|
|
|
// 获取计算结果
|
|
|
nextTick(() => {
|
|
|
try {
|
|
|
- // 获取计算结果
|
|
|
let getDataSource = {}
|
|
|
|
|
|
- // 尝试从工作表获取数据源
|
|
|
getDataSource = sheet.getDataSource().rT
|
|
|
console.log('从工作表获取数据源成功:', getDataSource)
|
|
|
|
|
|
- // 检查是否有计算结果
|
|
|
if (!getDataSource || (Array.isArray(getDataSource) && getDataSource.length === 0) || (typeof getDataSource === 'object' && Object.keys(getDataSource).length === 0)) {
|
|
|
throw new Error('计算结果为空')
|
|
|
}
|
|
|
|
|
|
- // 检查是否有计算结果
|
|
|
- if (Object.keys(getDataSource).length === 0) {
|
|
|
- throw new Error('计算结果为空')
|
|
|
- }
|
|
|
-
|
|
|
- // 检查总费用
|
|
|
+ // 获取总费用
|
|
|
const totalCostItem = allCalcPreFillField.value.find(x => x.colType === 'TOTAL')
|
|
|
const totalCostCode = totalCostItem?.code || '总费用'
|
|
|
const calculatedTotal = getDataSource[totalCostCode] || 0
|
|
|
@@ -437,13 +456,11 @@ const handleCalcFee = () => {
|
|
|
|
|
|
console.log('计算的总费用:', calculatedTotal)
|
|
|
ElMessage.success('费用计算完成')
|
|
|
-
|
|
|
} catch (error) {
|
|
|
console.error('获取计算结果时出错:', error)
|
|
|
ElMessage.error('费用计算失败:' + (error.message || '未知错误'))
|
|
|
}
|
|
|
})
|
|
|
-
|
|
|
} catch (error) {
|
|
|
console.error('计算费用时出错:', error)
|
|
|
ElMessage.error('费用计算失败:' + (error.message || '未知错误'))
|
|
|
@@ -455,9 +472,8 @@ const handleSave = () => {
|
|
|
const inputParams = Object.fromEntries(enterDataList.value.map(item => ([item.code, item.inputText])))
|
|
|
const outputParams = Object.fromEntries(outputDataList.value.map(item => ([item.code, item.inputText])))
|
|
|
emit('save', {
|
|
|
- ...props.templateInfo,
|
|
|
fee: feeForm.value.actualFee || 0,
|
|
|
- feeCalculateJson: JSON.stringify({...inputParams, ...feeForm.value, ...outputParams})
|
|
|
+ feeCalculateJson: JSON.stringify({ ...inputParams, totalCost: feeForm.value.totalCost, actualFee: feeForm.value.actualFee, ...outputParams })
|
|
|
})
|
|
|
handleClose()
|
|
|
}
|
|
|
@@ -466,7 +482,6 @@ const handleSave = () => {
|
|
|
const handleClose = () => {
|
|
|
emit('update:modelValue', false)
|
|
|
}
|
|
|
-
|
|
|
</script>
|
|
|
<style lang="scss" scoped>
|
|
|
.content-title {
|