| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545 |
- <template>
- <CustomDialog
- :model-value="modelValue"
- title="费用计算"
- width="600px"
- :before-close="handleClose"
- class="calcCheckItemDialog"
- v-loading="loading"
- >
- <div class="content" v-if="!isManualInput">
- <div class="content-title">
- 用户输入
- </div>
- <el-form ref="enterFormRef" :model="enterForm" :rules="enterFormRules">
- <el-table :data="enterDataList" border v-loading="loading">
- <el-table-column label="输入项" prop="name"/>
- <el-table-column label="用户输入区" prop="">
- <template #default="{row}">
- <el-form-item :prop="row.code">
- <el-input-number
- v-if="row.valType === 'number' || row.valType === 'NUMBER' || row.colType === 'NUMBER'"
- v-model="row.inputText"
- placeholder="请输入"
- :controls="false"
- />
- <el-input
- v-else
- v-model="row.inputText"
- placeholder="请输入"
- />
- </el-form-item>
- </template>
- </el-table-column>
- <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>
- </div>
- <div v-else class="empty-text">
- 暂无输入项
- </div>
- </el-form>
- </div>
- <div class="content" v-if="!isManualInput">
- <div class="content-title">
- 自动计算
- </div>
- <el-form ref="outputFormRef" :model="outputForm" :rules="outputFormRules">
- <el-table :data="outputDataList" border v-loading="loading">
- <el-table-column label="输出项" prop="name"/>
- <el-table-column label="自动计算" prop="">
- <template #default="{row}">
- <el-form-item :prop="row.code">
- <el-input
- v-model="row.inputText"
- placeholder="请输入"
- :disabled="true"
- />
- </el-form-item>
- </template>
- </el-table-column>
- <el-table-column label="说明" prop="description"/>
- </el-table>
- <div v-if="outputDataList.length === 0" class="empty-text">
- 暂无输出项
- </div>
- </el-form>
- </div>
- <div class="content">
- <div class="content-title">
- 项目检验费用
- </div>
- <el-form ref="feeFormRef" :model="feeForm" :rules="feeFormRules" label-width="160px">
- <el-form-item label="自动计算总费用:" prop="" v-if="!isBatch">
- {{ isManualInput ? '项目未设置计算规则,请手动填入实际费用' : feeForm.totalCost }}
- </el-form-item>
- <el-form-item label="历史收费:" prop="">
- {{ templateInfo.fee || 0 }}
- </el-form-item>
- <el-form-item label="实际费用:" prop="actualFee">
- <el-input-number v-model="feeForm.actualFee" :controls="false" :precision="2" :min="0"
- :step="0.01" style="width: 220px;"/>
- </el-form-item>
- </el-form>
- </div>
- <template #footer>
- <el-button type="primary" @click="handleSave">保存</el-button>
- <el-button type="default" @click="handleClose">取消</el-button>
- </template>
- </CustomDialog>
- <!-- 费用计算模板:不在页面显示 -->
- <Teleport to="body">
- <Designer
- v-if="!isManualInput"
- id="gc-designer-container"
- ref="spreadDesignerRef"
- @designer-initialized="handleDesignerInit"
- />
- </Teleport>
- </template>
- <script setup>
- 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'
- import '@grapecity-software/spread-sheets-io'
- import '@grapecity-software/spread-sheets-pdf'
- import '@grapecity-software/spread-sheets-print'
- import '@grapecity-software/spread-sheets-shapes'
- import '@grapecity-software/spread-sheets-barcode'
- import '@grapecity-software/spread-sheets-pivot-addon'
- 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";
- const {queryCheckItemCalcPreFillField} = BoilerTaskOrderApi
- import {getPressureReportTemplateInfo} from '@/api/pressure2/reportTemplate'
- import {PressureCheckerMyTaskStatus} from "@/utils/constants";
- import {BoilerConnectRecordReportApi} from "@/api/pressure2/boilerconnectrecordreport";
- import {DynamicTbFeeColApi} from "@/api/pressure2/dynamictbfeecol";
- const props = defineProps({
- modelValue: {
- type: Boolean,
- required: true
- },
- templateInfo: {
- type: Object,
- required: true,
- default: () => ({})
- },
- equipmentId: {
- type: String
- },
- isBatch: {
- type: Boolean,
- default: false
- }
- })
- const emit = defineEmits(['update:modelValue', 'refresh', 'save'])
- const loading = ref(true)
- const enterFormRef = ref(null)
- const outputFormRef = ref(null)
- const feeFormRef = ref(null)
- const enterForm = ref({})
- const outputForm = ref({})
- const feeForm = ref({
- actualFee: 0,
- totalCost: 0
- })
- 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 handleDesignerInit = async (instance) => {
- designer = instance
- // 设置设计器配置
- let config = JSON.parse(JSON.stringify(GC.Spread.Sheets.Designer.DefaultConfig))
- instance.setConfig(config)
- // 切换到设计模式
- let designModeCommand = GC.Spread.Sheets.Designer.getCommand(
- GC.Spread.Sheets.Designer.CommandNames.DesignMode
- )
- designModeCommand.execute(designer)
- isReport.value = props.templateInfo.taskStatus >= PressureCheckerMyTaskStatus.REPORT_INPUT
- await handleGetPressureReportTemplateInfo()
- // 加载费用计算模板
- await handleRenderFormulaTemplate()
- }
- // 获取费用模板信息
- 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' || !combineTemplateInfo.value.feeCalcType )
- const handleGetPressureReportTemplateInfo = async () => {
- console.log('combineTemplateInfo', combineTemplateInfo.value)
- const byTemplateId = await BoilerConnectRecordReportApi.getByTemplateId({
- templateId: isReport.value ? combineTemplateInfo.value.reportTemplateId : combineTemplateInfo.value.templateId,
- type: isReport.value ? 'report' : 'record'
- });
- if (byTemplateId.length !== 1) {
- ElMessage.error('获取费用模板信息失败')
- }
- fetchTemplateData.value = byTemplateId[0]
- }
- // 渲染费用计算模板
- const handleRenderFormulaTemplate = async () => {
- const spread = designer?.getWorkbook()
- if (isManualInput.value){
- return
- }
- if (!combineTemplateInfo.value.feeFileUrl) {
- console.warn('公式模板URL不存在')
- ElMessage.warning('费用计算模板未配置,请联系管理员')
- return
- }
- try {
- const newFileUrl = (combineTemplateInfo.value.feeFileUrl || '').startsWith('http')
- ? combineTemplateInfo.value.feeFileUrl
- : import.meta.env.VITE_FILE_URL + '/' + combineTemplateInfo.value.feeFileUrl
- // 直接使用 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 格式')
- }
- } catch (error) {
- console.error('加载公式模板失败:', error)
- ElMessage.error('加载费用计算模板失败: ' + (error.message || '未知错误'))
- }
- }
- const allCalcPreFillFieldKeyValue = ref({})
- const allCalcPreFillField = ref([])
- // 获取费用计算字段
- const handleQueryCheckItemCalcPreFillField = async () => {
- loading.value = true
- try {
- const fields = await DynamicTbFeeColApi.getAllField(isReport.value ? 'report' : 'record', isReport.value ? props.templateInfo.reportTemplateId : props.templateInfo.templateId)
- allCalcPreFillField.value = fields || []
- // 保存所有的输入项 (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的为输出项)
- 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到费用模板中
- 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
- }
- }
- })
- // 自动计算费用
- const handleCalcFee = () => {
- if (!designer || !spreadDesignerRef.value) {
- console.error('SpreadDesigner 未初始化')
- ElMessage.error('费用计算模板未初始化,请刷新页面重试')
- return
- }
- const spread = designer.getWorkbook()
- if (!spread) {
- console.error('无法获取 Spread 工作簿')
- ElMessage.error('费用计算模板加载失败,请刷新页面重试')
- return
- }
- // 如果 bindingPathSchema 不存在,使用默认值
- let bindingPathSchema = {
- "dataFields": allCalcPreFillField.value.map(item => ({
- "name": item.code,
- "displayName": item.name,
- "bindingPath": item.code
- }))
- }
- // 确保输入数据是数字类型
- const entryData = Object.fromEntries(enterDataList.value.map(item => {
- let value = item.inputText
- // 对于数值类型的字段,确保转换为数字
- if (item.colType === 'NUMBER' || item.valType === 'NUMBER') {
- value = Number(value) || '0'
- }
- return [item.code, value]
- }))
- // 确保预填充数据也是正确类型
- const preFillData = Object.fromEntries(Object.entries(allCalcPreFillFieldKeyValue.value).map(([key, value]) => {
- const field = enterDataList.value.find(item => item.code === key)
- if (field && (field.colType === 'NUMBER' || field.valType === 'NUMBER')) {
- return [key, Number(value) || '0']
- }
- return [key, value]
- }))
- const combineDataSource = {
- ...preFillData,
- ...entryData
- }
- 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))
- console.log('设置数据源成功')
- } catch (e) {
- ElMessage.error('设置数据源失败,尝试其他方法:', e.message)
- }
- // 强制重新计算
- spread.repaint()
- // 获取计算结果
- 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
- if (calculatedTotal === 0) {
- console.warn('计算结果为0,可能是模板配置问题或输入数据问题')
- ElMessage.warning('费用计算结果为0,请检查输入数据和模板配置')
- }
- // 更新输出项
- outputDataList.value = outputDataList.value.map(item => ({
- ...item,
- inputText: getDataSource[item.code] || ''
- }))
- // 更新总费用
- feeForm.value.totalCost = calculatedTotal
- feeForm.value.actualFee = calculatedTotal
- console.log('计算的总费用:', calculatedTotal)
- ElMessage.success('费用计算完成')
- } catch (error) {
- console.error('获取计算结果时出错:', error)
- ElMessage.error('费用计算失败:' + (error.message || '未知错误'))
- }
- })
- } catch (error) {
- console.error('计算费用时出错:', error)
- ElMessage.error('费用计算失败:' + (error.message || '未知错误'))
- }
- }
- // 保存
- 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})
- })
- handleClose()
- }
- // 关闭弹窗
- const handleClose = () => {
- emit('update:modelValue', false)
- }
- </script>
- <style lang="scss" scoped>
- .content-title {
- position: sticky;
- left: 0;
- top: 0;
- display: flex;
- justify-content: flex-start;
- align-items: center;
- width: 100%;
- height: 36px;
- margin-bottom: 16px;
- line-height: 36px;
- background-color: var(--el-color-primary-light-9);
- z-index: 1000;
- &::before {
- content: '';
- height: 70%;
- width: 4px;
- margin-right: 12px;
- background-color: var(--el-color-primary);
- }
- }
- .content {
- position: relative;
- margin-bottom: 16px;
- max-height: 400px;
- overflow-y: auto;
- .button-box {
- position: sticky;
- left: 0;
- bottom: 0;
- background-color: #fff;
- z-index: 1000;
- }
- }
- :deep(.el-input-number) {
- width: 100%;
- .el-input__inner {
- text-align: left;
- }
- }
- .button-box {
- display: flex;
- flex-direction: row;
- justify-content: center;
- padding-top: 16px;
- }
- .empty-text {
- text-align: center;
- color: #909399;
- padding: 20px 0;
- font-size: 14px;
- }
- </style>
- <style lang="scss">
- .calcCheckItemDialog {
- .el-dialog__footer {
- text-align: center;
- }
- }
- </style>
|