form.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. <template>
  2. <HcCard actionUi="text-center" :title="detailData?.projectName">
  3. <template #extra>
  4. <HcNewSwitch :datas="tabTab" :keys="tabKey" @change="tabChange" :round="false"/>
  5. </template>
  6. <template #search>
  7. <div class="hc-program-project-form-radio-group">
  8. <el-radio-group v-model="radioType" size="large">
  9. <el-radio-button label="1">市场部</el-radio-button>
  10. <el-radio-button label="2">研发部</el-radio-button>
  11. <el-radio-button label="3">实施部</el-radio-button>
  12. <el-radio-button label="4">维护部</el-radio-button>
  13. <el-radio-button label="5">管理支出</el-radio-button>
  14. <el-radio-button label="6">外包劳务</el-radio-button>
  15. </el-radio-group>
  16. </div>
  17. </template>
  18. <HcTable :isIndex="false" :column="tableColumn" :datas="tableData" :row-style="tableRowStyle">
  19. <template #planTaskType="{row,index}">
  20. <el-select v-model="row.planTaskType" v-if="row.isEdit">
  21. <el-option label="选项1" value="选项1"/>
  22. <el-option label="选项2" value="选项2"/>
  23. </el-select>
  24. <span v-else>{{row.planTaskType}}</span>
  25. </template>
  26. <template #planTaskDesc="{row,index}">
  27. <el-input v-model="row.planTaskDesc" v-if="row.isEdit"/>
  28. <span v-else>{{row.planTaskDesc}}</span>
  29. </template>
  30. <template #planTarget="{row,index}">
  31. <el-input v-model="row.planTarget" v-if="row.isEdit"/>
  32. <span v-else>{{row.planTarget}}</span>
  33. </template>
  34. <template #key7="{row,index}">
  35. <HcDatePicker :dates="[row.planStartTime,row.planEndTime]" @change="betweenTimeUpdate($event,row)" v-if="row.isEdit"/>
  36. <!-- <span v-else>{{row.key7}}</span> -->
  37. <span v-else>
  38. <span >{{row.planStartTime?row.planStartTime:''}}</span>
  39. <span v-if="row.planEndTime">~</span>
  40. <span >{{row.planEndTime?row.planEndTime:''}}</span>
  41. </span>
  42. </template>
  43. <template #planDays="{row,index}">
  44. <el-input v-model="row.planDays" v-if="row.isEdit" disabled/>
  45. <span v-else>{{row.planDays}}</span>
  46. </template>
  47. <!-- <template #key9="{row,index}">
  48. <el-input v-model="row.key9" v-if="row.isEdit"/>
  49. <span v-else>{{row.key9}}</span>
  50. </template> -->
  51. <template #action="{row,index}">
  52. <el-button size="small" type="success" v-if="row.isEdit" @click="getWorkDays(row)">保存</el-button>
  53. <el-button size="small" type="primary" v-else @click="row.isEdit = true">编辑</el-button>
  54. <el-button size="small" type="warning" @click="relatedModalShow(row)">关联回款</el-button>
  55. <el-button size="small" type="danger" @click="subplanModalShow(row)" :disabled="row?.isShowChildren==0">分解子计划</el-button>
  56. </template>
  57. </HcTable>
  58. <template #action>
  59. <el-button size="large" type="info" hc-btn @click="goBackClick">
  60. <HcIcon name="arrow-go-back"/>
  61. <span>取消并返回</span>
  62. </el-button>
  63. <el-button size="large" type="primary" hc-btn @click="saveClick" :loading="saveLoaing">
  64. <HcIcon name="check-double"/>
  65. <span>提交保存</span>
  66. </el-button>
  67. </template>
  68. <!--分解子计划-->
  69. <HcDialog bgColor="#ffffff" isToBody isTable
  70. title="分解子计划" widths="80%"
  71. saveText="保存"
  72. :show="subplanModal"
  73. @close="subplanCloseClick"
  74. @save="subplanSaveClick"
  75. >
  76. <template #extra>
  77. <el-button size="large" type="primary" @click="addplan()">新增</el-button>
  78. </template>
  79. <HcTable :isIndex="false" :column="tableSubplanColumn" :datas="tableSubplanData">
  80. <template #planTaskType="{row,index}">
  81. <el-select v-model="row.planTaskType">
  82. <el-option label="选项1" value="选项1"/>
  83. <el-option label="选项2" value="选项2"/>
  84. </el-select>
  85. </template>
  86. <template #planTaskDesc="{row,index}">
  87. <el-input v-model="row.planTaskDesc"/>
  88. </template>
  89. <template #planTarget="{row,index}">
  90. <el-input v-model="row.planTarget"/>
  91. </template>
  92. <template #key7="{row,index}">
  93. <HcDatePicker :dates="[row.planStartTime,row.planEndTime]" @change="subbetweenTimeUpdate($event,row)" v-if="true"/>
  94. <span v-else>
  95. <span >{{row.planStartTime?row.planStartTime:''}}</span>
  96. <span v-if="row.planEndTime">~</span>
  97. <span >{{row.planEndTime?row.planEndTime:''}}</span>
  98. </span>
  99. </template>
  100. <template #planDays="{row,index}" >
  101. <!-- <el-input v-model="row.planDays" disabled/> -->
  102. <el-input v-model="row.planDays" disabled />
  103. </template>
  104. <template #key9="{row,index}">
  105. <el-input v-model="row.key9"/>
  106. </template>
  107. <template #action="{row,index}">
  108. <el-button size="small" type="success" v-if="row.isEdit" @click="getWorkDays(row)">保存</el-button>
  109. <el-button size="small" type="primary" v-else @click="row.isEdit = true">编辑</el-button>
  110. </template>
  111. </HcTable>
  112. </HcDialog>
  113. <!--关联回款里程碑-->
  114. <HcDialog bgColor="#ffffff" isToBody isTable :footer="false" :show="relatedModal" title="关联回款里程碑" widths="70%" @close="relatedCloseClick">
  115. <HcTable :column="tableRelatedColumn" :datas="tableRelatedData">
  116. <template #action="{row,index}">
  117. <el-button size="small" type="success" v-if="row.isRelation==1" @click="relation(row,0)">取消关联</el-button>
  118. <el-button size="small" type="primary" v-else @click="relation(row,1)">关联</el-button>
  119. </template>
  120. </HcTable>
  121. </HcDialog>
  122. </HcCard>
  123. </template>
  124. <script setup>
  125. import {useRouter, useRoute} from 'vue-router'
  126. import {onActivated, ref,watch} from "vue";
  127. import projectApi from '~api/program/project.js';
  128. import contractApi from '~api/project/project-contract.js';
  129. import {getArrValue,getObjValue} from "js-fast-way"
  130. import { getuserList} from "~api/other";
  131. import {useAppStore} from "~src/store";
  132. const useAppState = useAppStore();
  133. //初始变量
  134. const router = useRouter()
  135. const useRoutes = useRoute()
  136. const dataId = ref(useRoutes?.query?.id ?? '')
  137. const dataType = ref(useRoutes?.query?.type ?? '')
  138. //缓存页面被激活时
  139. onActivated(() => {
  140. dataId.value = useRoutes?.query?.id ?? ''
  141. dataType.value = useRoutes?.query?.type ?? ''
  142. getUserDict()
  143. if(dataType.value!=='add'){
  144. getPlanByProjectId()
  145. }else if(dataType.value=='add'){
  146. }
  147. })
  148. const detailData=ref({})
  149. const constructionData=ref([])
  150. const buildData=ref([])
  151. const supervisorUnitData=ref([])
  152. //获取详情
  153. const getPlanByProjectId=async()=>{
  154. const {error, code, data} = await projectApi.getPlanByProjectId({id: dataId.value})
  155. if (!error && code === 200) {
  156. console.log(getObjValue(data),'详情');
  157. detailData.value=getObjValue(data)
  158. constructionData.value=detailData.value?.constructUnit||[]
  159. buildData.value=detailData.value?.buildUnit||[]
  160. supervisorUnitData.value=detailData.value?.supervisorUnit||[]
  161. tabKey.value='construction'
  162. radioType.value=1
  163. // milestoneData.value=getArrValue(data)
  164. } else {
  165. // milestoneData.value=[]
  166. }
  167. }
  168. //获取所有员工
  169. const userList=ref([])
  170. //获取部门人员列表
  171. const getUserDict=async()=>{
  172. const {error, code, data} = await getuserList({tenantId:useAppState.tenantId})
  173. if (!error && code === 200) {
  174. userList.value = getArrValue(data)
  175. } else {
  176. userList.value = []
  177. }
  178. }
  179. //类型tab数据和相关处理
  180. const tabKey = ref('')
  181. const tabTab = ref([
  182. {key: 'build', name: '施工单位成本'},
  183. {key: 'supervision', name: '监理单位成本'},
  184. {key: 'construction', name: '建设单位成本'}
  185. ]);
  186. const originTableData=ref([ {}])
  187. const tabChange = ({key}) => {
  188. tabKey.value = key
  189. if(key=='supervision'){
  190. tableData.value=supervisorUnitData.value&&supervisorUnitData.value[radioType.value]?.length>0?supervisorUnitData.value:originTableData.value
  191. detailData.value.supervisorUnit=tableData.value
  192. }else if(key=='construction'){
  193. tableData.value=constructionData.value[radioType.value]?.length>0?constructionData.value:originTableData.value
  194. detailData.value.constructUnit=tableData.value
  195. }else if(key=='bulid'){
  196. tableData.value=buildData.value[radioType.value]?.length>0?buildData.value:originTableData.value
  197. detailData.value.buildUnit=tableData.value
  198. }
  199. }
  200. const radioType = ref('')
  201. //深度监听
  202. watch(() => [
  203. radioType.value,
  204. tabKey.value
  205. ], ([radioType]) => {
  206. if(tabKey.value==='construction'){
  207. console.log(constructionData.value[radioType],'constructionData.value');
  208. tableData.value=constructionData.value[radioType]
  209. }else if(tabKey.value==='build'){
  210. console.log(buildData.value[radioType],'buildData.value');
  211. tableData.value=buildData.value[radioType]
  212. }else if(tabKey.value==='supervision'){
  213. console.log(supervisorUnitData.value[radioType],'supervisorUnitData.value');
  214. tableData.value=supervisorUnitData.value[radioType]
  215. }
  216. }, {deep: true})
  217. //表格
  218. const tableColumn = [
  219. {key: 'projectProcessValue', name: '项目环节', width: '160', align: 'center'},
  220. {key: 'budgetTypeValue', name: '预算类型', width: '160', align: 'center'},
  221. {key: 'taskDetailValue', name: '任务明细', width: '160', align: 'center'},
  222. {key: 'planTaskType', name: '任务类型', width: '160', align: 'center'},
  223. {key: 'planTaskDesc', name: '任务描述', minWidth: '200', align: 'center', isTooltip: true},
  224. {key: 'planTarget', name: '完成指标', minWidth: '200', align: 'center', isTooltip: true},
  225. {key: 'key7', name: '计划起止日期', width: '280', align: 'center'},
  226. {key: 'planDays', name: '预计工作量(小数/整数/天)', width: '160', align: 'center'},
  227. {key: 'postTypeValue', name: '投入岗位类型(日单价)', width: '160', align: 'center'},
  228. {key: 'staffCount', name: '投入人员数量', width: '160', align: 'center'},
  229. {key: 'returnedValue', name: '关联回款里程碑', minWidth: '200', isTooltip: true},
  230. {key: 'action', name: '操作', width: '280', align: 'center', fixed: 'right'},
  231. ]
  232. const tableData = ref([
  233. ])
  234. //表格行样式
  235. const tableRowStyle = ({row, rowIndex}) => {
  236. if (row.taskFinishedStatus === 1) {
  237. return {
  238. 'background-color': '#E99D42',
  239. '--el-fill-color-lighter': '#E99D42',
  240. '--el-table-row-hover-bg-color': '#E99D42'
  241. }
  242. } else if (row.taskFinishedStatus === 2 ) {
  243. return {
  244. 'background-color': '#7e9559',
  245. '--el-fill-color-lighter': '#7e9559',
  246. '--el-table-row-hover-bg-color': '#7e9559'
  247. }
  248. }
  249. }
  250. //日期时间被选择
  251. const betweenTime = ref(null)
  252. const betweenTimeUpdate = ({arr, query},item) => {
  253. item.planStartTime=arr[0]
  254. item.planEndTime=arr[1]
  255. item.betweenTime=arr
  256. }
  257. //日期时间被选择
  258. const subbetweenTime = ref(null)
  259. const subbetweenTimeUpdate = ({arr, query},item) => {
  260. console.log(item,'item');
  261. item.planStartTime=arr[0]
  262. item.planEndTime=arr[1]
  263. item.subbetweenTime=arr
  264. }
  265. const subplanModal = ref(false)
  266. const subPlanItem=ref({})
  267. const subplanModalShow = (row) => {
  268. console.log(row,'row');
  269. subPlanItem.value=row
  270. subplanModal.value = true
  271. tableSubplanData.value=row?.childrenList||[]
  272. }
  273. //表格
  274. const tableSubplanColumn = [
  275. {key: 'projectProcessValue', name: '项目环节', width: '160', align: 'center'},
  276. {key: 'budgetTypeValue', name: '预算类型', width: '160', align: 'center'},
  277. {key: 'taskDetailValue', name: '任务明细', width: '160', align: 'center'},
  278. {key: 'planTaskType', name: '任务类型', width: '160', align: 'center'},
  279. {key: 'planTaskDesc', name: '任务描述', minWidth: '200', align: 'center', isTooltip: true},
  280. {key: 'planTarget', name: '完成指标', minWidth: '200', align: 'center', isTooltip: true},
  281. {key: 'key7', name: '计划起止日期', width: '280', align: 'center'},
  282. {key: 'planDays', name: '预计工作量(小数/整数/天)', width: '160', align: 'center'},
  283. {key: 'action', name: '操作', width: '80', align: 'center', fixed: 'right'},
  284. ]
  285. const tableSubplanData = ref([
  286. {id: 1, key1: 'xx', key2: 'xx', key3: 'xx', key4: 'xx', key5: 'xx', key6: 'xx', key7: 'xx', key8: 'xx', key9: 'xx', key10: 'xx'},
  287. {id: 2, key1: 'xx', key2: 'xx', key3: 'xx', key4: 'xx', key5: 'xx', key6: 'xx', key7: 'xx', key8: 'xx', key9: 'xx', key10: 'xx'},
  288. {id: 3, key1: 'xx', key2: 'xx', key3: 'xx', key4: 'xx', key5: 'xx', key6: 'xx', key7: 'xx', key8: 'xx', key9: 'xx', key10: 'xx'},
  289. {id: 4, key1: 'xx', key2: 'xx', key3: 'xx', key4: 'xx', key5: 'xx', key6: 'xx', key7: 'xx', key8: 'xx', key9: 'xx', key10: 'xx'},
  290. {id: 5, key1: 'xx', key2: 'xx', key3: 'xx', key4: 'xx', key5: 'xx', key6: 'xx', key7: 'xx', key8: 'xx', key9: 'xx', key10: 'xx'},
  291. {id: 6, key1: 'xx', key2: 'xx', key3: 'xx', key4: 'xx', key5: 'xx', key6: 'xx', key7: 'xx', key8: 'xx', key9: 'xx', key10: 'xx'},
  292. ])
  293. const subplanCloseClick = () => {
  294. subplanModal.value = false
  295. }
  296. //分解子计划保存
  297. const subplanSaveClick = () => {
  298. tableData.value.forEach((ele)=>{
  299. if(ele.id==subPlanItem.id){
  300. ele.childrenList=tableSubplanData.value
  301. }
  302. })
  303. subplanModal.value = false
  304. // subPlanItem.value.childrenList=tableSubplanData.value
  305. }
  306. const addplan=()=>{
  307. tableSubplanData.value.push({
  308. projectProcessValue:tableSubplanData.value[0]?.projectProcessValue,
  309. budgetTypeValue:tableSubplanData.value[0]?.budgetTypeValue,
  310. taskDetailValue:tableSubplanData.value[0]?.taskDetailValue,
  311. planTaskType:tableSubplanData.value[0]?.planTaskType,
  312. isEdit:true
  313. })
  314. }
  315. const relatedModal = ref(false)
  316. const rePlanid=ref('')
  317. const relatedModalShow = (row) => {
  318. relatedModal.value = true
  319. rePlanid.value=row.id
  320. getListByProjectId(row.projectId)
  321. }
  322. //保存获取工作时长
  323. const getWorkDays=async(row)=>{
  324. row.isEdit = false
  325. if(row?.planStartTime&&row?.planEndTime){
  326. const {error, code, data,msg} = await projectApi.getWorkDays( {
  327. startDate:row.planStartTime,
  328. endDate:row.planEndTime,
  329. })
  330. if (!error && code === 200) {
  331. if(data){
  332. row.planDays=data
  333. }
  334. }
  335. }
  336. }
  337. //关联回款
  338. const relation=async(row,type)=>{
  339. const {error, code, data,msg} = await projectApi.relationPlanAndReturned( {
  340. planId:rePlanid.value,
  341. returnedId:row.id,
  342. type
  343. })
  344. if (!error && code === 200) {
  345. window.$message.success(msg)
  346. getListByProjectId(row.projectId).then()
  347. }
  348. }
  349. //表格
  350. const tableRelatedColumn = [
  351. {key: 'returnedCondition', name: '回款条件', minWidth: '260'},
  352. {key: 'shouldReturnedTime', name: '应收回款时间', width: '160', align: 'center'},
  353. {key: 'shouldReturnedMoney', name: '应收回款金额(元)', width: '160', align: 'center'},
  354. {key: 'reminderUserName', name: '催款执行人', width: '160', align: 'center'},
  355. {key: 'action', name: '操作', width: '130', align: 'center'},
  356. ]
  357. const tableRelatedData = ref([
  358. {id: 1, key1: 'xx', key2: 'xx', key3: 'xx', key4: 'xx', key5: 'xx', key6: 'xx', key7: 'xx', key8: 'xx', key9: 'xx', key10: 'xx'},
  359. {id: 2, key1: 'xx', key2: 'xx', key3: 'xx', key4: 'xx', key5: 'xx', key6: 'xx', key7: 'xx', key8: 'xx', key9: 'xx', key10: 'xx'},
  360. {id: 3, key1: 'xx', key2: 'xx', key3: 'xx', key4: 'xx', key5: 'xx', key6: 'xx', key7: 'xx', key8: 'xx', key9: 'xx', key10: 'xx'},
  361. {id: 4, key1: 'xx', key2: 'xx', key3: 'xx', key4: 'xx', key5: 'xx', key6: 'xx', key7: 'xx', key8: 'xx', key9: 'xx', key10: 'xx'},
  362. {id: 5, key1: 'xx', key2: 'xx', key3: 'xx', key4: 'xx', key5: 'xx', key6: 'xx', key7: 'xx', key8: 'xx', key9: 'xx', key10: 'xx'},
  363. {id: 6, key1: 'xx', key2: 'xx', key3: 'xx', key4: 'xx', key5: 'xx', key6: 'xx', key7: 'xx', key8: 'xx', key9: 'xx', key10: 'xx'},
  364. ])
  365. //合同里程碑
  366. const getListByProjectId=async(projectId,planId)=>{
  367. const {error, code, data} = await contractApi.getListByProjectId({projectId,planId:rePlanid.value})
  368. if (!error && code === 200) {
  369. tableRelatedData.value=getArrValue(data)
  370. } else {
  371. tableRelatedData.value=[]
  372. }
  373. }
  374. const relatedCloseClick = () => {
  375. relatedModal.value = false
  376. }
  377. //修改合同
  378. const updatePlan=async(obj)=>{
  379. console.log(obj,'编辑');
  380. saveLoaing.value=true;
  381. const {error, code, data,msg} = await projectApi.updatePlan( obj)
  382. saveLoaing.value=false;
  383. if (!error && code === 200) {
  384. window.$message.success(msg)
  385. getPlanByProjectId()
  386. }
  387. }
  388. const isEmptyObj=(obj)=> {
  389. let arr = Object.keys(obj);
  390. return(arr.length == 0)
  391. }
  392. //批量保存
  393. const saveLoaing=ref(false)
  394. const saveClick = () => {
  395. console.log(tableData.value,'tableData.value');
  396. console.log( detailData.value,' detailData.value');
  397. //取消空对象提交
  398. if(detailData.value?.supervisorUnit?.length>0){
  399. let suisnullObj= isEmptyObj(detailData.value?.supervisorUnit[0])
  400. if(suisnullObj===true){
  401. detailData.value.supervisorUnit=null
  402. }
  403. }
  404. if(detailData.value?.constructUnit?.length>0){
  405. let coisnullObj= isEmptyObj(detailData.value?.constructUnit[0])
  406. if(coisnullObj){
  407. detailData.value.constructUnit=null
  408. }
  409. }
  410. if(detailData.value?.buildUnit?.length>0){
  411. let buisnullObj= isEmptyObj(detailData.value?.buildUnit[0])
  412. if(buisnullObj){
  413. detailData.value.buildUnit=null
  414. }
  415. }
  416. updatePlan(detailData.value)
  417. }
  418. //取消并返回
  419. const goBackClick = () => {
  420. router.back()
  421. }
  422. </script>
  423. <style lang="scss">
  424. .hc-program-project-form-radio-group {
  425. position: relative;
  426. width: 100%;
  427. .el-radio-group, .el-radio-group .el-radio-button .el-radio-button__inner {
  428. width: 100%;
  429. }
  430. .el-radio-group .el-radio-button {
  431. flex: 1;
  432. }
  433. }
  434. </style>