unit.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. <template>
  2. <div class="relative h-full flex">
  3. <div :id="`hc_tree_card_${uuid}`">
  4. <hc-new-card scrollbar>
  5. <template #header>
  6. <el-button hc-btn type="primary" :loading="setLoading" @click="setTree">重新设置treeCode</el-button>
  7. </template>
  8. <hc-lazy-tree
  9. v-if="ishowTree"
  10. :auto-expand-keys="TreeAutoExpandKeys"
  11. tree-key="id"
  12. :h-props="treeProps"
  13. is-load-menu
  14. @load="treeLoadNode"
  15. @load-menu="treeLoadMenu"
  16. @menu-tap="treeMenuTap"
  17. @node-tap="treeNodeTap"
  18. />
  19. </hc-new-card>
  20. </div>
  21. <div :id="`hc_table_card_${uuid}`" class="flex-1">
  22. <hc-new-card scrollbar title="合同计量单元">
  23. <template #extra>
  24. <el-button hc-btn type="primary" @click="editModalShow = true">修改</el-button>
  25. <el-button hc-btn type="danger">删除</el-button>
  26. <el-button hc-btn type="warning" @click="treeModalShow = true">增补单元</el-button>
  27. <el-button hc-btn type="success">导入</el-button>
  28. </template>
  29. <div class="relative">
  30. <infoTable :info-data="curTreeData" />
  31. <HcTitle title="清单分解汇总列表">
  32. <template #extra>
  33. <div class="text-sm text-orange">温馨提示:累计分解量 > 合同变更后量,整行文字红色</div>
  34. </template>
  35. </HcTitle>
  36. <div style="height: calc(100vh - 420px);">
  37. <hc-table
  38. :is-stripe="false" :column="tableColumn" :datas="tableData" :loading="tableLoading"
  39. is-new :index-style="{ width: 60 }" :row-style="tableRowStyle"
  40. >
  41. <template #key1="{ row }">
  42. <i class="i-iconoir-open-select-hand-gesture inline-block" />
  43. </template>
  44. </hc-table>
  45. </div>
  46. </div>
  47. </hc-new-card>
  48. </div>
  49. <!-- 节点新增和编辑 -->
  50. <treeForm
  51. v-model="treeModalShow" :ids="curTreeData.id" :menu-type="menuType"
  52. :template-id="curTreeData.templateId" @finish="finishForm"
  53. />
  54. <!-- 修改合同计量单元 -->
  55. <rowData
  56. v-model="editModalShow" :is-table="isInfoView" :ids="curTreeData.id" :cur-tree-data="curTreeData"
  57. @finish="finishEdit" @close="closeEdit" @get-detail="getTreeNodeDetail"
  58. />
  59. <!-- 调整排序 -->
  60. <hc-new-dialog
  61. v-model="sortModalShow" is-table widths="1100px" title="调整排序" :loading="sortNodeLoading"
  62. @save="sortModalSave"
  63. >
  64. <hc-table
  65. ui="hc-table-row-drop"
  66. :column="sortTableColumn" :datas="sortTableData" :loading="sortTableLoading"
  67. is-row-drop quick-sort is-new :index-style="{ width: 80 }"
  68. @row-drop="sortTableRowDrop" @row-sort="sortTableRowDrop"
  69. >
  70. <template #key2="{ row }">
  71. <span class="text-link">{{ row?.key2 }}</span>
  72. </template>
  73. <template #action="{ index }">
  74. <span class="text-link text-xl" @click="upSortClick(index)">
  75. <HcIcon name="arrow-up" fill />
  76. </span>
  77. <span class="text-link ml-2 text-xl" @click="downSortClick(index)">
  78. <HcIcon name="arrow-down" fill />
  79. </span>
  80. </template>
  81. </hc-table>
  82. </hc-new-dialog>
  83. </div>
  84. </template>
  85. <script setup>
  86. import { nextTick, onMounted, ref } from 'vue'
  87. import { arrToId, getArrValue, getObjValue, getRandom } from 'js-fast-way'
  88. import infoTable from './components/unit/info-table.vue'
  89. import treeForm from './components/unit/tree-form.vue'
  90. import rowData from './components/unit/row-data.vue'
  91. import unitApi from '~api/project/debit/contract/unit.js'
  92. import { useAppStore } from '~src/store'
  93. import { getStoreValue, setStoreValue } from '~src/utils/storage'
  94. import { delMessageV2 } from '~com/message/index.js'
  95. import { getDictionary } from '~api/other'
  96. const useAppState = useAppStore()
  97. const projectId = ref(useAppState.getProjectId || '')
  98. const contractId = ref(useAppState.getContractId || '')
  99. defineOptions({
  100. name: 'ProjectDebitContractUnit',
  101. })
  102. const uuid = getRandom(4)
  103. //渲染完成
  104. onMounted(() => {
  105. setSplitRef()
  106. // getNodeType()
  107. })
  108. //初始化设置拖动分割线
  109. const setSplitRef = () => {
  110. //配置参考: https://split.js.org/#/?direction=vertical&snapOffset=0
  111. nextTick(() => {
  112. window.$split(['#hc_tree_card_' + uuid, '#hc_table_card_' + uuid], {
  113. sizes: [20, 80],
  114. snapOffset: 0,
  115. minSize: [50, 500],
  116. })
  117. })
  118. }
  119. //获节点类型
  120. const nodeOptions = ref([])
  121. const getNodeType = async (id) => {
  122. const { data } = await unitApi.getNodeTypeList({
  123. id,
  124. })
  125. nodeOptions.value = getArrValue(data)
  126. nodeOptions.value.forEach((ele) => {
  127. ele.dictKey = Number(ele.dictKey)
  128. })
  129. }
  130. //搜索表单
  131. const searchForm = ref({})
  132. //数据格式
  133. const treeProps = {
  134. label: 'nodeName',
  135. children: 'children',
  136. isLeaf: 'notExsitChild',
  137. }
  138. const ishowTree = ref(true)
  139. //重新设置树
  140. const setLoading = ref(false)
  141. const setTree = async () => {
  142. const { error, code, msg } = await unitApi.refresh({
  143. projectId: projectId.value,
  144. contractId: contractId.value,
  145. })
  146. setLoading.value = false
  147. if (!error && code === 200) {
  148. window.$message.success(msg)
  149. ishowTree.value = false
  150. setTimeout(() => {
  151. ishowTree.value = true
  152. }, 100)
  153. } else {
  154. // newlistdata.value = []
  155. }
  156. }
  157. //懒加载的数据
  158. const TreeAutoExpandKeys = ref(getStoreValue('wbsTreeExpandKeys') || [])
  159. const treeLoadNode = async ({ node, item, level }, resolve) => {
  160. let id = 0
  161. if (level !== 0) {
  162. const nodeData = getObjValue(item)
  163. id = nodeData?.id || ''
  164. }
  165. //获取数据
  166. const { error, code, data } = await unitApi.lazyTree({
  167. contractId: contractId.value,
  168. id: id,
  169. })
  170. resolve(getArrValue(data))
  171. }
  172. //节点点击
  173. const isInfoView = ref(false)
  174. const treeNodeTap = ({ node, data, keys }) => {
  175. getNodeType(data.id)
  176. isInfoView.value = node.isLeaf
  177. TreeAutoExpandKeys.value = keys || []
  178. setStoreValue('wbsTreeExpandKeys', keys)
  179. getTreeNodeDetail(data)
  180. }
  181. //获取节点详情
  182. const curTreeData = ref({})
  183. const getTreeNodeDetail = async ({ id }) => {
  184. const { error, code, data } = await unitApi.getNodeDetail({ id })
  185. if (!error && code === 200) {
  186. curTreeData.value = getObjValue(data)
  187. tableData.value = curTreeData.value['decompositionList']
  188. nodeOptions.value.forEach((ele) => {
  189. if (curTreeData.value.nodeType === ele.dictKey) {
  190. curTreeData.value.nodeTypeName = ele.dictValue
  191. }
  192. })
  193. } else {
  194. curTreeData.value = {}
  195. tableData.value = []
  196. }
  197. }
  198. //菜单
  199. const treeLoadMenu = ({ item, level }, resolve) => {
  200. const { isLock } = item
  201. if (level === 1) {
  202. if (isLock === 1) {
  203. return resolve([
  204. { icon: 'lock', label: '解锁', key: 'lock' },
  205. { icon: 'upload-cloud', label: '导入', key: 'lead' },
  206. { icon: 'add', label: '新增', key: 'add' },
  207. { icon: 'arrow-up-down-line', label: '排序', key: 'sort' },
  208. ])
  209. } else {
  210. return resolve([
  211. { icon: 'lock', label: '锁定', key: 'lock' },
  212. { icon: 'upload-cloud', label: '导入', key: 'lead' },
  213. { icon: 'add', label: '新增', key: 'add' },
  214. { icon: 'arrow-up-down-line', label: '排序', key: 'sort' },
  215. ])
  216. }
  217. } else {
  218. if (isLock === 1) {
  219. return resolve([
  220. { icon: 'lock', label: '解锁', key: 'lock' },
  221. { icon: 'upload-cloud', label: '导入', key: 'lead' },
  222. { icon: 'add', label: '新增', key: 'add' },
  223. { icon: 'pencil', label: '修改', key: 'edit' },
  224. { icon: 'arrow-up-down-line', label: '排序', key: 'sort' },
  225. { icon: 'close', label: '删除', key: 'del' },
  226. ])
  227. } else {
  228. return resolve([
  229. { icon: 'lock', label: '锁定', key: 'lock' },
  230. { icon: 'upload-cloud', label: '导入', key: 'lead' },
  231. { icon: 'add', label: '新增', key: 'add' },
  232. { icon: 'pencil', label: '修改', key: 'edit' },
  233. { icon: 'arrow-up-down-line', label: '排序', key: 'sort' },
  234. { icon: 'close', label: '删除', key: 'del' },
  235. ])
  236. }
  237. }
  238. }
  239. const menuType = ref('')
  240. const treeMenuTap = ({ key, node, data, keys }) => {
  241. const { isDecompositionData } = data
  242. isInfoView.value = node.isLeaf
  243. menuType.value = key
  244. getTreeNodeDetail(data)
  245. setStoreValue('wbsTreeExpandKeys', keys)
  246. TreeAutoExpandKeys.value = keys || []
  247. if (data?.isLock !== 1) {
  248. if (key === 'add') {
  249. if (isDecompositionData === 1) {
  250. window.$message.warning('该节点下面不允许新增节点')
  251. } else {
  252. treeModalShow.value = true
  253. }
  254. }
  255. if (key === 'edit') {
  256. editModalShow.value = true
  257. }
  258. if (key === 'sort') {
  259. let nodes = [], childNodes = []
  260. childNodes = node?.parent?.childNodes || node?.parent?.children || []
  261. for (let i = 0; i < childNodes.length; i++) {
  262. const res = childNodes[i]?.data
  263. nodes.push({
  264. nodeName: res?.nodeName,
  265. id: res?.id,
  266. })
  267. }
  268. sortTableData.value = nodes
  269. sortModalShow.value = true
  270. }
  271. if (key === 'del') {
  272. delModalClick()
  273. }
  274. }
  275. if (data?.isLock === 1 && key !== 'lock') {
  276. window.$message.warning('当前节点为锁定状态,不允许操作')
  277. }
  278. if (key === 'lock') {
  279. handleLockNode()
  280. }
  281. }
  282. //锁定节点
  283. const handleLockNode = async () => {
  284. const { error, code, msg } = await unitApi.getLock({
  285. id: curTreeData.value.id || '',
  286. lockStatus: curTreeData.value?.isLock === 1 ? 0 : 1,
  287. })
  288. if (!error && code === 200) {
  289. window?.$message?.success(msg)
  290. window?.location?.reload() //刷新页面
  291. }
  292. }
  293. //删除节点
  294. const delModalClick = () => {
  295. delMessageV2(async (action, instance, done) => {
  296. if (action === 'confirm') {
  297. instance.confirmButtonLoading = true
  298. removeContractTreeNode()
  299. instance.confirmButtonLoading = false
  300. done()
  301. } else {
  302. done()
  303. }
  304. })
  305. }
  306. const removeContractTreeNode = async () => {
  307. const loadingInstance = window.$loading.service({
  308. fullscreen: true,
  309. text: '删除节点中,请耐心等待...',
  310. background: 'rgba(0, 0, 0, 0.7)',
  311. })
  312. const { error, code } = await unitApi.deleteNode({
  313. id: curTreeData.value.id || '',
  314. })
  315. loadingInstance.close()
  316. if (!error && code === 200) {
  317. window?.$message?.success('删除成功')
  318. window?.location?.reload() //刷新页面
  319. }
  320. }
  321. //表格数据
  322. const tableLoading = ref(false)
  323. const tableColumn = ref([
  324. { key: 'key1', name: '分解明细', width: 80, align: 'center' },
  325. { key: 'formNumber', name: '清单编号', width: 120, align: 'center' },
  326. { key: 'formName', name: '清单名称', align: 'center' },
  327. { key: 'currentPrice', name: '单价', width: 90, align: 'center' },
  328. { key: 'contractTotal', name: '合同数量', width: 100, align: 'center' },
  329. { key: 'changeTotal', name: '变更后数量', width: 110, align: 'center' },
  330. { key: 'poseNum', name: '施工图数量', align: 'center' },
  331. { key: 'changeTotal', name: '施工图变更后数量', align: 'center' },
  332. ])
  333. const tableData = ref([])
  334. //设置某一行的样式
  335. const tableRowStyle = ({ row, rowIndex }) => {
  336. if (row.poseNum > row.contractTotal) {
  337. return '--el-table-tr-bg-color: #fe0000; color:white'
  338. }
  339. }
  340. //弹窗
  341. const treeModalShow = ref(false)
  342. const editModalShow = ref(false)
  343. const finishForm = () => {
  344. treeModalShow.value = false
  345. ishowTree.value = false
  346. setTimeout(() => {
  347. ishowTree.value = true
  348. }, 100)
  349. }
  350. const finishEdit = () => {
  351. editModalShow.value = false
  352. ishowTree.value = false
  353. setTimeout(() => {
  354. ishowTree.value = true
  355. }, 100)
  356. }
  357. const closeEdit = () => {
  358. editModalShow.value = false
  359. ishowTree.value = false
  360. setTimeout(() => {
  361. ishowTree.value = true
  362. }, 100)
  363. }
  364. //调整排序
  365. const sortModalShow = ref(false)
  366. //表格数据
  367. const sortTableColumn = ref([
  368. { key: 'nodeName', name: '节点名称' },
  369. { key: 'action', name: '排序', width: 90 },
  370. ])
  371. const sortTableLoading = ref(false)
  372. const sortTableData = ref([])
  373. //拖动完成
  374. const sortTableRowDrop = (rows) => {
  375. sortTableData.value = [] // 先清空,否则排序会异常
  376. nextTick(() => {
  377. sortTableData.value = rows
  378. })
  379. }
  380. //向下
  381. const downSortClick = (index) => {
  382. const indexs = index + 1
  383. const data = sortTableData.value
  384. if (indexs !== data.length) {
  385. const tmp = data.splice(indexs, 1)
  386. sortTableData.value.splice(index, 0, tmp[0])
  387. } else {
  388. window?.$message?.warning('已经处于置底,无法下移')
  389. }
  390. }
  391. //向上
  392. const upSortClick = (index) => {
  393. const data = sortTableData.value || []
  394. if (index !== 0) {
  395. const tmp = data.splice(index - 1, 1)
  396. sortTableData.value.splice(index, 0, tmp[0])
  397. } else {
  398. window?.$message?.warning('已经处于置顶,无法上移')
  399. }
  400. }
  401. const sortNodeLoading = ref(false)
  402. const sortModalSave = async () => {
  403. const ids = arrToId(sortTableData.value)
  404. //发起请求
  405. sortNodeLoading.value = true
  406. const { error, code } = await unitApi.sortForm({ ids })
  407. sortNodeLoading.value = false
  408. //判断状态
  409. if (!error && code === 200) {
  410. window?.$message?.success('保存成功')
  411. sortModalShow.value = false
  412. window?.location?.reload() //刷新页面
  413. }
  414. }
  415. </script>