change-form.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. <template>
  2. <div>
  3. <div class="tool-btn" style="width: 800px; margin: 0 auto; text-align: right;">
  4. <el-button size="small" type="primary" icon="el-icon-printer" v-print="'#changeDoc'" :disabled="!disabled">打印</el-button>
  5. </div>
  6. <div id="changeDoc" class="doc-page">
  7. <h3 class="page-title">项目变更申请表</h3>
  8. <table>
  9. <tbody>
  10. <tr>
  11. <td class="doc-label">研发项目名称</td>
  12. <td class="doc-input">
  13. <project-select v-model="currProjectId" @change="handleProjectChange" :disabled="disabled"></project-select>
  14. </td>
  15. <td class="doc-label">研发项目编号</td>
  16. <td class="doc-input">{{ selProject.xmbh }}</td>
  17. </tr>
  18. <tr>
  19. <td class="doc-label">项目负责人</td>
  20. <td class="doc-input">{{ selProject.xmfzrxm }} </td>
  21. <td class="doc-label">项目申请单位</td>
  22. <td class="doc-input">{{ selProject.xmsqdw }}</td>
  23. </tr>
  24. <tr>
  25. <td class="doc-label">变更类型</td>
  26. <td class="doc-input" colspan="3">
  27. <avue-select
  28. v-model="formData.zdmc"
  29. placeholder="请选择"
  30. :dic="changeTypeList"
  31. style="width: 100%"
  32. :disabled="disabled || !selProject.id"
  33. ></avue-select>
  34. </td>
  35. </tr>
  36. <tr>
  37. <td class="doc-label">变更前</td>
  38. <td class="doc-input" style="padding: 0;">
  39. <!-- 项目预算 -->
  40. <template v-if="formData.zdmc === 'xmyszemx'">
  41. <div style="height: 31px; line-height: 31px; border-bottom: 1px solid #000;">总计:{{ selProject.xmysze }}万元</div>
  42. <div class="year-list">
  43. <div class="year-list-item" v-for="item of oldXmyszemxList" :key="item.year">
  44. <div class="year" >{{ item.year }}</div>
  45. <div class="year-input"> {{ item.amount }} </div>
  46. </div>
  47. </div>
  48. </template>
  49. <span v-else>{{ getChangeBeforeVal }}</span>
  50. </td>
  51. <td class="doc-label">变更后</td>
  52. <td class="doc-input">
  53. <el-input v-if="formData.zdmc === 'xmmc'" placeholder="请输入项目名称" v-model="formData.bghdz" :disabled="disabled" />
  54. <!-- 项目结束时间 -->
  55. <el-date-picker
  56. v-if="'xmjssj'.indexOf(formData.zdmc) > -1"
  57. v-model="formData.bghdz"
  58. format="yyyy年MM月dd日"
  59. value-format="yyyy-MM-dd"
  60. placeholder="请选择日期"
  61. style="width: 100%"
  62. :disabled="disabled"
  63. @change="handleEndDateChange"
  64. ></el-date-picker>
  65. <!-- 项目负责人 -->
  66. <template v-if="formData.zdmc === 'xmfzr'">
  67. <person-select
  68. v-if="!disabled"
  69. v-model="formData.bghdz"
  70. :yearAndMonth="projectStartMonth"
  71. valueKey="id"
  72. :disabled="disabled"
  73. style="width: 100%"
  74. />
  75. <span v-else>{{ formData.bghdz }}</span>
  76. </template>
  77. <!-- 项目状态 -->
  78. <avue-select
  79. v-if="formData.zdmc === 'status'"
  80. v-model="formData.bghdz"
  81. placeholder="请选择"
  82. :dic="projectStateList"
  83. style="width: 100%"
  84. :disabled="disabled"
  85. ></avue-select>
  86. <!-- 所属二级单位 -->
  87. <template v-if="formData.zdmc === 'xmsqdwid'">
  88. <second-unit-cascader
  89. v-if="!disabled"
  90. v-model="formData.bghdz"
  91. :disabled="disabled"
  92. :yearAndMonth="currProjectYear"
  93. style="width: 100%;"
  94. />
  95. <span v-else>{{ formData.bghdz }}</span>
  96. </template>
  97. <!-- 项目预算明细 -->
  98. <div v-if="formData.zdmc === 'xmyszemx'" class="year-list">
  99. <div style="height: 31px; line-height: 31px; border-bottom: 1px solid #000;">总计:{{ newXmysTotal }}万元</div>
  100. <div class="year-list-item" v-for="item of newXmyszemxList" :key="item.year">
  101. <div class="year" >{{ item.year }}</div>
  102. <div class="year-input">
  103. <input v-model="item.amount" type="number" style="padding: 0 6px;" :disabled="disabled" />
  104. </div>
  105. </div>
  106. </div>
  107. </td>
  108. </tr>
  109. <!-- 变更结束时间,同时增加预算变更 -->
  110. <tr v-if="formData.zdmc === 'xmjssj' && !disabled"><td colspan="4" align="center" style="height: 46px; font-weight: 600;">预算变更</td></tr>
  111. <tr v-if="formData.zdmc === 'xmjssj' && !disabled">
  112. <td class="doc-label">变更前</td>
  113. <td class="doc-input" style="padding: 0;">
  114. <div style="height: 31px; line-height: 31px; border-bottom: 1px solid #000;">总计:{{ selProject.xmysze }}万元</div>
  115. <div class="year-list">
  116. <div class="year-list-item" v-for="item of oldXmyszemxList" :key="item.year">
  117. <div class="year">{{ item.year }}</div>
  118. <div class="year-input"> {{ item.amount }} </div>
  119. </div>
  120. </div>
  121. </td>
  122. <td class="doc-label">变更后</td>
  123. <td class="doc-input" style="padding: 0;">
  124. <div class="year-list">
  125. <div style="height: 31px; line-height: 31px; border-bottom: 1px solid #000;">总计:{{ newXmysTotal }}万元</div>
  126. <div class="year-list-item" v-for="item of newXmyszemxList" :key="item.year">
  127. <div class="year">{{ item.year }}</div>
  128. <div class="year-input">
  129. <input v-model="item.amount" type="number" style="padding: 0 6px;" :disabled="disabled" />
  130. </div>
  131. </div>
  132. </div>
  133. </td>
  134. </tr>
  135. <tr>
  136. <td class="doc-label">变更原因说明</td>
  137. <td class="doc-input" colspan="3">
  138. <el-input v-model="formData.bgyy" type="textarea" :rows="3" :disabled="disabled" />
  139. </td>
  140. </tr>
  141. <tr>
  142. <td class="doc-label">变更生效时间</td>
  143. <td class="doc-input" colspan="3">
  144. <el-date-picker
  145. v-model="formData.bgsxsj"
  146. format="yyyy年MM月dd日"
  147. value-format="yyyy-MM-dd"
  148. placeholder="请选择日期"
  149. style="width: 100%"
  150. :disabled="disabled"
  151. ></el-date-picker>
  152. </td>
  153. </tr>
  154. <tr>
  155. <td class="doc-label">附件上传</td>
  156. <td class="doc-input" colspan="3" style="text-align: left;">
  157. <div v-if="!disabled">
  158. <avue-upload
  159. action="/api/kd-resource/oss/endpoint/put-file"
  160. dataType="object"
  161. :props-http="{
  162. url: 'link',
  163. name: 'originalName',
  164. res: 'data',
  165. }"
  166. type="upload"
  167. v-model="formData.attachment"
  168. style="margin: 14px 0 14px 12px;"
  169. :disabled="disabled"
  170. :uploadPreview="handleUploadPreview"
  171. tip="上传变更凭证资料。注:可一次上传多个相关文件(文档、表格、图片等)。支持格式:DOC、PDF、Excel、PNG、JPG、PPT等。请确保文件内容清晰有效。"
  172. ></avue-upload>
  173. </div>
  174. <div v-else>
  175. <div v-for="(item, index) of formData.attachment" :key="index" style="line-height: 26px; padding: 6px;">
  176. <el-link type="primary" @click="handleDownFile(item)">{{ item.label }}</el-link>
  177. </div>
  178. </div>
  179. </td>
  180. </tr>
  181. </tbody>
  182. </table>
  183. <div v-if="!disabled" class="btn-wrap" style="text-align: center; margin-top: 30px;">
  184. <el-button type="primary" style="width: 160px;" @click="handleSaveBtn">保存</el-button>
  185. </div>
  186. </div>
  187. </div>
  188. </template>
  189. <script>
  190. import { getDictionary } from "@/api/system/dict";
  191. import { getList as getTechnicanRosterList } from "@/api/basicResource/technicianRoster";
  192. import { save } from "@/api/projectManage/change";
  193. import moment from "moment";
  194. import projectSelect from "@/components/project-select";
  195. import secondUnitCascader from "@/components/second-unit-cascader";
  196. import personSelect from "@/components/person-select";
  197. import Decimal from "decimal.js";
  198. import { downloadFileByUrl } from "@/util/util";
  199. export default {
  200. props: {
  201. projectId: [String, Number],
  202. data: Object,
  203. disabled: {
  204. type: Boolean,
  205. type: false
  206. }
  207. },
  208. components: {
  209. projectSelect,
  210. secondUnitCascader,
  211. personSelect
  212. },
  213. data() {
  214. return {
  215. currProjectId: '',
  216. formData: {
  217. attachment: []
  218. },
  219. // 变更类型
  220. changeTypeList: [],
  221. // 项目状态
  222. projectStateList: [],
  223. loading: false,
  224. selProject: {},
  225. // 项目负责人列表
  226. fzrList: [],
  227. // 变更前预算明细
  228. oldXmyszemxList: [],
  229. // 变更后预算明细
  230. newXmyszemxList: [],
  231. // 项目开始月份
  232. projectStartMonth: ""
  233. };
  234. },
  235. watch: {
  236. "formData.zdmc"(newVal) {
  237. if (newVal == 'xmfzr') {
  238. this.getTechnicanRosterListFunc(moment(this.selProject.xmkssj).format('yyyyMM'));
  239. }
  240. // else if (newVal == 'xmyszemx' || newVal == 'xmjssj') {
  241. // this.oldXmyszemxList = JSON.parse(this.selProject.xmyszemx);
  242. // this.newXmyszemxList = JSON.parse(this.selProject.xmyszemx);
  243. // }
  244. },
  245. },
  246. computed: {
  247. // 当前项目所属年份
  248. currProjectYear() {
  249. return this.selProject.xmkssj ? moment(this.selProject.xmkssj).format('YYYY') : '';
  250. },
  251. // 获取变更前的值
  252. getChangeBeforeVal() {
  253. if (this.disabled) {
  254. return this.formData.bgqdz;
  255. }
  256. if (this.formData.zdmc === 'xmfzr') {
  257. return this.selProject.xmfzrxm;
  258. } else if (this.formData.zdmc === 'xmsqdwid') {
  259. return this.selProject.xmsqdw;
  260. } else if (this.formData.zdmc === 'status') {
  261. let _currStatusObj = this.projectStateList.find(item => item.value == this.selProject.status);
  262. return _currStatusObj ? _currStatusObj.label: '';
  263. } else {
  264. return this.selProject[this.formData.zdmc];
  265. }
  266. },
  267. // 变更后的预算总值
  268. newXmysTotal() {
  269. let total = new Decimal(0);
  270. this.newXmyszemxList.forEach(item => {
  271. total = total.add(new Decimal(item.amount || 0));
  272. });
  273. return total;
  274. },
  275. },
  276. mounted() {
  277. this.getDictList("xm_biangeng_field", "changeTypeList");
  278. this.getDictList("project_state", "projectStateList");
  279. this.currProjectId = this.projectId;
  280. if (Object.keys(this.data).length) {
  281. this.formData = { ...this.data, zdmc: this.data.zdmc.toLowerCase() };
  282. if (this.formData.zdmc === 'xmyszemx') {
  283. this.oldXmyszemxList = this.formData.bgqdzJSON;
  284. this.newXmyszemxList = this.formData.bghdzJSON;
  285. }
  286. this.currProjectId = this.data.xmId;
  287. }
  288. },
  289. methods: {
  290. handleEndDateChange(e) {
  291. let startYear = moment(this.selProject.xmkssj).format('YYYY');
  292. let endYear = moment(e).format('YYYY');
  293. let newYearList = [];
  294. for (let i=startYear; i<=endYear; i++) {
  295. let _obj = this.oldXmyszemxList.find(item => item.year == i);
  296. newYearList.push({ year: i, amount: _obj ? _obj.amount : '' });
  297. }
  298. this.newXmyszemxList = newYearList;
  299. },
  300. /**
  301. * 获取人员花名册
  302. */
  303. getTechnicanRosterListFunc(yearAndMonth) {
  304. getTechnicanRosterList(1, 99999, { yearAndMonth }).then(res => {
  305. if (res.data.success) {
  306. this.fzrList = res.data.data.records.map(item => {
  307. return { label: item.name, value: item.id };
  308. });
  309. }
  310. });
  311. },
  312. getDictList(code, toListKey) {
  313. getDictionary({ code }).then((res) => {
  314. this[toListKey] = res.data.data.map((item) => {
  315. return { label: item.dictValue, value: item.dictKey.toLowerCase() };
  316. });
  317. });
  318. },
  319. handleProjectChange(data) {
  320. this.selProject = data;
  321. this.projectStartMonth = moment(data.xmkssj).format("yyyyMM");
  322. if (!this.disabled) {
  323. this.oldXmyszemxList = JSON.parse(data.xmyszemx);
  324. this.newXmyszemxList = JSON.parse(data.xmyszemx);
  325. }
  326. },
  327. validForm() {
  328. let errorText = '';
  329. if (!this.currProjectId) {
  330. errorText = '请选择变更的项目';
  331. } else if (!this.formData.zdmc) {
  332. errorText = '请选择变更类型';
  333. } else if (!this.formData.bgyy) {
  334. errorText = '请输入变更原因';
  335. } else if (!this.formData.bgsxsj) {
  336. errorText = '请选择变更生效时间';
  337. }
  338. if ('xmyszemx,xmjssj'.indexOf(this.formData.zdmc) > -1) {
  339. for (let i=0; i<this.newXmyszemxList.length; i++) {
  340. let _item = this.newXmyszemxList[i];
  341. if (_item.amount === null || _item.amount === undefined || _item.amount === '') {
  342. errorText = `请填写${_item.year}年的预算`;
  343. break;
  344. }
  345. }
  346. if (this.formData.zdmc === 'xmjssj' && !this.formData.bghdz) {
  347. errorText = '变更后的值不能为空!';
  348. }
  349. } else {
  350. if (!this.formData.bghdz || (this.formData.bghdz instanceof Array && !this.formData.bghdz.length)) {
  351. errorText = '变更后的值不能为空!';
  352. }
  353. }
  354. return errorText;
  355. },
  356. /**
  357. * 获取提交的参数
  358. * @param changeType 变更类型
  359. */
  360. getFormData(changeType) {
  361. const params = {
  362. ...this.formData,
  363. bgqdz: this.selProject[changeType],
  364. attachment: JSON.stringify(this.formData.attachment),
  365. xmId: this.currProjectId,
  366. zdmc: changeType.toUpperCase()
  367. };
  368. if (changeType === 'xmyszemx') {
  369. params.bghdz = JSON.stringify(this.newXmyszemxList);
  370. } else if (changeType === 'xmsqdwid') {
  371. params.bghdz = params.bghdz[params.bghdz.length - 1];
  372. }
  373. return params;
  374. },
  375. handleSaveBtn() {
  376. let errorText = this.validForm();
  377. if (errorText) {
  378. this.$message.error(errorText+'!');
  379. return;
  380. }
  381. if (this.formData.zdmc === 'xmjssj' && JSON.stringify(this.newXmyszemxList) != JSON.stringify(this.oldXmyszemxList)) {
  382. this.$confirm('检测到预算发生变化,点击确定,将同时发起一个预算变更,确认要操作吗?', '提示').then(() => {
  383. const params1 = this.getFormData(this.formData.zdmc);
  384. const params2 = this.getFormData('xmyszemx');
  385. params2.bgqdz = JSON.stringify(this.oldXmyszemxList);
  386. this.saveData(params1).then(res => {
  387. if (res.data.success) {
  388. return this.saveData(params2)
  389. }
  390. }).then(res => {
  391. if (res.data.success) {
  392. this.$message.success("保存成功!");
  393. this.$emit("success");
  394. }
  395. });
  396. // Promise.all([this.saveData(params1), this.saveData(params2)]).then(([res1, res2]) => {
  397. // if (res1.data.success) {
  398. // this.$message.success("保存成功!");
  399. // this.$emit("success");
  400. // }
  401. // });
  402. });
  403. return;
  404. }
  405. const params = this.getFormData(this.formData.zdmc);
  406. this.saveData(params).then(({ data }) => {
  407. if (data.code == 200) {
  408. this.$message.success("保存成功!");
  409. this.$emit("success");
  410. }
  411. });
  412. },
  413. saveData(params) {
  414. return save(params)
  415. },
  416. handleDownFile(file) {
  417. downloadFileByUrl(file.value, file.label);
  418. },
  419. handleUploadPreview (file, column, done) {
  420. downloadFileByUrl(file.url, file.name);
  421. return;
  422. },
  423. },
  424. };
  425. </script>
  426. <style lang="scss" scoped>
  427. .page-title {
  428. text-align: center;
  429. font-size: 24px;
  430. padding: 24px 0 16px;
  431. margin: 0;
  432. }
  433. .doc-page {
  434. width: 760px;
  435. margin: 0 auto;
  436. }
  437. table {
  438. width: 100%;
  439. border: 1px solid #333;
  440. }
  441. .doc-label {
  442. text-align: center;
  443. padding: 6px;
  444. color: #777;
  445. font-size: 14px;
  446. width: 15%;
  447. }
  448. .doc-input {
  449. text-align: center;
  450. color: #333;
  451. width: 35%;
  452. height: 40px;
  453. }
  454. .el-input__inner {
  455. border: none;
  456. }
  457. tr td {
  458. border: 1px solid #333;
  459. }
  460. .year-list {
  461. &-item {
  462. display: flex;
  463. height: 32px;
  464. line-height: 32px;
  465. border-bottom: 1px solid #333;
  466. &:last-child {
  467. border-bottom: 0;
  468. }
  469. .year {
  470. width: 65px;
  471. border-right: 1px solid #333;
  472. }
  473. .year-input {
  474. padding-left: 12px;
  475. input {
  476. border: none;
  477. }
  478. }
  479. }
  480. }
  481. </style>