index.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776
  1. <template>
  2. <div class="tenant">
  3. <byTable
  4. :source="sourceList.data"
  5. :pagination="sourceList.pagination"
  6. :config="config"
  7. :loading="loading"
  8. highlight-current-row
  9. :selectConfig="selectConfig"
  10. :row-class-name="getRowClass"
  11. :table-events="{
  12. select: selectRow,
  13. }"
  14. :action-list="[
  15. {
  16. text: '转生产',
  17. disabled: selectData.length === 0,
  18. action: () => transferToProduction(),
  19. },
  20. {
  21. text: '采购',
  22. disabled: selectData.length === 0,
  23. action: () => start(),
  24. },
  25. ]"
  26. @get-list="getList"
  27. >
  28. <template #claimTime="{ item }">
  29. <div>
  30. <span v-if="item.claimTime">{{ item.claimTime }}</span>
  31. <span v-else>未到账</span>
  32. </div>
  33. </template>
  34. <template #details="{ item }">
  35. <div>
  36. <el-button
  37. type="primary"
  38. link
  39. v-if="item.expendQuantity >= 0"
  40. @click="handleClickDetails(item)"
  41. >查看</el-button
  42. >
  43. <el-button
  44. type="primary"
  45. link
  46. style="color: #f54a45"
  47. v-else
  48. @click="handleClickDetails(item)"
  49. >查看</el-button
  50. >
  51. </div>
  52. </template>
  53. <template #btn="{ item }">
  54. <div v-if="item.expendQuantity > 0">
  55. <el-button type="primary" link @click="start(10, item)"
  56. >采购</el-button
  57. >
  58. <el-button type="primary" link @click="transferToProduction(item)"
  59. >转生产</el-button
  60. >
  61. </div>
  62. <div v-else-if="item.expendQuantity == 0">
  63. <el-button type="primary" link @click="handleFollow(item)"
  64. >跟进</el-button
  65. >
  66. <el-button type="primary" link @click="lookRecords(item)"
  67. >跟进记录</el-button
  68. >
  69. </div>
  70. <div v-else>
  71. <el-button
  72. type="primary"
  73. link
  74. style="color: #f54a45"
  75. @click="handleFollow(item)"
  76. >跟进</el-button
  77. >
  78. <el-button
  79. type="primary"
  80. link
  81. style="color: #f54a45"
  82. @click="lookRecords(item)"
  83. >跟进记录</el-button
  84. >
  85. </div>
  86. </template>
  87. </byTable>
  88. <el-dialog
  89. title="转生产"
  90. v-if="dialogVisible"
  91. v-model="dialogVisible"
  92. width="1200"
  93. v-loading="loadingDialog"
  94. >
  95. <byForm
  96. :formConfig="formConfig"
  97. :formOption="formOption"
  98. v-model="formData.data"
  99. :rules="rules"
  100. ref="submit"
  101. >
  102. <template #details>
  103. <div style="width: 100%">
  104. <el-table
  105. :data="formData.data.list"
  106. style="width: 100%; margin-top: 16px"
  107. >
  108. <el-table-column
  109. prop="productCode"
  110. label="物品编码"
  111. width="140"
  112. />
  113. <el-table-column
  114. prop="productName"
  115. label="物品名称"
  116. min-width="220"
  117. />
  118. <el-table-column
  119. prop="productUnit"
  120. label="单位"
  121. width="100"
  122. :formatter="
  123. (row) => dictValueLabel(row.productUnit, productUnit)
  124. "
  125. />
  126. <el-table-column
  127. prop="expendQuantity"
  128. label="待处理数量"
  129. width="120"
  130. />
  131. <el-table-column label="转生产数量" width="160">
  132. <template #default="{ row, $index }">
  133. <div style="width: 100%">
  134. <el-form-item
  135. :prop="'list.' + $index + '.quantity'"
  136. :rules="rules.quantity"
  137. :inline-message="true"
  138. >
  139. <el-input-number
  140. v-model="row.quantity"
  141. placeholder="请输入数量"
  142. style="width: 100%"
  143. :precision="0"
  144. :controls="false"
  145. :min="0"
  146. />
  147. </el-form-item>
  148. </div>
  149. </template>
  150. </el-table-column>
  151. <el-table-column label="完工期限" width="180">
  152. <template #default="{ row, $index }">
  153. <div style="width: 100%">
  154. <el-form-item
  155. :prop="'list.' + $index + '.time'"
  156. :rules="rules.time"
  157. :inline-message="true"
  158. >
  159. <el-date-picker
  160. v-model="row.time"
  161. type="date"
  162. placeholder="请选择"
  163. value-format="YYYY-MM-DD"
  164. />
  165. </el-form-item>
  166. </div>
  167. </template>
  168. </el-table-column>
  169. <el-table-column
  170. align="center"
  171. label="操作"
  172. width="80"
  173. fixed="right"
  174. >
  175. <template #default="{ row, $index }">
  176. <el-button type="primary" link @click="handleDelete($index)"
  177. >删除</el-button
  178. >
  179. </template>
  180. </el-table-column>
  181. </el-table>
  182. </div>
  183. </template>
  184. </byForm>
  185. <template #footer>
  186. <el-button @click="dialogVisible = false" size="large">取 消</el-button>
  187. <el-button type="primary" @click="submitForm()" size="large"
  188. >确 定</el-button
  189. >
  190. </template>
  191. </el-dialog>
  192. <el-dialog
  193. title="填写跟进"
  194. v-if="dialogVisibleOne"
  195. v-model="dialogVisibleOne"
  196. width="500"
  197. v-loading="loadingDialog"
  198. >
  199. <byForm
  200. :formConfig="formConfigOne"
  201. :formOption="formOption"
  202. v-model="formData.dataOne"
  203. :rules="rulesOne"
  204. ref="submitOne"
  205. >
  206. </byForm>
  207. <template #footer>
  208. <el-button @click="dialogVisibleOne = false" size="large"
  209. >取 消</el-button
  210. >
  211. <el-button type="primary" @click="submitFormOne()" size="large"
  212. >确 定</el-button
  213. >
  214. </template>
  215. </el-dialog>
  216. <el-dialog title="跟进记录" v-model="dialogVisibleTwo" width="500">
  217. <div style="width: 100%">
  218. <el-timeline :reverse="false">
  219. <el-timeline-item
  220. placement="top"
  221. v-for="(activity, index) in activities"
  222. :key="index"
  223. :timestamp="activity.followUpTime"
  224. >
  225. <div>
  226. 跟进结果:
  227. <span>{{ activity.resultType ? "已处理" : "处理中" }}</span>
  228. </div>
  229. <div style="margin-top: 5px">跟进记录: {{ activity.remark }}</div>
  230. </el-timeline-item>
  231. </el-timeline>
  232. </div>
  233. </el-dialog>
  234. <el-dialog
  235. title="交接单"
  236. v-if="openHandover"
  237. v-model="openHandover"
  238. width="800"
  239. >
  240. <byForm
  241. :formConfig="formHandoverConfig"
  242. :formOption="formOption"
  243. v-model="productRow.data"
  244. >
  245. <template #remark>
  246. <div style="width: 100%">
  247. <Editor
  248. ref="remarkEditor"
  249. :readOnly="true"
  250. :value="productRow.data.remark"
  251. />
  252. </div>
  253. </template>
  254. <template #file>
  255. <div style="width: 100%">
  256. <el-upload
  257. v-model:fileList="productRow.data.fileList"
  258. action="https://winfaster.obs.cn-south-1.myhuaweicloud.com"
  259. multiple
  260. :on-preview="onPreviewFile"
  261. >
  262. </el-upload>
  263. </div>
  264. </template>
  265. </byForm>
  266. <template #footer>
  267. <el-button @click="openHandover = false" size="large">关 闭</el-button>
  268. </template>
  269. </el-dialog>
  270. </div>
  271. </template>
  272. <script setup>
  273. import byTable from "@/components/byTable/index";
  274. import byForm from "@/components/byForm/index";
  275. import { computed, nextTick, ref } from "vue";
  276. import { ElMessage } from "element-plus";
  277. import Editor from "@/components/Editor/index.vue";
  278. const { proxy } = getCurrentInstance();
  279. const loading = ref(false);
  280. const sourceList = ref({
  281. data: [],
  282. pagination: {
  283. total: 3,
  284. pageNum: 1,
  285. pageSize: 10,
  286. status: "15",
  287. },
  288. });
  289. const isReceivedArr = ref([
  290. { label: "是", value: "1" },
  291. { label: "否", value: "0" },
  292. ]);
  293. const corporationArr = ref([]);
  294. const selectConfig = computed(() => [
  295. {
  296. label: "归属公司",
  297. prop: "corporationId",
  298. data: corporationArr.value,
  299. },
  300. {
  301. label: "是否到账",
  302. prop: "isReceived",
  303. data: isReceivedArr.value,
  304. },
  305. ]);
  306. const config = computed(() => {
  307. return [
  308. {
  309. type: "selection",
  310. attrs: {
  311. checkAtt: "isCheck",
  312. },
  313. },
  314. {
  315. attrs: {
  316. label: "归属公司",
  317. prop: "corporationName",
  318. },
  319. },
  320. {
  321. attrs: {
  322. label: "外销合同编号",
  323. prop: "contractCode",
  324. width: "160",
  325. },
  326. },
  327. {
  328. attrs: {
  329. label: "下单时间",
  330. prop: "contractTime",
  331. width: "155",
  332. },
  333. },
  334. {
  335. attrs: {
  336. label: "合同到账时间",
  337. slot: "claimTime",
  338. width: "155",
  339. },
  340. },
  341. {
  342. attrs: {
  343. label: "业务员",
  344. prop: "userName",
  345. },
  346. },
  347. {
  348. attrs: {
  349. label: "版本号",
  350. prop: "contractVersion",
  351. },
  352. render(contractVersion) {
  353. return "v" + contractVersion;
  354. },
  355. },
  356. {
  357. attrs: {
  358. label: "产品编码",
  359. prop: "productCode",
  360. },
  361. },
  362. {
  363. attrs: {
  364. label: "产品名称",
  365. prop: "productName",
  366. },
  367. },
  368. {
  369. attrs: {
  370. label: "单位",
  371. prop: "productUnit",
  372. width: "80",
  373. },
  374. render(unit) {
  375. return proxy.dictValueLabel(unit, productUnit.value);
  376. },
  377. },
  378. {
  379. attrs: {
  380. label: "已发起数量",
  381. prop: "startPurchaseCount",
  382. width: "110",
  383. },
  384. },
  385. {
  386. attrs: {
  387. label: "待处理数量",
  388. prop: "expendQuantity",
  389. width: "110",
  390. },
  391. },
  392. {
  393. attrs: {
  394. label: "详情",
  395. width: "100",
  396. align: "center",
  397. slot: "details",
  398. },
  399. },
  400. {
  401. attrs: {
  402. label: "操作",
  403. slot: "btn",
  404. width: "140",
  405. align: "center",
  406. fixed: "right",
  407. },
  408. },
  409. ];
  410. });
  411. const getList = async (req) => {
  412. sourceList.value.pagination = { ...sourceList.value.pagination, ...req };
  413. loading.value = true;
  414. proxy
  415. .post("/contractProduct/page", sourceList.value.pagination)
  416. .then((message) => {
  417. sourceList.value.data = message.rows.map((x) => ({
  418. ...x,
  419. isCheck: true,
  420. }));
  421. sourceList.value.pagination.total = message.total;
  422. setTimeout(() => {
  423. loading.value = false;
  424. }, 200);
  425. });
  426. };
  427. getList();
  428. const productUnit = ref([]);
  429. const getDict = () => {
  430. proxy
  431. .post("/corporation/page", { pageNum: 1, pageSize: 9999 })
  432. .then((res) => {
  433. corporationArr.value = res.rows.map((x) => ({
  434. ...x,
  435. label: x.name,
  436. value: x.id,
  437. }));
  438. });
  439. proxy.getDictOne(["unit"]).then((res) => {
  440. productUnit.value = res["unit"].map((x) => ({
  441. label: x.dictValue,
  442. value: x.dictKey,
  443. }));
  444. });
  445. };
  446. getDict();
  447. const selectData = ref([]);
  448. const selectRow = (data) => {
  449. selectData.value = data;
  450. };
  451. const start = (type, row) => {
  452. if (type === 10) {
  453. selectData.value = [row];
  454. }
  455. if (selectData.value.length > 0) {
  456. let ids = selectData.value.map((x) => x.id).join();
  457. let arr = selectData.value.map((x) => ({
  458. contractId: x.contractId,
  459. contractCode: x.contractCode,
  460. claimTime: x.contractTime,
  461. }));
  462. let newArr = [];
  463. for (let i = 0; i < arr.length; i++) {
  464. const e = arr[i];
  465. let flag = newArr.some((x) => x.contractId === e.contractId);
  466. if (!flag) {
  467. newArr.push(e);
  468. }
  469. }
  470. proxy.$router.replace({
  471. path: "/platform_manage/process/processApproval",
  472. query: {
  473. flowKey: "purchase_flow",
  474. type: "handoverSlip",
  475. random: proxy.random(),
  476. ids,
  477. arr: JSON.stringify(newArr),
  478. },
  479. });
  480. } else {
  481. return ElMessage({
  482. message: "请勾选数据!",
  483. type: "info",
  484. });
  485. }
  486. };
  487. const dialogVisible = ref(false);
  488. const dialogVisibleOne = ref(false);
  489. const dialogVisibleTwo = ref(false);
  490. const loadingDialog = ref(false);
  491. const submit = ref(null);
  492. const submitOne = ref(null);
  493. const formOption = reactive({
  494. inline: true,
  495. labelWidth: 100,
  496. itemWidth: 100,
  497. rules: [],
  498. });
  499. const formData = reactive({
  500. data: {
  501. list: [],
  502. },
  503. dataOne: {},
  504. });
  505. const formConfig = computed(() => {
  506. return [
  507. {
  508. type: "slot",
  509. slotName: "details",
  510. label: "产品明细",
  511. },
  512. ];
  513. });
  514. const rules = ref({
  515. quantity: [{ required: true, message: "请输入数量", trigger: "blur" }],
  516. time: [{ required: true, message: "请选择完工期限", trigger: "change" }],
  517. });
  518. const formConfigOne = computed(() => {
  519. return [
  520. {
  521. type: "select",
  522. prop: "resultType",
  523. label: "跟进结果",
  524. data: [
  525. {
  526. label: "处理中",
  527. value: "0",
  528. },
  529. {
  530. label: "已处理",
  531. value: "1",
  532. },
  533. ],
  534. },
  535. {
  536. type: "date",
  537. itemType: "datetime",
  538. prop: "followUpTime",
  539. label: "跟进时间",
  540. format: "YYYY-MM-DD HH:mm:ss",
  541. },
  542. {
  543. type: "input",
  544. prop: "remark",
  545. label: "跟进记录",
  546. itemType: "textarea",
  547. },
  548. ];
  549. });
  550. const rulesOne = ref({
  551. resultType: [
  552. { required: true, message: "请选择跟进结果", trigger: "change" },
  553. ],
  554. followUpTime: [
  555. { required: true, message: "请选择跟进时间", trigger: "change" },
  556. ],
  557. remark: [{ required: true, message: "请输入跟进记录", trigger: "blur" }],
  558. });
  559. const activities = ref([]);
  560. const lookRecords = (row) => {
  561. proxy
  562. .post("/contractProductFollowUp/list", {
  563. contractProductId: row.productId,
  564. })
  565. .then((res) => {
  566. if (res && res.length > 0) {
  567. activities.value = res;
  568. dialogVisibleTwo.value = true;
  569. } else {
  570. return ElMessage({
  571. message: "暂无跟进记录!",
  572. type: "info",
  573. });
  574. }
  575. });
  576. };
  577. const handleFollow = (row) => {
  578. formData.dataOne = {
  579. contractProductId: row.productId,
  580. };
  581. dialogVisibleOne.value = true;
  582. };
  583. const submitFormOne = () => {
  584. submitOne.value.handleSubmit(() => {
  585. loadingDialog.value = true;
  586. proxy.post("/contractProductFollowUp/add", formData.dataOne).then(
  587. () => {
  588. ElMessage({
  589. message: "提交成功",
  590. type: "success",
  591. });
  592. dialogVisibleOne.value = false;
  593. getList();
  594. },
  595. (err) => {
  596. console.log(err);
  597. loadingDialog.value = false;
  598. }
  599. );
  600. });
  601. };
  602. const transferToProduction = (row) => {
  603. if (row && row.id) {
  604. formData.data = {
  605. list: [
  606. {
  607. sourceId: row.id,
  608. source: "1",
  609. productId: row.productId,
  610. productCode: row.productCode,
  611. productName: row.productName,
  612. productUnit: row.productUnit,
  613. expendQuantity: row.expendQuantity,
  614. quantity: undefined,
  615. },
  616. ],
  617. };
  618. } else {
  619. formData.data = {
  620. list: selectData.value.map((item) => {
  621. return {
  622. sourceId: item.id,
  623. source: "1",
  624. productId: item.productId,
  625. productCode: item.productCode,
  626. productName: item.productName,
  627. productUnit: item.productUnit,
  628. expendQuantity: item.expendQuantity,
  629. quantity: undefined,
  630. };
  631. }),
  632. };
  633. }
  634. dialogVisible.value = true;
  635. };
  636. watch(selectData, (newVal) => {
  637. if (newVal.length == 0) {
  638. sourceList.value.data.forEach((x) => {
  639. x.isCheck = true;
  640. });
  641. } else if (newVal.length == 1) {
  642. const current = newVal[0];
  643. sourceList.value.data.forEach((x) => {
  644. if (x.contractId !== current.contractId) {
  645. x.isCheck = false;
  646. }
  647. });
  648. }
  649. });
  650. const submitForm = () => {
  651. submit.value.handleSubmit(() => {
  652. if (!(formData.data.list && formData.data.list.length > 0)) {
  653. return ElMessage("请至少添加一条产品明细");
  654. }
  655. loadingDialog.value = true;
  656. proxy.post("/workOrder/addBatch", formData.data.list).then(
  657. () => {
  658. ElMessage({
  659. message: "提交成功",
  660. type: "success",
  661. });
  662. dialogVisible.value = false;
  663. getList();
  664. },
  665. (err) => {
  666. console.log(err);
  667. loadingDialog.value = false;
  668. }
  669. );
  670. });
  671. };
  672. const handleDelete = (index) => {
  673. formData.data.list.splice(index, 1);
  674. };
  675. const getRowClass = ({ row }) => {
  676. if (row.expendQuantity < 0) {
  677. return "redClass";
  678. }
  679. return "";
  680. };
  681. const openHandover = ref(false);
  682. const productRow = reactive({
  683. data: {
  684. productName: "",
  685. productModel: "",
  686. remark: "",
  687. fileList: [],
  688. },
  689. });
  690. const formHandoverConfig = computed(() => {
  691. return [
  692. {
  693. type: "title",
  694. title: "产品信息",
  695. label: "",
  696. },
  697. {
  698. type: "input",
  699. prop: "productName",
  700. label: "产品名称",
  701. itemType: "text",
  702. disabled: true,
  703. },
  704. {
  705. type: "input",
  706. prop: "productModel",
  707. label: "规格型号",
  708. itemType: "text",
  709. disabled: true,
  710. },
  711. {
  712. type: "slot",
  713. slotName: "remark",
  714. label: "交接单",
  715. },
  716. {
  717. type: "slot",
  718. prop: "file",
  719. slotName: "file",
  720. label: "附件",
  721. },
  722. ];
  723. });
  724. const remarkEditor = ref(null);
  725. const handleClickDetails = (row) => {
  726. proxy
  727. .post("/flowProcess/getStartData", { flowId: row.flowId })
  728. .then(async (res) => {
  729. const current = res.contractProductList.find(
  730. (x) => x.productId === row.productId
  731. );
  732. productRow.data = current || {};
  733. productRow.data.fileList =
  734. current.fileList.map((x) => ({
  735. raw: x,
  736. name: x.fileName,
  737. url: x.fileUrl,
  738. })) || [];
  739. openHandover.value = true;
  740. await nextTick();
  741. remarkEditor.value.changeHtml(productRow.data.remark);
  742. });
  743. };
  744. const onPreviewFile = (file) => {
  745. window.open(file.raw.fileUrl, "_blank");
  746. };
  747. </script>
  748. <style lang="scss" scoped>
  749. .tenant {
  750. margin: 20px;
  751. }
  752. ::v-deep(.el-input-number .el-input__inner) {
  753. text-align: left;
  754. }
  755. :deep(.el-table__header-wrapper .el-checkbox) {
  756. display: none;
  757. }
  758. </style>
  759. <style>
  760. .redClass {
  761. color: #f54a45 !important;
  762. }
  763. </style>