calcCheckItemFee.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. <template>
  2. <CustomDialog
  3. :model-value="modelValue"
  4. title="费用计算"
  5. width="600px"
  6. :before-close="handleClose"
  7. class="calcCheckItemDialog"
  8. v-loading="loading"
  9. >
  10. <div class="content" v-if="!isManualInput">
  11. <div class="content-title">
  12. 用户输入
  13. </div>
  14. <el-form ref="enterFormRef" :model="enterForm" :rules="enterFormRules">
  15. <el-table :data="enterDataList" border>
  16. <el-table-column label="输入项" prop="name"/>
  17. <el-table-column label="用户输入区" prop="">
  18. <template #default="{row}">
  19. <el-form-item :prop="row.code">
  20. <el-input-number v-if="row.valueType === 'number'" v-model="row.inputText" placeholder="请输入" :controls="false"/>
  21. <el-input v-else v-model="row.inputText" placeholder="请输入"/>
  22. </el-form-item>
  23. </template>
  24. </el-table-column>
  25. <el-table-column label="说明" prop="description"/>
  26. </el-table>
  27. </el-form>
  28. <div class="button-box">
  29. <el-button type="primary" @click="handleCalcFee">费用计算</el-button>
  30. </div>
  31. </div>
  32. <div class="content" v-if="!isManualInput">
  33. <div class="content-title">
  34. 自动计算
  35. </div>
  36. <el-form ref="outputFormRef" :model="outputForm" :rules="outputFormRules">
  37. <el-table :data="outputDataList" border>
  38. <el-table-column label="输出项" prop="name"/>
  39. <el-table-column label="自动计算" prop="">
  40. <template #default="{row}">
  41. <el-form-item :prop="row.code">
  42. <el-input v-model="row.inputText" placeholder="请输入"/>
  43. </el-form-item>
  44. </template>
  45. </el-table-column>
  46. <el-table-column label="说明" prop="description"/>
  47. </el-table>
  48. </el-form>
  49. </div>
  50. <div class="content">
  51. <div class="content-title">
  52. 项目检验费用
  53. </div>
  54. <el-form ref="feeFormRef" :model="feeForm" :rules="feeFormRules" label-width="160px">
  55. <el-form-item label="自动计算总费用:" prop="" v-if="!isBatch">
  56. {{ isManualInput ? '项目未设置计算规则,请手动填入实际费用' : feeForm.totalCost }}
  57. </el-form-item>
  58. <el-form-item label="历史收费:" prop="">
  59. {{ templateInfo.fee || 0 }}
  60. </el-form-item>
  61. <el-form-item label="实际费用:" prop="actualFee">
  62. <el-input-number v-model="feeForm.actualFee" :controls="false" :precision="2" :min="0" :step="0.01" style="width: 220px;"/>
  63. </el-form-item>
  64. </el-form>
  65. </div>
  66. <template #footer>
  67. <el-button type="primary" @click="handleSave">保存</el-button>
  68. <el-button type="default" @click="handleClose">取消</el-button>
  69. </template>
  70. </CustomDialog>
  71. <!-- 费用计算模板:不在页面显示 -->
  72. <Teleport to="body">
  73. <SpreadDesigner v-if="!isManualInput" ref="spreadDesignerRef" @init="handleDesignerInit"/>
  74. </Teleport>
  75. </template>
  76. <script setup>
  77. import { nextTick } from 'vue'
  78. import SpreadDesigner from '@/components/SpreadDesigner/index.vue'
  79. import CustomDialog from '@/components/CustomDialog/index.vue'
  80. import { PipeTaskOrderApi } from '@/api/pressure2/pipetaskorder'
  81. import { buildFileUrl } from '@/utils'
  82. import axios from 'axios'
  83. import { is } from '@/utils/is'
  84. const { queryCheckItemCalcPreFillField } = PipeTaskOrderApi
  85. import { getPressureReportTemplateInfo } from '@/api/pressure2/reportTemplate'
  86. const props = defineProps({
  87. modelValue: {
  88. type: Boolean,
  89. required: true
  90. },
  91. templateInfo: {
  92. type: Object,
  93. required: true,
  94. default: () => ({})
  95. },
  96. equipmentId: {
  97. type: String
  98. },
  99. isBatch: {
  100. type: Boolean,
  101. default: false
  102. }
  103. })
  104. const emit = defineEmits(['update:modelValue', 'refresh', 'save'])
  105. const loading = ref(true)
  106. const enterFormRef = ref(null)
  107. const outputFormRef = ref(null)
  108. const feeFormRef = ref(null)
  109. const enterForm = ref({})
  110. const outputForm = ref({})
  111. const feeForm = ref({
  112. actualFee: 0,
  113. totalCost: 0
  114. })
  115. const enterFormRules = ref({})
  116. const outputFormRules = ref({})
  117. const feeFormRules = ref({})
  118. const enterDataList = ref([])
  119. const outputDataList = ref([])
  120. let designer = null
  121. const spreadDesignerRef = ref(null)
  122. // 费用模板初始化
  123. const handleDesignerInit = async (instance) => {
  124. designer = instance
  125. console.log('葡萄城初始化了!!!')
  126. await handleGetPressureReportTemplateInfo()
  127. // 加载费用计算模板
  128. await handleRenderFormulaTemplate()
  129. // 获取费用计算字段
  130. if (props.equipmentId) {
  131. await handleQueryCheckItemCalcPreFillField()
  132. }
  133. // 优先从已保存的 feeCalculateJson 中回显实际费用
  134. feeForm.value = {
  135. actualFee: feeCalculateJson.value['actualFee'] || combineTemplateInfo.value.fee || 0,
  136. totalCost: feeCalculateJson.value['totalCost'] || combineTemplateInfo.value.fee || 0
  137. }
  138. }
  139. // 获取费用模板信息
  140. const fetchTemplateData = ref({})
  141. const combineTemplateInfo = computed(() => ({...props.templateInfo, ...fetchTemplateData.value}))
  142. const feeCalculateJson = computed(() => !props.templateInfo.feeCalculateJson ? {} : JSON.parse(props.templateInfo.feeCalculateJson))
  143. // 是否手动录入 true是,false否
  144. const isManualInput = computed(() => combineTemplateInfo.value.isAutoAmount === '0')
  145. const handleGetPressureReportTemplateInfo = async () => {
  146. fetchTemplateData.value = await getPressureReportTemplateInfo({id: props.templateInfo.templateId})
  147. }
  148. // 渲染费用计算模板
  149. const handleRenderFormulaTemplate = async () => {
  150. const spread = designer?.getWorkbook()
  151. if(!combineTemplateInfo.value.formulaTemplateUrl) return
  152. const response = await axios.get(buildFileUrl(combineTemplateInfo.value.formulaTemplateUrl), { responseType: 'blob' })
  153. const blob = new Blob([response.data], { type: response.headers['content-type'] })
  154. spread.open(
  155. blob,
  156. function () {
  157. // 让活动工作表的单元格全部失焦
  158. spread.getActiveSheet().setActiveCell(null);
  159. console.log('费用计算模板加载成功')
  160. },
  161. function () {
  162. console.log('费用计算模板加载失败')
  163. }
  164. )
  165. }
  166. const calcFields = ref([])
  167. const allCalcPreFillFieldKeyValue = ref({})
  168. const allCalcPreFillField = ref([])
  169. // 获取费用计算字段
  170. const handleQueryCheckItemCalcPreFillField = async () => {
  171. loading.value = true
  172. try {
  173. const params = {
  174. equipId: props.equipmentId,
  175. templateId: combineTemplateInfo.value.templateId
  176. }
  177. const fieldResult = await queryCheckItemCalcPreFillField(params)
  178. allCalcPreFillField.value = fieldResult
  179. // 保存所有的输入项
  180. enterDataList.value = fieldResult.filter(field => field.type === '0').map(field => ({inputText: feeCalculateJson.value[field.code] || field.defaultValue, ...field}))
  181. // 保存所有的输出项
  182. outputDataList.value = JSON.parse(JSON.stringify(fieldResult.filter(field => field.type === '1').map(field => ({inputText: feeCalculateJson.value[field.code], ...field}))))
  183. // 获取所有需要field的code,转成{[code]: ''}格式,set到费用模板中
  184. allCalcPreFillFieldKeyValue.value = Object.fromEntries(enterDataList.value.map(item => ([item.code, feeCalculateJson.value[item.code] || ''])))
  185. // 检验费用数据回显
  186. feeForm.value = {
  187. actualFee: feeCalculateJson.value['actualFee'] || 0,
  188. totalCost: feeCalculateJson.value['totalCost'] || 0
  189. }
  190. } catch(error) {
  191. console.error('获取费用计算字段报错了', error)
  192. } finally {
  193. loading.value = false
  194. }
  195. }
  196. // 自动计算费用
  197. const handleCalcFee = () => {
  198. const spread = designer?.getWorkbook()
  199. const entryData = Object.fromEntries(enterDataList.value.map(item => ([item.code, item.inputText])))
  200. const combineDataSource = {
  201. ...allCalcPreFillFieldKeyValue.value,
  202. ...entryData
  203. }
  204. // console.log('combineDataSource', combineDataSource)
  205. // set数据
  206. spreadDesignerRef.value.newSetDataSource(combineTemplateInfo.value.bindingPathSchema, combineDataSource)
  207. // get数据
  208. nextTick(() => {
  209. const getDataSource = spreadDesignerRef.value.getDataSource()
  210. // 输出项的值
  211. outputDataList.value = outputDataList.value.map(item => {
  212. if(is(getDataSource[item.code], 'Null') || is(getDataSource[item.code], 'Undefined')) return item
  213. return {
  214. ...item,
  215. inputText: getDataSource[item.code]
  216. }
  217. })
  218. // console.log('输出项', outputDataList.value)
  219. const totalCostCode = allCalcPreFillField.value.find(x => x.type === '3')?.code || 'totalCost'
  220. // 自动计算总费用
  221. feeForm.value.totalCost = getDataSource[totalCostCode]
  222. feeForm.value.actualFee = getDataSource[totalCostCode]
  223. })
  224. }
  225. // 保存
  226. const handleSave = () => {
  227. const inputParams = Object.fromEntries(enterDataList.value.map(item => ([item.code, item.inputText])))
  228. const outputParams = Object.fromEntries(outputDataList.value.map(item => ([item.code, item.inputText])))
  229. emit('save', {...props.templateInfo, fee: feeForm.value.actualFee || 0, feeCalculateJson: JSON.stringify({...inputParams, ...feeForm.value, ...outputParams})})
  230. handleClose()
  231. }
  232. // 关闭弹窗
  233. const handleClose = () => {
  234. emit('update:modelValue', false)
  235. }
  236. </script>
  237. <style lang="scss" scoped>
  238. .content-title {
  239. position: sticky;
  240. left: 0;
  241. top: 0;
  242. display: flex;
  243. justify-content: flex-start;
  244. align-items: center;
  245. width: 100%;
  246. height: 36px;
  247. margin-bottom: 16px;
  248. line-height: 36px;
  249. background-color: var(--el-color-primary-light-9);
  250. z-index: 1000;
  251. &::before {
  252. content: '';
  253. height: 70%;
  254. width: 4px;
  255. margin-right: 12px;
  256. background-color: var(--el-color-primary);
  257. }
  258. }
  259. .content {
  260. position: relative;
  261. margin-bottom: 16px;
  262. max-height: 400px;
  263. overflow-y: auto;
  264. .button-box {
  265. position: sticky;
  266. left: 0;
  267. bottom: 0;
  268. background-color: #fff;
  269. z-index: 1000;
  270. }
  271. }
  272. :deep(.el-input-number) {
  273. width: 100%;
  274. .el-input__inner {
  275. text-align: left;
  276. }
  277. }
  278. .button-box {
  279. display: flex;
  280. flex-direction: row;
  281. justify-content: center;
  282. padding-top: 16px;
  283. }
  284. </style>
  285. <style lang="scss">
  286. .calcCheckItemDialog {
  287. .el-dialog__footer {
  288. text-align: center;
  289. }
  290. }
  291. </style>