CostBaseline.vue 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
  1. <template>
  2. <div :style="'padding:10px;background:#f7f7f7;min-height:'+tableHeight+'px;'">
  3. <div style="margin: 0 auto;width:1000px;">
  4. <el-row :gutter="24">
  5. <el-col :span="12">
  6. <div class="box" ref="allBox">
  7. <div >
  8. <!-- <div class="lableTxt">总成本基线</div> -->
  9. <label class="lableTxt">{{ $t('totalcostbaseline') }}<el-link v-if="permissions.projectCorrection" @click="correctBase" style="float:right;"><i class="el-icon-edit" ></i></el-link></label>
  10. <div class="lableCon" v-for="item in projectBaseCostData" :key="item.id">
  11. <div><span class="gray_label">{{item.baseName}}:</span></div>
  12. <div><span style="float:right;">¥{{item.baseAmount | numberToCurrency}}</span></div>
  13. <div>
  14. <span style="float:right;">{{ $t('restcanbedialed') }} <span :style="item.baseAmount * 0.9 < baseCostFilter(item.baseId) ? 'color:red;' : ''">¥{{(item.baseAmount - baseCostFilter(item.baseId)) | numberToCurrency}}</span></span>
  15. </div>
  16. </div>
  17. <div style="height:24px">
  18. <el-link style="float:right;margin-right:10px" @click="addCostAdd" size="small" v-if="permissions.projectAllocate" type="primary">{{ $t('allocatethecostbudget') }}</el-link>
  19. </div>
  20. </div>
  21. </div>
  22. </el-col>
  23. <el-col :span="12">
  24. <div class="box" ref="nowBox" :style="'height:' + nowBaseHeight + 'px;'">
  25. <label class="lableTxt">{{ $t('currentcostbaseline') }}</label>
  26. <div class="lableCon" v-for="item in nowBaseList" :key="item.id">
  27. <div><span class="gray_label">{{item.baseName}}:</span></div>
  28. <div><span style="float:right;">¥{{item.baseAmount==null?'-':item.baseAmount | numberToCurrency}}</span></div>
  29. <div class="nowBase_div3">
  30. <span style="float:right;">{{ $t('yi') }}<span>¥{{item.realCost | numberToCurrency}}</span></span>
  31. </div>
  32. <div class="nowBase_div4">
  33. <span style="float:right;">{{ $t('yu') }}<span :style="item.baseAmount * 0.9 < item.realCost ? 'color:red;' : ''">¥{{(item.baseAmount - item.realCost) | numberToCurrency}}</span></span>
  34. </div>
  35. </div>
  36. <div style="height:24px">
  37. <!-- <el-link style="float:right;margin-right:10px" @click="addCostAdd" size="small" v-if="permissions.projectAllocate" type="primary">下拨成本预算</el-link> -->
  38. </div>
  39. </div>
  40. </el-col>
  41. <el-col :span="24"></el-col>
  42. </el-row>
  43. <el-row :gutter="24">
  44. <el-col :span="24">
  45. <div class="box">
  46. <el-table :data="addList" :loading="ListLoading" :default-sort="{prop:'indate',order:'descending'}">
  47. <el-table-column v-for="item in addListColumns" :key="item.id" :label="item.name" align="right" header-align="center" min-width="150">
  48. <template slot-scope="scope">
  49. <span style="margin-right: 40px;">¥{{itemListFilter(scope.row.addItemList,item.id) | numberToCurrency}}</span>
  50. </template>
  51. </el-table-column>
  52. <el-table-column :label="$t('xiabo')" prop="indate" align="center" min-width="200"></el-table-column>
  53. <el-table-column :label="$t('caozuo')" prop="userName" align="center" min-width="120">
  54. <template slot-scope="scope">
  55. <div>
  56. <span v-if="user.userNameNeedTranslate != 1">
  57. {{scope.row.userName}}
  58. </span>
  59. <span v-if="user.userNameNeedTranslate == 1">
  60. <ww-open-data type='userName' :openid='scope.row.userName'></ww-open-data>
  61. </span>
  62. </div>
  63. </template>
  64. </el-table-column>
  65. <el-table-column :label="$t('bei-zhu')" prop="remark" align="left" header-align="left" show-overflow-tooltip min-width="200"></el-table-column>
  66. <el-table-column label="" align="center" fixed="right" v-if="permissions.projectAllocate">
  67. <template slot-scope="scope">
  68. <el-button size="small" @click="revert(scope.row.id)" v-if="scope.$index == 0">{{ $t('btn.undo') }}</el-button>
  69. </template>
  70. </el-table-column>
  71. </el-table>
  72. </div>
  73. </el-col>
  74. </el-row>
  75. <el-row :gutter="24">
  76. <el-col :span="24">
  77. <div class="box">
  78. <div class="box_head">
  79. 日期区间:
  80. <el-date-picker
  81. v-model="dateSelect"
  82. type="daterange"
  83. size="small"
  84. range-separator="-"
  85. :start-placeholder="$t('interval')"
  86. :end-placeholder="$t('interval')"
  87. value-format="yyyy-MM-dd"
  88. clearable
  89. @change="hiddens()">
  90. </el-date-picker>
  91. </div>
  92. <div class="boxBoxMax">
  93. <div class="boxBoxMax_con">
  94. <el-card class="box-card">
  95. <div slot="header" class="clearfix">
  96. 工时成本
  97. </div>
  98. <div class="text item boxBoxMax_con_one">
  99. <span>工时合计</span> {{ chengbenData.workingTime }} h
  100. </div>
  101. <div class="text item">
  102. <span style="letter-spacing: 26px;">成本</span>¥ {{ chengbenData.cost }}
  103. </div>
  104. </el-card>
  105. </div>
  106. <div class="boxBoxMax_con" v-if="user.company.packageExpense">
  107. <el-card class="box-card">
  108. <div slot="header" class="clearfix">
  109. 费用报销成本
  110. </div>
  111. <div class="text item boxBoxMax_con_two">
  112. <span>报销合计 </span>¥ {{ chengbenData.expense }}
  113. </div>
  114. </el-card>
  115. </div>
  116. </div>
  117. </div>
  118. </el-col>
  119. </el-row>
  120. </div>
  121. <el-dialog :title="$t('allocatethecostbudget')" v-if="addCostAddDialog" :visible.sync="addCostAddDialog" :close-on-click-modal="false" width="600px">
  122. <el-form>
  123. <el-form-item v-for="item,index in modBaseCostData" :key="item.id" :label="item.baseName" label-width="150px">
  124. <el-input :id="'nowBaseCost'+index" v-model="item.baseAmount" :placeholder="$t('peaseenterthe')" clearable @keyup.native="restrictNumber('nowBaseCost'+index)" style="width:350px"></el-input>
  125. </el-form-item>
  126. <el-form-item :label="$t('bei-zhu')" label-width="150px">
  127. <el-input v-model="remark" :placeholder="$t('peaseenterthe')" style="width:350px"></el-input>
  128. </el-form-item>
  129. </el-form>
  130. <div slot="footer" class="dialog-footer">
  131. <!-- <el-button @click="test">test</el-button> -->
  132. <el-button @click.native="addCostAddDialog = false">{{ $t('btn.cancel') }}</el-button>
  133. <el-button type="primary" @click="addCostAddSure" :loading="addLoading">{{ $t('btn.submit') }}</el-button>
  134. </div>
  135. </el-dialog>
  136. <el-dialog :title="$t('jiaoyan')" v-if="correctBaseDialog" :visible.sync="correctBaseDialog" :close-on-click-modal="false" width="600px">
  137. <el-form>
  138. <el-form-item v-for="item,index in correctBaseCostData" :key="item.id" :label="item.baseName" label-width="150px">
  139. <el-input :id="'baseCost'+index" v-model="item.baseAmount" :placeholder="$t('peaseenterthe')" clearable @keyup.native="restrictNumber('baseCost'+index)" style="width:350px"></el-input>
  140. </el-form-item>
  141. <el-form-item :label="$t('bei-zhu')" label-width="150px">
  142. <el-input v-model="remark" :placeholder="$t('reasoforcorrection')" style="width:350px"></el-input>
  143. </el-form-item>
  144. </el-form>
  145. <div slot="footer" class="dialog-footer">
  146. <!-- <el-button @click="test">test</el-button> -->
  147. <el-button @click.native="correctBaseDialog = false">{{ $t('btn.cancel') }}</el-button>
  148. <el-button type="primary" @click="correctBaseSure" :loading="addLoading">{{ $t('btn.submit') }}</el-button>
  149. </div>
  150. </el-dialog>
  151. </div>
  152. </template>
  153. <script>
  154. import util from "../../common/js/util";
  155. export default {
  156. data() {
  157. return {
  158. user: JSON.parse(sessionStorage.getItem('user')),
  159. permissions: JSON.parse(sessionStorage.getItem("permissions")),
  160. curProjectId: null,
  161. addLoading: false,
  162. projectBaseCostData: [],
  163. modBaseCostData: [],
  164. correctBaseCostData: [],
  165. remark: '',
  166. addCostAddDialog: false,
  167. addListColumns:[],
  168. ListLoading: false,
  169. nowBaseList: [],
  170. correctBaseDialog: false,
  171. projectContractAmount: null,
  172. nowBaseHeight: '',
  173. dateSelect: [],
  174. chengbenData: {}
  175. };
  176. },
  177. filters: {
  178. numberToCurrency(value) {
  179. // console.log('info numberToCurrency='+value);
  180. if (!value || value=='-') return '0.00'
  181. value = value.toFixed(2)
  182. const intPart = Math.trunc(value)
  183. const intPartFormat = intPart.toString().replace(/(\d)(?=(?:\d{3})+$)/g, '$1,')
  184. let floatPart = '.00'
  185. const valueArray = value.toString().split('.')
  186. if (valueArray.length === 2) { // 有小数部分
  187. floatPart = valueArray[1].toString() // 取得小数部分
  188. return intPartFormat + '.' + floatPart
  189. }
  190. return intPartFormat + floatPart
  191. }
  192. },
  193. methods: {
  194. hiddens() {
  195. this.getTimeCostAndExpenseByProject()
  196. },
  197. getTimeCostAndExpenseByProject() {
  198. this.http.post('/project/timeCostAndExpenseByProject',{
  199. startDate: this.dateSelect[0],
  200. endDate: this.dateSelect[1],
  201. projectId: this.curProjectId
  202. },res => {
  203. if(res.code == 'ok'){
  204. this.chengbenData = res.data
  205. }else {
  206. this.$message({
  207. message: res.msg,
  208. type: 'error'
  209. })
  210. }
  211. },err => {
  212. this.$message({
  213. message: err,
  214. type: 'error'
  215. })
  216. })
  217. },
  218. restrictNumber(targetId) {
  219. let inpu = document.getElementById(targetId);
  220. inpu.value = inpu.value.replace(/[^\d.]/g, ""); //仅保留数字和"."
  221. inpu.value = inpu.value.replace(/\.{2,}/g, "."); //两个连续的"."仅保留第一个"."
  222. inpu.value = inpu.value.replace(".", "$#*").replace(/\./g,'').replace('$#*','.');//去除其他"."
  223. inpu.value = inpu.value.replace(/^(\d+)\.(\d\d).*$/, '$1.$2');;//限制只能输入两个小数
  224. if (inpu.value.indexOf(".") < 0 && inpu.value != "") { //首位是0的话去掉
  225. inpu.value = parseFloat(inpu.value);
  226. }
  227. if (inpu.value == '') {
  228. inpu.value = 0;
  229. }
  230. },
  231. test(item){
  232. // let list = [
  233. // {id: 5, name: 'zs'},
  234. // {id: 6, name: 'ls'},
  235. // {id: 7, name: 'ww'}
  236. // ]
  237. // let lid = 5
  238. // let item = this.itemListFilter(list,lid)
  239. // console.log('test',this.addListColumns);
  240. },
  241. baseCostFilter(eId){
  242. let emItem = this.nowBaseList.filter((em)=>{
  243. return em.baseId == eId
  244. })
  245. if(emItem.length == 0){
  246. return 0
  247. }else{
  248. return emItem[0].baseAmount
  249. }
  250. },
  251. itemListFilter(emList,eId){
  252. let emItem = emList.filter((em)=>{
  253. return em.baseId == eId
  254. })
  255. if(emItem.length == 0){
  256. return 0
  257. }else{
  258. return emItem[0].baseAmount
  259. }
  260. },
  261. getProjectBaseData(projectId) {
  262. this.http.post('/project-basecost/get',{
  263. projectId: projectId
  264. },res => {
  265. if (res.code == "ok") {
  266. this.projectBaseCostData = res.data;
  267. } else {
  268. this.$message({
  269. message: res.msg,
  270. type: "error"
  271. });
  272. }
  273. },error => {
  274. this.$message({
  275. message: error,
  276. type: "error"
  277. });
  278. });
  279. },
  280. // 撤销操作
  281. revert(eId){
  282. this.http.post('/project-addcost-record/revert',{
  283. id: eId
  284. },res => {
  285. if(res.code == 'ok'){
  286. this.$message({
  287. message: this.$t('Revocationofsuccess'),
  288. type: 'success'
  289. })
  290. this.getAddList()
  291. this.getNowBase()
  292. }else {
  293. this.$message({
  294. message: res.msg,
  295. type: 'error'
  296. })
  297. }
  298. },err => {
  299. this.$message({
  300. message: err,
  301. type: 'error'
  302. })
  303. })
  304. },
  305. correctBase(){
  306. // 校正成本基线
  307. this.correctBaseCostData = JSON.parse(JSON.stringify(this.projectBaseCostData))
  308. this.correctBaseDialog = true
  309. },
  310. addCostAdd(){
  311. this.modBaseCostData = JSON.parse(JSON.stringify(this.projectBaseCostData))
  312. for (let i = 0; i < this.modBaseCostData.length; i++) {
  313. this.modBaseCostData[i].baseAmount = 0
  314. }
  315. this.addCostAddDialog = true
  316. },
  317. addCostAddSure(){
  318. this.addLoading = true
  319. let itemList = []
  320. let isAll0 = true
  321. for(let i=0; i<this.modBaseCostData.length; i++){
  322. // let itemListItem = {
  323. // baseId: this.modBaseCostData[i].baseId,
  324. // baseName: this.modBaseCostData[i].baseName,
  325. // baseAmount: this.modBaseCostData[i].baseAmount
  326. // }
  327. if(this.modBaseCostData[i].baseAmount != 0){
  328. isAll0 = false
  329. }
  330. itemList.push({
  331. baseId: this.modBaseCostData[i].baseId,
  332. baseName: this.modBaseCostData[i].baseName,
  333. baseAmount: this.modBaseCostData[i].baseAmount
  334. })
  335. }
  336. if(isAll0){
  337. this.$message({
  338. message: this.$t('saoyixiang'),
  339. type: 'warning'
  340. })
  341. this.addLoading = false
  342. return
  343. }
  344. this.http.post('/project-addcost-record/add',{
  345. projectId: this.curProjectId,
  346. userId: this.user.id,
  347. userName: this.user.name,
  348. itemList: JSON.stringify(itemList),
  349. remark: this.remark
  350. },res => {
  351. if(res.code == 'ok'){
  352. this.addLoading = false
  353. this.addCostAddDialog = false
  354. this.$message({
  355. message: this.$t('xiabochenggong'),
  356. type: 'success'
  357. })
  358. this.remark = ''
  359. this.getAddList()
  360. this.getNowBase()
  361. }else {
  362. this.addLoading = false
  363. this.$message({
  364. message: res.msg,
  365. type: 'error'
  366. })
  367. }
  368. },err => {
  369. this.addLoading = false
  370. this.$message({
  371. message: err,
  372. type: 'error'
  373. })
  374. })
  375. },
  376. correctBaseSure(){
  377. //如果没有做修改,不提交数据
  378. var hasChangeData = false;
  379. for (var i=0;i<this.correctBaseCostData.length; i++) {
  380. var item = this.correctBaseCostData[i];
  381. var oldAmount = this.projectBaseCostData.filter(p=>p.id == item.id)[0].baseAmount;
  382. if (item.baseAmount != oldAmount) {
  383. hasChangeData = true;
  384. break;
  385. }
  386. }
  387. if (!hasChangeData) {
  388. this.correctBaseDialog = false;
  389. return;
  390. }
  391. this.http.post('/project/adjustBase', {
  392. id: this.curProjectId,
  393. contractAmount: this.projectContractAmount,
  394. baseCostData:JSON.stringify(this.correctBaseCostData),
  395. remark: this.remark
  396. },
  397. res => {
  398. if (res.code == "ok") {
  399. this.correctBaseDialog = false;
  400. this.$message({
  401. message: this.$t('jiaozheng'),
  402. type: "success"
  403. });
  404. this.remark = ''
  405. this.getProjectBaseData(this.curProjectId)
  406. } else {
  407. this.$message({
  408. message: res.msg,
  409. type: "error"
  410. });
  411. }
  412. },
  413. error => {
  414. this.$message({
  415. message: error,
  416. type: "error"
  417. });
  418. });
  419. },
  420. getNowBase(){
  421. this.http.post('/project-currentcost/get',{
  422. companyId: this.user.companyId,
  423. projectId: this.curProjectId
  424. },res => {
  425. if(res.code == 'ok'){
  426. this.nowBaseList = res.data
  427. // if(res.data.length == 0){
  428. this.getBoxHeight()
  429. // let allboxh = this.$refs.allBox.scrollHeight - 20
  430. // console.log('getnowbase',allboxh);
  431. // this.nowBaseHeight = allboxh
  432. // }
  433. }else {
  434. this.$message({
  435. message: res.msg,
  436. type: 'error'
  437. })
  438. }
  439. },err => {
  440. this.$message({
  441. message: err,
  442. type: 'error'
  443. })
  444. })
  445. },
  446. getAddList(){
  447. this.ListLoading = true
  448. this.http.post('/project-addcost-record/getAddList',{
  449. companyId: this.user.companyId,
  450. projectId: this.curProjectId
  451. },res => {
  452. if(res.code == 'ok'){
  453. this.ListLoading = false
  454. this.addList = res.data.recordList
  455. this.addListColumns = res.data.columns
  456. // console.log('getaddlist',res.data);
  457. }else {
  458. this.ListLoading = false
  459. this.$message({
  460. message: res.msg,
  461. type: 'error'
  462. })
  463. }
  464. },err => {
  465. this.ListLoading = false
  466. this.$message({
  467. message: err,
  468. type: 'error'
  469. })
  470. })
  471. },
  472. getProjectInfo() {
  473. this.http.post('/project/detail', {
  474. id: this.curProjectId
  475. },
  476. res => {
  477. if (res.code == "ok") {
  478. this.projectContractAmount = res.data.contractAmount;
  479. // console.log('res.data',res.data);
  480. } else {
  481. this.$message({
  482. message: res.msg,
  483. type: "error"
  484. });
  485. }
  486. },
  487. error => {
  488. this.$message({
  489. message: error,
  490. type: "error"
  491. });
  492. });
  493. },
  494. refreshPage(){
  495. this.curProjectId = parseInt(this.$route.params.id);
  496. this.getProjectBaseData(this.curProjectId)
  497. this.getAddList()
  498. this.getNowBase()
  499. this.getProjectInfo()
  500. },
  501. getBoxHeight(){
  502. let allboxh = this.$refs.allBox.scrollHeight - 20
  503. this.nowBaseHeight = allboxh
  504. }
  505. },
  506. created() {
  507. let height = window.innerHeight;
  508. this.tableHeight = height - 160;
  509. const that = this;
  510. window.onresize = function temp() {
  511. that.tableHeight = window.innerHeight - 160;
  512. };
  513. },
  514. mounted() {
  515. this.curProjectId = parseInt(this.$route.params.id);
  516. // var _this = this;
  517. // var that = this
  518. // console.log("mounted")
  519. // window.addEventListener("resize", function() {
  520. // // _this.profitChart.resize();
  521. // // that.profitChart.resize();
  522. // });
  523. this.getProjectBaseData(this.curProjectId)
  524. this.getAddList()
  525. this.getNowBase()
  526. this.getProjectInfo()
  527. this.getTimeCostAndExpenseByProject()
  528. }
  529. };
  530. </script>
  531. <style scoped>
  532. .box {
  533. background:#fff;
  534. border: 1px solid #eeeeee;
  535. border-radius:5px;
  536. padding:10px;
  537. margin-top:10px;
  538. overflow-x: auto;
  539. }
  540. .el-row {
  541. margin-top:10px;
  542. }
  543. .lableTxt {
  544. color:#666;
  545. }
  546. .lableCon{
  547. margin: 20px 10px;
  548. height: 20px;
  549. }
  550. .lableCon div:nth-child(1){
  551. width: 30%;
  552. float: left;
  553. }
  554. .lableCon div:nth-child(2){
  555. width: 20%;
  556. float: left;
  557. }
  558. .lableCon div:nth-child(3){
  559. width: 50%;
  560. float: left;
  561. font-size: 13px;
  562. color: #999;
  563. line-height: 20px;
  564. }
  565. .lableCon .nowBase_div3{
  566. width: 25% !important;
  567. float: left;
  568. font-size: 13px;
  569. color: #999;
  570. line-height: 20px;
  571. }
  572. .lableCon .nowBase_div4{
  573. width: 25%;
  574. float: left;
  575. font-size: 13px;
  576. color: #999;
  577. line-height: 20px;
  578. }
  579. .gray_label {
  580. color: #999;
  581. }
  582. .box_head {
  583. margin: 20px 20px 20px 0px;
  584. color: #333;
  585. }
  586. .boxBoxMax {
  587. display: flex;
  588. justify-content: space-between;
  589. }
  590. .boxBoxMax_con {
  591. width: 48%;
  592. /* margin-right: 30px; */
  593. color: #333;
  594. }
  595. .boxBoxMax_con_one {
  596. margin-bottom: 8px;
  597. }
  598. .boxBoxMax_con_two {
  599. margin-bottom: 25px;
  600. }
  601. .boxBoxMax_con span{
  602. color: #999;
  603. display: inline-block;
  604. width: 80px;
  605. }
  606. </style>