form.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
  1. <template>
  2. <!-- 添加或修改项目信息对话框 -->
  3. <div class="el-drawer__wrapper">
  4. <el-drawer :title="title" v-model="visible" direction="rtl" size="100%">
  5. <div class="page-container form-container">
  6. <div class="form-btns-container">
  7. <span class="title-label"
  8. ><el-icon>
  9. <Document />
  10. </el-icon>
  11. 合同信息</span
  12. >
  13. <el-button-group>
  14. <el-button
  15. v-if="editStatus"
  16. type="primary"
  17. size="small"
  18. icon="Finished"
  19. @click="submitForm"
  20. >保存</el-button
  21. >
  22. <el-button
  23. v-else-if="form.verifyStatus == 0"
  24. type="warning"
  25. size="small"
  26. icon="Edit"
  27. @click="editStatus = true"
  28. >修改</el-button
  29. >
  30. <el-button
  31. v-if="form.id && editStatus"
  32. type="info"
  33. size="small"
  34. icon="Close"
  35. @click="editStatus = false"
  36. >取消修改</el-button
  37. >
  38. <el-button
  39. v-if="form.id"
  40. type="success"
  41. size="small"
  42. @click="getForm"
  43. >
  44. <i class="fa fa-refresh" aria-hidden="true" /> 刷新
  45. </el-button>
  46. <el-button
  47. v-show="form.id && !editStatus && form.verifyStatus == 0"
  48. v-hasPermi="['business:archive:order:verify']"
  49. type="warning"
  50. size="small"
  51. @click="verifyHandler"
  52. >审核通过</el-button
  53. >
  54. <el-button
  55. v-show="form.id && !editStatus && form.verifyStatus == 0"
  56. v-hasPermi="['business:archive:order:verify']"
  57. type="danger"
  58. size="small"
  59. @click="rejectHandler"
  60. >驳回</el-button
  61. >
  62. </el-button-group>
  63. <div class="screen-btn" @click="handleScreen">
  64. <template v-if="!isFullscreen">
  65. <i class="fa fa-window-maximize" aria-hidden="true" />
  66. <!-- <span>全屏</span> -->
  67. </template>
  68. <template v-else>
  69. <i class="fa fa-window-restore" aria-hidden="true" />
  70. <!-- <span>还原</span> -->
  71. </template>
  72. </div>
  73. <div class="close-btn" @click="cancel">
  74. <i class="fa fa-times" aria-hidden="true" />
  75. <!-- <span>关闭</span> -->
  76. </div>
  77. </div>
  78. <div
  79. class="Y-scrollbar"
  80. style="
  81. position: absolute;
  82. top: 32px;
  83. bottom: 0;
  84. width: 100%;
  85. overflow: auto;
  86. "
  87. ></div>
  88. <el-form
  89. ref="orderRef"
  90. class="master-container"
  91. size="small"
  92. :model="form"
  93. :rules="rules"
  94. label-width="100px"
  95. >
  96. <el-row :gutter="30">
  97. <el-col :span="6">
  98. <el-form-item label="收款流水号:">
  99. <el-input
  100. v-if="editStatus"
  101. style="width: 100%"
  102. v-model.trim="form.flowNo"
  103. readonly
  104. size="small"
  105. type="text"
  106. placeholder="收款流水号"
  107. :clearable="true"
  108. />
  109. <span v-else>{{ form.flowNo }}</span>
  110. </el-form-item>
  111. </el-col>
  112. <el-col :span="6">
  113. <el-form-item label="收款账户:" prop="subsidiaryName">
  114. <el-autocomplete
  115. v-if="editStatus"
  116. style="width: 100%"
  117. :fetch-suggestions="querySearchAccountAsync"
  118. :trigger-on-focus="true"
  119. v-model="form.subsidiaryName"
  120. placeholder="请输入收款账户"
  121. popper-class="my-autocomplete"
  122. @select="handleSelectAccount"
  123. >
  124. <template #default="{ item }">
  125. <div
  126. style="
  127. display: flex;
  128. flex-direction: row;
  129. justify-content: space-between;
  130. "
  131. >
  132. <div class="name" style="font-size: 12px">
  133. {{ item.name }}
  134. </div>
  135. <span
  136. class="code"
  137. style="font-size: 10px; color: darkgrey"
  138. >{{ item.code }}</span
  139. >
  140. </div>
  141. </template>
  142. </el-autocomplete>
  143. <span v-else>{{ form.subsidiaryName }}</span>
  144. </el-form-item>
  145. </el-col>
  146. <el-col :span="6">
  147. <el-form-item label="收款开户行:" prop="subsidiaryBankName">
  148. <el-input
  149. v-if="editStatus"
  150. style="width: 100%"
  151. v-model.trim="form.subsidiaryBankName"
  152. size="small"
  153. type="text"
  154. placeholder="开户行"
  155. :clearable="true"
  156. :readonly="true"
  157. />
  158. <span v-else>{{ form.subsidiaryBankName }}</span>
  159. </el-form-item>
  160. </el-col>
  161. <el-col :span="6">
  162. <el-form-item label="收款账号:" prop="subsidiaryBankAccount">
  163. <el-input
  164. v-if="editStatus"
  165. style="width: 100%"
  166. v-model.trim="form.subsidiaryBankAccount"
  167. size="small"
  168. type="text"
  169. placeholder="账号"
  170. :clearable="true"
  171. :readonly="true"
  172. />
  173. <span v-else>{{ form.subsidiaryBankAccount }}</span>
  174. </el-form-item>
  175. </el-col>
  176. <el-col :span="6">
  177. <el-form-item label="付款名称:" prop="applyName">
  178. <el-input
  179. v-if="editStatus"
  180. style="width: 100%"
  181. v-model.trim="form.applyName"
  182. size="small"
  183. type="text"
  184. placeholder="付款名称"
  185. :clearable="true"
  186. />
  187. <span v-else>{{ form.applyName }}</span>
  188. </el-form-item>
  189. </el-col>
  190. <el-col :span="6">
  191. <el-form-item label="付款账号:" prop="appyAccount">
  192. <el-input
  193. v-if="editStatus"
  194. style="width: 100%"
  195. v-model.trim="form.appyAccount"
  196. size="small"
  197. type="text"
  198. placeholder="付款账号"
  199. :clearable="true"
  200. />
  201. <span v-else>{{ form.appyAccount }}</span>
  202. </el-form-item>
  203. </el-col>
  204. <el-col :span="6">
  205. <el-form-item label="到账日期:" prop="arriveDate">
  206. <el-date-picker
  207. v-if="editStatus"
  208. style="width: 100%"
  209. v-model.trim="form.arriveDate"
  210. size="small"
  211. :clearable="true"
  212. format="YYYY-MM-DD"
  213. value-format="YYYY-MM-DD HH:mm:ss"
  214. align="center"
  215. type="date"
  216. placeholder="到账日期"
  217. />
  218. <span v-else>{{ form.arriveDate }}</span>
  219. </el-form-item>
  220. </el-col>
  221. <el-col :span="6">
  222. <el-form-item label="到账时间:" prop="arriveTime">
  223. <el-time-select
  224. v-if="editStatus"
  225. style="width: 100%"
  226. v-model.trim="form.arriveTime"
  227. :picker-options="timeOptions"
  228. placeholder="选择时间"
  229. />
  230. <span v-else>{{ form.arriveTime }}</span>
  231. </el-form-item>
  232. </el-col>
  233. <el-col :span="6">
  234. <el-form-item label="客户名称:" prop="companyName">
  235. <span>{{ form.companyName }}</span>
  236. </el-form-item>
  237. </el-col>
  238. <el-col :span="6">
  239. <el-form-item label="合同编号:" prop="remark">
  240. <span>{{ form.contractNo }}</span>
  241. </el-form-item>
  242. </el-col>
  243. <el-col :span="6">
  244. <el-form-item label="应收款金额:" prop="remark">
  245. <span>{{ rowNum(form.contractAmount) }}</span>
  246. </el-form-item>
  247. </el-col>
  248. <el-col :span="6">
  249. <el-form-item label="未收款金额:" prop="remark">
  250. <span>{{ rowNum(form.nonpaymentAmount) }}</span>
  251. </el-form-item>
  252. </el-col>
  253. <el-col :span="6">
  254. <el-form-item label="收款金额:" required>
  255. <el-input-number
  256. v-if="editStatus"
  257. style="width: 100%"
  258. v-model.trim="form.arriveAmount"
  259. size="small"
  260. placeholder="收款金额"
  261. :clearable="true"
  262. :precision="2"
  263. controls-position="right"
  264. :controls="false"
  265. @change="amountChange"
  266. />
  267. <span v-else>{{ form.arriveAmount }}</span>
  268. </el-form-item>
  269. </el-col>
  270. <el-col :span="12">
  271. <el-form-item label="备注:">
  272. <el-input
  273. v-if="editStatus"
  274. style="width: 100%"
  275. v-model.trim="form.remark"
  276. size="small"
  277. type="text"
  278. placeholder="备注"
  279. :clearable="true"
  280. />
  281. <span v-else style="word-break: break-all">{{
  282. form.remark
  283. }}</span>
  284. </el-form-item>
  285. </el-col>
  286. </el-row>
  287. </el-form>
  288. <div class="details-container">
  289. <el-row :gutter="2" style="height: 100%">
  290. <el-col :span="18">
  291. <div class="details-head">
  292. <div class="title">
  293. <i class="fa fa-th-list" aria-hidden="true" /> 收款明细
  294. </div>
  295. </div>
  296. <div class="details-body">
  297. <el-table
  298. ref="filesTable"
  299. :data="form.details"
  300. size="small"
  301. height="100%"
  302. border
  303. header-row-class-name="list-header-row"
  304. highlight-current-row
  305. >
  306. <el-table-column
  307. type="index"
  308. label="序号"
  309. width="47"
  310. align="center"
  311. />
  312. <el-table-column
  313. label="任务名称"
  314. prop="taskTypeName"
  315. align="center"
  316. show-overflow-tooltip
  317. >
  318. </el-table-column>
  319. <el-table-column
  320. label="任务金额"
  321. prop="amount"
  322. width="100"
  323. align="center"
  324. >
  325. <template #default="scope">
  326. <span>{{ rowNum(scope.row.amount) }}</span>
  327. </template>
  328. </el-table-column>
  329. <el-table-column
  330. label="本次付款金额"
  331. prop="arriveAmount"
  332. width="100"
  333. align="center"
  334. >
  335. <template #default="scope">
  336. <template v-if="editStatus">
  337. <el-input-number
  338. v-model="scope.row.arriveAmount"
  339. size="small"
  340. :min="0.0"
  341. placeholder="本次付款金额"
  342. :precision="2"
  343. :controls="false"
  344. style="width: 100%"
  345. @change="
  346. (arg) =>
  347. amountChangeHandler(
  348. arg,
  349. scope.row,
  350. 'arriveAmount'
  351. )
  352. "
  353. />
  354. </template>
  355. <template v-else>{{ scope.row.arriveAmount }}</template>
  356. </template>
  357. </el-table-column>
  358. <el-table-column
  359. label="已付款金额"
  360. prop="arrived"
  361. width="100"
  362. align="center"
  363. >
  364. <template #default="scope">
  365. <span>{{ rowNum(scope.row.arrived) }}</span>
  366. </template>
  367. </el-table-column>
  368. </el-table>
  369. </div>
  370. </el-col>
  371. <el-col :span="6">
  372. <div class="details-head">
  373. <div class="title">
  374. <i class="fa fa-th-list" aria-hidden="true" /> 附件
  375. <i style="color: red">*</i>
  376. </div>
  377. <el-upload
  378. v-if="editStatus"
  379. action="#"
  380. :http-request="upload"
  381. :with-credentials="true"
  382. :show-file-list="false"
  383. multiple
  384. >
  385. <el-button size="small" type="primary" icon="Upload"
  386. >点击上传</el-button
  387. >
  388. </el-upload>
  389. </div>
  390. <div class="details-body">
  391. <el-table
  392. ref="filesTable"
  393. :data="form.files"
  394. size="small"
  395. height="100%"
  396. border
  397. header-row-class-name="list-header-row"
  398. highlight-current-row
  399. >
  400. <el-table-column
  401. type="index"
  402. label="序号"
  403. width="47"
  404. align="center"
  405. />
  406. <el-table-column
  407. label="文件名"
  408. prop="originalFileName"
  409. align="center"
  410. show-overflow-tooltip
  411. >
  412. <template #default="scope">
  413. <el-button
  414. size="small"
  415. type="primary"
  416. link
  417. @click="openFile(scope.row)"
  418. >{{ scope.row.originalFileName }}</el-button
  419. >
  420. </template>
  421. </el-table-column>
  422. <el-table-column label="操作" width="50" align="center">
  423. <template #default="scope">
  424. <div v-if="editStatus">
  425. <el-button
  426. size="small"
  427. link
  428. type="danger"
  429. @click="handlerDelAttach(scope.row, scope.$index)"
  430. >删除</el-button
  431. >
  432. </div>
  433. </template>
  434. </el-table-column>
  435. </el-table>
  436. </div>
  437. </el-col>
  438. </el-row>
  439. </div>
  440. </div>
  441. </el-drawer>
  442. <el-dialog
  443. title="驳回详情"
  444. v-model="rejectOpen"
  445. width="500px"
  446. append-to-body
  447. draggable
  448. >
  449. <el-form ref="dictRef" :model="form" label-width="100">
  450. <el-form-item label="驳回原因" :prop="verifyRemark">
  451. <el-input
  452. v-model.trim="form.verifyRemark"
  453. type="textarea"
  454. :rows="3"
  455. placeholder="请输入驳回原因"
  456. />
  457. </el-form-item>
  458. </el-form>
  459. <template #footer>
  460. <div class="dialog-footer">
  461. <el-button type="primary" @click="verifyUpload(4)">确 定</el-button>
  462. <el-button @click="rejectCancel">取 消</el-button>
  463. </div>
  464. </template>
  465. </el-dialog>
  466. </div>
  467. </template>
  468. <script setup>
  469. import { uploadFile } from "@/api/tool/file";
  470. import {
  471. listAccount,
  472. listContract,
  473. getCollection,
  474. getCollectionByContract,
  475. saveCollection,
  476. setCollectionStatus,
  477. } from "@/api/business/financial/collection";
  478. import { listSource } from "@/api/settings/source";
  479. import { listCompany } from "@/api/business/crm/company";
  480. import { listUser } from "@/api/system/user";
  481. import CustomerFormCom from "@/components/CustomerFormCom";
  482. import { formatDate } from "@/utils/index";
  483. import { ref } from "vue";
  484. import useUserStore from "@/store/modules/user";
  485. const { proxy } = getCurrentInstance();
  486. const baseUrl = import.meta.env.VITE_APP_BASE_API;
  487. /** 父组件传参 */
  488. const props = defineProps({
  489. getList: {
  490. type: Function,
  491. default: () => {},
  492. },
  493. });
  494. const { getList } = toRefs(props);
  495. /** 字典数组区 */
  496. const { virtual_address } = proxy.useDict("virtual_address");
  497. /** 表单抽屉 页变量 */
  498. const title = ref("");
  499. const loading = ref(false);
  500. const multiple = ref(true);
  501. const visible = ref(false);
  502. const editStatus = ref(false);
  503. const loopTasks = ref([]);
  504. const onceTasks = ref([]);
  505. const sourceCategories = ref([]);
  506. const loops = ref([]);
  507. const onces = ref([]);
  508. const type = ref("");
  509. const rejectOpen = ref(false);
  510. // 循环任务明细
  511. const loopDetails = ref([]);
  512. // 单次任务明细
  513. const onceDetails = ref([]);
  514. const detailEmpty = {
  515. id: null,
  516. taskTypeName: "",
  517. taskTypeId: null,
  518. serviceNum: undefined,
  519. freeNum: undefined,
  520. price: undefined,
  521. amount: undefined,
  522. addressStyle: undefined,
  523. address: undefined,
  524. fictionAddressId: undefined,
  525. tenantId: undefined,
  526. province: "",
  527. city: "",
  528. district: "",
  529. addressStyle: 1,
  530. provinceId: undefined,
  531. processes: [],
  532. defaultProcesses: [],
  533. };
  534. const provinces = ref(proxy.region.getProvinces());
  535. provinces.value.unshift({ code: "", name: "全部" });
  536. const cities = ref([]);
  537. const districts = ref([]);
  538. const archiveInput = ref({});
  539. const contractEmpty = {
  540. serviceType: 1,
  541. contractType: 0,
  542. formDate: formatDate(new Date(), "yyyy-MM-dd"),
  543. signerId: useUserStore().user.userId,
  544. signerName: useUserStore().user.nickName,
  545. files: [],
  546. };
  547. const isFullscreen = ref(false);
  548. const webHost = import.meta.env.VITE_APP_BASE_API;
  549. const data = reactive({
  550. form: {},
  551. rules: {},
  552. timeOptions: { start: "08:30", step: "00:15", end: "18:30" },
  553. });
  554. const { form, rules, timeOptions } = toRefs(data);
  555. /*********************** 方法区 ****************************/
  556. /** 打开抽屉 */
  557. function open(row) {
  558. reset();
  559. archiveInput.value = row;
  560. visible.value = true;
  561. getCollectionByContract(row).then((res) => {
  562. form.value = res.data;
  563. editStatus.value = true;
  564. });
  565. }
  566. // 数字格式化
  567. function rowNum(num) {
  568. if (!num) {
  569. return;
  570. }
  571. num = num.toLocaleString(); // 3,000
  572. if (num.indexOf(".") == -1) {
  573. num = num + ".00"; //3,000.00
  574. } else if (num.charAt(num.indexOf(".") == num.length - 2)) {
  575. num = num + "0";
  576. }
  577. return num;
  578. }
  579. /** 取消按钮 */
  580. function cancel() {
  581. visible.value = false;
  582. reset();
  583. }
  584. /** 表单重置 */
  585. function reset() {
  586. form.value = JSON.parse(JSON.stringify(contractEmpty));
  587. loops.value = [];
  588. onces.value = [];
  589. loopDetails.value = [];
  590. onceDetails.value = [];
  591. type.value = "";
  592. }
  593. /** 全屏缩放 */
  594. function handleScreen() {
  595. const dom = document.querySelector(
  596. ".list-container > .el-drawer__wrapper > .el-overlay"
  597. );
  598. isFullscreen.value = !isFullscreen.value;
  599. dom.style.position = isFullscreen.value ? "fixed" : "absolute";
  600. }
  601. /** 提交按钮 */
  602. function submitForm() {
  603. if (form.value.arriveAmount > form.value.contractAmount) {
  604. proxy.$modal.msgError("实际收款金额不能大于合同收款金额");
  605. return;
  606. }
  607. proxy.$refs["orderRef"].validate((valid) => {
  608. if (valid && detailValid()) {
  609. const formValue = form.value;
  610. saveCollection(formValue).then((res) => {
  611. if (res.code === 200) {
  612. archiveInput.value.collectionStatus = 1;
  613. setCollectionStatus(archiveInput.value);
  614. proxy.$message.success("保存成功");
  615. }
  616. cancel();
  617. getList.value();
  618. });
  619. }
  620. });
  621. }
  622. function detailValid() {
  623. // if (form.value.files.length === 0) {
  624. // proxy.$modal.msgError("收款附件为空");
  625. // return false;
  626. // }
  627. return true;
  628. }
  629. /** 查询表单信息 */
  630. function getForm() {
  631. loading.value = true;
  632. getOrder(form.value.id).then((response) => {
  633. loading.value = false;
  634. form.value = response.data;
  635. });
  636. }
  637. function handleServiceTypeClick(tab) {
  638. computedService();
  639. }
  640. function verifyHandler() {
  641. proxy.$modal
  642. .confirm("是否确认审核?")
  643. .then((_) => {
  644. verifyUpload(1);
  645. })
  646. .catch((_) => {
  647. proxy.$modal.msg("取消审核");
  648. });
  649. }
  650. function rejectHandler() {
  651. rejectOpen.value = true;
  652. }
  653. function rejectCancel() {
  654. rejectOpen.value = false;
  655. }
  656. function rejectSubmitHandler() {
  657. if (form.value.verifyRemark === "" || form.value.verifyRemark == null) {
  658. proxy.$modal.msgError("请填写驳回原因");
  659. return;
  660. }
  661. }
  662. function verifyUpload(status) {
  663. const formValue = proxy.deepClone(form.value);
  664. formValue.verifyStatus = status;
  665. formValue.status = status;
  666. verifyOrder(formValue).then((res) => {
  667. getForm();
  668. getList.value();
  669. rejectCancel();
  670. proxy.$modal.msg("保存成功");
  671. });
  672. }
  673. /** 文件上传 */
  674. function upload(param) {
  675. const fileForm = new FormData();
  676. fileForm.append("file", param.file);
  677. uploadFile(fileForm).then((res) => {
  678. if (res.code === 200) {
  679. const file = {};
  680. file.fileName = res.newFileName;
  681. file.url = res.url;
  682. file.originalFileName = res.originalFilename;
  683. file.fileUrl = res.fileName;
  684. form.value.files.push(file);
  685. }
  686. });
  687. }
  688. function handleDelFile(index) {
  689. form.value.files.splice(index, 1);
  690. }
  691. function amountChangeHandler(arg, row, field) {
  692. computeTotalAmount();
  693. }
  694. function computeTotalAmount() {
  695. let amount = 0;
  696. for (let index = 0; index < form.value.details.length; index++) {
  697. const element = form.value.details[index];
  698. amount += element.arriveAmount == null ? 0 : element.arriveAmount;
  699. }
  700. form.value.arriveAmount = amount;
  701. }
  702. function amountChange() {
  703. let amount = 0;
  704. if (form.value.details.length > 0) {
  705. for (let i = 0; i < form.value.details.length; i++) {
  706. amount = amount + form.value.details[i].arriveAmount;
  707. }
  708. form.value.arriveAmount = amount;
  709. }
  710. }
  711. function openFile(row) {
  712. window.open(`${baseUrl}${row.fileUrl}`);
  713. }
  714. function handlerDelAttach(row, index) {
  715. proxy.$modal
  716. .confirm("确认删除该项么?")
  717. .then((_) => {
  718. form.value.files.splice(index, 1);
  719. })
  720. .catch((err) => {
  721. proxy.$modal.msgError("取消删除");
  722. });
  723. }
  724. function querySearchAccountAsync(queryString, cb) {
  725. const query =
  726. queryString.length > 0
  727. ? { keyword: queryString, pageSize: 20, pageNum: 1 }
  728. : { pageSize: 20, pageNum: 1 };
  729. listAccount(query).then((res) => {
  730. cb(res.rows);
  731. });
  732. }
  733. function handleSelectAccount(item) {
  734. form.value.subsidiaryName = item.name;
  735. form.value.subsidiaryBankName = item.accountOpen;
  736. form.value.subsidiaryBankAccount = item.accountNum;
  737. }
  738. /** 暴露给父组件的方法 */
  739. defineExpose({
  740. open,
  741. });
  742. </script>