index.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737
  1. <template>
  2. <div class="tenant">
  3. <div class="content">
  4. <byTable :source="sourceList.data" :pagination="sourceList.pagination" :config="config" :loading="loading" :selectConfig="selectConfig"
  5. highlight-current-row @get-list="getList">
  6. <template #productType="{ item }">
  7. <div>
  8. <div v-if="item.productDefinition == 1">
  9. {{ dictValueLabel(item.productType, productType) }}
  10. </div>
  11. <div v-else>
  12. {{ dictValueLabel(item.productType, materialType) }}
  13. </div>
  14. </div>
  15. </template>
  16. <template #unit="{ item }">
  17. <div>
  18. <div v-if="item.productDefinition == 1">
  19. {{ dictValueLabel(item.productUnit, productUnit) }}
  20. </div>
  21. <div v-else>
  22. {{ dictValueLabel(item.productUnit, materialUnit) }}
  23. </div>
  24. </div>
  25. </template>
  26. <template #waitQuantity="{item}">
  27. <div style="width:100%">
  28. <span style="padding: 4px" class="active">
  29. {{
  30. item.waitQuantity
  31. }}</span>
  32. </div>
  33. </template>
  34. </byTable>
  35. </div>
  36. <el-dialog title="出库" v-if="dialogVisible" v-model="dialogVisible" width="1300" v-loading="loadingDialog">
  37. <div style="width:100%;display:flex">
  38. <div style="flex:1;overflow:auto;height:calc(100vh - 270px)">
  39. <byForm :formConfig="formConfig" :formOption="formOption" v-model="formData.data" :rules="rules" ref="submit">
  40. <template #details>
  41. <div style="width: 100%">
  42. <el-button type="primary" @click="clickAdd()">添加明细</el-button>
  43. <el-table :data="formData.data.stockWaitDetailsList" show-summary :summary-method="getSummaries" style=" margin-top: 16px">
  44. <el-table-column type="index" width="60" align="center" />
  45. <el-table-column label="是否扫码" width="80">
  46. <template #default="{ row, $index }">
  47. <div>
  48. <span style="padding: 4px" :class="[row.isScan == 1 ? 'active' : 'disActive']">
  49. {{
  50. proxy.dictValueLabel(row.isScan, scanData)
  51. }}
  52. </span>
  53. </div>
  54. </template>
  55. </el-table-column>
  56. <el-table-column prop="productCode" label="产品编码" width="140" />
  57. <el-table-column prop="productName" label="产品名称" min-width="160" />
  58. <el-table-column prop="productSpec" label="规格型号" width="120" />
  59. <el-table-column prop="productUnit" label="单位" width="80" :formatter="
  60. (row) => dictValueLabel(row.productUnit, productUnit)
  61. " />
  62. <el-table-column label="出库数量" width="160" prop="quantity">
  63. <template #default="{ row, $index }">
  64. <div style="width: 100%">
  65. <el-form-item :prop="'stockWaitDetailsList.' + $index + '.quantity'" :rules="rules.quantity" :inline-message="true">
  66. <el-input-number v-model="row.quantity" placeholder="请输入出库数量" style="width: 100%" :precision="0" :controls="false" :min="1"
  67. :disabled="row.isScan =='1'" onmousewheel="return false;" />
  68. </el-form-item>
  69. </div>
  70. </template>
  71. </el-table-column>
  72. <el-table-column align="center" label="操作" width="80" fixed="right">
  73. <template #default="{ row, $index }">
  74. <el-button type="primary" link @click="handleDelete($index)">删除</el-button>
  75. </template>
  76. </el-table-column>
  77. </el-table>
  78. </div>
  79. </template>
  80. </byForm>
  81. </div>
  82. <div style="width:300px;margin-left:20px">
  83. <el-card class="box-card" style="height:100%;position: relative;">
  84. <div class="box">
  85. <img src="@/assets/images/scanz.png" class="img" v-if="!isScan" />
  86. <img src="@/assets/images/scaning.gif" class="img" v-if="isScan" />
  87. <div class="titlea" @click.stop="handleClickScan">
  88. {{btnTitle}}
  89. </div>
  90. <el-input v-model="scanValue" type="password" :disabled="!isScan" ref="scanInput" @change="handleScanValueChange" @blur="handleScanBlur"
  91. class="input" />
  92. </div>
  93. </el-card>
  94. </div>
  95. </div>
  96. <!-- <div style="display:flex">
  97. <div style="width:50%">
  98. <byForm :formConfig="formConfig" :formOption="formOption" v-model="formData.data" :rules="rules" ref="submit">
  99. </byForm>
  100. </div>
  101. <div style="width:50%;padding-left:20px;padding-top:10px">
  102. <div>
  103. <el-button type="primary" @click="handleClickScan">{{btnTitle}}</el-button>
  104. </div>
  105. <div style="margin:15px 0px">
  106. <el-input v-model="textarea" :rows="5" type="textarea" :disabled="!isScan" ref="scanInput" />
  107. </div>
  108. <TitleInfo content="已扫码产品"></TitleInfo>
  109. <el-table :data="scanCodeData" style="margin-top: 10px">
  110. <el-table-column prop="productName" label="产品名称" />
  111. <el-table-column prop="productSpec" label="规格型号" width="100" />
  112. <el-table-column prop="productSpec" label="单价" width="100" />
  113. </el-table>
  114. </div>
  115. </div> -->
  116. <template #footer>
  117. <el-button @click="dialogVisible = false" size="large">取 消</el-button>
  118. <el-button type="primary" @click="submitForm()" size="large">确 定</el-button>
  119. </template>
  120. </el-dialog>
  121. </div>
  122. </template>
  123. <script setup>
  124. import { computed, ref } from "vue";
  125. import byTable from "@/components/byTable/index";
  126. import byForm from "@/components/byForm/index";
  127. import { ElMessage } from "element-plus";
  128. import useUserStore from "@/store/modules/user";
  129. import TitleInfo from "@/components/TitleInfo/index.vue";
  130. const { proxy } = getCurrentInstance();
  131. const warehouseList = ref([]);
  132. const productType = ref([]);
  133. const productUnit = ref([]);
  134. const materialType = ref([]);
  135. const materialUnit = ref([]);
  136. const status = ref([
  137. {
  138. label: "待出库",
  139. value: 0,
  140. },
  141. {
  142. label: "部分出库",
  143. value: 1,
  144. },
  145. {
  146. label: "出库完成",
  147. value: 2,
  148. },
  149. ]);
  150. const businessType = ref([
  151. {
  152. label: "线边回仓",
  153. value: 1,
  154. },
  155. {
  156. label: "完工入库",
  157. value: 2,
  158. },
  159. {
  160. label: "采购到货",
  161. value: 3,
  162. },
  163. {
  164. label: "退货出库",
  165. value: 4,
  166. },
  167. {
  168. label: "京东订单",
  169. value: 5,
  170. },
  171. {
  172. label: "销售订单出库",
  173. value: 6,
  174. },
  175. {
  176. label: "生产任务出库",
  177. value: 7,
  178. },
  179. {
  180. label: "销售换货",
  181. value: 10,
  182. },
  183. {
  184. label: "工单出库",
  185. value: 11,
  186. },
  187. {
  188. label: "销售合同出库",
  189. value: 12,
  190. },
  191. {
  192. label: "售后物料出库",
  193. value: 13,
  194. },
  195. ]);
  196. const sourceList = ref({
  197. data: [],
  198. pagination: {
  199. total: 0,
  200. pageNum: 1,
  201. pageSize: 10,
  202. keyword: "",
  203. status: "",
  204. type: 2,
  205. },
  206. });
  207. const loading = ref(false);
  208. const selectConfig = computed(() => {
  209. return [
  210. {
  211. label: "出库状态",
  212. prop: "status",
  213. data: status.value,
  214. },
  215. ];
  216. });
  217. const config = computed(() => {
  218. return [
  219. {
  220. attrs: {
  221. label: "数据来源",
  222. prop: "businessType",
  223. width: 120,
  224. },
  225. render(type) {
  226. return proxy.dictValueLabel(type, businessType.value);
  227. },
  228. },
  229. {
  230. attrs: {
  231. label: "单号",
  232. prop: "businessCode",
  233. width: 160,
  234. },
  235. },
  236. {
  237. attrs: {
  238. label: "物品类型",
  239. slot: "productType",
  240. width: 120,
  241. },
  242. // render(type) {
  243. // return proxy.dictValueLabel(type, productType.value);
  244. // },
  245. },
  246. {
  247. attrs: {
  248. label: "物品编码",
  249. prop: "productCode",
  250. width: 140,
  251. },
  252. },
  253. {
  254. attrs: {
  255. label: "物品名称",
  256. prop: "productName",
  257. "min-width": 200,
  258. },
  259. },
  260. {
  261. attrs: {
  262. label: "规格型号",
  263. prop: "productSpec",
  264. width: 140,
  265. },
  266. },
  267. {
  268. attrs: {
  269. label: "单位",
  270. width: 100,
  271. slot: "unit",
  272. },
  273. },
  274. {
  275. attrs: {
  276. label: "需出库数量",
  277. prop: "quantity",
  278. width: 120,
  279. },
  280. },
  281. {
  282. attrs: {
  283. label: "待出库数量",
  284. slot: "waitQuantity",
  285. width: 120,
  286. },
  287. },
  288. {
  289. attrs: {
  290. label: "出库状态",
  291. prop: "status",
  292. width: 140,
  293. },
  294. render(type) {
  295. return proxy.dictValueLabel(type, status.value);
  296. },
  297. },
  298. {
  299. attrs: {
  300. label: "操作",
  301. width: "80",
  302. align: "center",
  303. },
  304. renderHTML(row) {
  305. return [
  306. row.status !== 2
  307. ? {
  308. attrs: {
  309. label: "出库",
  310. type: "primary",
  311. text: true,
  312. },
  313. el: "button",
  314. click() {
  315. clickOperation(row);
  316. },
  317. }
  318. : {},
  319. ];
  320. },
  321. },
  322. ];
  323. });
  324. const getDict = () => {
  325. proxy.post("/warehouse/page", { pageNum: 1, pageSize: 999 }).then((res) => {
  326. if (res.rows && res.rows.length > 0) {
  327. warehouseList.value = res.rows.map((item) => {
  328. return {
  329. label: item.name,
  330. value: item.id,
  331. };
  332. });
  333. }
  334. });
  335. proxy
  336. .getDictOne(["product_type", "unit", "material_type", "material_unit"])
  337. .then((res) => {
  338. productType.value = res["product_type"].map((x) => ({
  339. label: x.dictValue,
  340. value: x.dictKey,
  341. }));
  342. productUnit.value = res["unit"].map((x) => ({
  343. label: x.dictValue,
  344. value: x.dictKey,
  345. }));
  346. materialType.value = res["material_type"].map((x) => ({
  347. label: x.dictValue,
  348. value: x.dictKey,
  349. }));
  350. materialUnit.value = res["material_unit"].map((x) => ({
  351. label: x.dictValue,
  352. value: x.dictKey,
  353. }));
  354. });
  355. };
  356. const getList = async (req) => {
  357. sourceList.value.pagination = { ...sourceList.value.pagination, ...req };
  358. loading.value = true;
  359. proxy
  360. .post("/stockWaitDetails/page", sourceList.value.pagination)
  361. .then((res) => {
  362. sourceList.value.data = res.rows.map((x) => ({
  363. ...x,
  364. waitQuantity: (x.quantity - x.receiptQuantity).toFixed(4),
  365. }));
  366. sourceList.value.pagination.total = res.total;
  367. setTimeout(() => {
  368. loading.value = false;
  369. }, 200);
  370. });
  371. };
  372. getDict();
  373. getList();
  374. const dialogVisible = ref(false);
  375. const loadingDialog = ref(false);
  376. const submit = ref(null);
  377. const formOption = reactive({
  378. inline: true,
  379. labelWidth: 100,
  380. itemWidth: 100,
  381. rules: [],
  382. });
  383. const formData = reactive({
  384. data: {},
  385. });
  386. const formConfig = computed(() => {
  387. return [
  388. {
  389. label: "待出库信息",
  390. },
  391. {
  392. type: "select",
  393. prop: "businessType",
  394. label: "数据来源",
  395. disabled: true,
  396. data: businessType.value,
  397. },
  398. {
  399. type: "input",
  400. prop: "businessCode",
  401. label: "单号",
  402. itemType: "text",
  403. disabled: true,
  404. },
  405. {
  406. type: "input",
  407. prop: "productName",
  408. label: "物品名称",
  409. itemType: "text",
  410. disabled: true,
  411. },
  412. {
  413. type: "input",
  414. prop: "productSpec",
  415. label: "规格型号",
  416. itemType: "text",
  417. disabled: true,
  418. },
  419. {
  420. type: "input",
  421. prop: "quantity",
  422. label: "需出库数量",
  423. itemType: "text",
  424. disabled: true,
  425. },
  426. {
  427. type: "input",
  428. prop: "waitQuantity",
  429. label: "待出库数量",
  430. itemType: "text",
  431. disabled: true,
  432. },
  433. {
  434. label: "本次出库",
  435. },
  436. {
  437. type: "select",
  438. prop: "warehouseId",
  439. label: "仓库名称",
  440. required: true,
  441. data: warehouseList.value,
  442. },
  443. // {
  444. // type: "number",
  445. // prop: "warehousingQuantity",
  446. // label: "出库数量",
  447. // precision: 0,
  448. // min: 1,
  449. // controls: false,
  450. // },
  451. {
  452. type: "slot",
  453. slotName: "details",
  454. label: "出库明细",
  455. },
  456. {
  457. type: "input",
  458. prop: "exWarehousePerson",
  459. label: "出库人",
  460. itemWidth: 50,
  461. },
  462. {
  463. type: "input",
  464. prop: "receivingPerson",
  465. label: "接收人",
  466. itemWidth: 50,
  467. },
  468. ];
  469. });
  470. const rules = ref({
  471. warehouseId: [{ required: true, message: "请选择仓库", trigger: "change" }],
  472. warehousingQuantity: [
  473. { required: true, message: "请输入出库数量", trigger: "blur" },
  474. ],
  475. quantity: [{ required: true, message: "请输入出库数量", trigger: "blur" }],
  476. exWarehousePerson: [
  477. { required: true, message: "请输入出库人", trigger: "blur" },
  478. ],
  479. receivingPerson: [
  480. { required: true, message: "请输入接收人", trigger: "blur" },
  481. ],
  482. });
  483. const submitForm = () => {
  484. submit.value.handleSubmit(() => {
  485. loadingDialog.value = true;
  486. if (!(formData.data.stockWaitDetailsList.length > 0)) {
  487. return ElMessage({
  488. message: "请添加出库明细",
  489. type: "info",
  490. });
  491. }
  492. const total = formData.data.stockWaitDetailsList.reduce(
  493. (val, x) => (val += x.quantity),
  494. 0
  495. );
  496. if (Number(total) > Number(formData.data.waitQuantity)) {
  497. return ElMessage({
  498. message: "出库数量不可大于待出库数量",
  499. type: "info",
  500. });
  501. }
  502. proxy
  503. .post("/stockWait/add", {
  504. id: formData.data.stockWaitId,
  505. warehouseId: formData.data.warehouseId,
  506. quantity: formData.data.warehousingQuantity,
  507. exWarehousePerson: formData.data.exWarehousePerson,
  508. receivingPerson: formData.data.receivingPerson,
  509. stockWaitDetailsList: formData.data.stockWaitDetailsList,
  510. })
  511. .then(
  512. () => {
  513. ElMessage({
  514. message: "提交成功",
  515. type: "success",
  516. });
  517. dialogVisible.value = false;
  518. getList();
  519. },
  520. (err) => {
  521. console.log(err);
  522. loadingDialog.value = false;
  523. }
  524. );
  525. });
  526. };
  527. const rowData = ref({});
  528. const clickOperation = (row) => {
  529. rowData.value = row;
  530. formData.data = row;
  531. formData.data.stockWaitDetailsList = [];
  532. formData.data.exWarehousePerson = useUserStore().user.nickName;
  533. loadingDialog.value = false;
  534. dialogVisible.value = true;
  535. };
  536. const scanData = ref([
  537. {
  538. label: "是",
  539. value: "1",
  540. },
  541. {
  542. label: "否",
  543. value: "0",
  544. },
  545. ]);
  546. const scanValue = ref("");
  547. const isScan = ref(false);
  548. const btnTitle = ref("扫码出库");
  549. const scanInput = ref(null);
  550. const handleClickScan = () => {
  551. isScan.value = !isScan.value;
  552. btnTitle.value = isScan.value ? "扫码中···" : "扫码出库";
  553. if (isScan.value) {
  554. scanInput.value.focus();
  555. }
  556. };
  557. const handleScanBlur = () => {
  558. setTimeout(() => {
  559. scanValue.value = "";
  560. isScan.value = false;
  561. btnTitle.value = "扫码出库";
  562. }, 100);
  563. };
  564. const timer = ref(null);
  565. const handleScanValueChange = (val) => {
  566. if (val) {
  567. let value = val;
  568. let arr = [];
  569. if (value.includes("sn")) {
  570. arr = value.split("=");
  571. }
  572. scanValue.value = "";
  573. timer.value && clearTimeout(timer.value);
  574. timer.value = setTimeout(() => {
  575. if (arr.length > 1) {
  576. return proxy
  577. .post("/productionTaskDetail/snInfo", {
  578. productSn: arr[arr.length - 1],
  579. })
  580. .then(
  581. (res) => {
  582. if (res.productId != rowData.value.productId) {
  583. ElMessage({
  584. message: "请扫正确的产品",
  585. type: "info",
  586. });
  587. } else {
  588. formData.data.stockWaitDetailsList.push({
  589. id: rowData.value.id,
  590. isScan: "1",
  591. productCode: res.productCode,
  592. productId: res.productId,
  593. productName: res.productName,
  594. productSpec: res.productSpec,
  595. productUnit: res.productUnit,
  596. quantity: 1,
  597. purchaseDetailId: "",
  598. });
  599. }
  600. },
  601. (err) => {}
  602. );
  603. }
  604. proxy.post("/purchaseDetail/detail", { id: value }).then(
  605. (res) => {
  606. if (res.bussinessId != rowData.value.productId) {
  607. ElMessage({
  608. message: "请扫正确的产品",
  609. type: "info",
  610. });
  611. } else {
  612. formData.data.stockWaitDetailsList.push({
  613. id: rowData.value.id,
  614. isScan: "1",
  615. productCode: res.productCode,
  616. productId: res.bussinessId,
  617. productName: res.productName,
  618. productSpec: res.productSpec,
  619. productUnit: res.productUnit,
  620. quantity: 1,
  621. purchaseDetailId: val,
  622. });
  623. }
  624. },
  625. (err) => {}
  626. );
  627. }, 301);
  628. }
  629. };
  630. const getSummaries = (param) => {
  631. const { columns, data } = param; //columns是每列的信息,data是每行的信息
  632. const sums = [];
  633. columns.forEach((column, index) => {
  634. if (index === 0) {
  635. sums[index] = "合计"; //此处是在index=0的这一列显示为“合计”
  636. return;
  637. }
  638. const values = data.map((item) => Number(item[column.property]));
  639. if (column.property === "quantity") {
  640. sums[index] = values.reduce((prev, curr) => {
  641. const value = Number(curr);
  642. if (!isNaN(value)) {
  643. return Number(parseFloat(prev + curr).toFixed(4));
  644. } else {
  645. return prev;
  646. }
  647. }, 0);
  648. sums[index];
  649. }
  650. });
  651. return sums;
  652. };
  653. const clickAdd = () => {
  654. formData.data.stockWaitDetailsList.push({
  655. id: formData.data.id,
  656. isScan: "0",
  657. productCode: formData.data.productCode,
  658. productId: formData.data.productId,
  659. productName: formData.data.productName,
  660. productSpec: formData.data.productSpec,
  661. productUnit: formData.data.productUnit,
  662. quantity: undefined,
  663. purchaseDetailId: "",
  664. });
  665. };
  666. const handleDelete = (index) => {
  667. formData.data.stockWaitDetailsList.splice(index, 1);
  668. };
  669. </script>
  670. <style lang="scss" scoped>
  671. .tenant {
  672. padding: 20px;
  673. }
  674. ::v-deep(.el-input-number .el-input__inner) {
  675. text-align: left;
  676. }
  677. .active {
  678. background: #a6dd82;
  679. color: #fff;
  680. border-radius: 4px;
  681. }
  682. .box {
  683. position: absolute;
  684. top: 50%;
  685. transform: translate(0, -50%);
  686. .img {
  687. width: 260px;
  688. height: 260px;
  689. object-fit: contain;
  690. vertical-align: middle;
  691. // cursor: pointer;
  692. z-index: 10;
  693. }
  694. .titlea {
  695. position: absolute;
  696. top: 50%;
  697. left: 50%;
  698. transform: translate(-50%, -50%);
  699. font-size: 30px;
  700. font-weight: 700;
  701. color: #5df5e9;
  702. cursor: pointer;
  703. }
  704. .input {
  705. opacity: 0;
  706. position: absolute;
  707. top: 0;
  708. left: 0;
  709. }
  710. }
  711. .active {
  712. background: #a6dd82;
  713. color: #fff;
  714. border-radius: 4px;
  715. }
  716. .disActive {
  717. background: #fa9841;
  718. color: #fff;
  719. border-radius: 4px;
  720. }
  721. </style>