Commit b6d40292 by qinjianhui

Merge branch 'dev_new_pod_order' into 'release_v2.21.0'

Dev new pod order

See merge request !186
parents 652bd4a8 d52d379f
---
description
glob
alwaysApply: true
---
## 技术栈
Vue3 + Element Plus + Pinia + Axios + TypeScript + Vite
## UI 相关编码要求
- 当有上下两个子表时,使用 `src/components/splitDiv/splitDiv.vue` 组件,使用时注意传递对应的 props 和插槽
- 表格相关布局时,使用 `src/components/TableView.vue` 组件,使用时注意传递对应的 props 和插槽
- `ElDialog`组件中`footer`插槽都放在中间位置
- 写入表格`columns`时,如果需要对齐,使用`align`属性,数字相关的列, `align`属性值为`right`,长度一致的列, `align`属性值为`center`, 长度不一致的列, `align`属性值为`left`
## 接口相关
- 所有接口统一放在 `src/api/`目录下,当模块多时,可按模块创建子目录,例如 `src/api/factoryOrderNew/xxx1`、`src/api/factoryOrderNew/xxx2`、`src/api/factoryOrderNew/xxx3`
## TS 类型定义相关
- 所有类型定义统一放在 `src/types/`目录下,当模块多时,可按模块创建子目录,例如 `src/types/factoryOrderNew/xxx1`、`src/types/factoryOrderNew/xxx2`、`src/types/factoryOrderNew/xxx3`
## 代码编写原则
- 只编写解决问题所需的最少代码
- 避免冗余实现和过渡设计
- 实现一个复杂功能时,需要合理拆分组件,使每个文件的代码更易维护
- 封装组件时,要考虑组件的复用性
...@@ -55,6 +55,12 @@ ...@@ -55,6 +55,12 @@
<ul class="icon_lists dib-box"> <ul class="icon_lists dib-box">
<li class="dib"> <li class="dib">
<span class="icon iconfont">&#xe605;</span>
<div class="name">提示</div>
<div class="code-name">&amp;#xe605;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe62e;</span> <span class="icon iconfont">&#xe62e;</span>
<div class="name">查看详情</div> <div class="name">查看详情</div>
<div class="code-name">&amp;#xe62e;</div> <div class="code-name">&amp;#xe62e;</div>
...@@ -132,9 +138,9 @@ ...@@ -132,9 +138,9 @@
<pre><code class="language-css" <pre><code class="language-css"
>@font-face { >@font-face {
font-family: 'iconfont'; font-family: 'iconfont';
src: url('iconfont.woff2?t=1729077025378') format('woff2'), src: url('iconfont.woff2?t=1774496872018') format('woff2'),
url('iconfont.woff?t=1729077025378') format('woff'), url('iconfont.woff?t=1774496872018') format('woff'),
url('iconfont.ttf?t=1729077025378') format('truetype'); url('iconfont.ttf?t=1774496872018') format('truetype');
} }
</code></pre> </code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3> <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
...@@ -161,6 +167,15 @@ ...@@ -161,6 +167,15 @@
<ul class="icon_lists dib-box"> <ul class="icon_lists dib-box">
<li class="dib"> <li class="dib">
<span class="icon iconfont icon-tishi"></span>
<div class="name">
提示
</div>
<div class="code-name">.icon-tishi
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-chakanxiangqing"></span> <span class="icon iconfont icon-chakanxiangqing"></span>
<div class="name"> <div class="name">
查看详情 查看详情
...@@ -279,6 +294,14 @@ ...@@ -279,6 +294,14 @@
<li class="dib"> <li class="dib">
<svg class="icon svg-icon" aria-hidden="true"> <svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-tishi"></use>
</svg>
<div class="name">提示</div>
<div class="code-name">#icon-tishi</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-chakanxiangqing"></use> <use xlink:href="#icon-chakanxiangqing"></use>
</svg> </svg>
<div class="name">查看详情</div> <div class="name">查看详情</div>
......
@font-face { @font-face {
font-family: "iconfont"; /* Project id 4462827 */ font-family: "iconfont"; /* Project id 4462827 */
src: url('iconfont.woff2?t=1729077025378') format('woff2'), src: url('iconfont.woff2?t=1774496872018') format('woff2'),
url('iconfont.woff?t=1729077025378') format('woff'), url('iconfont.woff?t=1774496872018') format('woff'),
url('iconfont.ttf?t=1729077025378') format('truetype'); url('iconfont.ttf?t=1774496872018') format('truetype');
} }
.iconfont { .iconfont {
...@@ -13,6 +13,10 @@ ...@@ -13,6 +13,10 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-tishi:before {
content: "\e605";
}
.icon-chakanxiangqing:before { .icon-chakanxiangqing:before {
content: "\e62e"; content: "\e62e";
} }
......
window._iconfont_svg_string_4462827='<svg><symbol id="icon-chakanxiangqing" viewBox="0 0 1024 1024"><path d="M383.6 62.8h256.3c213.6 0 320.4 106.8 320.4 320.4v256.3c0 213.6-106.8 320.4-320.4 320.4H383.6C170 959.8 63.2 853 63.2 639.4V383.2C63.2 169.6 170 62.8 383.6 62.8z" fill="#2084FE" ></path><path d="M254.1 515.3H416v-53.2H254.1zM254.1 358h363.1v53.2H254.1z" fill="#FFFFFF" opacity=".7" ></path><path d="M700.5 663.3c13.9-20.8 20.8-43.9 20.8-71.7 0-71.7-57.8-129.5-127.2-129.5S466.8 520 466.8 591.6 524.7 721 594 721c25.5 0 50.8-9.3 71.7-23.1l71.7 74 34.7-34.6-71.6-74z m-108.7 9.2c-41.7 0-76.3-34.7-76.3-78.6 0-43.9 34.7-78.6 76.3-78.6 41.7 0 76.3 34.7 76.3 78.6 0 41.7-34.7 78.6-76.3 78.6z" fill="#FFFFFF" ></path><path d="M254.1 254h363.1v53.2H254.1z" fill="#FFFFFF" opacity=".7" ></path></symbol><symbol id="icon-baojiachaxun" viewBox="0 0 1024 1024"><path d="M0 0m178.086957 0l667.826086 0q178.086957 0 178.086957 178.086957l0 667.826086q0 178.086957-178.086957 178.086957l-667.826086 0q-178.086957 0-178.086957-178.086957l0-667.826086q0-178.086957 178.086957-178.086957Z" fill="#2E7BFC" ></path><path d="M790.327652 721.363478l23.730087 23.730087a42.073043 42.073043 0 0 1 0 59.481044l-9.48313 9.48313a42.073043 42.073043 0 0 1-59.481044 0l-23.730087-23.730087a42.073043 42.073043 0 0 1 0-59.503304l9.46087-9.46087a42.073043 42.073043 0 0 1 59.503304 0zM505.499826 226.081391c154.312348 0 279.418435 125.106087 279.418435 279.418435 0 154.312348-125.106087 279.418435-279.418435 279.418435-154.312348 0-279.418435-125.106087-279.418435-279.418435 0-154.312348 125.106087-279.418435 279.418435-279.418435z m-100.797217 158.764522a19.300174 19.300174 0 0 0-0.24487 25.377391l1.847652 1.869913 44.076522 39.201392h-58.278956l-2.626783 0.178087a19.300174 19.300174 0 0 0 0.200348 38.288695l2.426435 0.133566h84.902956v38.622608h-61.751652l-2.604522 0.178087a19.300174 19.300174 0 0 0 0.200348 38.288696l2.404174 0.133565h61.751652l0.022261 83.656348 0.178087 2.604522a19.300174 19.300174 0 0 0 38.288696-0.200348l0.155826-2.404174-0.044522-83.656348h61.796174l2.626782-0.178087a19.300174 19.300174 0 0 0-0.200347-38.288696l-2.426435-0.133565h-61.796174v-38.622608h84.969739l2.626783-0.178087a19.300174 19.300174 0 0 0-0.222609-38.288696l-2.404174-0.133565h-58.323478l44.121043-39.179131 2.003478-2.070261a19.300174 19.300174 0 0 0-25.577739-28.404869l-2.070261 1.602782-64.400695 57.210435-64.378435-57.210435-2.270609-1.736347a19.300174 19.300174 0 0 0-24.976695 3.33913z" fill="#FFFFFF" ></path></symbol><symbol id="icon-caozuorizhi" viewBox="0 0 1024 1024"><path d="M312.416187 5.855995C376.992127 1.951998 443.520064 0 512 0c68.479936 0 135.007873 1.951998 199.583813 5.855995a319.9997 319.9997 0 0 1 298.14372 279.359738 1811.582302 1811.582302 0 0 1 0 453.535574 319.9997 319.9997 0 0 1-298.14372 279.359739A3304.316902 3304.316902 0 0 1 512 1023.99904c-68.479936 0-135.007873-1.951998-199.583813-5.855995a319.9997 319.9997 0 0 1-298.14372-279.359738 1811.582302 1811.582302 0 0 1 0-453.535574A319.9997 319.9997 0 0 1 312.416187 5.887994z" fill="#40C791" ></path><path d="M384.00012 298.65572A85.34392 85.34392 0 0 0 469.34404 383.99964h85.31192A85.34392 85.34392 0 0 0 639.99988 298.65572h42.65596A85.34392 85.34392 0 0 1 767.99976 383.99964v298.65572A85.34392 85.34392 0 0 1 682.65584 767.99928H341.34416A85.34392 85.34392 0 0 1 256.00024 682.65536V383.99964a85.34392 85.34392 0 0 1 85.34392-85.34392H384.00012z m0 1.311999h255.99976-255.99976z m149.34386 169.375841a21.34398 21.34398 0 1 0 0 42.65596h127.99988a21.34398 21.34398 0 0 0 0-42.65596h-127.99988z m0 127.99988a21.34398 21.34398 0 1 0 0 42.65596h127.99988a21.34398 21.34398 0 0 0 0-42.65596h-127.99988z m-140.479868-119.167888l-15.103986-15.103986a21.34398 21.34398 0 0 0-30.175972 30.207972l30.175972 30.143971a21.27998 21.27998 0 0 0 30.175972 0l60.319943-60.351943a21.34398 21.34398 0 1 0-30.175972-30.143972l-45.247957 45.247958z m0 127.99988l-15.103986-15.103986a21.34398 21.34398 0 0 0-30.175972 30.207972l30.175972 30.143971a21.27998 21.27998 0 0 0 30.175972 0l60.319943-60.351943a21.34398 21.34398 0 1 0-30.175972-30.143972l-45.247957 45.247958zM469.31204 255.99976h85.31192a42.65596 42.65596 0 1 1 0 85.34392h-85.31192a42.65596 42.65596 0 1 1 0-85.34392z" fill="#FFFFFF" ></path></symbol><symbol id="icon-querenshenhexunjia" viewBox="0 0 1024 1024"><path d="M474.500517 639.29203a320.016772 320.016772 0 0 0 93.819328 378.808334 51.976684 51.976684 0 0 1-53.777507-7.16798l-72.950971-58.120668-65.323958 58.120668c-9.321905 8.121357-21.327389 12.535138-33.650666 12.429207a50.140551 50.140551 0 0 1-33.368184-12.287966L243.995222 952.952957l-65.994852 58.615011a50.987997 50.987997 0 0 1-33.544735 12.429207 50.140551 50.140551 0 0 1-33.438805-12.287966L34.499524 943.595742a35.310247 35.310247 0 0 1-6.249914-7.06205A67.407262 67.407262 0 0 1 0.001412 882.756186V51.41172C0.001412 23.057592 23.023694 0 50.742238 0h794.480567c28.036336 0 50.776136 22.84573 50.776136 51.41172V474.569725a319.981462 319.981462 0 0 0-421.498424 164.686994zM127.612647 227.397993h333.575907c14.088789 0 25.211517-11.334589 25.211517-25.282137a25.458688 25.458688 0 0 0-25.211517-25.282137H127.612647a25.070276 25.070276 0 0 0-25.211517 24.964345v0.317792c0 13.559135 11.263969 25.282137 25.211517 25.282137z m231.423361 227.327373a24.752483 24.752483 0 0 0 24.964345-25.211516 25.423378 25.423378 0 0 0-24.964345-25.282138H127.365475a24.823104 24.823104 0 0 0-24.964345 24.717174v0.564964c0 13.488515 11.193348 25.211517 24.964345 25.211516h231.670533z m-26.235514 176.833719a25.176206 25.176206 0 0 0-24.646552-25.211516H127.047683a24.929035 24.929035 0 0 0-24.646553 25.211516c0 13.559135 11.016797 25.282137 24.646553 25.282138h181.141569a24.964345 24.964345 0 0 0 24.611242-25.282138zM766.09254 511.998588a255.999294 255.999294 0 1 1 0 511.998587 255.999294 255.999294 0 0 1 0-511.998587z m145.195738 206.105914a26.553306 26.553306 0 0 0-37.605414-37.428862l-131.001018 131.001017-84.25025-84.21494a26.447375 26.447375 0 1 0-37.428862 37.428863l102.999992 103.035302a26.517996 26.517996 0 0 0 37.428862 0l149.85669-149.82138z" fill="#389AFB" ></path></symbol><symbol id="icon-shenhe" viewBox="0 0 1024 1024"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#00CD82" ></path><path d="M512 744.727273h-211.549091a16.989091 16.989091 0 0 1-16.989091-16.989091V318.836364a16.989091 16.989091 0 0 1 16.989091-16.989091h335.127273a16.989091 16.989091 0 0 1 16.989091 16.989091v144.756363a17.221818 17.221818 0 0 0 34.210909 0V318.836364a51.2 51.2 0 0 0-50.967273-50.967273H300.450909a51.2 51.2 0 0 0-50.967273 50.967273v408.203636a51.2 51.2 0 0 0 50.967273 51.2H512A17.221818 17.221818 0 0 0 512 744.727273z m0 0" fill="#FFFFFF" ></path><path d="M596.945455 361.425455H330.007273a16.989091 16.989091 0 1 0 0 33.978181h266.938182a16.989091 16.989091 0 0 0 0-33.978181z m0 84.945454H330.007273a17.221818 17.221818 0 1 0 0 34.210909h266.938182a17.221818 17.221818 0 0 0 0-34.210909z m-42.821819 85.178182h-224.116363a16.989091 16.989091 0 1 0 0 33.978182h224.349091a16.989091 16.989091 0 0 0 16.989091-16.989091 17.454545 17.454545 0 0 0-17.221819-16.989091z m220.16 262.050909h-184.087272a15.592727 15.592727 0 1 0 0 31.185455h184.087272a16.756364 16.756364 0 0 0 17.221819-16.058182 16.290909 16.290909 0 0 0-17.221819-15.127273z m-4.887272-122.181818h-36.072728c-4.887273-9.774545 0-28.625455 16.290909-50.734546a84.014545 84.014545 0 0 0 13.265455-43.985454 78.196364 78.196364 0 0 0-82.152727-74.007273 77.498182 77.498182 0 0 0-81.687273 74.007273 83.316364 83.316364 0 0 0 13.265455 43.752727v1.629091c15.127273 22.109091 21.178182 40.494545 16.523636 50.734545h-34.676364a35.607273 35.607273 0 0 0-37.003636 34.443637v54.458182a14.894545 14.894545 0 0 0 15.36 13.963636H791.272727a14.429091 14.429091 0 0 0 15.127273-13.498182v-56.32a35.607273 35.607273 0 0 0-37.003636-34.443636z m3.025454 71.447273h-181.061818v-37.003637a2.792727 2.792727 0 0 1 2.792727-2.792727h48.64a7.447273 7.447273 0 0 0 4.887273-1.861818c26.530909-19.549091 25.134545-53.76-4.421818-96.581818a57.250909 57.250909 0 0 1-9.309091-29.556364 48.174545 48.174545 0 0 1 95.650909 0 54.225455 54.225455 0 0 1-9.541818 30.021818c-29.323636 42.821818-30.487273 77.032727-4.189091 96.581818a7.447273 7.447273 0 0 0 4.887273 1.861818H768a2.792727 2.792727 0 0 1 2.792727 2.792728v37.003636z m-215.272727-126.37091h-227.141818a17.221818 17.221818 0 1 0 0 34.21091h227.141818a17.221818 17.221818 0 1 0 0-34.21091z m0 0" fill="#FFFFFF" ></path></symbol><symbol id="icon-shenhe1" viewBox="0 0 1024 1024"><path d="M0.000608 512c0 282.766309 229.228574 512 512 512 282.766309 0 512-229.233691 512-512C1024.000608 229.228574 794.766917 0 512.000608 0 229.229182 0 0.000608 229.228574 0.000608 512z m0 0" fill="#FF7A65" ></path><path d="M708.338383 848.605944h-392.67555c-4.845762 0-8.71009-3.934943-8.710091-8.71009v-37.856297c0-4.845762 3.94006-8.71009 8.710091-8.710091h392.754351c4.845762 0 8.70395 3.934943 8.70395 8.710091v37.856297c0 4.845762-3.936989 8.71009-8.782751 8.71009z m-383.966484-17.490794h375.262534v-20.366527H324.371899v20.366527z m0 0" fill="#FFFFFF" ></path><path d="M739.601989 810.064997H284.38797c-28.925154 0-52.473407-23.547229-52.473407-52.473406v-72.007916c0-28.925154 23.548252-52.472383 52.473407-52.472383h455.219136c28.925154 0 52.473407 23.547229 52.473406 52.472383v72.007916c0 28.927201-23.548252 52.473407-52.478523 52.473406zM284.38797 668.093904c-9.614769 0-17.491818 7.870909-17.491818 17.489771v72.007916c0 9.614769 7.877049 17.491818 17.491818 17.491817h455.219136c9.613745 0 17.490794-7.877049 17.490794-17.491817v-72.007916c0-9.618862-7.877049-17.489771-17.490794-17.489771H284.38797z m0 0" fill="#FFFFFF" ></path><path d="M610.890987 664.989956H413.188007c-5.529388 0-10.829535-2.652632-14.082899-7.116669-3.331141-4.473247-4.24196-10.302488-2.575878-15.602636l55.426916-177.55853c-54.287881-23.697668-90.862891-78.291543-90.862891-138.414806 0-83.214059 67.693294-150.908376 150.908376-150.908376 83.214059 0 150.908376 67.694318 150.908377 150.908376 0 60.123263-36.57501 114.789799-90.862892 138.414806l55.503671 177.637331c1.665059 5.300148 0.677486 11.051611-2.574854 15.597519-3.331141 4.391376-8.560675 7.044008-14.088016 7.044008h0.00307z m-173.923441-34.981589h150.147996l-53.383203-170.747856a17.901175 17.901175 0 0 1 1.214767-13.400296 17.564479 17.564479 0 0 1 10.44781-8.558628c48.610102-14.610969 82.610258-60.272678 82.610257-111.004272 0-63.901626-52.019021-116.003542-115.926787-116.003542-63.903673 0-115.927811 52.023114-115.927811 116.003542 0 50.808348 34.000156 96.392279 82.615374 111.004272 4.463013 1.365205 8.249564 4.391376 10.44781 8.558628 2.193129 4.163159 2.647515 8.931143 1.20965 13.400296l-53.455863 170.747856z m0 0" fill="#FFFFFF" ></path></symbol><symbol id="icon-shenhe2" viewBox="0 0 1024 1024"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#A5BFFF" ></path><path d="M345.043478 602.980174v-43.408696a45.568 45.568 0 0 1 47.415652-43.408695h72.125218v-16.695653a87.151304 87.151304 0 0 1-47.415652-75.79826A91.336348 91.336348 0 0 1 512 336.695652a91.314087 91.314087 0 0 1 94.831304 86.973218 85.481739 85.481739 0 0 1-47.593739 74.974608v16.695652h71.969392a45.568 45.568 0 0 1 47.749565 44.410435v43.408696l-333.913044-0.178087z m258.114783 84.324174l-183.808-0.178087v-49.753044l185.299478 0.178087v49.753044h-1.491478z" fill="#5782FD" ></path></symbol><symbol id="icon-shenhe3" viewBox="0 0 1060 1024"><path d="M921.234286 714.715429h-69.961143c-4.498286-9.947429-13.458286-34.377143-17.408-78.628572a165.449143 165.449143 0 1 0-127.341714 3.547429c-3.584 25.161143-10.569143 58.697143-23.478858 75.081143h-72.484571a127.524571 127.524571 0 0 0-127.524571 127.524571v91.465143h565.76v-91.428572a127.524571 127.524571 0 0 0-127.561143-127.561142zM539.062857 965.595429h453.741714c14.262857 0 21.321143 7.094857 21.321143 21.284571v10.752c0 14.226286-7.094857 21.321143-21.321143 21.321143H539.062857c-14.189714 0-21.321143-7.094857-21.321143-21.321143v-10.752c0-14.189714 7.131429-21.284571 21.321143-21.284571z" fill="#108EE9" ></path><path d="M695.296 997.668571H50.834286A50.834286 50.834286 0 0 1 0 946.834286V50.834286C0 22.747429 22.747429 0 50.834286 0h644.461714c28.050286 0 50.834286 22.747429 50.834286 50.834286v896a50.834286 50.834286 0 0 1-50.834286 50.834285zM584.118857 198.217143c0-22.381714-18.176-40.521143-40.521143-40.521143H202.496a40.484571 40.484571 0 1 0 0 81.005714h341.101714c22.345143 0 40.484571-18.102857 40.484572-40.484571z m0 179.053714c0-22.345143-18.176-40.484571-40.521143-40.484571H202.496a40.484571 40.484571 0 0 0 0 81.005714h341.101714c22.345143 0 40.484571-18.139429 40.484572-40.521143z m0 179.090286c0-22.381714-18.176-40.521143-40.521143-40.521143H202.496a40.484571 40.484571 0 1 0 0 81.005714h341.101714c22.345143 0 40.484571-18.139429 40.484572-40.484571z" fill="#108EE9" opacity=".5" ></path></symbol><symbol id="icon-zu54" viewBox="0 0 1024 1024"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#007AFF" ></path><path d="M717.824 407.466667a52.992 52.992 0 0 0-49.194667 72.533333l-83.242666 33.28-55.808-111.573333a52.992 52.992 0 1 0-47.36 0l-55.808 111.573333-83.285334-33.28a52.992 52.992 0 1 0-49.152 33.28 48.341333 48.341333 0 0 0 8.832-0.725333l28.245334 167.68a51.2 51.2 0 0 0 50.346666 44.970666h246.4a53.888 53.888 0 0 0 52.992-44.970666l28.245334-167.68a48.085333 48.085333 0 0 0 8.832 0.725333 52.992 52.992 0 1 0 0-105.941333z m-161.194667 276.608l-50.730666-26.666667-50.730667 26.666667 9.685333-56.490667-41.045333-39.978667 56.704-8.234666 25.344-51.2 25.344 51.2 56.704 8.234666-41.045333 40.021334 9.685333 56.490666z" fill="#FFFFFF" ></path></symbol><symbol id="icon-a-2labadianji3x" viewBox="0 0 1024 1024"><path d="M752 416a16 16 0 0 1 16 16v160a16 16 0 1 1-32 0v-160a16 16 0 0 1 16-16z m64-32a16 16 0 0 1 16 16v224a16 16 0 1 1-32 0v-224a16 16 0 0 1 16-16z m64-32a16 16 0 0 1 16 16v288a16 16 0 1 1-32 0v-288a16 16 0 0 1 16-16z m-220.864-134.176A64 64 0 0 1 672 256.32v513.76a64 64 0 0 1-101.728 51.712L321.152 640H192a64 64 0 0 1-64-64v-128a64 64 0 0 1 64-64h140.096l237.408-178.784a64 64 0 0 1 89.6 12.608z" fill="#2875FF" ></path></symbol></svg>',(l=>{var a=(t=(t=document.getElementsByTagName("script"))[t.length-1]).getAttribute("data-injectcss"),t=t.getAttribute("data-disable-injectsvg");if(!t){var h,e,i,c,o,n=function(a,t){t.parentNode.insertBefore(a,t)};if(a&&!l.__iconfont__svg__cssinject__){l.__iconfont__svg__cssinject__=!0;try{document.write("<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>")}catch(a){console&&console.log(a)}}h=function(){var a,t=document.createElement("div");t.innerHTML=l._iconfont_svg_string_4462827,(t=t.getElementsByTagName("svg")[0])&&(t.setAttribute("aria-hidden","true"),t.style.position="absolute",t.style.width=0,t.style.height=0,t.style.overflow="hidden",t=t,(a=document.body).firstChild?n(t,a.firstChild):a.appendChild(t))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(h,0):(e=function(){document.removeEventListener("DOMContentLoaded",e,!1),h()},document.addEventListener("DOMContentLoaded",e,!1)):document.attachEvent&&(i=h,c=l.document,o=!1,m(),c.onreadystatechange=function(){"complete"==c.readyState&&(c.onreadystatechange=null,d())})}function d(){o||(o=!0,i())}function m(){try{c.documentElement.doScroll("left")}catch(a){return void setTimeout(m,50)}d()}})(window); window._iconfont_svg_string_4462827='<svg><symbol id="icon-tishi" viewBox="0 0 1024 1024"><path d="M919.67 723.12s-126.22-89-137.9-301.44c-9-163-97.38-230.29-180.5-252.81a69.41 69.41 0 0 0-138.39-2.28c-89.74 19.93-182.11 84.48-184.48 237.66-3 192.22-134 320.9-134 320.9 94.76 68.74 186.32 106.19 271.32 123.09 5.62 40.43 55.54 72.07 116.33 72.07 61.11 0 111.19-32 116.38-72.69 164.79-33.29 271.24-124.5 271.24-124.5z" fill="#FED547" ></path><path d="M781.78 421.68c-9-163-97.38-230.29-180.5-252.81a69.4 69.4 0 0 0-100.86-57.57 69.49 69.49 0 0 1 37.7 57.57c83.12 22.52 171.54 89.82 180.5 252.81 11.68 212.42 137.9 301.44 137.9 301.44s-106.45 91.21-271.27 124.49c-4.25 33.39-38.8 60.92-84.82 69.71a168.37 168.37 0 0 0 31.59 3c61.11 0 111.19-32 116.38-72.69 164.82-33.28 271.27-124.49 271.27-124.49S793.45 634.1 781.78 421.68z" fill="#E2B742" ></path><path d="M500.43 925.77c-61.45 0-112.55-31.08-121.09-72.92-92.43-19-183.17-60.45-269.78-123.28l-5.23-3.8 4.61-4.53c1.29-1.28 129.49-129.45 132.39-317.09 2.67-172.34 115.27-225.61 185-242.05a74.89 74.89 0 0 1 148.48 2.51c147.27 42 176.55 178.63 180.85 256.76C767 628.75 890 717.77 891.23 718.65l5.75 4.06-5.34 4.57c-1.07 0.91-107.8 91.13-270 124.93-8.16 42.21-59.3 73.56-121.21 73.56zM121.07 724.38C206 785 294.82 824.9 385.16 842.87l3.82 0.76 0.54 3.85c5.25 37.77 54 67.35 110.91 67.35 57.36 0 106.1-29.83 110.95-67.91l0.5-3.9 3.85-0.78C757 813.72 856.07 741.38 879.3 723.17 854.42 702.52 755 608.75 744.72 422c-4.17-75.83-32.64-208.86-176.47-247.83l-3.79-1-0.24-3.92a63.94 63.94 0 0 0-127.48-2.11l-0.37 4-3.89 0.87c-66.18 14.69-177.59 64.43-180.2 232.4-2.67 171.87-108.04 294.96-131.21 319.97z" fill="#28CA67" ></path></symbol><symbol id="icon-chakanxiangqing" viewBox="0 0 1024 1024"><path d="M383.6 62.8h256.3c213.6 0 320.4 106.8 320.4 320.4v256.3c0 213.6-106.8 320.4-320.4 320.4H383.6C170 959.8 63.2 853 63.2 639.4V383.2C63.2 169.6 170 62.8 383.6 62.8z" fill="#2084FE" ></path><path d="M254.1 515.3H416v-53.2H254.1zM254.1 358h363.1v53.2H254.1z" fill="#FFFFFF" opacity=".7" ></path><path d="M700.5 663.3c13.9-20.8 20.8-43.9 20.8-71.7 0-71.7-57.8-129.5-127.2-129.5S466.8 520 466.8 591.6 524.7 721 594 721c25.5 0 50.8-9.3 71.7-23.1l71.7 74 34.7-34.6-71.6-74z m-108.7 9.2c-41.7 0-76.3-34.7-76.3-78.6 0-43.9 34.7-78.6 76.3-78.6 41.7 0 76.3 34.7 76.3 78.6 0 41.7-34.7 78.6-76.3 78.6z" fill="#FFFFFF" ></path><path d="M254.1 254h363.1v53.2H254.1z" fill="#FFFFFF" opacity=".7" ></path></symbol><symbol id="icon-baojiachaxun" viewBox="0 0 1024 1024"><path d="M0 0m178.086957 0l667.826086 0q178.086957 0 178.086957 178.086957l0 667.826086q0 178.086957-178.086957 178.086957l-667.826086 0q-178.086957 0-178.086957-178.086957l0-667.826086q0-178.086957 178.086957-178.086957Z" fill="#2E7BFC" ></path><path d="M790.327652 721.363478l23.730087 23.730087a42.073043 42.073043 0 0 1 0 59.481044l-9.48313 9.48313a42.073043 42.073043 0 0 1-59.481044 0l-23.730087-23.730087a42.073043 42.073043 0 0 1 0-59.503304l9.46087-9.46087a42.073043 42.073043 0 0 1 59.503304 0zM505.499826 226.081391c154.312348 0 279.418435 125.106087 279.418435 279.418435 0 154.312348-125.106087 279.418435-279.418435 279.418435-154.312348 0-279.418435-125.106087-279.418435-279.418435 0-154.312348 125.106087-279.418435 279.418435-279.418435z m-100.797217 158.764522a19.300174 19.300174 0 0 0-0.24487 25.377391l1.847652 1.869913 44.076522 39.201392h-58.278956l-2.626783 0.178087a19.300174 19.300174 0 0 0 0.200348 38.288695l2.426435 0.133566h84.902956v38.622608h-61.751652l-2.604522 0.178087a19.300174 19.300174 0 0 0 0.200348 38.288696l2.404174 0.133565h61.751652l0.022261 83.656348 0.178087 2.604522a19.300174 19.300174 0 0 0 38.288696-0.200348l0.155826-2.404174-0.044522-83.656348h61.796174l2.626782-0.178087a19.300174 19.300174 0 0 0-0.200347-38.288696l-2.426435-0.133565h-61.796174v-38.622608h84.969739l2.626783-0.178087a19.300174 19.300174 0 0 0-0.222609-38.288696l-2.404174-0.133565h-58.323478l44.121043-39.179131 2.003478-2.070261a19.300174 19.300174 0 0 0-25.577739-28.404869l-2.070261 1.602782-64.400695 57.210435-64.378435-57.210435-2.270609-1.736347a19.300174 19.300174 0 0 0-24.976695 3.33913z" fill="#FFFFFF" ></path></symbol><symbol id="icon-caozuorizhi" viewBox="0 0 1024 1024"><path d="M312.416187 5.855995C376.992127 1.951998 443.520064 0 512 0c68.479936 0 135.007873 1.951998 199.583813 5.855995a319.9997 319.9997 0 0 1 298.14372 279.359738 1811.582302 1811.582302 0 0 1 0 453.535574 319.9997 319.9997 0 0 1-298.14372 279.359739A3304.316902 3304.316902 0 0 1 512 1023.99904c-68.479936 0-135.007873-1.951998-199.583813-5.855995a319.9997 319.9997 0 0 1-298.14372-279.359738 1811.582302 1811.582302 0 0 1 0-453.535574A319.9997 319.9997 0 0 1 312.416187 5.887994z" fill="#40C791" ></path><path d="M384.00012 298.65572A85.34392 85.34392 0 0 0 469.34404 383.99964h85.31192A85.34392 85.34392 0 0 0 639.99988 298.65572h42.65596A85.34392 85.34392 0 0 1 767.99976 383.99964v298.65572A85.34392 85.34392 0 0 1 682.65584 767.99928H341.34416A85.34392 85.34392 0 0 1 256.00024 682.65536V383.99964a85.34392 85.34392 0 0 1 85.34392-85.34392H384.00012z m0 1.311999h255.99976-255.99976z m149.34386 169.375841a21.34398 21.34398 0 1 0 0 42.65596h127.99988a21.34398 21.34398 0 0 0 0-42.65596h-127.99988z m0 127.99988a21.34398 21.34398 0 1 0 0 42.65596h127.99988a21.34398 21.34398 0 0 0 0-42.65596h-127.99988z m-140.479868-119.167888l-15.103986-15.103986a21.34398 21.34398 0 0 0-30.175972 30.207972l30.175972 30.143971a21.27998 21.27998 0 0 0 30.175972 0l60.319943-60.351943a21.34398 21.34398 0 1 0-30.175972-30.143972l-45.247957 45.247958z m0 127.99988l-15.103986-15.103986a21.34398 21.34398 0 0 0-30.175972 30.207972l30.175972 30.143971a21.27998 21.27998 0 0 0 30.175972 0l60.319943-60.351943a21.34398 21.34398 0 1 0-30.175972-30.143972l-45.247957 45.247958zM469.31204 255.99976h85.31192a42.65596 42.65596 0 1 1 0 85.34392h-85.31192a42.65596 42.65596 0 1 1 0-85.34392z" fill="#FFFFFF" ></path></symbol><symbol id="icon-querenshenhexunjia" viewBox="0 0 1024 1024"><path d="M474.500517 639.29203a320.016772 320.016772 0 0 0 93.819328 378.808334 51.976684 51.976684 0 0 1-53.777507-7.16798l-72.950971-58.120668-65.323958 58.120668c-9.321905 8.121357-21.327389 12.535138-33.650666 12.429207a50.140551 50.140551 0 0 1-33.368184-12.287966L243.995222 952.952957l-65.994852 58.615011a50.987997 50.987997 0 0 1-33.544735 12.429207 50.140551 50.140551 0 0 1-33.438805-12.287966L34.499524 943.595742a35.310247 35.310247 0 0 1-6.249914-7.06205A67.407262 67.407262 0 0 1 0.001412 882.756186V51.41172C0.001412 23.057592 23.023694 0 50.742238 0h794.480567c28.036336 0 50.776136 22.84573 50.776136 51.41172V474.569725a319.981462 319.981462 0 0 0-421.498424 164.686994zM127.612647 227.397993h333.575907c14.088789 0 25.211517-11.334589 25.211517-25.282137a25.458688 25.458688 0 0 0-25.211517-25.282137H127.612647a25.070276 25.070276 0 0 0-25.211517 24.964345v0.317792c0 13.559135 11.263969 25.282137 25.211517 25.282137z m231.423361 227.327373a24.752483 24.752483 0 0 0 24.964345-25.211516 25.423378 25.423378 0 0 0-24.964345-25.282138H127.365475a24.823104 24.823104 0 0 0-24.964345 24.717174v0.564964c0 13.488515 11.193348 25.211517 24.964345 25.211516h231.670533z m-26.235514 176.833719a25.176206 25.176206 0 0 0-24.646552-25.211516H127.047683a24.929035 24.929035 0 0 0-24.646553 25.211516c0 13.559135 11.016797 25.282137 24.646553 25.282138h181.141569a24.964345 24.964345 0 0 0 24.611242-25.282138zM766.09254 511.998588a255.999294 255.999294 0 1 1 0 511.998587 255.999294 255.999294 0 0 1 0-511.998587z m145.195738 206.105914a26.553306 26.553306 0 0 0-37.605414-37.428862l-131.001018 131.001017-84.25025-84.21494a26.447375 26.447375 0 1 0-37.428862 37.428863l102.999992 103.035302a26.517996 26.517996 0 0 0 37.428862 0l149.85669-149.82138z" fill="#389AFB" ></path></symbol><symbol id="icon-shenhe" viewBox="0 0 1024 1024"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#00CD82" ></path><path d="M512 744.727273h-211.549091a16.989091 16.989091 0 0 1-16.989091-16.989091V318.836364a16.989091 16.989091 0 0 1 16.989091-16.989091h335.127273a16.989091 16.989091 0 0 1 16.989091 16.989091v144.756363a17.221818 17.221818 0 0 0 34.210909 0V318.836364a51.2 51.2 0 0 0-50.967273-50.967273H300.450909a51.2 51.2 0 0 0-50.967273 50.967273v408.203636a51.2 51.2 0 0 0 50.967273 51.2H512A17.221818 17.221818 0 0 0 512 744.727273z m0 0" fill="#FFFFFF" ></path><path d="M596.945455 361.425455H330.007273a16.989091 16.989091 0 1 0 0 33.978181h266.938182a16.989091 16.989091 0 0 0 0-33.978181z m0 84.945454H330.007273a17.221818 17.221818 0 1 0 0 34.210909h266.938182a17.221818 17.221818 0 0 0 0-34.210909z m-42.821819 85.178182h-224.116363a16.989091 16.989091 0 1 0 0 33.978182h224.349091a16.989091 16.989091 0 0 0 16.989091-16.989091 17.454545 17.454545 0 0 0-17.221819-16.989091z m220.16 262.050909h-184.087272a15.592727 15.592727 0 1 0 0 31.185455h184.087272a16.756364 16.756364 0 0 0 17.221819-16.058182 16.290909 16.290909 0 0 0-17.221819-15.127273z m-4.887272-122.181818h-36.072728c-4.887273-9.774545 0-28.625455 16.290909-50.734546a84.014545 84.014545 0 0 0 13.265455-43.985454 78.196364 78.196364 0 0 0-82.152727-74.007273 77.498182 77.498182 0 0 0-81.687273 74.007273 83.316364 83.316364 0 0 0 13.265455 43.752727v1.629091c15.127273 22.109091 21.178182 40.494545 16.523636 50.734545h-34.676364a35.607273 35.607273 0 0 0-37.003636 34.443637v54.458182a14.894545 14.894545 0 0 0 15.36 13.963636H791.272727a14.429091 14.429091 0 0 0 15.127273-13.498182v-56.32a35.607273 35.607273 0 0 0-37.003636-34.443636z m3.025454 71.447273h-181.061818v-37.003637a2.792727 2.792727 0 0 1 2.792727-2.792727h48.64a7.447273 7.447273 0 0 0 4.887273-1.861818c26.530909-19.549091 25.134545-53.76-4.421818-96.581818a57.250909 57.250909 0 0 1-9.309091-29.556364 48.174545 48.174545 0 0 1 95.650909 0 54.225455 54.225455 0 0 1-9.541818 30.021818c-29.323636 42.821818-30.487273 77.032727-4.189091 96.581818a7.447273 7.447273 0 0 0 4.887273 1.861818H768a2.792727 2.792727 0 0 1 2.792727 2.792728v37.003636z m-215.272727-126.37091h-227.141818a17.221818 17.221818 0 1 0 0 34.21091h227.141818a17.221818 17.221818 0 1 0 0-34.21091z m0 0" fill="#FFFFFF" ></path></symbol><symbol id="icon-shenhe1" viewBox="0 0 1024 1024"><path d="M0.000608 512c0 282.766309 229.228574 512 512 512 282.766309 0 512-229.233691 512-512C1024.000608 229.228574 794.766917 0 512.000608 0 229.229182 0 0.000608 229.228574 0.000608 512z m0 0" fill="#FF7A65" ></path><path d="M708.338383 848.605944h-392.67555c-4.845762 0-8.71009-3.934943-8.710091-8.71009v-37.856297c0-4.845762 3.94006-8.71009 8.710091-8.710091h392.754351c4.845762 0 8.70395 3.934943 8.70395 8.710091v37.856297c0 4.845762-3.936989 8.71009-8.782751 8.71009z m-383.966484-17.490794h375.262534v-20.366527H324.371899v20.366527z m0 0" fill="#FFFFFF" ></path><path d="M739.601989 810.064997H284.38797c-28.925154 0-52.473407-23.547229-52.473407-52.473406v-72.007916c0-28.925154 23.548252-52.472383 52.473407-52.472383h455.219136c28.925154 0 52.473407 23.547229 52.473406 52.472383v72.007916c0 28.927201-23.548252 52.473407-52.478523 52.473406zM284.38797 668.093904c-9.614769 0-17.491818 7.870909-17.491818 17.489771v72.007916c0 9.614769 7.877049 17.491818 17.491818 17.491817h455.219136c9.613745 0 17.490794-7.877049 17.490794-17.491817v-72.007916c0-9.618862-7.877049-17.489771-17.490794-17.489771H284.38797z m0 0" fill="#FFFFFF" ></path><path d="M610.890987 664.989956H413.188007c-5.529388 0-10.829535-2.652632-14.082899-7.116669-3.331141-4.473247-4.24196-10.302488-2.575878-15.602636l55.426916-177.55853c-54.287881-23.697668-90.862891-78.291543-90.862891-138.414806 0-83.214059 67.693294-150.908376 150.908376-150.908376 83.214059 0 150.908376 67.694318 150.908377 150.908376 0 60.123263-36.57501 114.789799-90.862892 138.414806l55.503671 177.637331c1.665059 5.300148 0.677486 11.051611-2.574854 15.597519-3.331141 4.391376-8.560675 7.044008-14.088016 7.044008h0.00307z m-173.923441-34.981589h150.147996l-53.383203-170.747856a17.901175 17.901175 0 0 1 1.214767-13.400296 17.564479 17.564479 0 0 1 10.44781-8.558628c48.610102-14.610969 82.610258-60.272678 82.610257-111.004272 0-63.901626-52.019021-116.003542-115.926787-116.003542-63.903673 0-115.927811 52.023114-115.927811 116.003542 0 50.808348 34.000156 96.392279 82.615374 111.004272 4.463013 1.365205 8.249564 4.391376 10.44781 8.558628 2.193129 4.163159 2.647515 8.931143 1.20965 13.400296l-53.455863 170.747856z m0 0" fill="#FFFFFF" ></path></symbol><symbol id="icon-shenhe2" viewBox="0 0 1024 1024"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#A5BFFF" ></path><path d="M345.043478 602.980174v-43.408696a45.568 45.568 0 0 1 47.415652-43.408695h72.125218v-16.695653a87.151304 87.151304 0 0 1-47.415652-75.79826A91.336348 91.336348 0 0 1 512 336.695652a91.314087 91.314087 0 0 1 94.831304 86.973218 85.481739 85.481739 0 0 1-47.593739 74.974608v16.695652h71.969392a45.568 45.568 0 0 1 47.749565 44.410435v43.408696l-333.913044-0.178087z m258.114783 84.324174l-183.808-0.178087v-49.753044l185.299478 0.178087v49.753044h-1.491478z" fill="#5782FD" ></path></symbol><symbol id="icon-shenhe3" viewBox="0 0 1060 1024"><path d="M921.234286 714.715429h-69.961143c-4.498286-9.947429-13.458286-34.377143-17.408-78.628572a165.449143 165.449143 0 1 0-127.341714 3.547429c-3.584 25.161143-10.569143 58.697143-23.478858 75.081143h-72.484571a127.524571 127.524571 0 0 0-127.524571 127.524571v91.465143h565.76v-91.428572a127.524571 127.524571 0 0 0-127.561143-127.561142zM539.062857 965.595429h453.741714c14.262857 0 21.321143 7.094857 21.321143 21.284571v10.752c0 14.226286-7.094857 21.321143-21.321143 21.321143H539.062857c-14.189714 0-21.321143-7.094857-21.321143-21.321143v-10.752c0-14.189714 7.131429-21.284571 21.321143-21.284571z" fill="#108EE9" ></path><path d="M695.296 997.668571H50.834286A50.834286 50.834286 0 0 1 0 946.834286V50.834286C0 22.747429 22.747429 0 50.834286 0h644.461714c28.050286 0 50.834286 22.747429 50.834286 50.834286v896a50.834286 50.834286 0 0 1-50.834286 50.834285zM584.118857 198.217143c0-22.381714-18.176-40.521143-40.521143-40.521143H202.496a40.484571 40.484571 0 1 0 0 81.005714h341.101714c22.345143 0 40.484571-18.102857 40.484572-40.484571z m0 179.053714c0-22.345143-18.176-40.484571-40.521143-40.484571H202.496a40.484571 40.484571 0 0 0 0 81.005714h341.101714c22.345143 0 40.484571-18.139429 40.484572-40.521143z m0 179.090286c0-22.381714-18.176-40.521143-40.521143-40.521143H202.496a40.484571 40.484571 0 1 0 0 81.005714h341.101714c22.345143 0 40.484571-18.139429 40.484572-40.484571z" fill="#108EE9" opacity=".5" ></path></symbol><symbol id="icon-zu54" viewBox="0 0 1024 1024"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#007AFF" ></path><path d="M717.824 407.466667a52.992 52.992 0 0 0-49.194667 72.533333l-83.242666 33.28-55.808-111.573333a52.992 52.992 0 1 0-47.36 0l-55.808 111.573333-83.285334-33.28a52.992 52.992 0 1 0-49.152 33.28 48.341333 48.341333 0 0 0 8.832-0.725333l28.245334 167.68a51.2 51.2 0 0 0 50.346666 44.970666h246.4a53.888 53.888 0 0 0 52.992-44.970666l28.245334-167.68a48.085333 48.085333 0 0 0 8.832 0.725333 52.992 52.992 0 1 0 0-105.941333z m-161.194667 276.608l-50.730666-26.666667-50.730667 26.666667 9.685333-56.490667-41.045333-39.978667 56.704-8.234666 25.344-51.2 25.344 51.2 56.704 8.234666-41.045333 40.021334 9.685333 56.490666z" fill="#FFFFFF" ></path></symbol><symbol id="icon-a-2labadianji3x" viewBox="0 0 1024 1024"><path d="M752 416a16 16 0 0 1 16 16v160a16 16 0 1 1-32 0v-160a16 16 0 0 1 16-16z m64-32a16 16 0 0 1 16 16v224a16 16 0 1 1-32 0v-224a16 16 0 0 1 16-16z m64-32a16 16 0 0 1 16 16v288a16 16 0 1 1-32 0v-288a16 16 0 0 1 16-16z m-220.864-134.176A64 64 0 0 1 672 256.32v513.76a64 64 0 0 1-101.728 51.712L321.152 640H192a64 64 0 0 1-64-64v-128a64 64 0 0 1 64-64h140.096l237.408-178.784a64 64 0 0 1 89.6 12.608z" fill="#2875FF" ></path></symbol></svg>',(t=>{var a=(l=(l=document.getElementsByTagName("script"))[l.length-1]).getAttribute("data-injectcss"),l=l.getAttribute("data-disable-injectsvg");if(!l){var h,i,e,c,o,n=function(a,l){l.parentNode.insertBefore(a,l)};if(a&&!t.__iconfont__svg__cssinject__){t.__iconfont__svg__cssinject__=!0;try{document.write("<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>")}catch(a){console&&console.log(a)}}h=function(){var a,l=document.createElement("div");l.innerHTML=t._iconfont_svg_string_4462827,(l=l.getElementsByTagName("svg")[0])&&(l.setAttribute("aria-hidden","true"),l.style.position="absolute",l.style.width=0,l.style.height=0,l.style.overflow="hidden",l=l,(a=document.body).firstChild?n(l,a.firstChild):a.appendChild(l))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(h,0):(i=function(){document.removeEventListener("DOMContentLoaded",i,!1),h()},document.addEventListener("DOMContentLoaded",i,!1)):document.attachEvent&&(e=h,c=t.document,o=!1,m(),c.onreadystatechange=function(){"complete"==c.readyState&&(c.onreadystatechange=null,d())})}function d(){o||(o=!0,e())}function m(){try{c.documentElement.doScroll("left")}catch(a){return void setTimeout(m,50)}d()}})(window);
\ No newline at end of file \ No newline at end of file
...@@ -6,6 +6,13 @@ ...@@ -6,6 +6,13 @@
"description": "", "description": "",
"glyphs": [ "glyphs": [
{ {
"icon_id": "3833188",
"name": "提示",
"font_class": "tishi",
"unicode": "e605",
"unicode_decimal": 58885
},
{
"icon_id": "22718987", "icon_id": "22718987",
"name": "查看详情", "name": "查看详情",
"font_class": "chakanxiangqing", "font_class": "chakanxiangqing",
......
import axios from './axios'
import type { BasePaginationData, BaseRespData } from '@/types/api'
import type {
BatchManageData,
FactoryOrderNewListData,
LogListData,
operateOrderListData,
PickCompleteData,
PickFailData,
ProductListData,
RestockData,
SearchForm,
StatusTreeNode,
} from '@/types/api/factoryOrderNew'
import { ResultInfoDataItem } from '@/types/api/order/common'
import { ExportParams } from '@/types/api/order/factoryOrderNew'
import type { OrderData } from '@/types/api/podMakeOrder'
import type { PrintData } from '@/types/api/podOrder'
function normalizePodOrderQueryPayload(
data: Record<string, unknown>,
): Record<string, unknown> {
const { receiverCountry, ...rest } = data
if (!Array.isArray(receiverCountry)) {
return data
}
return {
...rest,
...(receiverCountry.length
? { receiverCountry: receiverCountry.join(',') }
: {}),
}
}
export function getPodOrderStateGroupListApi() {
return axios.get<never, BaseRespData<StatusTreeNode[]>>(
'factory/podOrder/findStateGroupList',
)
}
export function getFactoryOrderNewListApi(
data: SearchForm,
currentPage: number,
pageSize: number,
status?: string,
subStatus?: number,
pauseReason?: number,
) {
const body = normalizePodOrderQueryPayload({ ...data } as Record<
string,
unknown
>)
return axios.post<never, BasePaginationData<FactoryOrderNewListData>>(
'factory/podOrder/list_page',
{
...body,
currentPage,
pageSize,
status,
acceptedStatus: subStatus,
pauseReason,
},
)
}
/** 请求体与 `factory/podOrder/list_page` 一致 */
export function getPodOrderAcceptedStatisticsApi(
data: SearchForm,
currentPage: number,
pageSize: number,
status?: string,
subStatus?: number,
) {
return axios.post<
never,
BaseRespData<{
acceptedOutOfStockCount?: number
pendingCount?: number
totalCount?: number
}>
>('factory/podOrder/acceptedStatistics', {
...normalizePodOrderQueryPayload({ ...data } as Record<string, unknown>),
currentPage,
pageSize,
status,
acceptedStatus: subStatus,
})
}
export function getFactoryOrderNewDetailApi(id: number | string) {
return axios.get<never, BaseRespData<ProductListData[]>>(
'factory/podOrderProduct/getListByPodOrderId',
{
params: { id },
},
)
}
export function getFactoryOrderNewOperateDetailApi(id: number | string) {
return axios.get<never, BaseRespData<operateOrderListData>>(
'factory/podOrderOperation/get',
{
params: { id },
},
)
}
export function getFactoryOrderNewLogApi(id: number | string) {
return axios.get<never, BaseRespData<LogListData[]>>(
'factory/podOrderLog/getPodOrderLog',
{
params: { id },
},
)
}
export function refreshProductInfoApi(data: {
orderIds?: number | string
productIds?: number | string
}) {
return axios.post<never, BaseRespData<void>>(
'factory/podOrderProduct/refreshProductInfo',
data,
)
}
export function transferOldFlowApi(ids: (number | string)[]) {
return axios.post<never, BaseRespData<void>>(
'factory/orderNew/transferOldFlow',
{ ids },
)
}
export function confirmOrderWithWarehouseApi(
ids: (number | string)[],
warehouseId: number | string,
warehouseName: string,
) {
return axios.post<never, BaseRespData<ResultInfoDataItem[]>>(
'factory/podOrder/ordersAccepted',
{
podOrderIds: ids,
warehouseId,
warehouseName,
},
)
}
export function cancelOrderWithReasonApi(
ids: (number | string)[],
reason: string,
) {
return axios.post<never, BaseRespData<void>>(
'factory/podOrder/cancelOrders',
{
podOrderIds: ids,
cancelReason: reason,
},
)
}
export function suspendOrderApi(
ids: (number | string)[],
reason: string,
pauseType: string,
) {
return axios.post<never, BaseRespData<void>>(
'factory/podOrder/suspendOrders',
{
podOrderIds: ids,
pauseReason: reason,
pauseType,
},
)
}
export function cancelSuspendApi(ids: (number | string)[]) {
return axios.post<never, BaseRespData<void>>('factory/podOrder/unSuspend', {
podOrderIds: ids,
})
}
export function archiveOrderApi(ids: number | string) {
return axios.get<never, BaseRespData<void>>('factory/podOrder/archiving', {
params: { ids },
})
}
export function printPickOrderApi(ids: (number | string)[]) {
return axios.post<never, BaseRespData<void>>(
'factory/podOrderOperation/printPickPdf',
{ ids: ids.join(',') },
)
}
export function applyReplenishApi(ids: (number | string)[]) {
return axios.post<never, BaseRespData<void>>(
'factory/podOrderOperation/applyForReplenishment',
ids,
)
}
export function batchDeleteApi(ids: (number | string)[]) {
return axios.get<never, BaseRespData<never>>(
'factory/podOrderBatchDownload/delete',
{
params: { ids: ids.join(',') },
},
)
}
export function restockCheckApi(
factoryId: number | string,
warehouseSku: string,
warehouseId: number | string,
) {
return axios.post<never, BaseRespData<void>>(
'factory/podOrder/outOfStock/autoMark',
{ factoryId, warehouseSku, warehouseId },
)
}
/** 按仓库标记缺货:同一仓库一次请求,多个 SKU 传 warehouseSkuList */
export function markStockOutOfApi(data: {
warehouseId: number | string
warehouseSkuList: string[]
}) {
return axios.post<never, BaseRespData<void>>(
'factory/podOrder/outOfStock/batchAutoMark',
data,
)
}
export function pickCompleteByIdsDataApi(ids: (number | string)[]) {
return axios.post<
never,
BaseRespData<{
overallMessage?: string
pickingSituationList?: PickCompleteData[]
allAvailableOrderIds?: number[]
partialAvailableOrderIds?: number[]
unavailableOrderIds?: number[]
}>
>('factory/podOrderOperation/getPickingSituation', ids)
}
export function pickCompleteApi(ids: (number | string)[]) {
return axios.post<never, BaseRespData<void>>(
'factory/podOrderOperation/pickingComplete',
ids,
)
}
export function replenishmentCompleteApi(ids: (number | string)[]) {
return axios.post<never, BaseRespData<void>>(
'factory/podOrderOperation/replenishmentComplete',
ids,
)
}
export function pickFailApi(ids: (number | string)[]) {
return axios.post<never, BaseRespData<PickFailData[]>>(
'factory/podOrderOperation/listByIds',
{ ids },
)
}
export function getCardLayoutListApi(
data: Record<string, unknown>,
currentPage: number,
pageSize: number,
) {
const body = normalizePodOrderQueryPayload({ ...data })
return axios.post<never, BasePaginationData<FactoryOrderNewListData>>(
'factory/podOrderOperation/list_page',
{ ...body, currentPage, pageSize },
)
}
export function getSuspendListApi(
data: SearchForm,
currentPage: number,
pageSize: number,
pauseReason?: number,
) {
const body = normalizePodOrderQueryPayload({ ...data } as Record<
string,
unknown
>)
return axios.post<never, BasePaginationData<FactoryOrderNewListData>>(
'factory/podOrderPauseControl/list_page',
{ ...body, currentPage, pageSize, pauseReason },
)
}
export function getSuspendStatisticsApi(
data: SearchForm,
currentPage: number,
pageSize: number,
pauseReason?: number,
) {
const body = normalizePodOrderQueryPayload({ ...data } as Record<
string,
unknown
>)
return axios.post<never, BaseRespData<Record<string, number>>>(
'factory/podOrderPauseControl/suspendStatistics',
{ ...body, currentPage, pageSize, pauseReason },
)
}
export function getSuspendDetailApi(id: number | string) {
return axios.get<never, BaseRespData<ProductListData[]>>(
'factory/podOrderPauseControl/get',
{ params: { id } },
)
}
export function getSuspendLogApi(id: number | string) {
return axios.get<never, BaseRespData<LogListData[]>>(
'factory/podOrderPauseControl/getLog',
{ params: { id } },
)
}
export function getRestockListApi(
data: Record<string, unknown>,
currentPage: number,
pageSize: number,
) {
return axios.post<never, BasePaginationData<RestockData>>(
'factory/podOrder/awaitingRestockListPage',
{ ...data, currentPage, pageSize },
)
}
export function getBatchManageListApi(
data: Record<string, unknown>,
currentPage: number,
pageSize: number,
) {
return axios.post<never, BasePaginationData<BatchManageData>>(
'factory/podOrderBatchDownload/list_page',
{ ...data, currentPage, pageSize },
)
}
// 获取跟踪号
export function getTrackingNumberApi(orderIds: (string | number)[]) {
return axios.post<never, BaseRespData<never>>(
'factory/podOrder/getTrackingNumber',
orderIds,
)
}
// 获取打印面单
export function getfaceSimplexFileApi(orderIds: (string | number)[]) {
return axios.post<never, BaseRespData<never>>(
'factory/podOrder/getfaceSimplexFile',
orderIds,
)
}
// 取消物流订单
export function cancelLogisticsOrderApi(orderIds: (string | number)[]) {
return axios.post<never, BaseRespData<never>>(
'factory/podOrder/cancelLogisticsOrder',
orderIds,
)
}
/** 待创建物流 → 待排单 */
export function updateLogisticsToArrangeApi(params: { ids: string }) {
return axios.get<never, BaseRespData<unknown>>(
'factory/podOrder/updateLogisticsToArrange',
{ params },
)
}
export function arrangeFinishApi(params: {
productIdList: number[]
templateWidth?: number
type?: string
}) {
return axios.post<never, BaseRespData<never>>(
'factory/podOrderOperation/arrangeFinish',
{
operationIdList: params.productIdList,
templateWidth: params.templateWidth,
type: params.type,
},
)
}
export function batchDownloadDesignImagesApi(params: {
id: number
templateWidth?: number
type?: string
}) {
return axios.get<never, BaseRespData<never>>(
'factory/podOrderBatchDownload/reComposingDesignImages',
{
params,
},
)
}
export function printPickPdfByBatchNumberApi(params: {
batchArrangeNumber: string
}) {
return axios.get<never, BaseRespData<never>>(
`factory/podOrderOperation/printPickPdfByBatchNumber`,
{
params,
},
)
}
// 打印生产单item
export function printProductionPdfByBatchNumberApi(params: {
batchArrangeNumber: string
}) {
return axios.get<never, BaseRespData<never>>(
`factory/podOrderOperation/printProductionPdfByBatchNumber`,
{
params,
},
)
}
export function downloadMaterialApi(id: number[]) {
return axios.post<never, BaseRespData<never>>(
'factory/podOrder/downloadDesignImages',
id,
)
}
export function downloadOperationMaterialApi(id: number[]) {
return axios.post<never, BaseRespData<never>>(
'factory/podOrderOperation/downloadDesignImages',
id,
)
}
export function downloadBatchMaterialApi(id: number | string, type?: string) {
return axios.get<never, BaseRespData<never>>(
'factory/podOrderBatchDownload/download',
{ params: { id, type } },
)
}
export function composingNewPodOrderDesignImages(
data: number[],
type?: string,
templateWidth?: number,
) {
return axios.post<never, BaseRespData<never>>(
`factory/podOrderOperation/replenishmentComposingDesignImages?type=${type}&templateWidth=${templateWidth}`,
data,
)
}
export function printNewPodOrderProductionOrderApi(ids: number[]) {
return axios.post<never, BaseRespData<never>>(
'factory/podOrderOperation/printProducePdf',
ids,
)
}
export function syncReceiverAddress(data: number[]) {
return axios.post<never, BaseRespData<never>>(
'factory/podOrder/syncReceiverAddress',
data,
)
}
export function updateReceiverAddressApi(data: object) {
return axios.post<never, BaseRespData<never>>(
'factory/podOrder/updateReceiverAddress',
data,
)
}
export function updateRemarkApi(id: number | string, remark: string) {
return axios.post<never, BaseRespData<void>>(
'factory/podOrderProduct/addRemark',
{ id, content: remark },
)
}
export function applyForReplenishByIdApi(
ids: (number | string)[],
url: string,
) {
return axios.post<never, BaseRespData<operateOrderListData[]>>(url, ids)
}
export function completeDeliveryApi(ids: (number | string)[]) {
return axios.post<never, BaseRespData<ResultInfoDataItem[]>>(
'factory/podOrderOperation/completeDelivery',
ids,
)
}
export function applyForProductByIdApi(id: string, url: string) {
return axios.get<never, BaseRespData<operateOrderListData[]>>(url, {
params: { podProductId: id },
})
}
export function getByOperationNoLogApi(operationNo: string) {
return axios.get<never, BaseRespData<operateOrderListData>>(
'factory/podOrderOperation/getByOperationNoLog',
{
params: { operationNo },
},
)
}
export function listByNoPodOrderApi(params: {
type: string
no: string
logisticsCompanyCode?: string
}) {
return axios.get<never, BaseRespData<never>>('factory/podOrder/listByNo', {
params,
})
}
export function orderWeighingPodOrderApi(params: unknown) {
return axios.post<never, BaseRespData<never>>(
'factory/podOrder/orderWeighing',
params as {
podOrderWeighingParams: { id?: string; outWarehouseWeight?: string }[]
},
)
}
// 单件打单:根据 SKU/单号查询明细
export function getSingleQueryPodOrderApi(
code: string,
factoryNo: number,
warehouseId: number | string,
) {
return axios.get<never, BaseRespData<OrderData>>(
'factory/podOrder/getDetailsBySkuOrNo',
{
params: {
podOrderNo: code,
factoryNo,
warehouseId,
},
},
)
}
// 单件打单完成
export function submitPodPrintOrderCompleteApi(
data: { orderParamList: { id: number }[] },
warehouseId: number | string,
boxIndex: number | null = 0,
) {
const params = new URLSearchParams()
params.set('warehouseId', String(warehouseId))
if (boxIndex !== undefined && boxIndex !== null) {
params.set('box', String(boxIndex))
}
return axios.post<never, BaseRespData<never>>(
`factory/podOrderPacking/podPrintOrderComplete?${params.toString()}`,
data,
)
}
export function statusPushApi(ids: (string | number)[]) {
return axios.post<never, BaseRespData<never>>(
'factory/podOrder/statusPush',
ids,
)
}
export function batchCheckPrintPodOrder(ids: string) {
return axios.get<never, BaseRespData<PrintData[]>>(
`/factory/podOrder/batchCheckPrintPodOrder?operationIds=${ids}`,
)
}
export function getCustomTagListPodOrderApi() {
return axios.get<never, BaseRespData<never>>(
`factory/podOrder/getCustomTagList`,
)
}
export function exportFactoryOrderInfo(data: ExportParams) {
return axios.post<never, BaseRespData<never>>(
'factory/podOrder/exportPodOrder',
data,
)
}
...@@ -242,10 +242,7 @@ export function productionQueryApi(id: number, podJomallOrderCnId: number) { ...@@ -242,10 +242,7 @@ export function productionQueryApi(id: number, podJomallOrderCnId: number) {
) )
} }
export function printProductionOrderApi(url: string, orderIds: number[]) { export function printProductionOrderApi(url: string, orderIds: number[]) {
return axios.post<never, BaseRespData<string>>( return axios.post<never, BaseRespData<string>>(url, orderIds)
url,
orderIds,
)
} }
export function printPrintOrderApi(orderIds: number[]) { export function printPrintOrderApi(orderIds: number[]) {
return axios.post<never, BaseRespData<string>>( return axios.post<never, BaseRespData<string>>(
...@@ -630,11 +627,15 @@ export function updateToWaitShipmentApi(params: { ...@@ -630,11 +627,15 @@ export function updateToWaitShipmentApi(params: {
} }
//创建物流订单 //创建物流订单
export function createLogisticsOrdersApi(orderIdList: (string | number)[], logisticsWayId: number | null) { export function createLogisticsOrdersApi(
return axios.post<never, BaseRespData<never>>( url: string,
`factory/podJomallOrderCn/createLogisticsOrders`, orderIdList: (string | number)[],
{ orderIdList, logisticsWayId }, logisticsWayId: number | null,
) ) {
return axios.post<never, BaseRespData<never>>(url, {
orderIdList,
logisticsWayId,
})
} }
// 获取跟踪号 // 获取跟踪号
...@@ -747,13 +748,16 @@ export function allErpCodeListApi() { ...@@ -747,13 +748,16 @@ export function allErpCodeListApi() {
'/logisticsCompany/allErpCodeList', '/logisticsCompany/allErpCodeList',
) )
} }
export function updateCustomDeclarationInfoApi({ export function updateCustomDeclarationInfoApi(
url: string,
{
params, params,
ids, ids,
}: { }: {
params: CustomDeclarationInfoForm params: CustomDeclarationInfoForm
ids: string ids: string
}) { },
) {
const formData = new FormData() const formData = new FormData()
formData.append('ids', ids) formData.append('ids', ids)
Object.keys(params).forEach((key) => { Object.keys(params).forEach((key) => {
...@@ -763,13 +767,9 @@ export function updateCustomDeclarationInfoApi({ ...@@ -763,13 +767,9 @@ export function updateCustomDeclarationInfoApi({
} }
}) })
return axios.post<never, BaseRespData<never>>( return axios.post<never, BaseRespData<never>>(url, formData, {
'factory/podJomallOrderCn/batchUpdateCustomsClearanceInfo',
formData,
{
headers: { 'content-type': 'multipart/form-data' }, headers: { 'content-type': 'multipart/form-data' },
}, })
)
} }
// 标记缺货/移除缺货共用 // 标记缺货/移除缺货共用
export function updateProductOutOfStockApi(params: { export function updateProductOutOfStockApi(params: {
......
...@@ -179,10 +179,7 @@ export function productionQueryApi(id: number, podJomallOrderUsId: number) { ...@@ -179,10 +179,7 @@ export function productionQueryApi(id: number, podJomallOrderUsId: number) {
) )
} }
export function printProductionOrderApi(url: string, orderIds: number[]) { export function printProductionOrderApi(url: string, orderIds: number[]) {
return axios.post<never, BaseRespData<string>>( return axios.post<never, BaseRespData<string>>(url, orderIds)
url,
orderIds,
)
} }
export function printNormalProductionOrderApi(orderIds: number[]) { export function printNormalProductionOrderApi(orderIds: number[]) {
return axios.post<never, BaseRespData<string>>( return axios.post<never, BaseRespData<string>>(
...@@ -232,66 +229,59 @@ export function getOrderDetailById(id: number) { ...@@ -232,66 +229,59 @@ export function getOrderDetailById(id: number) {
) )
} }
export function getPackingDataApi( export function getPackingDataApi(
code: string, url: string,
factoryNo: number,
box: number | null,
warehouseId: number | string,
) {
return axios.get<never, BaseRespData<PodMakeOrderData>>(
'/factory/podJomallOrderUs/local/putPackingSafe',
{
params: { params: {
podJomallUsNo: code, podJomallUsNo?: string
box, box?: number | null
factoryNo, factoryNo?: number
warehouseId, warehouseId?: number | string
},
}, },
) ) {
return axios.get<never, BaseRespData<PodMakeOrderData>>(url, {
params,
})
} }
export function getPodBoxListApi( export function getPodBoxListApi(
url: string,
factoryNo: number | string | undefined, factoryNo: number | string | undefined,
warehouseId: number | string, warehouseId: number | string,
) { ) {
return axios.get<never, BaseRespData<PodMakeOrderData[]>>( return axios.get<never, BaseRespData<PodMakeOrderData[]>>(url, {
'factory/podJomallOrderUs/local/getPodBoxOrderDetails',
{
params: { factoryNo, warehouseId }, params: { factoryNo, warehouseId },
}, })
)
} }
export function submitInspectionApi( export function submitInspectionApi(
orderId: number | undefined, url: string,
params: {
orderId?: number | undefined
},
boxIndex: number | null, boxIndex: number | null,
warehouseId: number | string, warehouseId: number | string,
) { ) {
return axios.post<never, BaseRespData<never>>( return axios.post<never, BaseRespData<never>>(
`factory/podJomallOrderUs/podPrintOrderComplete?box=${boxIndex}&warehouseId=${warehouseId}`, `${url}?box=${boxIndex}&warehouseId=${warehouseId}`,
{ params,
orderId,
},
) )
} }
export function clearBoxApi( export function clearBoxApi(
url: string,
factoryNo: number, factoryNo: number,
box: number | null, box: number | null,
warehouseId: number | string, warehouseId: number | string,
) { ) {
return axios.get<never, BaseRespData<never>>( return axios.get<never, BaseRespData<never>>(url, {
'factory/podJomallOrderUs/local/delPodBoxOrderDetailsByBox',
{
params: { factoryNo, box, warehouseId }, params: { factoryNo, box, warehouseId },
}, })
)
} }
export function clearAllBoxApi(warehouseId: string | number, factoryNo: string | number | undefined) { export function clearAllBoxApi(
return axios.get<never, BaseRespData<never>>( url: string,
'factory/podJomallOrderUs/local/delPodBoxOrderDetails', warehouseId: string | number,
{ factoryNo: string | number | undefined,
) {
return axios.get<never, BaseRespData<never>>(url, {
params: { warehouseId, factoryNo }, params: { warehouseId, factoryNo },
}, })
)
} }
export function updateRemarkApi(id: number, content: string) { export function updateRemarkApi(id: number, content: string) {
return axios.post<never, BaseRespData<never>>( return axios.post<never, BaseRespData<never>>(
......
<template> <template>
<div class="commodity-card" :class="{ active: active }"> <div class="commodity-card" :class="{ active: active }">
<div class="commodity-card-image"> <div class="commodity-card-image">
<div class="before"></div> <div class="before" :style="{ paddingTop: paddingTop }"></div>
<div class="image"> <div class="image">
<img :src="mainImageSrc" /> <img :src="mainImageSrc" />
</div> </div>
...@@ -110,6 +110,7 @@ interface Props { ...@@ -110,6 +110,7 @@ interface Props {
imageField?: string imageField?: string
imageListField?: string imageListField?: string
imagePathField?: string imagePathField?: string
paddingTop?: string
} }
// 定义默认值 // 定义默认值
...@@ -122,6 +123,7 @@ const props = withDefaults(defineProps<Props>(), { ...@@ -122,6 +123,7 @@ const props = withDefaults(defineProps<Props>(), {
imageField: 'variantImage', imageField: 'variantImage',
imageListField: 'imageList', imageListField: 'imageList',
imagePathField: 'imagePath', imagePathField: 'imagePath',
paddingTop: '120%',
}) })
// 获取主图片源 // 获取主图片源
...@@ -217,7 +219,6 @@ const copy = (text: string) => { ...@@ -217,7 +219,6 @@ const copy = (text: string) => {
.before { .before {
height: 0; height: 0;
padding-top: 120%;
} }
.image { .image {
......
<template> <template>
<i <i
v-if="unicodeIcon" v-if="unicodeIcon"
class="erpIconfont erp unicode-icon" class="iconfont factory unicode-icon"
v-html="unicodeIcon" v-html="unicodeIcon"
></i> ></i>
<svg v-else class="svg-icon erp" aria-hidden="true"> <svg v-else class="svg-icon factory" aria-hidden="true">
<slot name="title"></slot> <slot name="title"></slot>
<use :xlink:href="svgIcon"></use> <use :xlink:href="svgIcon"></use>
</svg> </svg>
...@@ -19,7 +19,7 @@ const props = defineProps({ ...@@ -19,7 +19,7 @@ const props = defineProps({
} }
}) })
const unicodeIcon = computed(() => { const unicodeIcon = computed(() => {
if (props.name.match(/^x[a-f0-9]{4}$/)) { if (props.name.match(/^x[a-f0-9]{4,}$/)) {
return `&#${props.name};` return `&#${props.name};`
} }
return undefined return undefined
......
...@@ -32,9 +32,12 @@ defineProps({ ...@@ -32,9 +32,12 @@ defineProps({
.log-list { .log-list {
height: 500px; height: 500px;
overflow: auto; overflow: auto;
font-size: 14px;
} }
.log-item { .log-item {
line-height: 26px; line-height: 26px;
white-space: pre-wrap;
word-break: break-all;
} }
.log-item div:not(:last-child) { .log-item div:not(:last-child) {
margin-right: 6px; margin-right: 6px;
......
...@@ -32,7 +32,12 @@ ...@@ -32,7 +32,12 @@
</template> </template>
</ElTableColumn> </ElTableColumn>
</template> </template>
<ElTableColumn v-else header-align="center" v-bind="col"> <template v-else>
<ElTableColumn
v-if="col.showColumn !== false"
header-align="center"
v-bind="col"
>
<template #header="{ column, $index }"> <template #header="{ column, $index }">
<component <component
:is="() => col.headerRender(column, $index)" :is="() => col.headerRender(column, $index)"
...@@ -52,10 +57,7 @@ ...@@ -52,10 +57,7 @@
</template> </template>
<!-- 如果传递按钮数组,就展示按钮组 END--> <!-- 如果传递按钮数组,就展示按钮组 END-->
<!-- render函数 (START) 使用内置的component组件可以支持h函数渲染和txs语法--> <!-- render函数 (START) 使用内置的component组件可以支持h函数渲染和txs语法-->
<component <component :is="() => col.render(row, $index)" v-if="col.render" />
:is="() => col.render(row, $index)"
v-if="col.render"
/>
<slot <slot
v-else-if="col.slot" v-else-if="col.slot"
:name="col.slot" :name="col.slot"
...@@ -65,6 +67,7 @@ ...@@ -65,6 +67,7 @@
<span v-else>{{ row[col.prop!] }}</span> <span v-else>{{ row[col.prop!] }}</span>
</template> </template>
</ElTableColumn> </ElTableColumn>
</template>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { Column } from 'element-plus' import type { Column } from 'element-plus'
......
import { ElMessage } from 'element-plus' import useLodop from '@/utils/hooks/useLodop.ts'
import useLodop, { LODOPObject } from '@/utils/hooks/useLodop.ts' import { printWithLodop } from '@/utils/lodopPrinter'
const { getCLodop } = useLodop() const { getCLodop } = useLodop()
const lodopCall = async (fn: (lodop: LODOPObject) => string) => {
if (!window._lodop) {
window._lodop = getCLodop(null, null)
if (window._lodop) {
window._lodop.On_Return_Remain = true
window._lodop.SET_PRINT_MODE('CATCH_PRINT_STATUS', true)
window._lodopCallback = {}
window._lodop.On_Return = function (id, value) {
const cb = window._lodopCallback[id]
if (!cb) return
delete window._lodopCallback[id]
cb(value)
}
}
}
return new Promise((resolve) => {
let id
if (window._lodop) {
id = fn(window._lodop)
}
window._lodopCallback[id || 0] = resolve
})
}
function downloadPDF(url: string) {
if (!/^https?:/i.test(url)) return url
let xhr,
arrybuffer = false,
dataArray = null
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest()
} else {
xhr = new window.ActiveXObject('MSXML2.XMLHTTP')
}
xhr.open('GET', url, false) //同步方式
if (xhr.overrideMimeType)
try {
xhr.responseType = 'arraybuffer'
arrybuffer = true
} catch (err) {
xhr.overrideMimeType('text/plain; charset=x-user-defined')
}
xhr.send(null)
const data = xhr.response || xhr.responseBody
if (typeof Uint8Array !== 'undefined') {
if (arrybuffer) {
dataArray = new Uint8Array(data)
} else {
dataArray = new Uint8Array(data.length)
for (let i = 0; i < dataArray.length; i++) {
dataArray[i] = data.charCodeAt(i)
}
}
} else {
dataArray = window.VBS_BinaryToArray(data).toArray() //兼容IE低版本
}
const digits =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
let strData = ''
for (let i = 0, ii = dataArray.length; i < ii; i += 3) {
if (isNaN(dataArray[i])) break
const b1 = dataArray[i] & 0xff,
b2 = dataArray[i + 1] & 0xff,
b3 = dataArray[i + 2] & 0xff
const d1 = b1 >> 2,
d2 = ((b1 & 3) << 4) | (b2 >> 4)
const d3 = i + 1 < ii ? ((b2 & 0xf) << 2) | (b3 >> 6) : 64
const d4 = i + 2 < ii ? b3 & 0x3f : 64
strData +=
digits.substring(d1, d1 + 1) +
digits.substring(d2, d2 + 1) +
digits.substring(d3, d3 + 1) +
digits.substring(d4, d4 + 1)
}
return strData
}
export const print = async (url: string,printDevice:string) => { export const print = async (url: string,printDevice:string) => {
const lodop = getCLodop(null, null) //调用getLodop获取LODOP对象 await printWithLodop({
if (!lodop) return getCLodop: () => getCLodop(null, null),
lodop.PRINT_INIT('打印内容') printer: printDevice,
// SET_PRINTER_INDEX 指定打印设备 data: { filePath: url },
const setPrintRes = lodop.SET_PRINTER_INDEX(printDevice) callback: (_status: boolean) => {},
if (!setPrintRes) { baseUrl: '',
ElMessage.warning('设置面单打印机出错')
return
}
lodop.ADD_PRINT_PDF(0, 0, '100%', '100%', downloadPDF(url))
console.log(lodop)
if (lodop.CVERSION) {
const startTime = Date.now()
const jobCode = await lodopCall((lodop: LODOPObject) => {
lodop.SET_PRINT_MODE('CATCH_PRINT_STATUS', true)
return lodop.PRINT()
}) })
console.log('[LODOP] job ' + jobCode)
// eslint-disable-next-line no-constant-condition
while (true) {
const ok = await lodopCall((lodop: LODOPObject) =>
lodop.GET_VALUE('PRINT_STATUS_OK', jobCode),
)
if (ok == 1) {
// 打印状态:成功
return
}
// 如果打印状态表示未成功或者返回了空,继续获取任务是否存在
const exist = await lodopCall((lodop: LODOPObject) =>
lodop.GET_VALUE('PRINT_STATUS_EXIST', jobCode),
)
console.log(
'[LODOP] PRINT_STATUS OK,EXIST',
jobCode,
ok,
exist,
`(${Date.now() - startTime}ms)`,
)
if (exist == 0) {
// 任务不存在了
return
}
await new Promise((r) => setTimeout(r, 500))
if (Date.now() - startTime >= 30000) {
ElMessage.error('打印超时(30秒)')
return
}
}
} else {
lodop.PRINT()
}
} }
...@@ -78,12 +78,18 @@ const playAudio = (key: AudioKey, message?: string) => { ...@@ -78,12 +78,18 @@ const playAudio = (key: AudioKey, message?: string) => {
console.error(`No audio found for key: ${key}`) console.error(`No audio found for key: ${key}`)
} }
} }
const getOrderFrom = (factorySubOrderNumber: string): string => {
if (/^CN/i.test(factorySubOrderNumber)) return 'CN'
if (/^US/i.test(factorySubOrderNumber)) return 'US'
if (/^OP/i.test(factorySubOrderNumber)) return 'NEWP'
return ''
}
const getPrintData = async () => { const getPrintData = async () => {
if(!searchData.value) return if(!searchData.value) return
const user = JSON.parse(localStorage.getItem('user') || '{}') const orderFrom = getOrderFrom(searchData.value.factorySubOrderNumber || '')
const data = await printProductionQrCode( const data = await printProductionQrCode(
searchData.value.id, searchData.value.id,
user.factory?.countryCode.toLowerCase(), orderFrom,
) )
// const path = 'http://10.168.1.209/local_files/factory' + data.message // const path = 'http://10.168.1.209/local_files/factory' + data.message
const path = filePath + data.message const path = filePath + data.message
......
...@@ -99,6 +99,14 @@ const router = createRouter({ ...@@ -99,6 +99,14 @@ const router = createRouter({
import('@/views/order/podUsSchedulingRules/index.vue'), import('@/views/order/podUsSchedulingRules/index.vue'),
}, },
{ {
path: '/order/factory-order-new',
meta: {
title: '工厂订单(NEW)',
},
component: () =>
import('@/views/order/factoryOrderNew/index.vue'),
},
{
path: '/pod-cn-order/orderTracking', path: '/pod-cn-order/orderTracking',
meta: { meta: {
title: 'POD(CN)订单跟踪', title: 'POD(CN)订单跟踪',
......
...@@ -146,6 +146,11 @@ const menu: MenuItem[] = [ ...@@ -146,6 +146,11 @@ const menu: MenuItem[] = [
id: 11, id: 11,
label: 'POD(US)排单规则', label: 'POD(US)排单规则',
}, },
{
index: '/order/factory-order-new',
id: 12,
label: '工厂订单(NEW)',
},
], ],
}, },
{ {
......
...@@ -20,6 +20,7 @@ const useOrderStore = defineStore('order', { ...@@ -20,6 +20,7 @@ const useOrderStore = defineStore('order', {
}, },
actions: { actions: {
async setPodBoxList(content: { async setPodBoxList(content: {
url: string
boxList: PodMakeOrderData[] | OrderData | null boxList: PodMakeOrderData[] | OrderData | null
factoryNo: number | string | undefined factoryNo: number | string | undefined
fromUser: number fromUser: number
...@@ -27,10 +28,10 @@ const useOrderStore = defineStore('order', { ...@@ -27,10 +28,10 @@ const useOrderStore = defineStore('order', {
box?: number box?: number
data?: OrderData data?: OrderData
}) { }) {
console.log(content,'content') console.log(content, 'content')
const { factoryNo, warehouseId, boxList, box, data,fromUser } = content const { url, factoryNo, warehouseId, boxList, box, data, fromUser } = content
if (Array.isArray(boxList)) { if (Array.isArray(boxList)) {
boxList.forEach(item => { boxList.forEach((item) => {
item.fromUser = fromUser item.fromUser = fromUser
}) })
this.podBoxList = boxList this.podBoxList = boxList
...@@ -38,7 +39,7 @@ const useOrderStore = defineStore('order', { ...@@ -38,7 +39,7 @@ const useOrderStore = defineStore('order', {
const index = this.podBoxList?.findIndex((item) => item.box === box) const index = this.podBoxList?.findIndex((item) => item.box === box)
if (index === -1) { if (index === -1) {
try { try {
const res = await getPodBoxListApi(factoryNo, warehouseId) const res = await getPodBoxListApi(url, factoryNo, warehouseId)
const boxList = res.data.map((item) => { const boxList = res.data.map((item) => {
item.fromUser = fromUser item.fromUser = fromUser
if (res.data) { if (res.data) {
......
export interface StatusTreeNode {
status: string
statusName: string
quantity: number
children: StatusTreeNode[] | null
}
export interface SearchForm {
platform?: string
craftCode?: string | string[]
thirdSkuCode?: string
supplierProductNo?: string
batchArrangeNumber?: string
orderNumber?: string
customerOrderNumber?: string
shopOrderNumber?: string
productMark?: string | number | (string | number)[]
multi?: boolean | null
timeType?: number | null
startTime?: string | null
endTime?: string | null
logisticsWayCode?: string
receiverCountry?: string | string[]
factoryOrderNumber?: string
userMark?: string
sku?: string
trackingNumber?: string
replaceShipment?: number | string
shipmentType?: number | string
tagsIdArr?: string[]
/** list_page 查询:客户标签 id,逗号分隔(由 tagsIdArr 转换) */
tagsId?: string
source?: string
size?: string
logisticsCompanyCode?: string
interceptStatus?: boolean
standardDesignImage?: number
productMarkList?: string[]
podCustomizedQuantity?: string
cpCustomizedQuantity?: string
/** list_page 排序字段,需与 order 同时传递 */
prop?: string
/** list_page 排序方向:asc 正序,desc 倒序 */
order?: 'asc' | 'desc'
}
export interface FactoryOrderNewListData {
id: number
factoryOrderNumber?: string
factoryNo?: number
factoryCode?: string
factoryCountryCode?: string
warehouseId?: number
warehouseName?: string
namespace?: string
status?: string
platform?: string
shopNumber?: string
weight?: number
productAmount?: number
totalAmount?: number
productNum?: number
shipmentType?: number
expressSheet?: string
trackingNumber?: string
processNumber?: string
userMark?: string
receiverName?: string
receiverPhone?: string
receiverCountry?: string
receiverProvince?: string
receiverCity?: string
receiverDistrict?: string
receiverAddress1?: string
receiverAddress2?: string
receiverPostCode?: string
trackStatus?: number
prepaidAmount?: number
pause?: boolean
serviceAmount?: number
mixed?: boolean
tagsId?: string
source?: string
logisticsCompanyName?: string
logisticsCompanyCode?: string
selfOwned?: boolean
createTime?: string
updateTime?: string
version?: number
totalCustomsWeight?: number
totalCustomsValue?: number
statusName?: string
thirdOrderNumber?: string
}
export interface ProductListData {
id: number
podOrderId: number
factoryCode?: string
productName?: string
baseSku?: string
variantSku?: string
variantImage?: string
variant_image?: string
productMark?: string
diyId?: string
templateType?: number
customTemplateItemId?: number
endProductId?: string
craftSource?: number
craftCode?: string
craftName?: string
craftType?: string
customizedQuantity: number
categoryId?: number
categoryName?: string
productPrice?: number
templatePrice?: number
craftPrice?: number
imageAry?: string
designImages?: string
trimDesignImages?: string
sizeType?: number
num?: number
passNum?: number
notPassNum?: number
payAmount?: number
weight?: number
createTime?: string
updateTime?: string
version?: number
remark?: string
}
export interface operateOrderListData {
id: number
operationNo?: string
podOrderId?: number
podOrderNo?: string
podOrderProductId?: number
baseSku?: string
variantSku?: string
variantImage?: string
thirdSpuCode?: string
thirdSkuCode?: string
supplierProductNo?: string
productName?: string
productMark?: string
customizedQuantity?: number
quantity?: number
isReplenishment?: boolean
totalProductionNum?: number
status?: string
outOfStock?: boolean
arrange?: boolean
version?: number
createTime?: string
updateTime?: string
factoryNo?: number
factoryCode?: string
factoryCountryCode?: string
warehouseId?: number
namespace?: string
statusName?: string
[key: string]: unknown
}
export interface LogListData {
id: number
bizId?: number | string
employeeName?: string
description?: string
createTime?: string
}
export interface BatchManageData {
id: number
factoryId?: number
batchArrangeNum?: string
billType?: string
url?: string
tiffUrl?: string
syntheticStatus?: boolean
downloadStatus?: boolean
productNum?: number
materialNum?: number
craftType?: string
employeeAccount?: string
employeeId?: number
createTime?: string
failReason?: string
failTime?: string
automaticComposing?: boolean
composingParam?: string
printProductOrder?: boolean
printPickOrder?: boolean
prnUrl?: string
prnDownloadStatus?: string
enableArrange?: boolean
standardDesignImage?: number
type?: string
}
export interface RestockData {
id: number
factoryId: number
warehouseId: number
warehouseName?: string
locationId?: number
locationCode?: string
warehouseSku?: string
customSku?: string
skuName?: string
unit: string | null
occupyInventory?: number
freezeInventory?: number
inventory?: number
upperLimit?: number
floorLimit?: number
productNo?: string
customerName?: string
userMark?: string
createTime?: string
updateTime?: string
productItem?: string
usableInventory?: number
image?: string
price?: number
costPrice?: number | null
skuImage?: string | null
currencyCode?: string
currencyName?: string
sumOccupyInventory?: number
thirdSkuCode?: string
}
export interface PickCompleteData {
id: number
warehouseId: number
warehouseName?: string
skuImage?: string
productName?: string
supplierProductNo?: string
thirdSkuCode?: string
selectedQuantity?: number
availableInventory?: number
inventory?: number
producingQuantity?: number
occupyInventory?: number
pickingStatus?: string
availableOrderIds?: number[]
allOrderIds: number[]
freezeInventory?: number
}
export interface PickFailData {
id: number
warehouseName?: string
skuImage?: string
productName?: string
styleNo?: string
stockSku?: string
currentStock?: number
occupiedQuantity?: number
currentAvailableStock?: number
producingQuantity?: number
suggestOutQuantity?: number
afterOutStock?: number
afterOutOccupied?: number
afterOutAvailable?: number
}
export interface ResultInfoDataItem {
id: string | number
status?: boolean
factoryOrderNumber?: string
message?: string
}
export interface StatusTreeNode {
status: string
statusName: string
quantity: number
children: StatusTreeNode[] | null
}
export interface SearchForm {
platform?: string
craftCode?: string | string[]
thirdSkuCode?: string
supplierProductNo?: string
batchArrangeNumber?: string
orderNumber?: string
customerOrderNumber?: string
shopOrderNumber?: string
productMark?: string | number | (string | number)[]
multi?: boolean | null
timeType?: number | null
startTime?: string | null
endTime?: string | null
logisticsWayCode?: string
receiverCountry?: string | string[]
factoryOrderNumber?: string
userMark?: string
sku?: string
trackingNumber?: string
replaceShipment?: number | string
shipmentType?: number | string
tagsIdArr?: string[]
/** list_page 查询:客户标签 id,逗号分隔(由 tagsIdArr 转换) */
tagsId?: string
source?: string
size?: string
logisticsCompanyCode?: string
interceptStatus?: boolean
shopNumber?: string
newStandard?: number
productMarkList?: string[]
/** list_page:POD 商品单面/多面,与 productMarkList 中 pod 联动 */
podCustomizedQuantity?: string
/** list_page:一件定制局部印单面/多面,与 productMarkList 中 custom_part 联动 */
cpCustomizedQuantity?: string
operationNo?: string
/** list_page 排序字段,需与 order 同时传递 */
prop?: string
/** list_page 排序方向:asc 正序,desc 倒序 */
order?: 'asc' | 'desc'
}
export interface FactoryOrderNewListData {
id: number
factoryOrderNumber?: string
factoryNo?: number
factoryCode?: string
factoryCountryCode?: string
warehouseId?: number
warehouseName?: string
namespace?: string
status?: string
platform?: string
shopNumber?: string
weight?: number
productAmount?: number
totalAmount?: number
productNum?: number
shipmentType?: number
replaceShipment?: number
expressSheet?: string
trackingNumber?: string
processNumber?: string
userMark?: string
receiverName?: string
receiverPhone?: string
receiverCountry?: string
receiverProvince?: string
receiverCity?: string
receiverDistrict?: string
receiverAddress1?: string
receiverAddress2?: string
receiverPostCode?: string
rfcNumber?: string
trackStatus?: number
prepaidAmount?: number
pause?: boolean
serviceAmount?: number
mixed?: boolean
tagsId?: string
source?: string
logisticsCompanyName?: string
logisticsCompanyCode?: string
selfOwned?: boolean
createTime?: string
updateTime?: string
version?: number
totalCustomsWeight?: number
totalCustomsValue?: number
statusName?: string
thirdOrderNumber?: string
customTagList?: { id: string; name: string }[]
}
export interface ProductListData {
id: number
podOrderId: number
factoryCode?: string
productName?: string
baseSku?: string
variantSku?: string
variantImage?: string
variant_image?: string
productMark?: string
diyId?: string
templateType?: number
customTemplateItemId?: number
endProductId?: string
craftSource?: number
craftCode?: string
craftName?: string
craftType?: string
customizedQuantity: number
categoryId?: number
categoryName?: string
productPrice?: number
templatePrice?: number
craftPrice?: number
imageAry?: string
designImages?: string
trimDesignImages?: string
sizeType?: number
num?: number
passNum?: number
notPassNum?: number
payAmount?: number
weight?: number
inventory?: number
occupyInventory?: number
freezeInventory?: number
createTime?: string
updateTime?: string
version?: number
remark?: string
}
export interface operateOrderListData {
id: number
operationNo?: string
podOrderId?: number
podOrderNo?: string
podOrderProductId?: number
baseSku?: string
variantSku?: string
variantImage?: string
thirdSpuCode?: string
thirdSkuCode?: string
supplierProductNo?: string
productName?: string
productMark?: string
customizedQuantity?: number
quantity?: number
isReplenishment?: boolean
totalProductionNum?: number
status?: string
outOfStock?: boolean
arrange?: boolean
version?: number
createTime?: string
updateTime?: string
factoryNo?: number
factoryCode?: string
factoryCountryCode?: string
warehouseId?: number
namespace?: string
statusName?: string
[key: string]: unknown
}
export interface ExportParams extends SearchForm {
idList?: number[]
exportAll: boolean
orderTracking?: boolean
}
export interface LogListData {
id: number
bizId?: number | string
employeeName?: string
description?: string
createTime?: string
}
export interface BatchManageData {
id: number
factoryId?: number
batchArrangeNum?: string
billType?: string
url?: string
tiffUrl?: string
syntheticStatus?: boolean
downloadStatus?: boolean
productNum?: number
materialNum?: number
craftType?: string
employeeAccount?: string
employeeId?: number
createTime?: string
failReason?: string
failTime?: string
automaticComposing?: boolean
composingParam?: string
printProductOrder?: boolean
printPickOrder?: boolean
prnUrl?: string
prnDownloadStatus?: string
enableArrange?: boolean
standardDesignImage?: number
}
export interface RestockData {
id: number
warehouseName?: string
skuImage?: string
productName?: string
styleNo?: string
stockSku?: string
shortageQuantity?: number
availableQuantity?: number
stockQuantity?: number
occupiedQuantity?: number
}
export interface PickCompleteData {
id: number
warehouseId?: number
warehouseName?: string
skuImage?: string
productName?: string
supplierProductNo?: string
thirdSkuCode?: string
selectedQuantity?: number
availableInventory?: number
inventory?: number
producingQuantity?: number
occupyInventory?: number
pickingStatus?: string
availableOrderIds?: number[]
allOrderIds: number[]
}
export interface PickFailData {
id: number
warehouseName?: string
skuImage?: string
productName?: string
styleNo?: string
stockSku?: string
currentStock?: number
occupiedQuantity?: number
currentAvailableStock?: number
producingQuantity?: number
suggestOutQuantity?: number
afterOutStock?: number
afterOutOccupied?: number
afterOutAvailable?: number
}
...@@ -56,6 +56,9 @@ export interface ProductList { ...@@ -56,6 +56,9 @@ export interface ProductList {
podJomallCnNo?: string podJomallCnNo?: string
thirdSkuCode?: string thirdSkuCode?: string
previewImgs?: { sort?: string | number; title?: string; url: string }[] previewImgs?: { sort?: string | number; title?: string; url: string }[]
operationNos?: string
variantSku?: string
productName?: string
} }
export interface LogisticBill { export interface LogisticBill {
......
import type { Column as ElColumn } from 'element-plus/lib/components/index.js' import type { Column as ElColumn } from 'element-plus/lib/components/index.js'
interface Column<D> extends Omit<ElColumn, 'width'> { interface Column<D> extends Omit<ElColumn, 'width'> {
/** 为 false 时隐藏该列;未设置时默认显示 */
showColumn?: boolean
width?: number | string width?: number | string
minWidth?: number | string minWidth?: number | string
key?: string key?: string
......
...@@ -47,7 +47,7 @@ declare global { ...@@ -47,7 +47,7 @@ declare global {
interface Window { interface Window {
LODOP: new () => LODOPObject LODOP: new () => LODOPObject
_lodop: LODOPObject | null _lodop: LODOPObject | null
_lodopCallback: { [key: string]: Function } _lodopCallback: { [key: string]: (value: string) => void }
CLODOP: CLodopObject CLODOP: CLodopObject
getCLodop: () => LODOPObject getCLodop: () => LODOPObject
} }
......
...@@ -26,12 +26,15 @@ export default function usePageList<T>(options: UsePageListOptions<T>) { ...@@ -26,12 +26,15 @@ export default function usePageList<T>(options: UsePageListOptions<T>) {
sumTotalPrice: 0, sumTotalPrice: 0,
sumNotPassNum: 0, sumNotPassNum: 0,
}) })
/** 防止快速切换查询条件时,旧请求晚返回覆盖新数据 */
const loadRequestId = ref(0)
const loadData = async () => { const loadData = async () => {
const { query } = options const { query } = options
const myId = ++loadRequestId.value
try { try {
loading.value = true loading.value = true
// Promise 链式调用
const res = await query(currentPage.value, pageSize.value) const res = await query(currentPage.value, pageSize.value)
if (myId !== loadRequestId.value) return
if (statistics.value) { if (statistics.value) {
totalData.value = res as never totalData.value = res as never
total.value = res.page!.total total.value = res.page!.total
...@@ -45,12 +48,15 @@ export default function usePageList<T>(options: UsePageListOptions<T>) { ...@@ -45,12 +48,15 @@ export default function usePageList<T>(options: UsePageListOptions<T>) {
data.value = res.records data.value = res.records
} }
} catch (error) { } catch (error) {
if (myId !== loadRequestId.value) return
console.error(error) console.error(error)
// showError(error) // showError(error)
} finally { } finally {
if (myId === loadRequestId.value) {
loading.value = false loading.value = false
} }
} }
}
const onCurrentPageChange = (page: number) => { const onCurrentPageChange = (page: number) => {
currentPage.value = page currentPage.value = page
......
import { ElMessage } from 'element-plus'
import type { LODOPObject } from '@/utils/hooks/useLodop'
type LodopGetter = () => LODOPObject | null
export function downloadPDF(url: string): string {
if (!/^https?:/i.test(url)) return url
const xhr = new XMLHttpRequest()
let arrayBuffer = false
xhr.open('GET', url, false)
if (xhr.overrideMimeType) {
try {
xhr.responseType = 'arraybuffer'
arrayBuffer = true
} catch {
xhr.overrideMimeType('text/plain; charset=x-user-defined')
}
}
xhr.send(null)
const data = (xhr.response ||
(xhr as unknown as { responseBody?: unknown }).responseBody) as
| ArrayBuffer
| string
let dataArray: Uint8Array
if (arrayBuffer) {
dataArray = new Uint8Array(data as ArrayBuffer)
} else {
const text = data as string
dataArray = new Uint8Array(text.length)
for (let i = 0; i < dataArray.length; i++) dataArray[i] = text.charCodeAt(i)
}
const digits =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
let strData = ''
for (let i = 0, ii = dataArray.length; i < ii; i += 3) {
const b1 = dataArray[i] & 0xff
const b2 = dataArray[i + 1] & 0xff
const b3 = dataArray[i + 2] & 0xff
const d1 = b1 >> 2
const d2 = ((b1 & 3) << 4) | (b2 >> 4)
const d3 = i + 1 < ii ? ((b2 & 0xf) << 2) | (b3 >> 6) : 64
const d4 = i + 2 < ii ? b3 & 0x3f : 64
strData +=
digits.charAt(d1) +
digits.charAt(d2) +
digits.charAt(d3) +
digits.charAt(d4)
}
return 'data:application/pdf;base64,' + strData
}
export function createLodopCaller(getCLodop: LodopGetter) {
let lodop: LODOPObject | null = null
let lodopCallback: { [key: string]: (value: string) => void } = {}
return (fn: (lodop: LODOPObject) => string) => {
if (!lodop) {
lodop = getCLodop()
if (!lodop) return Promise.resolve('')
lodop.On_Return_Remain = true
lodop.SET_PRINT_MODE('CATCH_PRINT_STATUS', true)
lodopCallback = {}
lodop.On_Return = (id, value) => {
const cb = lodopCallback[id]
if (!cb) return
delete lodopCallback[id]
cb(value)
}
}
return new Promise<string>((resolve) => {
if (!lodop) {
resolve('')
return
}
const id = fn(lodop)
lodopCallback[id] = resolve
})
}
}
interface PrintData {
filePath?: string
fileData?: string
}
interface PrintWithLodopOptions {
getCLodop: LodopGetter
printer: string
data: PrintData
callback?: (success: boolean) => void
timeout?: number
baseUrl?: string
}
export async function printWithLodop(options: PrintWithLodopOptions) {
const {
getCLodop,
printer,
data,
callback,
timeout = 30000,
baseUrl = '',
} = options
const lodop = getCLodop()
if (!lodop) return
const lodopCall = createLodopCaller(getCLodop)
lodop.PRINT_INIT('打印内容')
const printerIndex = lodop.SET_PRINTER_INDEX(printer)
if (!printerIndex) {
ElMessage.error('打印机设置失败')
callback?.(false)
return
}
if (data.filePath) {
const strURL = /^https?:/i.test(data.filePath)
? data.filePath
: baseUrl + data.filePath
lodop.ADD_PRINT_PDF(
0,
0,
'100%',
'100%',
/^https?:/i.test(strURL) ? downloadPDF(strURL) : strURL,
)
} else {
lodop.SEND_PRINT_RAWDATA(data.fileData || '')
}
if (lodop.CVERSION) {
const startTime = Date.now()
const jobCode = await lodopCall((instance) => {
instance.SET_PRINT_MODE('CATCH_PRINT_STATUS', true)
return instance.PRINT()
})
let pending = true
while (pending) {
const ok = await lodopCall((instance) =>
instance.GET_VALUE('PRINT_STATUS_OK', jobCode),
)
if (ok == '1' || ok == '1.0') {
callback?.(true)
return
}
const exist = await lodopCall((instance) =>
instance.GET_VALUE('PRINT_STATUS_EXIST', jobCode),
)
if (exist == '0' || exist == '0.0') {
callback?.(true)
return
}
await new Promise((r) => setTimeout(r, 500))
if (Date.now() - startTime >= timeout) {
ElMessage.error(`打印超时(${Math.floor(timeout / 1000)}秒)`)
callback?.(false)
pending = false
}
}
return
}
lodop.PRINT()
callback?.(false)
}
...@@ -85,9 +85,12 @@ import { ...@@ -85,9 +85,12 @@ import {
} from '@/api/logistics' } from '@/api/logistics'
import { createLogisticsOrdersApi } from '@/api/podCnOrder' import { createLogisticsOrdersApi } from '@/api/podCnOrder'
import { FactoryOrderNewListData } from '@/types/api/factoryOrderNew'
const createLogisticDialogVisible = ref(false) const createLogisticDialogVisible = ref(false)
const props = defineProps<{
newOrderSelection?: FactoryOrderNewListData[],
}>()
const isAutoMatch = ref(false) const isAutoMatch = ref(false)
const logisticCompanyList = ref<ILogisticsCompany[]>([]) const logisticCompanyList = ref<ILogisticsCompany[]>([])
...@@ -129,7 +132,11 @@ const selectLogisticCompany = async (item: ILogisticsCompany) => { ...@@ -129,7 +132,11 @@ const selectLogisticCompany = async (item: ILogisticsCompany) => {
} }
const confirmDialog = async () => { const confirmDialog = async () => {
await createLogisticsOrdersApi(orderIdList.value, logisticsWayId.value) const url =
props.newOrderSelection && props.newOrderSelection.length > 0
? 'factory/podOrder/createLogisticsOrders'
: 'factory/podJomallOrderCn/createLogisticsOrders'
await createLogisticsOrdersApi(url, orderIdList.value, logisticsWayId.value)
.then((res) => { .then((res) => {
emits('show-result', res.data) emits('show-result', res.data)
}) })
......
...@@ -78,15 +78,32 @@ ...@@ -78,15 +78,32 @@
</div> </div>
</div> </div>
<div class="div-text"> <div class="div-text">
<b>生产单信息</b> <b v-if="!showOperationNoRow">生产单信息</b>
<b v-else>操作单信息</b>
<div class="div-content"> <div class="div-content">
<div :title="detail?.factorySubOrderNumber" class="div-item"> <div
v-if="showOperationNoRow"
:title="String(operationNoDisplay)"
class="div-item"
>
<span>操作单号</span>
<p>{{ operationNoDisplay }}</p>
</div>
<div
v-else
:title="detail?.factorySubOrderNumber"
class="div-item"
>
<span>生产单号</span> <span>生产单号</span>
<p> <p>
{{ detail?.factorySubOrderNumber }} {{ detail?.factorySubOrderNumber }}
</p> </p>
</div> </div>
<div :title="detail?.thirdSubOrderNumber || ''" class="div-item"> <div
v-if="!showOperationNoRow"
:title="detail?.thirdSubOrderNumber || ''"
class="div-item"
>
<span>第三方生产单号</span> <span>第三方生产单号</span>
<p> <p>
{{ detail?.thirdSubOrderNumber }} {{ detail?.thirdSubOrderNumber }}
...@@ -107,9 +124,14 @@ ...@@ -107,9 +124,14 @@
<span>变体SKU</span> <span>变体SKU</span>
<p>{{ detail?.variantSku }}</p> <p>{{ detail?.variantSku }}</p>
</div> </div>
<div :title="String(detail?.num)" class="div-item"> <div
:title="
String(showOperationNoRow ? detail?.quantity : detail?.num)
"
class="div-item"
>
<span>数量</span> <span>数量</span>
<p>{{ detail?.num }}</p> <p>{{ showOperationNoRow ? detail?.quantity : detail?.num }}</p>
</div> </div>
<div :title="String(detail?.size)" class="div-item"> <div :title="String(detail?.size)" class="div-item">
...@@ -183,7 +205,11 @@ ...@@ -183,7 +205,11 @@
flex-direction: column; flex-direction: column;
" "
> >
<div v-if="detail?.note" style="height: 100%" class="div-content"> <div
v-if="Array.isArray(detail?.note) && detail.note.length"
style="height: 100%"
class="div-content"
>
<b style="position: absolute; top: -12px">客户留言信息</b> <b style="position: absolute; top: -12px">客户留言信息</b>
<div <div
v-for="(item, index) in detail?.note" v-for="(item, index) in detail?.note"
...@@ -211,22 +237,31 @@ import { ...@@ -211,22 +237,31 @@ import {
import { cardImages, PodOrderRes } from '@/types/api/podCnOrder' import { cardImages, PodOrderRes } from '@/types/api/podCnOrder'
import { showConfirm } from '@/utils/ui' import { showConfirm } from '@/utils/ui'
import { filePath } from '@/api/axios' import { filePath } from '@/api/axios'
import { ref, watch, defineProps, defineEmits } from 'vue' import { computed, ref, watch } from 'vue'
import { BaseRespData } from '@/types/api'
interface HistoryDataItem { interface HistoryDataItem {
orderNumber: string orderNumber: string
finished: boolean finished: boolean
} }
export type FastProductionDetail = PodOrderRes & {
operationNo?: string | null
quantity?: number | null
podOrderNo?: string | null
podOrderProductId?: number | null
}
const trackingNumberRef = ref() const trackingNumberRef = ref()
const historyData = ref<HistoryDataItem[]>([]) const historyData = ref<HistoryDataItem[]>([])
const placeholderText = ref('') const placeholderText = ref('')
const sendNum = ref(0) const sendNum = ref(0)
const isDownloadImage = ref(false) const isDownloadImage = ref(false)
const isAutoSure = ref(true) const isAutoSure = ref(true)
const detail = ref<PodOrderRes>({ const emptyDetail = (): FastProductionDetail => ({
id: -1, id: -1,
podJomallOrderCnId: -1, podJomallOrderCnId: -1,
imgList: [] as cardImages[], imgList: [] as cardImages[],
}) })
const detail = ref<FastProductionDetail>(emptyDetail())
const dialogVisible = ref(false) const dialogVisible = ref(false)
// 尺码类型表 // 尺码类型表
const sizeList = ref<{ name: string; value: number }[]>([ const sizeList = ref<{ name: string; value: number }[]>([
...@@ -251,46 +286,151 @@ const audios = { ...@@ -251,46 +286,151 @@ const audios = {
).href, ).href,
} }
const TrackingNumber = ref('') const TrackingNumber = ref('')
const props = defineProps({ const props = withDefaults(
type: { defineProps<{
default: 0, type?: number
type: Number, detailVisible?: boolean
}, detailData?: Record<string, unknown> | null
detailVisible: { fastKey?: string
default: false, historyStorageKey?: string
type: Boolean, trackingPlaceholder?: string
isNewOrder?: boolean
queryApi?: (orderNumber: string) => Promise<{ data?: unknown }>
completeApi?: (
ids: number[],
detail: Record<string, unknown>,
) => Promise<unknown>
downloadApi?: (
ids: number[],
) => Promise<{ code?: number; message?: string }>
/** 打开弹框时「自动完成上一单」的初始勾选;默认 false,与原有 podCN 快捷生产一致 */
defaultAutoSure?: boolean
/** 为 true 时展示「操作单号」一行,数据字段为 `operationNo`(不影响 podCN 默认) */
showOperationNoRow?: boolean
/** 查询无数据时的提示 */
notFoundMessage?: string
/** 未生产完成提醒里对单号的称呼,如「生产单号」「操作单号」 */
pendingOrderLabel?: string
/** 未扫码时的提示 */
pleaseScanTip?: string
/** 扫码输入为空时音频/提示文案 */
searchInputAudioTip?: string
}>(),
{
type: 0,
detailVisible: false,
detailData: null,
fastKey: '',
isNewOrder: false,
historyStorageKey: 'historyCnData',
trackingPlaceholder:
'扫描枪输入生产单号,录入下一单本单自动生产完成,最后一单扫两次完成生产',
defaultAutoSure: true,
showOperationNoRow: false,
notFoundMessage: '生产单不存在',
pendingOrderLabel: '生产单号',
pleaseScanTip: '请扫码生产单号',
searchInputAudioTip: '请录入生产单号',
queryApi: (orderNumber: string) => getSubOrderBySubOrderNumber(orderNumber),
completeApi: (ids: number[], detailData: Record<string, unknown>) =>
productionQueryApi(ids[0], Number(detailData.podJomallOrderCnId || -1)),
downloadApi: (ids: number[]) => downloadMaterialApi(ids),
}, },
detailData: { )
default: null,
type: Object,
},
fastKey: {
default: '',
type: String,
},
})
const emit = defineEmits(['update:detailVisible', 'close', 'onSuccess']) const emit = defineEmits(['update:detailVisible', 'close', 'onSuccess'])
const historyKeyFromDetail = (d: FastProductionDetail): string => {
const raw = d as unknown as Record<string, unknown>
const op = raw.operationNo
if (op !== undefined && op !== null && String(op).trim() !== '') {
return String(op)
}
return String(d.factorySubOrderNumber ?? '')
}
const operationNoDisplay = computed(() => {
const raw = detail.value as unknown as Record<string, unknown>
const v = raw.operationNo
if (v === undefined || v === null) return ''
return String(v)
})
const parseImages = (value: unknown): cardImages[] => {
if (!value) return []
if (Array.isArray(value)) {
return value
.filter((item) => !!item?.url)
.map((item, index) => ({
title: String(item?.title || ''),
url: String(item?.url || ''),
sort: Number(item?.sort ?? index + 1),
id: item?.id,
}))
}
if (typeof value === 'string') {
const source = value.trim()
if (!source) return []
if (source.startsWith('http')) {
return source.split(',').map((url, index) => ({
title: '',
url: url.trim(),
sort: index + 1,
}))
}
try {
const parsed = JSON.parse(source)
if (Array.isArray(parsed)) {
return parsed
.filter((item) => !!item?.url)
.map((item, index) => ({
title: String(item?.title || ''),
url: String(item?.url || ''),
sort: Number(item?.sort ?? index + 1),
id: item?.id,
}))
}
} catch (_e) {
return []
}
}
return []
}
const normalizeDetail = (value: unknown): FastProductionDetail => {
const raw = (value || {}) as Record<string, unknown>
const d = JSON.parse(JSON.stringify(raw)) as Record<string, unknown>
if (typeof d.note === 'string') {
try {
d.note = JSON.parse(d.note)
} catch (_e) {
d.note = []
}
}
if (!Array.isArray(d.note)) d.note = []
const imageSource = d.imageAry || d.designImages || d.imgList
d.imgList = parseImages(imageSource)
return d as unknown as FastProductionDetail
}
watch( watch(
() => props.detailVisible, () => props.detailVisible,
(newVal: boolean) => { (newVal: boolean) => {
dialogVisible.value = newVal dialogVisible.value = newVal
detail.value = { id: -1, podJomallOrderCnId: -1, imgList: [] } detail.value = { id: -1, podJomallOrderCnId: -1, imgList: [] }
if (newVal) { if (newVal) {
const history = localStorage.getItem('historyCnData') const history = localStorage.getItem(props.historyStorageKey)
historyData.value = history ? JSON.parse(history) : [] historyData.value = history ? JSON.parse(history) : []
const len = historyData.value const len = historyData.value
if (len.length > 0 && props.fastKey === 'fastProduction') { if (len.length > 0 && props.fastKey === 'fastProduction') {
confirmQuery(len, 0) confirmQuery(len, 0)
} }
placeholderText.value = placeholderText.value = props.trackingPlaceholder
'扫描枪输入生产单号,录入下一单本单自动生产完成,最后一单扫两次完成生产'
trackingNumberRef.value && trackingNumberRef.value.focus() trackingNumberRef.value && trackingNumberRef.value.focus()
TrackingNumber.value = '' TrackingNumber.value = ''
isAutoSure.value = true isAutoSure.value = props.defaultAutoSure
sendNum.value = 0 sendNum.value = 0
} }
}, },
...@@ -304,29 +444,21 @@ watch( ...@@ -304,29 +444,21 @@ watch(
imgList: [], imgList: [],
} }
if (newVal && Object.keys(newVal).length > 0) { if (newVal && Object.keys(newVal).length > 0) {
const d = JSON.parse(JSON.stringify(newVal)) detail.value = normalizeDetail(newVal)
if (d.note) {
d.note = JSON.parse(d.note)
} else {
d.note = []
}
if (d.imageAry) {
d.imgList = d.imageAry
} else {
d.imgList = []
}
detail.value = d
} }
}, },
{ deep: true }, { deep: true },
) )
const confirmQuery = (len: HistoryDataItem[], i: number) => { const confirmQuery = (len: HistoryDataItem[], i: number) => {
const el = len[i] const el = len[i]
showConfirm(`生产单号 ${el.orderNumber} 未生产完成,取消则不提醒?`, { showConfirm(
`${props.pendingOrderLabel} ${el.orderNumber} 未生产完成,取消则不提醒?`,
{
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning', type: 'warning',
}) },
)
.then(async () => { .then(async () => {
TrackingNumber.value = el.orderNumber TrackingNumber.value = el.orderNumber
await trackCodeInput() await trackCodeInput()
...@@ -342,7 +474,10 @@ const confirmQuery = (len: HistoryDataItem[], i: number) => { ...@@ -342,7 +474,10 @@ const confirmQuery = (len: HistoryDataItem[], i: number) => {
) )
if (index >= 0) { if (index >= 0) {
historyData.value.splice(index, 1) historyData.value.splice(index, 1)
localStorage.setItem('historyCnData', JSON.stringify(historyData.value)) localStorage.setItem(
props.historyStorageKey,
JSON.stringify(historyData.value),
)
} }
if (len[i + 1]) { if (len[i + 1]) {
confirmQuery(len, i + 1) confirmQuery(len, i + 1)
...@@ -353,23 +488,31 @@ const confirmQuery = (len: HistoryDataItem[], i: number) => { ...@@ -353,23 +488,31 @@ const confirmQuery = (len: HistoryDataItem[], i: number) => {
const changeStatus = async () => { const changeStatus = async () => {
if (!detail.value || Object.keys(detail.value).length <= 1) { if (!detail.value || Object.keys(detail.value).length <= 1) {
return ElMessage.warning('请扫码生产单号') return ElMessage.warning(props.pleaseScanTip)
} }
showConfirm('确定生产完成?', { showConfirm('确定生产完成?', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning', type: 'warning',
}).then(() => { }).then(() => {
setData(detail.value.factorySubOrderNumber || '') setData(historyKeyFromDetail(detail.value))
}) })
} }
const setData = async (orderNumber: string) => { const setData = async (orderNumber: string) => {
if (!detail.value || detail.value?.id === -1) return if (!detail.value || detail.value?.id === -1) return
try { try {
const id = detail.value.id const id = detail.value.id
const podJomallOrderCnId = detail.value.podJomallOrderCnId const res = (await props.completeApi(
await productionQueryApi(id, podJomallOrderCnId) [id],
detail.value as unknown as Record<string, unknown>,
)) as unknown as BaseRespData<
{
factoryOrderNumber?: number
id: number
message?: string
status?: boolean
}[]
>
if (orderNumber) { if (orderNumber) {
const index = historyData.value.findIndex( const index = historyData.value.findIndex(
(el: HistoryDataItem) => el.orderNumber === orderNumber, (el: HistoryDataItem) => el.orderNumber === orderNumber,
...@@ -377,10 +520,13 @@ const setData = async (orderNumber: string) => { ...@@ -377,10 +520,13 @@ const setData = async (orderNumber: string) => {
if (index >= 0) { if (index >= 0) {
// 扫单完成删除 // 扫单完成删除
historyData.value.splice(index, 1) historyData.value.splice(index, 1)
localStorage.setItem('historCnData', JSON.stringify(historyData.value)) localStorage.setItem(
props.historyStorageKey,
JSON.stringify(historyData.value),
)
} }
} }
emit('onSuccess') emit('onSuccess', res.data)
playAudio('weight_success') playAudio('weight_success')
detail.value = { detail.value = {
id: -1, id: -1,
...@@ -408,17 +554,19 @@ const handleDownload = () => { ...@@ -408,17 +554,19 @@ const handleDownload = () => {
Object.keys(detail.value).length <= 1 || Object.keys(detail.value).length <= 1 ||
detail.value.id == -1 detail.value.id == -1
) { ) {
return ElMessage.warning('请扫码生产单号') return ElMessage.warning(props.pleaseScanTip)
} }
download() download()
} }
const download = async () => { const download = async () => {
if (detail.value && detail.value?.id != -1) { if (detail.value && detail.value?.id != -1) {
try { try {
const id = detail.value.id const id = props.isNewOrder
? detail.value.podOrderProductId as number
: detail.value.id
if (id !== undefined) { if (id !== undefined) {
try { try {
const res = await downloadMaterialApi([id]) const res = await props.downloadApi([id])
if (res.code !== 200) return if (res.code !== 200) return
// window.open(filePath + res.message) // window.open(filePath + res.message)
const a = document.createElement('a') const a = document.createElement('a')
...@@ -446,7 +594,7 @@ const playAudio = (key: AudioKey, message?: string) => { ...@@ -446,7 +594,7 @@ const playAudio = (key: AudioKey, message?: string) => {
text = '' text = ''
break break
case 'weight_search_error': case 'weight_search_error':
text = '请录入生产单号' text = props.searchInputAudioTip
break break
case 'weight_success': case 'weight_success':
text = '' text = ''
...@@ -470,8 +618,7 @@ const playAudio = (key: AudioKey, message?: string) => { ...@@ -470,8 +618,7 @@ const playAudio = (key: AudioKey, message?: string) => {
const trackCodeInput = async () => { const trackCodeInput = async () => {
if (!TrackingNumber.value) { if (!TrackingNumber.value) {
// ElMessage.warning('请扫描生产单号') playAudio('weight_search_error', props.searchInputAudioTip)
playAudio('weight_search_error')
trackingNumberRef.value && trackingNumberRef.value.focus() trackingNumberRef.value && trackingNumberRef.value.focus()
return return
} }
...@@ -484,7 +631,10 @@ const trackCodeInput = async () => { ...@@ -484,7 +631,10 @@ const trackCodeInput = async () => {
orderNumber: TrackingNumber.value, orderNumber: TrackingNumber.value,
finished: false, finished: false,
}) })
localStorage.setItem('historyCnData', JSON.stringify(historyData.value)) localStorage.setItem(
props.historyStorageKey,
JSON.stringify(historyData.value),
)
} }
const orderNumber = TrackingNumber.value const orderNumber = TrackingNumber.value
...@@ -496,23 +646,11 @@ const trackCodeInput = async () => { ...@@ -496,23 +646,11 @@ const trackCodeInput = async () => {
} }
try { try {
const res = await getSubOrderBySubOrderNumber(orderNumber) const res = await props.queryApi(orderNumber)
if (!res.data) { if (!res.data) {
return ElMessage.error('生产单不存在') return ElMessage.error(props.notFoundMessage)
}
const d = JSON.parse(JSON.stringify(res.data))
if (d.note) {
d.note = JSON.parse(d.note)
} else {
d.note = []
}
if (d.imageAry) {
d.imgList = JSON.parse(d.imageAry)
} else {
d.imgList = []
} }
detail.value = d detail.value = normalizeDetail(res.data)
if (isDownloadImage.value) { if (isDownloadImage.value) {
download() download()
......
...@@ -381,6 +381,7 @@ const coverImage = ref<string>('') ...@@ -381,6 +381,7 @@ const coverImage = ref<string>('')
let currentCode = '' let currentCode = ''
const tableRef = ref() const tableRef = ref()
watch(visible, async (value: boolean) => { watch(visible, async (value: boolean) => {
console.log('visible', value)
if (value) { if (value) {
podOrderDetailsData.value = {} podOrderDetailsData.value = {}
currentCode = '' currentCode = ''
......
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
<div style="margin: 15px 0"></div> <div style="margin: 15px 0"></div>
<el-checkbox-group v-model="selectedList" @change="checkChange"> <el-checkbox-group v-model="selectedList" @change="checkChange">
<div style="display: block" v-for="(item, index) in list" :key="index"> <div style="display: block" v-for="(item, index) in list" :key="index">
<el-checkbox :value="item"> <el-checkbox :value="item" style="user-select: text;">
{{ '工厂订单号:' + item.factoryOrderNumber + ' ' + item.message }} {{ '工厂订单号:' + item.factoryOrderNumber + ' ' + item.message }}
</el-checkbox> </el-checkbox>
</div> </div>
...@@ -53,17 +53,11 @@ ...@@ -53,17 +53,11 @@
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { copyText } from '@/utils/index' import { copyText } from '@/utils/index'
import { ResultInfoDataItem } from '@/types/api/order/common';
interface IList {
id: string | number
shopNumber?: string
factoryOrderNumber?: string
message: string
status: boolean
}
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
list: IList[] list: ResultInfoDataItem[]
}>(), }>(),
{ {
list: () => [], list: () => [],
...@@ -73,7 +67,7 @@ const props = withDefaults( ...@@ -73,7 +67,7 @@ const props = withDefaults(
const resultDialog = ref(false) const resultDialog = ref(false)
const isIndeterminate = ref(false) const isIndeterminate = ref(false)
const checkAll = ref(false) const checkAll = ref(false)
const selectedList = ref<IList[]>([]) const selectedList = ref<ResultInfoDataItem[]>([])
let key = '' let key = ''
// 显示弹窗 // 显示弹窗
const showDialog = (type?: string) => { const showDialog = (type?: string) => {
...@@ -161,7 +155,7 @@ defineExpose({ ...@@ -161,7 +155,7 @@ defineExpose({
showDialog, showDialog,
}) })
const emits = defineEmits<{ const emits = defineEmits<{
(e: 'confirm', data: IList[]): void (e: 'confirm', data: ResultInfoDataItem[]): void
}>() }>()
</script> </script>
......
...@@ -57,6 +57,7 @@ ...@@ -57,6 +57,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { updateCustomDeclarationInfoApi } from '@/api/podCnOrder' import { updateCustomDeclarationInfoApi } from '@/api/podCnOrder'
import { FactoryOrderNewListData } from '@/types/api/factoryOrderNew'
import { import {
CustomDeclarationInfoForm, CustomDeclarationInfoForm,
PodCnOrderListData, PodCnOrderListData,
...@@ -67,7 +68,8 @@ defineOptions({ ...@@ -67,7 +68,8 @@ defineOptions({
}) })
const props = defineProps<{ const props = defineProps<{
modelValue: boolean modelValue: boolean
orderSelection: PodCnOrderListData[] orderSelection: FactoryOrderNewListData[] | PodCnOrderListData[]
isNewOrder?: boolean
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void (e: 'update:modelValue', value: boolean): void
...@@ -101,8 +103,16 @@ const closeDialog = async () => { ...@@ -101,8 +103,16 @@ const closeDialog = async () => {
visible.value = false visible.value = false
} }
const submit = async () => { const submit = async () => {
const url = props.isNewOrder
? 'factory/podOrder/batchUpdateCustomsClearanceInfo'
: 'factory/podJomallOrderCn/batchUpdateCustomsClearanceInfo'
const loading = ElLoading.service({
fullscreen: true,
text: '操作中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try { try {
const res = await updateCustomDeclarationInfoApi({ const res = await updateCustomDeclarationInfoApi(url, {
params: form.value, params: form.value,
ids: props.orderSelection.map((item) => item.id).join(','), ids: props.orderSelection.map((item) => item.id).join(','),
}) })
...@@ -112,6 +122,8 @@ const submit = async () => { ...@@ -112,6 +122,8 @@ const submit = async () => {
emit('refreshTable') emit('refreshTable')
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} finally {
loading.close()
} }
} }
defineExpose({ defineExpose({
...@@ -119,7 +131,7 @@ defineExpose({ ...@@ -119,7 +131,7 @@ defineExpose({
}) })
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.dialog-footer { .dialog-footer {
text-align: center; text-align: center;
} }
</style> </style>
...@@ -22,7 +22,16 @@ ...@@ -22,7 +22,16 @@
placeholder="" placeholder=""
> >
<el-option label="跟踪号" value="trackingNumber"></el-option> <el-option label="跟踪号" value="trackingNumber"></el-option>
<el-option label="交运单号" value="shopNumber"></el-option> <el-option
v-if="!isNewOrder"
label="交运单号"
value="shopNumber"
></el-option>
<el-option
v-if="isNewOrder"
label="客户交运单号"
value="shopNumber"
></el-option>
</el-select> </el-select>
<input <input
ref="weighInput" ref="weighInput"
...@@ -33,7 +42,7 @@ ...@@ -33,7 +42,7 @@
weight.weightInput weight.weightInput
? selectType === 'trackingNumber' ? selectType === 'trackingNumber'
? '请输入跟踪号' ? '请输入跟踪号'
: '请输入交运单号' : isNewOrder ? '请输入客户交运单号' : '请输入交运单号'
: '请输入重量' : '请输入重量'
" "
@keyup.enter="weightChange" @keyup.enter="weightChange"
...@@ -110,10 +119,11 @@ interface ILogisticsList { ...@@ -110,10 +119,11 @@ interface ILogisticsList {
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import weight from '../components/weigh.js' import weight from '../components/weigh.js'
import { import {
listByNoApi, listByNoApi as defaultListByNoApi,
orderWeighingApi, orderWeighingApi as defaultOrderWeighingApi,
allErpCodeListApi, allErpCodeListApi,
} from '@/api/podCnOrder' } from '@/api/podCnOrder'
import type { BaseRespData } from '@/types/api'
// import { logisticsCompanyAllCodelist } from '@/api/logistics.ts' // import { logisticsCompanyAllCodelist } from '@/api/logistics.ts'
import CustomizeTable from '@/components/VxeTable.tsx' import CustomizeTable from '@/components/VxeTable.tsx'
import { TableColumn } from '@/components/VxeTable' import { TableColumn } from '@/components/VxeTable'
...@@ -196,6 +206,41 @@ const tableConfig = ref<TableColumn[]>([ ...@@ -196,6 +206,41 @@ const tableConfig = ref<TableColumn[]>([
}, },
}, },
]) ])
const props = withDefaults(
defineProps<{
/** 默认 podCN:`factory/podJomallOrderCn/listByNo` */
listByNoApi?: typeof defaultListByNoApi
/** 默认 podCN:`factory/podJomallOrderCn/orderWeighing` */
orderWeighingApi?: (payload: unknown) => Promise<BaseRespData<never>>
/**
* 提交称重时的请求体构造;默认 `{ podCnWeighingParams: rows }`,
* 若后端字段与 podCN 分叉时可单独传入。
*/
buildOrderWeighingPayload?: (rows: IpodCnWeighingParams[]) =>
| {
podCnWeighingParams: { id?: string; outWarehouseWeight?: string }[]
}
| {
podOrderWeighingParams: { id?: string; outWarehouseWeight?: string }[]
}
/** 是否是新订单 */
isNewOrder?: boolean
}>(),
{
isNewOrder: false,
},
)
const resolveListByNoApi = () => props.listByNoApi ?? defaultListByNoApi
const resolveOrderWeighingApi = () =>
(props.orderWeighingApi ?? defaultOrderWeighingApi) as (
payload: unknown,
) => Promise<BaseRespData<never>>
const resolveWeighingPayload = (rows: IpodCnWeighingParams[]) =>
(props.buildOrderWeighingPayload?.(rows) ?? {
podCnWeighingParams: rows,
}) as unknown
const emits = defineEmits<{ const emits = defineEmits<{
(e: 'updateList'): void (e: 'updateList'): void
}>() }>()
...@@ -259,7 +304,7 @@ const weightChange = async () => { ...@@ -259,7 +304,7 @@ const weightChange = async () => {
params.logisticsCompanyCode = logisticsCompanyCode.value params.logisticsCompanyCode = logisticsCompanyCode.value
weight.check( weight.check(
noValue, noValue,
listByNoApi, resolveListByNoApi(),
params, params,
(arr) => { (arr) => {
tableData.value = [...arr] tableData.value = [...arr]
...@@ -302,7 +347,8 @@ const weightGet = async () => { ...@@ -302,7 +347,8 @@ const weightGet = async () => {
background: 'rgba(0, 0, 0, 0.3)', background: 'rgba(0, 0, 0, 0.3)',
}) })
try { try {
orderWeighingApi({ podCnWeighingParams: tableData.value }).then((res) => { resolveOrderWeighingApi()(resolveWeighingPayload(tableData.value)).then(
(res) => {
if (res.code === 200) { if (res.code === 200) {
weight.clear() weight.clear()
ElMessage.success('保存称重分拣成功') ElMessage.success('保存称重分拣成功')
...@@ -313,7 +359,8 @@ const weightGet = async () => { ...@@ -313,7 +359,8 @@ const weightGet = async () => {
handleClose() handleClose()
emits('updateList') emits('updateList')
} }
}) },
)
} catch (error) { } catch (error) {
console.log(error) console.log(error)
} finally { } finally {
...@@ -323,6 +370,7 @@ const weightGet = async () => { ...@@ -323,6 +370,7 @@ const weightGet = async () => {
const open = () => { const open = () => {
isweight.value = true isweight.value = true
weight.stashToCache()
tableData.value = [] tableData.value = []
selectType.value = 'trackingNumber' selectType.value = 'trackingNumber'
logisticsCompanyCode.value = '' logisticsCompanyCode.value = ''
......
<script setup lang="ts"> <script setup lang="ts">
import { batchCheckPrintPodCn, batchCheckPrintPodUs } from '@/api/podCnOrder.ts' import { batchCheckPrintPodCn, batchCheckPrintPodUs } from '@/api/podCnOrder.ts'
import { batchCheckPrintPodOrder } from '@/api/factoryOrderNew.ts'
import { InterWarehousePage } from '@/types/api/warehouse.ts' import { InterWarehousePage } from '@/types/api/warehouse.ts'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { factoryWarehouseInventoryPrint } from '@/api/warehouse.ts' import { factoryWarehouseInventoryPrint } from '@/api/warehouse.ts'
...@@ -9,14 +10,16 @@ import { PrintData } from '@/types/api/podOrder.ts' ...@@ -9,14 +10,16 @@ import { PrintData } from '@/types/api/podOrder.ts'
const showPrintDialog = ref(false) const showPrintDialog = ref(false)
const printData = ref<PrintData[]>([]) const printData = ref<PrintData[]>([])
const open = async (type: number,ids:string) => { const apiMap: Record<number, (ids: string) => Promise<{ data: PrintData[] }>> = {
let res 1: batchCheckPrintPodCn,
2: batchCheckPrintPodUs,
3: batchCheckPrintPodOrder,
}
const open = async (type: number, ids: string) => {
printData.value = [] printData.value = []
if (type === 1) { const api = apiMap[type] ?? batchCheckPrintPodCn
res = await batchCheckPrintPodCn(ids) const res = await api(ids)
}else{
res= await batchCheckPrintPodUs(ids)
}
showPrintDialog.value = true showPrintDialog.value = true
printData.value = res.data printData.value = res.data
} }
......
...@@ -55,6 +55,7 @@ type AudioFiles = Record<AudioKey, string> ...@@ -55,6 +55,7 @@ type AudioFiles = Record<AudioKey, string>
class Weigh extends Lock { class Weigh extends Lock {
public weightInput: boolean public weightInput: boolean
public list: WeighItem[] public list: WeighItem[]
public cachedList: WeighItem[]
public selectType: string public selectType: string
private audios: AudioFiles private audios: AudioFiles
private audioElements: Map<AudioKey, HTMLAudioElement> private audioElements: Map<AudioKey, HTMLAudioElement>
...@@ -64,6 +65,7 @@ class Weigh extends Lock { ...@@ -64,6 +65,7 @@ class Weigh extends Lock {
this.weightInput = true this.weightInput = true
this.selectType = 'trackingNumber' this.selectType = 'trackingNumber'
this.list = [] this.list = []
this.cachedList = []
this.audios = { this.audios = {
weight_warning: new URL( weight_warning: new URL(
'@/assets/audio/weight_warning.mp3', '@/assets/audio/weight_warning.mp3',
...@@ -102,22 +104,32 @@ class Weigh extends Lock { ...@@ -102,22 +104,32 @@ class Weigh extends Lock {
clear(): void { clear(): void {
this.list = [] this.list = []
this.cachedList = []
this.weightInput = true this.weightInput = true
this.selectType = 'trackingNumber' this.selectType = 'trackingNumber'
} }
stashToCache(): void {
if (this.list.length) {
this.cachedList = [...this.list]
}
this.list = []
this.weightInput = true
}
updatedList(data: WeighItem[]) { updatedList(data: WeighItem[]) {
this.weightInput = true this.weightInput = true
this.list = [...data] this.list = [...data]
} }
// 去重逻辑优化
private deduplicate( private deduplicate(
value: string, value: string,
callback?: (list: WeighItem[]) => void, callback?: (list: WeighItem[]) => void,
): boolean { ): boolean {
const existingIndex = this.list.findIndex( const existingIndex = this.list.findIndex(
(item) => item.trackingNumber === value, (item) =>
item.trackingNumber === value ||
(this.selectType === 'shopNumber' && item.shopNumber === value),
) )
if (existingIndex !== -1) { if (existingIndex !== -1) {
...@@ -233,12 +245,23 @@ class Weigh extends Lock { ...@@ -233,12 +245,23 @@ class Weigh extends Lock {
this.playAudio('weight_warning', '请录入重量') this.playAudio('weight_warning', '请录入重量')
return return
} }
// 去重检查
if (this.deduplicate(value, callback)) { if (this.deduplicate(value, callback)) {
return return
} }
const cachedItem = this.cachedList.find(
(item) =>
(this.selectType === 'trackingNumber' && item.trackingNumber === value) ||
(this.selectType === 'shopNumber' && item.shopNumber === value),
)
if (cachedItem) {
this.list = [cachedItem, ...this.list]
this.weightInput = !!cachedItem.outWarehouseWeight
callback?.(this.list)
this.playAudio('weight_search_success')
return
}
try { try {
const response = await apiCall(params) const response = await apiCall(params)
console.log(211, response) console.log(211, response)
...@@ -250,20 +273,6 @@ class Weigh extends Lock { ...@@ -250,20 +273,6 @@ class Weigh extends Lock {
return return
} }
// const waitWeighingList = data.filter(
// (el) => el.status === 'WAIT_WEIGHING',
// )
// if (waitWeighingList.length === 0) {
// this.playAudio(
// 'weight_search_error',
// `必须是待称重状态的订单下的${
// this.selectType === 'trackingNumber' ? '跟踪号' : '店铺单号'
// }才能使用`,
// )
// return
// }
if (this.list?.length) { if (this.list?.length) {
const firstLogisticsCode = data[0]?.logisticsCompanyCode const firstLogisticsCode = data[0]?.logisticsCompanyCode
......
<template>
<ElDialog
v-model="visible"
:title="dialogTitle"
width="520px"
:close-on-click-modal="false"
>
<ElForm :model="form">
<ElFormItem
v-if="showAutoSwitch"
label="自动排版(烫画工艺推荐自动排版)"
style="margin-bottom: 10px"
>
<el-switch
v-model="isAuto"
inline-prompt
style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
active-text="是"
inactive-text="否"
@change="handleAutoChange"
/>
</ElFormItem>
<template v-if="isAuto || !showAutoSwitch">
<ElFormItem label="排版宽度">
<el-radio-group v-model="form.templateWidth">
<el-radio :value="42">40+2cm</el-radio>
<el-radio :value="60">60cm</el-radio>
</el-radio-group>
</ElFormItem>
</template>
</ElForm>
<template #footer>
<div style="text-align: center">
<ElButton @click="visible = false">取消</ElButton>
<ElButton type="primary" @click="handleSubmit"> 确定 </ElButton>
</div>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { BaseRespData } from '@/types/api'
type OpenPayload = {
productIdList?: number[]
title?: string
showAutoSwitch?: boolean
id?: number
}
const emit = defineEmits<{
success: []
}>()
const props = defineProps<{
submitApi: (params: {
productIdList?: number[]
templateWidth?: number
type?: string
}) => Promise<BaseRespData<never>>
}>()
const visible = ref(false)
const payload = ref<OpenPayload | null>(null)
const isAuto = ref(true)
const form = ref<{ type?: string; templateWidth?: number }>({})
const showAutoSwitch = ref(true)
const dialogTitle = ref('排单')
const handleAutoChange = () => {
form.value = {}
}
const open = (p: OpenPayload) => {
payload.value = p
showAutoSwitch.value = p.showAutoSwitch ?? true
dialogTitle.value = p.title || '排单'
isAuto.value = true
form.value = {}
visible.value = true
}
const handleSubmit = async () => {
if (!payload.value?.productIdList?.length) {
return ElMessage.warning('请选择订单')
}
const { templateWidth } = form.value
const mustFill = isAuto.value || !showAutoSwitch.value
if (mustFill && !templateWidth) {
return ElMessage.warning('请选择排版宽度')
}
const loading = ElLoading.service({
fullscreen: true,
text: '操作中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
const res = await props.submitApi({
productIdList: payload.value.productIdList,
templateWidth: isAuto.value ? templateWidth : undefined,
type: isAuto.value ? 'png' : undefined,
})
if (res.code !== 200) return
ElMessage.success(res.message)
visible.value = false
emit('success')
} catch (e) {
console.error(e)
} finally {
loading.close()
}
}
defineExpose({ open })
</script>
<template>
<div class="batch-manage">
<div class="batch-manage-filter">
<ElForm class="search-form" :inline="true" :model="filterForm">
<ElFormItem label="创建时间">
<el-date-picker
v-model="filterForm.createTimeRange"
type="datetimerange"
value-format="YYYY-MM-DD HH:mm:ss"
start-placeholder="开始时间"
end-placeholder="结束时间"
style="width: 320px"
/>
</ElFormItem>
<ElFormItem label="创建人">
<ElSelect
v-model="filterForm.employeeId"
placeholder="请选择"
clearable
filterable
style="width: 120px"
>
<ElOption
v-for="item in employeeList"
:key="item.id"
:label="item.account"
:value="item.id"
/>
</ElSelect>
</ElFormItem>
<ElFormItem label="工艺类型">
<ElSelect
v-model="filterForm.craftType"
placeholder="请选择"
clearable
style="width: 120px"
>
<ElOption
v-for="(item, index) in processType"
:key="index"
:value="item.value"
:label="item.label"
></ElOption>
</ElSelect>
</ElFormItem>
<ElFormItem label="下载状态">
<ElSelect
v-model="filterForm.downloadStatus"
placeholder="请选择"
clearable
style="width: 120px"
>
<ElOption label="未下载" :value="0" />
<ElOption label="已下载" :value="1" />
</ElSelect>
</ElFormItem>
<ElFormItem label="排版状态">
<ElSelect
v-model="filterForm.syntheticStatus"
placeholder="请选择"
clearable
style="width: 120px"
>
<ElOption label="否" :value="0" />
<ElOption label="是" :value="1" />
</ElSelect>
</ElFormItem>
<ElFormItem label="自动排版">
<ElSelect
v-model="filterForm.automaticComposing"
placeholder="请选择"
clearable
style="width: 120px"
>
<ElOption label="是" :value="1" />
<ElOption label="否" :value="0" />
</ElSelect>
</ElFormItem>
<ElFormItem label="批次号">
<ElInput
v-model="filterForm.batchArrangeNumber"
placeholder="批次号"
clearable
style="width: 140px"
/>
</ElFormItem>
</ElForm>
</div>
<div class="batch-manage-actions">
<span class="item">
<ElButton type="primary" @click="refresh">查询</ElButton>
</span>
<span class="item">
<ElButton type="danger" @click="() => handleBatchDelete()">批量删除</ElButton>
</span>
</div>
<div class="batch-manage-table">
<TableView
v-loading="loading"
:paginated-data="tableData"
:columns="columns"
selectionable
@selection-change="handleSelectionChange"
>
<template #downloadStatus="{ row }">
<el-tag
:type="row.downloadStatus === '已下载' ? 'success' : 'info'"
size="small"
>
{{ row.downloadStatus || '未下载' }}
</el-tag>
</template>
<template #operate="{ row }">
<ElButton
:disabled="
(!row.url && !row.tiffUrl) || row.enableArrange === false
"
link
type="primary"
size="small"
@click="handleDownload(row)"
>
下载
</ElButton>
<ElButton
type="primary"
link
size="small"
@click="handlePrintPick(row)"
>
拣货单
</ElButton>
<ElButton
type="primary"
link
size="small"
@click="handlePrintProduction(row)"
>
生产单
</ElButton>
<ElButton
type="warning"
link
size="small"
@click="handleReArrange(row)"
>
重排
</ElButton>
<ElButton
type="danger"
link
size="small"
@click="handleBatchDelete(row)"
>
删除
</ElButton>
</template>
</TableView>
</div>
<ArrangeDialog
ref="arrangeDialogRef"
:submit-api="submitBatchDownloadDesignImagesApi"
@success="refresh"
/>
<div class="table-pagination-bar">
<div class="selected-count-text">
已选择
<strong style="color: red">{{ selectedRows.length }}</strong> 条数据
</div>
<ElPagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[50, 100, 200, 300]"
background
layout="total, sizes, prev, pager, next, jumper"
:total="total"
style="margin: 0 auto"
@size-change="onPageSizeChange"
@current-change="onCurrentPageChange"
/>
</div>
</div>
</template>
<script setup lang="tsx">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
getBatchManageListApi,
batchDeleteApi,
downloadBatchMaterialApi,
printPickPdfByBatchNumberApi,
printProductionPdfByBatchNumberApi,
} from '@/api/factoryOrderNew'
import type { BatchManageData } from '@/types/api/factoryOrderNew'
import type { PaginationData } from '@/types/api'
import TableView from '@/components/TableView.vue'
import ArrangeDialog from './ArrangeDialog.vue'
import { getEmployeeListApi } from '@/api/common'
import type { userData } from '@/types/api/user'
import usePageList from '@/utils/hooks/usePageList'
import { filePath } from '@/api/axios'
import { batchDownloadDesignImagesApi } from '@/api/factoryOrderNew'
const selectedRows = ref<BatchManageData[]>([])
const employeeList = ref<userData[]>([])
const arrangeDialogRef = ref<InstanceType<typeof ArrangeDialog>>()
interface ProcessTypeData {
label: string
value: string
}
const processType = ref<ProcessTypeData[]>([
{
label: '烫画',
value: 'TH',
},
{
label: '直喷',
value: 'ZP',
},
{
label: '刺绣',
value: 'CX',
},
{
label: '雕刻',
value: 'DK',
},
{
label: '白胚',
value: 'BP',
},
{
label: '其他',
value: 'QT',
},
])
const getStandardDesignImageText = (value: number): string => {
const map: Record<number, string> = { 0: '否', 1: '是', 2: '混合' }
return map[value] ?? '-'
}
const columns = [
{
label: '批次号',
prop: 'batchArrangeNum',
width: 180,
align: 'center',
},
{
label: '下载状态',
slot: 'downloadStatus',
width: 90,
align: 'center',
render: (row: BatchManageData) => {
return (
<div>
<el-tag type={row?.downloadStatus ? 'success' : 'danger'}>
{row?.downloadStatus ? '已下载' : '未下载'}
</el-tag>
</div>
)
},
},
{
label: '订单数量',
prop: 'operationNum',
width: 85,
align: 'center',
},
{
label: '素材数量',
width: 85,
prop: 'materialNum',
align: 'center',
},
{
label: '创建人',
width: 85,
prop: 'employeeAccount',
align: 'center',
},
{
label: '工艺类型',
width: 150,
prop: 'craftType',
align: 'center',
render: (row: BatchManageData) => {
if (row && !row.craftType) {
return (
<div>
<span>-</span>
</div>
)
}
const labels: string[] =
row?.craftType?.split(',') ??
[]
.map((type: string) => type.trim())
.map(
(type: string) =>
processType.value.find((e: ProcessTypeData) => e.value === type)
?.label || type,
)
.filter((type: string | undefined) => type !== undefined)
return (
<div>
<span>{labels.join(',')}</span>
</div>
)
},
},
{
label: '规范素材',
minWidth: 180,
prop: 'standardDesignImage',
align: 'center',
render: (row: BatchManageData) => {
return (
<div>
<span>
{getStandardDesignImageText(row?.standardDesignImage ?? 0)}
</span>
</div>
)
},
},
{
label: '失败原因',
minWidth: 300,
prop: 'failReason',
align: 'left',
render: (row: BatchManageData) => {
return (
<div style="white-space: pre-line">
<span v-html={row?.failReason}></span>
</div>
)
},
},
{
label: '创建时间',
width: 180,
prop: 'createTime',
align: 'center',
},
{
label: '完成时间',
width: 180,
prop: 'finishTime',
align: 'center',
},
{
label: '自动排版',
width: 85,
prop: 'automaticComposing',
align: 'center',
render: (row: BatchManageData) => {
return (
<div>
<span>{row?.automaticComposing ? '是' : '否'}</span>
</div>
)
},
},
{
label: '排版参数',
width: 140,
prop: 'composingParam',
align: 'left',
render: (row: BatchManageData) => {
return (
<div style="white-space: pre-line">
<span>{row?.composingParam?.split(';').join('\n')}</span>
</div>
)
},
},
{
label: '操作',
slot: 'operate',
width: 260,
align: 'center',
fixed: 'right',
prop: 'operate',
},
]
const filterForm = reactive({
createTimeRange: [] as string[],
employeeId: '' as string | number,
craftType: '',
downloadStatus: '' as string | number,
syntheticStatus: '' as string | number,
automaticComposing: '' as string | number,
batchArrangeNumber: '',
})
const {
loading,
currentPage,
pageSize,
total,
data: tableData,
onCurrentPageChange,
onPageSizeChange,
refresh,
} = usePageList<BatchManageData>({
initPageSize: 50,
query: async (current, size) => {
const { createTimeRange, ...rest } = filterForm
const params: Record<string, unknown> = { ...rest }
if (createTimeRange?.length === 2) {
params.startTime = createTimeRange[0]
params.endTime = createTimeRange[1]
}
const res = await getBatchManageListApi(params, current, size)
return (res.data || {
total: 0,
size,
current,
records: [],
}) as PaginationData<BatchManageData>
},
})
const loadEmployeeList = async () => {
try {
const res = await getEmployeeListApi()
if (res.code !== 200) return
employeeList.value = res.data || []
} catch (e) {
console.error(e)
}
}
const handleSelectionChange = (rows: BatchManageData[]) => {
selectedRows.value = rows
}
const handleBatchDelete = async (row?: BatchManageData) => {
let ids
if (!row) {
if (!selectedRows.value.length) {
ElMessage.warning('请先选择数据')
return
}
ids = selectedRows.value.map((r) => r.id)
} else {
ids = [row.id]
}
try {
await ElMessageBox.confirm('确定删除选中的数据吗?', '提示', {
type: 'warning',
})
} catch {
return
}
const loading = ElLoading.service({
fullscreen: true,
text: '操作中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
const res = await batchDeleteApi(ids)
if (res.code !== 200) return
ElMessage.success(res.message)
refresh()
} catch (e) {
console.error(e)
} finally {
loading.close()
}
}
const handleDownload = async (row: BatchManageData) => {
const loading = ElLoading.service({
fullscreen: true,
text: '操作中...',
background: 'rgba(0, 0, 0, 0.3)',
})
const type = row.url && row.tiffUrl ? 'tiff' : row.url ? 'png' : 'tiff'
try {
const res = await downloadBatchMaterialApi(row.id, type)
if (res.code !== 200) return
window.open(filePath + res.message, '_blank')
} catch (e) {
console.error(e)
} finally {
loading.close()
}
}
const submitBatchDownloadDesignImagesApi = (params: {
productIdList?: number[]
templateWidth?: number
type?: string
}) => {
return batchDownloadDesignImagesApi({
id: params.productIdList?.[0] as number,
templateWidth: params.templateWidth,
type: params.type,
})
}
const handlePrintPick = async (row: BatchManageData) => {
const loading = ElLoading.service({
fullscreen: true,
text: '操作中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
const res = await printPickPdfByBatchNumberApi({
batchArrangeNumber: row.batchArrangeNum as string,
})
if (res?.code !== 200) return
ElMessage.success('操作成功')
window.open(filePath + res?.message)
} catch (e) {
console.error(e)
} finally {
loading.close()
}
}
const handlePrintProduction = async (row: BatchManageData) => {
const loading = ElLoading.service({
fullscreen: true,
text: '操作中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
const res = await printProductionPdfByBatchNumberApi({
batchArrangeNumber: row.batchArrangeNum as string,
})
if (res?.code !== 200) return
ElMessage.success('操作成功')
window.open(filePath + res?.message)
} catch (e) {
console.error(e)
} finally {
loading.close()
}
}
const handleReArrange = (row: BatchManageData) => {
arrangeDialogRef.value?.open({
productIdList: [row.id],
title: '重排',
showAutoSwitch: false,
})
}
onMounted(() => {
loadEmployeeList()
})
defineExpose({ refresh })
</script>
<style scoped lang="scss">
.batch-manage {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.batch-manage-filter {
flex-shrink: 0;
width: 100%;
min-width: 0;
}
.batch-manage-actions {
flex-shrink: 0;
margin-bottom: 10px;
}
.batch-manage-table {
flex: 1;
overflow: auto;
}
.table-pagination-bar {
margin-top: 10px;
display: flex;
align-items: center;
}
.selected-count-text {
font-size: 14px;
color: #606266;
white-space: nowrap;
}
.search-form {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0;
min-width: 0;
:deep(.el-form-item) {
margin-right: 10px;
margin-bottom: 10px;
}
:deep(.el-form-item__content) {
min-width: 0;
}
}
</style>
<template>
<ElDialog
v-model="visible"
title="取消订单"
width="450px"
:close-on-click-modal="false"
@close="handleClose"
>
<ElForm ref="formRef" :model="form" :rules="rules" label-width="100px">
<ElFormItem label="取消原因" prop="reason">
<ElSelect
v-model="form.reason"
placeholder="请选择取消原因"
style="width: 100%"
clearable
>
<ElOption
v-for="item in cancelReasons"
:key="item"
:label="item"
:value="item"
/>
</ElSelect>
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer" style="text-align: center">
<ElButton @click="visible = false">取消</ElButton>
<ElButton type="primary" :loading="submitLoading" @click="handleSubmit">
确认
</ElButton>
</div>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { cancelOrderWithReasonApi } from '@/api/factoryOrderNew'
const emit = defineEmits<{
success: []
}>()
const visible = ref(false)
const submitLoading = ref(false)
const formRef = ref<FormInstance>()
const orderIds = ref<(number | string)[]>([])
const cancelReasons = ['协商取消', '客户取消', '其他']
const form = reactive({
reason: '',
})
const rules: FormRules = {
reason: [{ required: true, message: '请选择取消原因', trigger: 'change' }],
}
const open = (ids: (number | string)[]) => {
orderIds.value = ids
form.reason = ''
visible.value = true
}
const handleClose = () => {
formRef.value?.resetFields()
}
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate()
submitLoading.value = true
try {
await cancelOrderWithReasonApi(orderIds.value, form.reason)
ElMessage.success('取消订单成功')
visible.value = false
emit('success')
} catch (e: unknown) {
console.error(e)
} finally {
submitLoading.value = false
}
}
defineExpose({ open })
</script>
<template>
<div v-loading="loading" element-loading-text="加载中..." class="card-layout">
<div v-if="cardData.length > 0" class="card-content">
<div class="card-grid">
<div
v-for="item in cardData"
:key="item.id"
class="card-grid-item"
@click="handleCardClick(item)"
@contextmenu.prevent="(e: MouseEvent) => rightClick(e, item)"
>
<CommonCard
:card-item="item"
:active="isSelected(item)"
:show-sku="false"
:show-product-info="false"
:image-field="'variantImage'"
:padding-top="'100%'"
>
<template #bottom_left>
<span
class="operation-number"
:title="`操作单号:${item.operationNo}`"
@click.stop="copyText(item.operationNo ?? '')"
>
{{ item.operationNo }}
</span>
</template>
<template #top_left>
<el-tooltip
v-if="item.outOfStock"
effect="light"
content="缺货"
placement="bottom"
>
<div
style="
background-color: #f56c6c;
color: #fff;
padding: 2px 4px;
border-radius: 4px;
font-size: 12px;
"
>
</div>
</el-tooltip>
</template>
<template #top_right>
<img
v-if="item.craftCode && ['ZPZY', 'CXZY', 'THZY'].includes(item.craftCode as string)"
:src="`/images/pic/${item.craftCode}.png`"
width="60"
height="60"
/>
</template>
<template #images>
<div class="flex-between">
<div class="images-position">
<div v-if="item.imageAry" class="images-container">
<div
v-for="(img, index) in JSON.parse(item.imageAry as string)"
:key="index"
:title="img.title"
class="item-image"
>
<el-image
:src="img?.url"
height="50"
:preview-src-list="JSON.parse(item.imageAry as string).map((i: any) => i.url)"
/>
</div>
</div>
</div>
<div class="flex-row flex-row-gap15">
<el-tooltip
class="item"
effect="dark"
:content="'用户编码: ' + item?.userMark"
placement="bottom"
>
<span
style="
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
display: inline-block;
"
@click.stop="copyText(String(item?.userMark || ''))"
>
{{ item?.userMark || 'AAAF' }}
</span>
</el-tooltip>
<img
v-if="item.platform"
:src="`/images/icon/${getPlatformImg(item.platform as string)}`"
style="width: 30px; height: 30px"
/>
</div>
</div>
</template>
<template #operations>
<div class="flex-row flex-row-gap6">
<div
v-if="
item?.productMark === 'custom_normal' ||
item?.productMark === 'normal'
"
class="product-mark-badge"
:title="`类型:${setProductMark(item?.productMark)}类`"
>
{{ setProductMark(item?.productMark) }}
</div>
<div
v-if="item.customizedQuantity"
class="quantity-badge"
:class="
item.customizedQuantity === 1
? 'single-quantity-badge-color'
: 'multiple-quantity-badge-color'
"
:title="`类型:${
item.customizedQuantity === 1 ? '单面' : '双面'
}`"
>
{{ item.customizedQuantity === 1 ? '单' : '多' }}
</div>
<Icon
name="caozuorizhi"
style="width: 28px; height: 28px"
@click.stop="openLogDialog(item)"
>
<template #title>
<title>操作日志</title>
</template>
</Icon>
<Icon
name="chakanxiangqing"
@click.stop="handleViewDetail(item)"
>
<template #title>
<title>查看详情</title>
</template>
</Icon>
</div>
</template>
<template #info>
<div class="card-info-grid">
<div class="card-info-row full">
<span
class="info-value ellipsis"
:title="(item.productName as string) || ''"
>
{{ item.productName }}
</span>
</div>
<div class="card-info-row">
<span
class="info-value clickable ellipsis"
:title="`变体SKU:${(item.variantSku as string) || ''}`"
@click.stop="copyText((item.variantSku as string) || '')"
>
{{ item.variantSku }}
</span>
</div>
<div class="card-info-row">
<span class="info-label">工艺:</span>
<span class="info-value">{{ item.craftName }}</span>
</div>
<div class="card-info-row">
<span
class="info-value clickable ellipsis"
:title="`库存SKU:${(item.thirdSkuCode as string) || ''}`"
@click.stop="copyText((item.thirdSkuCode as string) || '')"
>
{{ item.thirdSkuCode }}
</span>
</div>
<div class="card-info-row">
<span class="info-label">店铺单号:</span>
<span
class="info-value clickable ellipsis"
:title="`店铺单号:${(item.shopNumber as string) || ''}`"
@click.stop="copyText((item.shopNumber as string) || '')"
>{{ item.shopNumber }}</span
>
</div>
<div class="card-info-row">
<span class="info-label">款号:</span>
<span
class="info-value clickable ellipsis"
:title="`款号:${(item.supplierProductNo as string) || ''}`"
@click.stop="
copyText((item.supplierProductNo as string) || '')
"
>{{ item.supplierProductNo }}</span
>
</div>
<div class="card-info-row">
<span class="info-label">客户单号:</span>
<span
class="info-value clickable ellipsis"
:title="`客户单号:${(item.thirdOrderNumber as string) || ''}`"
@click.stop="
copyText((item.thirdOrderNumber as string) || '')
"
>{{ item.thirdOrderNumber }}</span
>
</div>
<div class="card-info-row">
<span class="info-label">批次号:</span>
<span
class="info-value clickable ellipsis"
:title="`批次号:${(item.batchArrangeNumber as string) || ''}`"
@click.stop="
copyText((item.batchArrangeNumber as string) || '')
"
>{{ item.batchArrangeNumber }}</span
>
</div>
<div class="card-info-row" style="color: red">
<span class="info-label">状态:</span>
<span class="info-value">{{ item.statusName }}</span>
</div>
<div class="card-info-row full delay-time-row">
<span
class="delay-time-value"
:title="`订单延期:${getOrderDelayText(item.orderCreateTime as string)}`"
>
{{ getOrderDelayText(item.orderCreateTime as string) }}
</span>
</div>
</div>
</template>
</CommonCard>
</div>
</div>
</div>
<div v-else class="card-empty">暂无数据</div>
<div class="card-pagination">
<div class="card-selected-info">
已选择
<span class="highlight">{{ selectedItems.length }}</span>
条数据
</div>
<ElPagination
:current-page="currentPage"
:page-size="pageSize"
:page-sizes="[50, 100, 200, 300]"
background
layout="total, sizes, prev, pager, next, jumper"
:total="total"
style="margin: 0 auto"
@size-change="onPageSizeChange"
@current-change="onCurrentPageChange"
/>
</div>
<el-dialog
v-model="logVisible"
title="操作日志"
width="1000px"
:close-on-click-modal="false"
>
<LogList :log-list="logList" />
<div v-if="!logList.length" class="empty-content">暂无数据</div>
</el-dialog>
</div>
<RightClickMenu
ref="rightMenuRef"
:show-copy-count="false"
:show-copy-sub-shop-number="false"
@on-change="rightChange"
>
<template #default>
<div class="menu-item" @click="rightChange('order-number')">
复制订单号
</div>
<div class="menu-item" @click="rightChange('operationNo')">
复制操作单号
</div>
</template>
</RightClickMenu>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import dayjs from 'dayjs'
import { ElMessage } from 'element-plus'
import {
getCardLayoutListApi,
getFactoryOrderNewLogApi,
} from '@/api/factoryOrderNew'
import type { PaginationData } from '@/types/api'
import usePageList from '@/utils/hooks/usePageList'
import { operateOrderListData } from '@/types/api/factoryOrderNew'
import type { LogListData } from '@/types/api/order'
import LogList from '@/components/LogList.vue'
import RightClickMenu from '@/components/RightClickMenu.vue'
import platformJson from '../../../../json/platform.json'
const props = defineProps<{
status: string
queryPayload: Record<string, unknown>
initPageSize?: number
}>()
const emit = defineEmits<{
'selection-change': [items: operateOrderListData[]]
'view-detail': [item: operateOrderListData]
}>()
const {
loading,
currentPage,
pageSize,
total,
data: cardData,
onCurrentPageChange,
onPageSizeChange,
refresh,
} = usePageList<operateOrderListData>({
initPageSize: props.initPageSize || 100,
initLoad: false,
query: async (current, size) => {
const payload = {
...props.queryPayload,
status: props.status === 'ALL' ? undefined : props.status,
}
const res = await getCardLayoutListApi(payload, current, size)
return res.data as unknown as PaginationData<operateOrderListData>
},
})
const selectedItems = ref<operateOrderListData[]>([])
const logVisible = ref(false)
const logList = ref<LogListData[]>([])
const rightMenuRef = ref()
const rightClickItem = ref<operateOrderListData | null>(null)
const isSelected = (item: operateOrderListData) =>
selectedItems.value.some((s) => s.id === item.id)
const handleCardClick = (item: operateOrderListData) => {
const idx = selectedItems.value.findIndex((s) => s.id === item.id)
if (idx >= 0) {
selectedItems.value.splice(idx, 1)
} else {
selectedItems.value.push(item)
}
emit('selection-change', [...selectedItems.value])
}
const handleViewDetail = (item: operateOrderListData) => {
emit('view-detail', item)
}
const copyText = (text: string) => {
navigator.clipboard.writeText(text)
ElMessage.success('复制成功')
}
const rightClick = (e: MouseEvent, item: operateOrderListData) => {
rightClickItem.value = item
rightMenuRef.value?.setPosition({
x: e.clientX,
y: e.clientY,
})
}
const getRightCopyData = () => {
if (selectedItems.value.length > 0) return selectedItems.value
if (rightClickItem.value) return [rightClickItem.value]
return []
}
const rightChange = (code: string) => {
if (code === 'select-all') {
selectedItems.value = [...(cardData.value as operateOrderListData[])]
emit('selection-change', [...selectedItems.value])
} else if (code === 'cancel-select') {
selectedItems.value = []
emit('selection-change', [])
} else if (code === 'copy_shopNumber') {
const str = getRightCopyData()
.map((item) => item?.shopNumber)
.filter(Boolean)
.join(',')
if (!str) return ElMessage.warning('当前数据没有店铺单号')
navigator.clipboard.writeText(str)
ElMessage.success('复制成功')
} else if (code === 'order-number') {
const str = getRightCopyData()
.map((item) => item?.podOrderNo)
.filter(Boolean)
.join(',')
if (!str) return ElMessage.warning('当前数据没有订单号')
navigator.clipboard.writeText(str)
ElMessage.success('复制成功')
} else if (code === 'operationNo') {
const str = getRightCopyData()
.map((item) => item?.operationNo)
.filter(Boolean)
.join(',')
if (!str) return ElMessage.warning('当前数据没有操作单号')
navigator.clipboard.writeText(str)
ElMessage.success('复制成功')
}
}
const openLogDialog = async (item: operateOrderListData) => {
if (!item.podOrderId) return
try {
const res = await getFactoryOrderNewLogApi(item.podOrderId as number)
logList.value = (res.data || []) as unknown as LogListData[]
logVisible.value = true
} catch (e) {
// ignore
}
}
const clearSelection = () => {
selectedItems.value = []
emit('selection-change', [])
}
function setProductMark(productMark: string) {
if (!productMark) return ''
if (productMark === 'custom_normal') return 'CB'
if (productMark === 'normal') return 'G'
return ''
}
const getSelectedIds = () => selectedItems.value.map((i) => i.id)
const getPlatformImg = (code: string) => {
const lowerCode = code.toLowerCase()
const item = platformJson.find((el) =>
el.type.toLowerCase().includes(lowerCode),
)
if (item) {
return item.icon.split('/').pop()
}
return ''
}
const getOrderDelayText = (createTime?: string | null) => {
if (!createTime) return '--'
const createAt = dayjs(createTime)
if (!createAt.isValid()) return '--'
const diffMinutes = Math.max(0, dayjs().diff(createAt, 'minute'))
const baseHours = Math.floor(diffMinutes / 60)
const remainMinutes = diffMinutes % 60
const hours = baseHours + (remainMinutes > 30 ? 1 : 0)
return `${hours}h`
}
defineExpose({ clearSelection, getSelectedIds, refresh })
</script>
<style scoped lang="scss">
.card-layout {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.card-content {
flex: 1;
overflow-y: auto;
}
.card-grid {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
grid-template-rows: max-content;
gap: 10px;
padding: 5px;
}
.card-grid-item {
cursor: pointer;
}
.card-empty {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
color: #909399;
font-size: 14px;
}
.card-pagination {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 0;
}
.card-selected-info {
font-size: 14px;
color: #606266;
.highlight {
color: red;
font-weight: bold;
}
}
.operation-number {
font-size: 12px;
cursor: pointer;
}
.quantity-badge {
background-color: #f56c6c;
color: #fff;
padding: 5px 8px;
border-radius: 4px;
font-size: 12px;
}
.multiple-quantity-badge-color {
background-color: #e6a23c;
}
.product-mark-badge {
border: 2px solid #F56C6C;
color: #F56C6C;
font-weight: 700;
height: 28px;
width: 28px;
text-align: center;
border-radius: 6px;
font-size: 16px;
}
.card-info-grid {
display: grid;
grid-template-columns: 1fr 150px;
gap: 4px;
font-size: 12px;
margin-top: 6px;
.full {
grid-column: 1 / -1;
}
}
.card-info-row {
display: flex;
overflow: hidden;
}
.delay-time-row {
justify-content: flex-end;
margin-top: 2px;
}
.delay-time-value {
color: red;
font-weight: 600;
}
.info-label {
flex-shrink: 0;
color: #909399;
}
.info-value {
flex: 1;
min-width: 0;
&.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&.clickable {
cursor: pointer;
color: #409eff;
&:hover {
text-decoration: underline;
}
}
}
.flex-between {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
overflow: hidden;
gap: 6px;
.images-position {
flex: 1;
overflow: auto hidden;
.images-container {
width: 100%;
display: flex;
gap: 6px;
}
.item-image {
width: 50px;
min-width: 50px;
height: 50px;
border: 1px solid #eee;
cursor: pointer;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
}
b {
margin-right: 5px;
font-size: 15px;
}
}
.flex-row {
display: flex;
align-items: center;
justify-content: center;
gap: 15px;
}
.flex-row-gap15 {
gap: 15px;
}
.flex-row-gap6 {
gap: 6px;
}
</style>
<template>
<ElDialog
v-model="visible"
title="确认接单"
width="450px"
:close-on-click-modal="false"
@close="handleClose"
>
<ElForm ref="formRef" :model="form" :rules="rules" label-width="100px">
<ElFormItem label="发货仓库" prop="warehouseId">
<ElSelect
v-model="form.warehouseId"
placeholder="请选择发货仓库"
filterable
style="width: 100%"
>
<ElOption
v-for="item in warehouseList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</ElSelect>
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer" style="text-align: center">
<ElButton @click="visible = false">取消</ElButton>
<ElButton type="primary" :loading="submitLoading" @click="handleSubmit">
确认
</ElButton>
</div>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
import { loadWarehouseListApi } from '@/api/common'
import { confirmOrderWithWarehouseApi } from '@/api/factoryOrderNew'
import type { WarehouseListData } from '@/types'
const emit = defineEmits<{
success: [
data: {
factoryOrderNumber?: string
message?: string
id: number | string
status?: boolean
}[],
]
}>()
const visible = ref(false)
const submitLoading = ref(false)
const formRef = ref<FormInstance>()
const warehouseList = ref<WarehouseListData[]>([])
const orderIds = ref<(number | string)[]>([])
const form = reactive({
warehouseId: '' as number | string,
})
const rules: FormRules = {
warehouseId: [
{ required: true, message: '请选择发货仓库', trigger: 'change' },
],
}
const loadWarehouseList = async () => {
try {
const res = await loadWarehouseListApi()
warehouseList.value = res.data || []
} catch (_e) {
/* empty */
}
}
const open = (ids: (number | string)[]) => {
orderIds.value = ids
form.warehouseId = ''
visible.value = true
loadWarehouseList()
}
const handleClose = () => {
formRef.value?.resetFields()
}
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate()
const warehouseName = warehouseList.value.find(
(item) => item.id === form.warehouseId,
)?.name
submitLoading.value = true
try {
const res = await confirmOrderWithWarehouseApi(
orderIds.value,
form.warehouseId,
warehouseName || '',
)
if (res.code !== 200) return
visible.value = false
emit('success', res.data)
} catch (e) {
console.error(e)
} finally {
submitLoading.value = false
}
}
defineExpose({ open })
</script>
<template>
<ElDialog
v-model="newDialogVisible"
title="创建出库单"
width="80%"
:close-on-click-modal="false"
>
<div class="dialog-form">
<ElForm
ref="editFormRef"
:model="editForm"
:rules="rules"
inline
label-width="90px"
>
<ElFormItem label="出库单号" prop="account">
<ElInput v-model.trim="editForm.outNo" clearable disabled />
</ElFormItem>
<ElFormItem label="工厂:" prop="factoryCode">
<span>{{ editForm.factoryCode }}</span>
</ElFormItem>
<ElFormItem label="仓库" prop="warehouseId" required>
<ElSelect
v-model="editForm.warehouseId"
clearable
disabled
placeholder="请选择仓库"
style="width: 160px"
>
<ElOption
v-for="item in warehouseList"
:key="item.id"
:label="item.name"
:value="item.id"
></ElOption>
</ElSelect>
</ElFormItem>
<ElFormItem label="备注" prop="remark" style="width: 45%">
<ElInput
v-model.trim="editForm.remark"
placeholder="请输入备注"
clearable
/>
</ElFormItem>
</ElForm>
<ElTable
size="small"
:data="otherPurchaseData"
height="500px"
border
@selection-change="productSelectionChange"
>
<ElTableColumn
type="selection"
width="70"
header-align="center"
align="center"
></ElTableColumn>
<ElTableColumn
show-overflow-tooltip
width="60"
align="center"
label="序号"
type="index"
></ElTableColumn>
<ElTableColumn
show-overflow-tooltip
align="center"
width="100"
label="SKU图片"
prop="skuImage"
>
<template #default="{ row }">
<ImageView :src="row.skuImage" width="40px" height="40px" />
</template>
</ElTableColumn>
<ElTableColumn
show-overflow-tooltip
align="center"
label="库存SKU"
prop="warehouseSku"
/>
<ElTableColumn
show-overflow-tooltip
align="center"
label="商品名称"
prop="skuName"
/>
<ElTableColumn
show-overflow-tooltip
align="center"
label="款号"
prop="productNo"
/>
<ElTableColumn
show-overflow-tooltip
align="center"
label="可用库存数量"
prop="usableInventory"
/>
<ElTableColumn align="center" label="出库数量" prop="outCount">
<template #default="{ row }">
<el-input
v-model.number="row.outCount"
placeholder="出库数量"
style="width: 120px"
clearable
size="small"
@input="setCostPrice(row)"
></el-input>
</template>
</ElTableColumn>
<ElTableColumn
width="80"
align="center"
label="币种"
prop="currencyName"
/>
<ElTableColumn
width="100"
align="center"
label="成本价"
prop="costPrice"
/>
<ElTableColumn
align="center"
width="100"
label="总成本"
prop="totalPrice"
/>
<ElTableColumn align="center" label="库位" prop="locationCode">
<template #default="{ row }">
<span v-if="row.locationCode">{{ row.locationCode }}</span>
<ElSelect
v-else
v-model="row.locationId"
clearable
placeholder="请输入库位"
style="width: 120px"
filterable
@change="handleLocationChange(row.locationId, row)"
>
<ElOption
v-for="item in locationList"
:key="item.locationId"
:label="item.locationCode"
:value="item.locationId"
></ElOption>
</ElSelect>
</template>
</ElTableColumn>
<ElTableColumn
show-overflow-tooltip
align="center"
label="所属客户"
prop="userMark"
/>
<ElTableColumn
show-overflow-tooltip
align="center"
width="240"
label="备注"
prop="remark"
>
<template #default="{ row }">
<ElInput v-model.trim="row.remark" clearable size="small" />
</template>
</ElTableColumn>
</ElTable>
</div>
<template #footer>
<div class="product-dialog-footer">
<div>
<el-button
type="danger"
size="small"
@click="deleteOtherWarehousing()"
>
删除
</el-button>
</div>
<div>
<el-button
size="small"
style="margin-left: 10px"
@click="newDialogVisible = false"
>
取消
</el-button>
<el-button type="primary" size="small" @click="addOtherCurrency()">
保存
</el-button>
</div>
</div>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import BigNumber from 'bignumber.js'
import { ElMessage, ElLoading } from 'element-plus'
import ImageView from '@/components/ImageView.vue'
import {
type InterWarehouseDetail,
type InterProductList,
type InterskuList,
type ILocation,
} from '@/types/api/warehouse'
import {
warehouseInfoGetAll,
type warehouseInfo,
getBySkuAndWarehouseIdApi,
getByWareHouseIdAndCodeApi,
addOutRecordApi,
} from '@/api/warehouse'
interface OpenParamsItem {
thirdSkuCode: string
suggestOutQuantity: number
}
interface OpenParams {
warehouseId: number | string
warehouseName?: string
items: OpenParamsItem[]
}
const emit = defineEmits<{
success: []
}>()
const warehouseList = ref<warehouseInfo[]>([])
const locationList = ref<ILocation[]>([])
const newDialogVisible = ref(false)
const editFormRef = ref()
const editForm = ref<InterWarehouseDetail>({
outNo: '',
warehouseId: '',
warehouseName: '',
remark: '',
factoryCode: '',
factoryId: 0,
productList: [],
})
const rules = {
warehouseId: [{ required: true, message: '请选择仓库', trigger: 'change' }],
}
const otherPurchaseData = ref<InterProductList[]>([])
const otherWarehouseSelection = ref<InterProductList[]>([])
const setCostPrice = (item: InterProductList) => {
if (item.costPrice !== 0 && !item.costPrice) {
ElMessage.warning('商品成本价为空,请完善商品成本价')
return
}
if (item) {
const outCount = item.outCount ?? 0
const costPrice = item.costPrice ?? 0
const amount = new BigNumber(outCount).multipliedBy(costPrice).toFixed(2)
item.totalPrice = Number(amount)
}
}
const fetchWarehouseList = async () => {
if (warehouseList.value.length) return
try {
const res = await warehouseInfoGetAll()
warehouseList.value = res.data || []
} catch (e) {
console.error(e)
}
}
const fetchLocationList = async (warehouseId: number | string | undefined) => {
if (!warehouseId) return
try {
const res = await getByWareHouseIdAndCodeApi(warehouseId, '')
const result = res.data || []
locationList.value = result.map((item: ILocation) => {
return {
locationId: item.id,
locationCode: item.locationCode,
}
})
} catch (e) {
console.error(e)
locationList.value = []
}
}
const handleLocationChange = (val: number, row: InterProductList) => {
const found = locationList.value.find(
(item: ILocation) => item.locationId === val,
)
row.locationCode = found ? found.locationCode : ''
}
const productSelectionChange = (v: InterProductList[]) => {
otherWarehouseSelection.value = v
}
const deleteOtherWarehousing = () => {
const arr = otherWarehouseSelection.value
if (!arr.length) return
const idList = arr.map((v: InterProductList) => v.warehouseSku)
otherPurchaseData.value = otherPurchaseData.value.filter(
(item: InterProductList) => !idList.includes(item.warehouseSku),
)
}
const addOtherCurrency = async () => {
try {
await editFormRef.value?.validate()
} catch {
return
}
const arr = otherPurchaseData.value
if (!arr.length) {
ElMessage.error('请至少选择一条数据')
return
}
for (let i = 0; i < arr.length; i++) {
if (!arr[i].outCount) {
ElMessage.error('请输入出库数量')
return
}
const usableInventory = arr[i].usableInventory || 0
if ((arr[i].outCount as number) > usableInventory) {
ElMessage.error('出库数量不能大于可用库存数量')
return
}
if (!arr[i].locationId) {
ElMessage.error('请选择库位')
return
}
}
const params: InterWarehouseDetail = {
...editForm.value,
productList: otherPurchaseData.value,
}
try {
await addOutRecordApi(params)
ElMessage.success('保存成功')
newDialogVisible.value = false
emit('success')
} catch (e) {
console.error(e)
}
}
const open = async (params: OpenParams) => {
editForm.value.outNo = ''
editForm.value.warehouseId = params.warehouseId
editForm.value.warehouseName =
params.warehouseName ||
warehouseList.value.find(
(item: warehouseInfo) => item.id === params.warehouseId,
)?.name ||
''
editForm.value.remark = ''
const userJson = localStorage.getItem('user')
if (userJson) {
try {
const userData = JSON.parse(userJson)
editForm.value.factoryCode = userData.factoryCode || ''
editForm.value.factoryId = userData.factoryId || 0
} catch {
// ignore
}
}
otherPurchaseData.value = []
const skuList = params.items
.map((item) => item.thirdSkuCode)
.filter((sku) => !!sku)
const skuToSuggestQuantity = new Map<string, number>()
params.items.forEach((item) => {
if (item.thirdSkuCode) {
skuToSuggestQuantity.set(item.thirdSkuCode, item.suggestOutQuantity || 0)
}
})
const loading = ElLoading.service({
text: '加载库存中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
const res = await getBySkuAndWarehouseIdApi(
params.warehouseId,
skuList.join(','),
)
if (res.code !== 200) return
await fetchWarehouseList()
await fetchLocationList(params.warehouseId)
const arr: InterskuList[] = res.data || []
const mergedProductList = arr.map((skuItem) => {
const warehouseSku = skuItem.warehouseSku || ''
const suggestOutQuantity = skuToSuggestQuantity.get(warehouseSku) ?? 0
const outCount = suggestOutQuantity
const costPrice = skuItem.price ?? 0
const totalPrice = new BigNumber(outCount)
.multipliedBy(costPrice || 0)
.toNumber()
return {
skuImage: skuItem.image,
customerId: skuItem.customerId,
userMark: skuItem.userMark,
customerName: skuItem.customerName,
currencyName: skuItem.currencyName ?? undefined,
currencyCode: skuItem.currencyCode ?? undefined,
warehouseSku: skuItem.warehouseSku,
skuName: skuItem.skuName,
productNo: skuItem.productNumber,
locationCode: skuItem.locationCode ?? '',
locationId: skuItem.locationId ?? null,
costPrice,
outCount,
totalPrice,
usableInventory: skuItem.usableInventory,
inventoryId: skuItem.id,
remark: skuItem.remark ?? null,
} as InterProductList
})
otherPurchaseData.value = mergedProductList
} catch (e) {
console.error(e)
otherPurchaseData.value = []
} finally {
loading.close()
}
newDialogVisible.value = true
}
defineExpose({ open })
</script>
<style scoped lang="scss">
.product-dialog-footer {
display: flex;
justify-content: space-between;
margin: 8px 0;
}
</style>
<template>
<ElDialog
v-model="visible"
:title="title"
width="1500px"
:close-on-click-modal="false"
>
<div class="operate-order-details-content">
<div v-if="!pickable" class="operate-order-tip">
请选择具体的操作单,申请补胚
</div>
<div v-else class="pick-data">
<div class="pick-data-item">
<span>商品名称: </span>
<span>{{ selectedRow?.productName }}</span>
</div>
<div class="pick-data-item">
<span>库存SKU: </span>
<span>{{ selectedRow?.thirdSkuCode }}</span>
</div>
<div class="pick-data-item">
<span>仓库: </span>
<span>{{ selectedRow?.warehouseName }}</span>
</div>
<div class="pick-data-item" style="color: red">
<span>当前拣胚数量: </span>
<span>{{ selectedItems.length }}</span>
</div>
<div class="pick-data-item">
<span>库存数量: </span>
<span>{{ selectedRow?.inventory }}</span>
</div>
<div class="pick-data-item">
<span>生产中数量: </span>
<span>{{ selectedRow?.producingQuantity }}</span>
</div>
</div>
<div v-loading="loading" class="operate-order-card-grid">
<div
v-for="item in cardList"
:key="item.id"
class="operate-order-card-item"
@click="toggleSelect(item)"
>
<CommonCard
:card-item="item"
:active="isSelected(item)"
:show-sku="false"
:show-product-info="false"
:image-field="'variantImage'"
:padding-top="'100%'"
>
<template #bottom_left>
<span
class="operation-number"
:title="`操作单号:${item.operationNo}`"
@click.stop="String(item.operationNo)"
>
{{ item.operationNo }}
</span>
</template>
<template #top_right>
<img
v-if="item.craftCode && ['ZPZY', 'CXZY', 'THZY'].includes(item.craftCode as string)"
:src="`/images/pic/${item.craftCode}.png`"
width="60"
height="60"
/>
</template>
<template #images>
<div class="flex-between">
<div class="images-position">
<div v-if="item.imageAry" class="images-container">
<div
v-for="(img, index) in JSON.parse(item.imageAry as string)"
:key="index"
:title="img.title"
class="item-image"
>
<el-image
:src="img?.url"
height="50"
:preview-src-list="JSON.parse(item.imageAry as string).map((i: any) => i.url)"
/>
</div>
</div>
</div>
<div class="flex-row flex-row-gap15">
<el-tooltip
class="item"
effect="dark"
:content="'用户编码: ' + item?.userMark"
placement="bottom"
>
<span
style="
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
display: inline-block;
"
@click.stop="copyText(String(item?.userMark || ''))"
>
{{ item?.userMark || 'AAAF' }}
</span>
</el-tooltip>
<img
v-if="item.platform"
:src="`/images/icon/${getPlatformImg(item.platform as string)}`"
style="width: 30px; height: 30px"
/>
</div>
</div>
</template>
<template #operations>
<div class="flex-row flex-row-gap6">
<div
v-if="item.customizedQuantity"
class="quantity-badge"
:class="
item.customizedQuantity === 1
? 'single-quantity-badge-color'
: 'multiple-quantity-badge-color'
"
:title="`类型:${
item.customizedQuantity === 1 ? '单面' : '双面'
}`"
>
{{ item.customizedQuantity === 1 ? '单' : '多' }}
</div>
</div>
</template>
<template #info>
<div class="card-info-grid">
<div class="card-info-row full">
<span
class="info-value ellipsis"
:title="(item.productName as string) || ''"
>
{{ item.productName }}
</span>
</div>
<div class="card-info-row">
<span
class="info-value clickable ellipsis"
:title="`变体SKU:${(item.variantSku as string) || ''}`"
@click.stop="copyText((item.variantSku as string) || '')"
>
{{ item.variantSku }}
</span>
</div>
<div class="card-info-row">
<span class="info-label">工艺:</span>
<span class="info-value">{{ item.craftName }}</span>
</div>
<div class="card-info-row">
<span
class="info-value clickable ellipsis"
:title="`库存SKU:${(item.thirdSkuCode as string) || ''}`"
@click.stop="copyText((item.thirdSkuCode as string) || '')"
>
{{ item.thirdSkuCode }}
</span>
</div>
<div class="card-info-row">
<span class="info-label">店铺单号:</span>
<span
class="info-value clickable ellipsis"
:title="`店铺单号:${(item.shopNumber as string) || ''}`"
@click.stop="copyText((item.shopNumber as string) || '')"
>{{ item.shopNumber }}</span
>
</div>
<div class="card-info-row">
<span class="info-label">款号:</span>
<span
class="info-value clickable ellipsis"
:title="`款号:${(item.supplierProductNo as string) || ''}`"
@click.stop="
copyText((item.supplierProductNo as string) || '')
"
>{{ item.supplierProductNo }}</span
>
</div>
<div class="card-info-row">
<span class="info-label">客户单号:</span>
<span
class="info-value clickable ellipsis"
:title="`客户单号:${(item.thirdOrderNumber as string) || ''}`"
@click.stop="
copyText((item.thirdOrderNumber as string) || '')
"
>{{ item.thirdOrderNumber }}</span
>
</div>
<div class="card-info-row">
<span class="info-label">批次号:</span>
<span
class="info-value clickable ellipsis"
:title="`批次号:${(item.batchArrangeNumber as string) || ''}`"
@click.stop="
copyText((item.batchArrangeNumber as string) || '')
"
>{{ item.batchArrangeNumber }}</span
>
</div>
<div class="card-info-row" style="color: red">
<span class="info-label">状态:</span>
<span class="info-value ellipsis">{{ item.statusName }}</span>
</div>
<div class="card-info-row full delay-time-row">
<span
class="delay-time-value"
:title="`订单延期:${getOrderDelayText(item.orderCreateTime as string)}`"
>
{{ getOrderDelayText(item.orderCreateTime as string) }}
</span>
</div>
</div>
</template>
</CommonCard>
</div>
<div v-if="!loading && cardList.length === 0" class="replenish-empty">
暂无数据
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<div style="font-size: 14px">
已选择
<strong style="color: red">{{ selectedItems.length }}</strong>
条数据
</div>
<div style="margin: 0 auto">
<ElButton @click="visible = false">取消</ElButton>
<ElButton type="primary" :loading="submitting" @click="handleConfirm">
确定
</ElButton>
</div>
</div>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import dayjs from 'dayjs'
import { ElMessage } from 'element-plus'
import CommonCard from '@/components/CommonCard.vue'
import {
applyForProductByIdApi,
applyForReplenishByIdApi,
} from '@/api/factoryOrderNew'
import platformJson from '../../../../json/platform.json'
import type {
operateOrderListData,
PickCompleteData,
} from '@/types/api/factoryOrderNew'
const emit = defineEmits(['adjustPickOrderSuccess'])
const props = defineProps<{
title: string
pickable?: boolean
}>()
const visible = ref(false)
const loading = ref(false)
const submitting = ref(false)
const cardList = ref<operateOrderListData[]>([])
const selectedItems = ref<operateOrderListData[]>([])
const selectedRow = ref<PickCompleteData | null>(null)
const isSelected = (item: operateOrderListData) => {
return selectedItems.value.some((s) => s.id === item.id)
}
const toggleSelect = (item: operateOrderListData) => {
const idx = selectedItems.value.findIndex((s) => s.id === item.id)
if (idx >= 0) {
selectedItems.value.splice(idx, 1)
} else {
selectedItems.value.push(item)
}
}
const copyText = (text: string) => {
navigator.clipboard.writeText(text)
ElMessage.success('复制成功')
}
const open = async ({
row,
ids,
url,
}: {
row: PickCompleteData
ids: (number | string)[]
url: string
}) => {
selectedItems.value = []
cardList.value = []
loading.value = true
try {
const res = props.pickable
? await applyForReplenishByIdApi(ids, url)
: await applyForProductByIdApi(ids[0] as string, url)
if (res.code !== 200) return
cardList.value = Array.isArray(res.data) ? res.data : []
if (row.availableOrderIds?.length) {
selectedItems.value = cardList.value.filter((item) => {
return row.availableOrderIds?.includes(item.id)
})
}
visible.value = true
selectedRow.value = row
} catch (e) {
console.error(e)
} finally {
loading.value = false
}
}
const getPlatformImg = (code: string) => {
const lowerCode = code.toLowerCase()
const item = platformJson.find((el) =>
el.type.toLowerCase().includes(lowerCode),
)
if (item) {
return item.icon.split('/').pop()
}
return ''
}
const getOrderDelayText = (createTime?: string | null) => {
if (!createTime) return '--'
const createAt = dayjs(createTime)
if (!createAt.isValid()) return '--'
const diffMinutes = Math.max(0, dayjs().diff(createAt, 'minute'))
const baseHours = Math.floor(diffMinutes / 60)
const remainMinutes = diffMinutes % 60
const hours = baseHours + (remainMinutes > 30 ? 1 : 0)
return `${hours}h`
}
const handleConfirm = async () => {
if (!selectedItems.value.length) {
return ElMessage.warning('请选择操作单')
}
if (props.pickable) {
if (
selectedItems.value.length >
(selectedRow.value?.availableOrderIds?.length ?? 0)
) {
return ElMessageBox.confirm(
`<div>
<span>当前库存数量: ${selectedRow.value?.inventory}</span>
<span style="margin: 0 4px">生产中数量: ${selectedRow.value?.producingQuantity}</span>
<span>最多只能拣胚 ${selectedRow.value?.availableOrderIds?.length} </span>
<div>请重新选择!</div>
</div>`,
'提示',
{
type: 'warning',
dangerouslyUseHTMLString: true,
confirmButtonText: '确定',
},
)
}
}
emit(
'adjustPickOrderSuccess',
null,
selectedItems.value.map((item) => item.id),
selectedRow.value,
)
visible.value = false
}
defineExpose({ open })
</script>
<style scoped lang="scss">
.operate-order-details-content {
height: 600px;
display: flex;
flex-direction: column;
}
.operate-order-tip {
font-size: 14px;
color: #606266;
margin-bottom: 12px;
}
.operate-order-card-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: max-content;
gap: 12px;
overflow-y: auto;
flex: 1;
}
.operate-order-card-item {
cursor: pointer;
}
.operate-order-empty {
grid-column: 1 / -1;
display: flex;
align-items: center;
justify-content: center;
color: #909399;
font-size: 14px;
min-height: 200px;
}
.operation-number {
font-size: 12px;
}
.card-info-grid {
display: grid;
grid-template-columns: 1fr 120px;
gap: 4px;
font-size: 12px;
margin-top: 6px;
.full {
grid-column: 1 / -1;
}
}
.card-info-row {
display: flex;
overflow: hidden;
}
.delay-time-row {
justify-content: flex-end;
margin-top: 2px;
}
.delay-time-value {
color: red;
font-weight: 600;
}
.info-label {
flex-shrink: 0;
color: #909399;
}
.info-value {
flex: 1;
min-width: 0;
&.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&.clickable {
cursor: pointer;
color: #409eff;
&:hover {
text-decoration: underline;
}
}
}
.flex-row {
display: flex;
align-items: center;
justify-content: center;
gap: 15px;
}
.flex-row-gap15 {
gap: 15px;
}
.flex-row-gap6 {
gap: 6px;
}
.quantity-badge {
background-color: #f56c6c;
color: #fff;
padding: 5px 8px;
border-radius: 4px;
font-size: 12px;
}
.multiple-quantity-badge-color {
background-color: #e6a23c;
}
.pick-data {
display: flex;
align-items: center;
gap: 20px;
margin-bottom: 10px;
}
.flex-between {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
overflow: hidden;
gap: 6px;
.images-position {
flex: 1;
overflow: auto hidden;
.images-container {
width: 100%;
display: flex;
gap: 6px;
}
.item-image {
width: 50px;
min-width: 50px;
height: 50px;
border: 1px solid #eee;
cursor: pointer;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
}
b {
margin-right: 5px;
font-size: 15px;
}
}
.dialog-footer {
display: flex;
align-items: center;
}
</style>
<template>
<ElDialog
v-model="visible"
:title="dialogTitle"
width="1400px"
:close-on-click-modal="false"
@close="handleClose"
>
<div v-if="pickData" class="pick-order-data">
<div class="pick-order-data-message">
<Icon style="width: 24px; height: 24px" name="tishi" />
<span> {{ pickData.overallMessage }}! </span>
</div>
<div class="pick-complete-actions" style="margin-bottom: 10px">
<span class="item">
<ElButton type="success" @click="handleCreateInbound">
创建入库单
</ElButton>
</span>
</div>
<div class="table-view">
<TableView
:paginated-data="pickData.pickingSituationList || []"
:columns="columns"
serial-numberable
selectionable
@selection-change="handleSelectionChange"
>
<template #skuImage="{ row }">
<el-image
v-if="row.skuImage"
:src="row.skuImage"
style="width: 50px; height: 50px"
fit="contain"
:preview-src-list="[row.skuImage]"
preview-teleported
/>
</template>
<template #pickQuantity="{ row }">
<span style="color: #e6a23c; font-weight: bold">{{
row.selectedQuantity
}}</span>
</template>
<template #pickStatus="{ row }">
<span
v-if="row.pickingStatus === 'fail'"
style="color: #f56c6c; font-weight: 500"
>
✕ 无法拣胚
</span>
<span v-else-if="row.pickingStatus === 'partial'" class="item">
<ElButton
type="warning"
size="small"
@click="handleAdjustPickOrder(row)"
>调整拣胚顺序</ElButton
>
</span>
<span
v-else-if="row.pickingStatus === 'success'"
style="color: #67c23a; font-weight: 500"
>
✓ 直接拣胚
</span>
<span v-else style="color: #999; font-weight: 500">未知状态</span>
</template>
</TableView>
</div>
</div>
<template #footer>
<div class="dialog-footer" style="text-align: center">
<span class="item">
<ElButton @click="visible = false">取消</ElButton>
</span>
<span class="item">
<ElButton type="primary" @click="handleSubmit"> 确定 </ElButton>
</span>
</div>
</template>
</ElDialog>
<OperateDetailsDialog
ref="operateDetailsDialogRef"
title="调整拣胚顺序"
:pickable="true"
@adjust-pick-order-success="onAdjustPickOrderSuccess"
/>
<ReceiptProductDialog
ref="receiptProductDialogRef"
v-model:visible="receiptDialogVisible"
v-model:user-mark="receiptUserMark"
v-model:select-sku="receiptSelectSku"
title="创建入库单"
:disable-warehouse="true"
:show-user-mark-filter="false"
:show-query-button="false"
:show-batch-add-button="false"
:show-import-button="false"
:edit-form="receiptEditForm"
:rules="receiptRules"
:warehouse-list="warehouseList"
:other-purchase-data="otherPurchaseData"
:location-list="locationList"
:user-mark-list="userMarkList"
:sku-data="skuData"
:filter-sku-data="filterSkuData"
@warehouse-change="handleReceiptWarehouseChange"
@selection-change="handleReceiptProductSelectionChange"
@set-cost-price="setCostPrice"
@location-change="handleReceiptLocationChange"
@update:remark="updateReceiptRemark"
@update:warehouse-id="updateReceiptWarehouseId"
@delete="deleteReceiptProducts"
@save="handleReceiptSave"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ElMessage, ElLoading } from 'element-plus'
import {
pickCompleteByIdsDataApi,
pickCompleteApi,
replenishmentCompleteApi,
markStockOutOfApi,
} from '@/api/factoryOrderNew'
import type { PickCompleteData } from '@/types/api/factoryOrderNew'
import type { BaseRespData } from '@/types/api'
import type {
InterProductList,
InterWarehouseDetail,
InterskuList,
ILocation,
} from '@/types/api/warehouse'
import {
warehouseInfoGetAll,
getBySkuAndUserMarkApi,
getByWareHouseIdAndCodeApi,
addInRecordApi,
type warehouseInfo,
} from '@/api/warehouse'
import { useValue } from '@/utils/hooks/useValue'
import { useReceiptProductDialog } from '@/views/warehouse/hooks/useReceiptProductDialog'
import TableView from '@/components/TableView.vue'
import Icon from '@/components/Icon.vue'
import OperateDetailsDialog from './OperateDetailsDialog.vue'
import ReceiptProductDialog from '@/views/warehouse/components/ReceiptProductDialog.vue'
interface PickData {
overallMessage?: string
pickingSituationList?: PickCompleteData[]
allAvailableOrderIds?: number[]
partialAvailableOrderIds?: number[]
unavailableOrderIds?: number[]
}
const emit = defineEmits<{
success: []
}>()
const receiptRules = {
warehouseId: [{ required: true, message: '请选择仓库', trigger: 'change' }],
}
const [receiptEditForm, resetReceiptEditForm] = useValue<InterWarehouseDetail>({
inNo: '',
warehouseId: '',
warehouseName: '',
remark: '',
factoryCode: '',
factoryId: 0,
productList: [],
})
const receiptDialogVisible = ref(false)
const warehouseList = ref<warehouseInfo[]>([])
const otherPurchaseData = ref<InterProductList[]>([])
const locationList = ref<ILocation[]>([])
const receiptUserMark = ref(0)
const batchUserMark = ref(0)
const importUserMark = ref(0)
const receiptSelectSku = ref('')
const userMarkList = ref<
{ userId: number; userMark: string; userName: string }[]
>([{ userId: 0, userMark: '', userName: '' }])
const { skuData, setCostPrice, filterSkuData } = useReceiptProductDialog({
editForm: receiptEditForm,
otherPurchaseData,
userMark: receiptUserMark,
batchUserMark,
importUserMark,
selectSku: receiptSelectSku,
userMarkList,
})
const receiptProductDialogRef = ref<{
validateForm: () => Promise<void>
} | null>(null)
const otherReceiptSelection = ref<InterProductList[]>([])
const fetchReceiptLocationList = async (query: string) => {
if (!receiptEditForm.value.warehouseId) return
try {
const res = await getByWareHouseIdAndCodeApi(
receiptEditForm.value.warehouseId,
query,
)
const result = res.data || []
locationList.value = result.map((item: ILocation) => ({
locationId: item.id,
locationCode: item.locationCode,
}))
} catch (e) {
console.error(e)
}
}
const updateReceiptRemark = (value: string) => {
receiptEditForm.value.remark = value
}
const updateReceiptWarehouseId = (value: number | string | undefined) => {
receiptEditForm.value.warehouseId = value
}
const handleReceiptWarehouseChange = (val: number | string | undefined) => {
const found = warehouseList.value.find(
(item: warehouseInfo) => item.id === val,
)
receiptEditForm.value.warehouseName = found ? found.name : ''
}
const handleReceiptProductSelectionChange = (v: InterProductList[]) => {
otherReceiptSelection.value = v
}
const handleReceiptLocationChange = (
val: number | null | undefined,
row: InterProductList,
) => {
const found = locationList.value.find(
(item: ILocation) => item.locationId === val,
)
row.locationCode = found ? found.locationCode : ''
}
const deleteReceiptProducts = () => {
const arr = otherReceiptSelection.value
if (arr.length === 0) return
const idList = arr.map((v: InterProductList) => v.warehouseSku)
otherPurchaseData.value = otherPurchaseData.value.filter(
(item: InterProductList) => !idList.includes(item.warehouseSku),
)
}
function mapInterskuToProduct(val: InterskuList): InterProductList {
const warehouseSku = val.warehouseSku || val.sku || ''
const skuNameVal = val.skuName || val.productName || ''
let customerId: number | null = null
if (val.customerId != null && val.customerId !== '') {
const n = Number(val.customerId)
if (!Number.isNaN(n)) customerId = n
}
return {
skuImage: val.skuImage || val.image || '',
warehouseSku,
skuName: skuNameVal,
productNo: val.productNo ?? null,
locationCode: val.locationCode ?? '',
locationId: val.locationId ?? null,
costPrice: val.costPrice ?? null,
buyStored: null,
totalPrice: null,
currencyName: val.currencyName ?? null,
currencyCode: val.currencyCode ?? null,
customerId,
customerName: val.customerName ?? null,
userMark: val.userMark ?? null,
}
}
const handleReceiptSave = async () => {
try {
await receiptProductDialogRef.value?.validateForm()
} catch {
return
}
const arr = otherPurchaseData.value
if (arr.length === 0) {
ElMessage.error('请至少选择一条数据')
return
}
for (let i = 0; i < arr.length; i++) {
if (!arr[i].buyStored) {
ElMessage.error('请输入入库数量')
return
}
if (!arr[i].locationId) {
ElMessage.error('请选择库位')
return
}
const found = locationList.value.find(
(item: ILocation) => item.locationId === arr[i].locationId,
)
if (!arr[i].locationCode) {
arr[i].locationCode = found ? found?.locationCode : ''
}
}
const params = {
...receiptEditForm.value,
productList: otherPurchaseData.value,
}
const loading = ElLoading.service({
text: '保存中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
await addInRecordApi(params)
ElMessage.success('保存成功')
receiptDialogVisible.value = false
} catch (e) {
console.error(e)
} finally {
loading.close()
}
}
const visible = ref(false)
const pickData = ref<PickData | null>(null)
const selections = ref<PickCompleteData[]>([])
const dialogTitle = ref('拣胚完成')
const submitApi =
ref<(ids: (number | string)[]) => Promise<BaseRespData<void>>>(
pickCompleteApi,
)
const columns = [
{
prop: 'warehouseName',
label: '仓库名称',
minWidth: 120,
},
{
label: 'SKU图片',
width: 100,
align: 'center',
slot: 'skuImage',
},
{
key: 'productName',
prop: 'productName',
label: '商品名称',
minWidth: 120,
showOverflowTooltip: true,
},
{
prop: 'supplierProductNo',
label: '款号',
width: 100,
align: 'center',
},
{
prop: 'thirdSkuCode',
label: '库存SKU',
align: 'center',
width: 180,
},
{
prop: 'selectedQuantity',
label: '本次拣胚数量',
width: 110,
align: 'right',
slot: 'pickQuantity',
},
{
prop: 'availableInventory',
label: '可调配库存',
width: 100,
align: 'right',
},
{
prop: 'inventory',
label: '库存数量',
width: 90,
align: 'right',
},
{
prop: 'producingQuantity',
label: '生产中数量',
width: 100,
align: 'right',
},
{
prop: 'occupyInventory',
label: '占用数量',
width: 90,
align: 'right',
},
{
label: '拣胚情况',
width: 130,
align: 'center',
fixed: 'right',
slot: 'pickStatus',
},
]
const open = async (
ids: (number | string)[],
_adjustOrderIds?: number[],
_selectedRow?: PickCompleteData,
options?: {
title?: string
submitType?: 'pick' | 'replenish'
},
) => {
selections.value = []
pickData.value = null
dialogTitle.value = options?.title || '拣胚完成'
submitApi.value =
options?.submitType === 'replenish'
? replenishmentCompleteApi
: pickCompleteApi
const loading = ElLoading.service({
text: '操作中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
const res = await pickCompleteByIdsDataApi(ids)
if (res.code !== 200) return
pickData.value = res.data || null
visible.value = true
} catch (e) {
console.error(e)
} finally {
loading.close()
}
}
const onAdjustPickOrderSuccess = async (
_: unknown,
adjustOrderIds: number[],
selectedRow: PickCompleteData,
) => {
const previousPickList = pickData.value?.pickingSituationList ?? []
if (selectedRow && adjustOrderIds && adjustOrderIds.length > 0) {
const adjustPickData = previousPickList.find(
(item: PickCompleteData) =>
item.thirdSkuCode === selectedRow.thirdSkuCode,
)
if (adjustPickData) {
adjustPickData.availableOrderIds = adjustOrderIds
}
}
pickData.value = {
...pickData.value,
pickingSituationList: previousPickList,
}
}
const handleClose = () => {
pickData.value = null
}
const handleCreateInbound = async () => {
if (selections.value.length === 0) {
ElMessage.warning('请选择需创建入库单的数据')
return
}
const firstWid = selections.value[0].warehouseId
if (selections.value.some((r) => r.warehouseId !== firstWid)) {
return ElMessage.error('请选择相同仓库的库存SKU!')
}
resetReceiptEditForm()
otherPurchaseData.value = []
receiptUserMark.value = 0
receiptSelectSku.value = ''
skuData.value = []
otherReceiptSelection.value = []
const userJson = localStorage.getItem('user')
if (userJson) {
try {
const userData = JSON.parse(userJson)
receiptEditForm.value.factoryCode = userData.factoryCode || ''
receiptEditForm.value.factoryId = userData.factoryId || 0
} catch {
// ignore
}
}
receiptEditForm.value.warehouseId = firstWid
receiptEditForm.value.warehouseName = selections.value[0].warehouseName || ''
const wh = warehouseList.value.find((w) => w.id == firstWid)
if (wh) {
receiptEditForm.value.warehouseName = wh.name
}
const loading = ElLoading.service({
text: '加载中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
const skus = selections.value.map((r) => r.thirdSkuCode as string)
const res = await getBySkuAndUserMarkApi(firstWid, skus.join(','), null)
if (res.code !== 200) return
otherPurchaseData.value = res.data.map((item: InterskuList) =>
mapInterskuToProduct(item),
)
await fetchReceiptLocationList('')
const listRes = await warehouseInfoGetAll()
warehouseList.value = listRes.data || []
receiptDialogVisible.value = true
} catch (e) {
console.error(e)
} finally {
loading.close()
}
}
const operateDetailsDialogRef = ref()
const handleSelectionChange = (selection: PickCompleteData[]) => {
selections.value = selection
}
const handleAdjustPickOrder = async (row: PickCompleteData) => {
operateDetailsDialogRef.value?.open({
row,
ids: row.allOrderIds as (number | string)[],
url: 'factory/podOrderOperation/listByIds',
})
}
const handleSubmit = async () => {
const loading = ElLoading.service({
text: '操作中...',
background: 'rgba(0, 0, 0, 0.3)',
})
const orderIds = pickData.value?.pickingSituationList
?.map((item) => item.availableOrderIds)
.flat()
try {
if (orderIds && orderIds.length > 0) {
const res = await submitApi.value(orderIds as (number | string)[])
if (res.code !== 200) return
}
const pickFailedOrderList = pickData.value?.pickingSituationList?.filter(
(item) => item.pickingStatus !== 'success',
)
if (pickFailedOrderList && pickFailedOrderList.length > 0) {
const warehouseMap: Record<number, PickCompleteData[]> = {}
for (const item of pickFailedOrderList) {
const wid = item.warehouseId
if (!warehouseMap[wid]) warehouseMap[wid] = []
warehouseMap[wid].push(item)
}
try {
for (const [widStr, rows] of Object.entries(warehouseMap)) {
const warehouseSkuList = [
...new Set(
rows
.map((r) => r.thirdSkuCode)
.filter((s): s is string => typeof s === 'string' && s !== ''),
),
]
const markRes = await markStockOutOfApi({
warehouseId: widStr,
warehouseSkuList,
})
if (markRes.code !== 200) return
}
} catch (e) {
console.error(e)
}
}
ElMessage.success('操作成功')
visible.value = false
emit('success')
} catch (e) {
console.error(e)
} finally {
loading.close()
}
}
defineExpose({ open })
</script>
<style scoped lang="scss">
.table-view {
height: 600px;
}
.pick-order-data-message {
font-size: 14px;
color: #f56c6c;
margin-bottom: 10px;
font-weight: bold;
display: flex;
align-items: center;
}
</style>
<template>
<ElDialog
v-model="visible"
:title="dialogTitle"
width="1400px"
:close-on-click-modal="false"
@close="handleClose"
>
<div class="pick-fail-info">
<span>
您选择了
<strong>{{ orderIds.length }}</strong>
件操作单,如拣胚失败,库位没有实物库存,
<strong style="color: #f56c6c"> 建议创建出库单 </strong>
,详细库存信息如下:
</span>
</div>
<div class="table-view">
<TableView
:paginated-data="tableData"
:columns="columns"
selectionable
serial-numberable
@selection-change="handleSelectionChange"
>
<template #skuImage="{ row }">
<el-image
v-if="row.skuImage"
:src="row.skuImage"
style="width: 50px; height: 50px"
fit="contain"
:preview-src-list="[row.skuImage]"
preview-teleported
/>
</template>
<template #producingQuantity="{ row }">
<span style="color: #e6a23c; font-weight: bold">{{
row.producingQuantity
}}</span>
</template>
</TableView>
</div>
<template #footer>
<div class="dialog-footer" style="text-align: center">
<span class="item">
<ElButton @click="visible = false">取消</ElButton>
</span>
<span class="item">
<ElButton type="primary" @click="handleCreateOutbound">
快速创建出库单
</ElButton>
</span>
</div>
</template>
</ElDialog>
<CreateOutboundDialog
ref="createOutboundDialogRef"
@success="
() => {
visible = false
emit('success')
}
"
/>
</template>
<script setup lang="tsx">
import { ref } from 'vue'
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus'
import BigNumber from 'bignumber.js'
import { pickCompleteByIdsDataApi } from '@/api/factoryOrderNew'
import type { PickCompleteData } from '@/types/api/factoryOrderNew'
import TableView from '@/components/TableView.vue'
import _ from 'lodash'
import CreateOutboundDialog from './CreateOutboundDialog.vue'
/** 当前可用库存 = 当前库存数量 − 占用 − 冻结 */
function getCurrentAvailableInventory(row: PickCompleteData): BigNumber {
return new BigNumber(row.inventory ?? 0)
.minus(row.occupyInventory ?? 0)
.minus(row.freezeInventory ?? 0)
}
/** 建议出库数量 = 当前库存 − 生产中数量 */
function getSuggestOutQuantity(row: PickCompleteData): BigNumber {
return new BigNumber(row.inventory ?? 0).minus(row.producingQuantity ?? 0)
}
/** 出库后库存数量 = 当前库存 − 建议出库数量 */
function getAfterOutStock(row: PickCompleteData): BigNumber {
return new BigNumber(row.inventory ?? 0).minus(getSuggestOutQuantity(row))
}
/** 出库后可用库存 = 出库后库存 − 占用 − 冻结 */
function getAfterOutAvailable(row: PickCompleteData): BigNumber {
return getAfterOutStock(row)
.minus(row.occupyInventory ?? 0)
.minus(row.freezeInventory ?? 0)
}
const emit = defineEmits<{
success: []
}>()
const visible = ref(false)
const tableData = ref<PickCompleteData[]>([])
const orderIds = ref<(number | string)[]>([])
const dialogTitle = ref('拣胚失败')
const selections = ref<PickCompleteData[]>([])
const columns = [
{
key: 'warehouseName',
prop: 'warehouseName',
label: '仓库名称',
minWidth: 120,
},
{
key: 'skuImage',
label: 'SKU图片',
width: 100,
align: 'center',
slot: 'skuImage',
},
{
key: 'productName',
prop: 'productName',
label: '商品名称',
minWidth: 160,
showOverflowTooltip: true,
},
{
prop: 'supplierProductNo',
label: '款号',
width: 140,
align: 'center',
},
{
key: 'thirdSkuCode',
prop: 'thirdSkuCode',
label: '库存SKU',
minWidth: 160,
},
{
prop: 'inventory',
label: '当前库存数量',
minWidth: 120,
align: 'right',
},
{
prop: 'occupyInventory',
label: '占用数量',
minWidth: 90,
align: 'right',
},
{
key: 'currentAvailableInventory',
prop: 'currentAvailableInventory',
label: '当前可用库存',
minWidth: 120,
align: 'right',
render: (row: PickCompleteData) => {
return <span>{getCurrentAvailableInventory(row).toNumber()}</span>
},
},
{
prop: 'producingQuantity',
label: '生产中数量',
minWidth: 110,
align: 'right',
slot: 'producingQuantity',
},
{
key: 'suggestOutQuantity',
prop: 'suggestOutQuantity',
label: '建议出库数量',
minWidth: 120,
align: 'right',
render: (row: PickCompleteData) => {
return (
<span style="color: #f56c6c; font-weight: bold">
{getSuggestOutQuantity(row).toNumber()}
</span>
)
},
},
{
key: 'afterOutStock',
prop: 'afterOutStock',
label: '出库后库存数量',
minWidth: 130,
align: 'right',
render: (row: PickCompleteData) => {
return (
<span style="color: #e6a23c; font-weight: bold">
{getAfterOutStock(row).toNumber()}
</span>
)
},
},
{
prop: 'occupyInventory',
label: '出库后占用数量',
minWidth: 130,
align: 'right',
},
{
prop: 'afterOutAvailable',
label: '出库后可用库存',
minWidth: 130,
align: 'right',
render: (row: PickCompleteData) => {
return (
<span style="color: #f56c6c; font-weight: bold">
{getAfterOutAvailable(row).toNumber()}
</span>
)
},
},
]
const handleSelectionChange = (selection: PickCompleteData[]) => {
selections.value = selection
}
const open = async (
ids: (number | string)[],
options?: { title?: string; submitType?: string },
) => {
dialogTitle.value = options?.title || '拣胚失败'
orderIds.value = ids
visible.value = true
const loading = ElLoading.service({
text: '操作中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
const res = await pickCompleteByIdsDataApi(ids)
tableData.value = res.data?.pickingSituationList || []
} catch (_e) {
tableData.value = []
} finally {
loading.close()
}
}
const handleClose = () => {
tableData.value = []
}
const createOutboundDialogRef = ref<InstanceType<
typeof CreateOutboundDialog
> | null>(null)
const handleCreateOutbound = () => {
if (selections.value.length === 0) {
ElMessage.warning('请至少选择一条数据')
return
}
const warehouseIds = _.uniq(
selections.value
.map((item) => item.warehouseId)
.filter((id) => id !== undefined && id !== null),
)
if (warehouseIds.length !== 1) {
ElMessage.warning('请选择相同仓库的库存SKU!')
return
}
const warehouseId = warehouseIds[0] as number | string
const firstSelection = selections.value[0] as PickCompleteData & {
warehouseName?: string
}
const warehouseName = firstSelection.warehouseName
const items = selections.value
.map((item) => {
const row = item as PickCompleteData & {
thirdSkuCode?: string
inventory?: number
producingQuantity?: number
}
return {
thirdSkuCode: row.thirdSkuCode || '',
suggestOutQuantity: getSuggestOutQuantity(row).toNumber(),
}
})
.filter((item) => item.suggestOutQuantity !== 0)
if (items.length === 0) {
ElMessageBox.alert('建议出库数量=0,无法快速创建出库单!', '提示', {
type: 'warning',
})
return
}
createOutboundDialogRef.value?.open({
warehouseId,
warehouseName,
items,
})
}
defineExpose({ open })
</script>
<style scoped lang="scss">
.pick-fail-info {
font-size: 14px;
color: #606266;
line-height: 1.8;
}
.table-view {
height: 600px;
}
</style>
<template>
<ElPopover
placement="bottom-start"
:width="420"
trigger="click"
:teleported="false"
popper-class="product-type-filter-popper"
>
<div class="ptf-panel">
<div v-for="group in options" :key="String(group.value)" class="ptf-group">
<div class="ptf-group-header">
<div class="ptf-group-title">{{ group.label }}</div>
<div class="ptf-group-actions">
<ElCheckbox
v-if="isMulti"
:model-value="isGroupAllChecked(group)"
:indeterminate="isGroupIndeterminate(group)"
@change="toggleGroupAll(group)"
/>
<template v-if="isMulti">
<span class="ptf-select-all" @click="toggleGroupAll(group)">全选</span>
</template>
<template v-else>
<ElCheckbox
:model-value="isGroupAllChecked(group)"
:indeterminate="isGroupIndeterminate(group)"
@change="toggleGroupAll(group)"
/>
<span class="ptf-select-all" @click="toggleGroupAll(group)">全选</span>
</template>
</div>
</div>
<div class="ptf-items">
<div
v-for="item in group.children"
:key="String(item.value)"
class="ptf-item"
:class="{ active: isSelected(item.value) }"
@click="handleItemClick(item.value)"
>
<ElCheckbox
:model-value="isSelected(item.value)"
@change="() => handleItemClick(item.value)"
@click.stop
/>
<span class="ptf-item-label">{{ item.label }}</span>
</div>
</div>
</div>
</div>
<template #reference>
<ElInput
class="ptf-trigger"
:model-value="displayText"
:placeholder="placeholder"
readonly
clearable
:style="{ width: width }"
@clear="clearValue"
>
<template #suffix>
<el-icon class="ptf-arrow"><ArrowDown /></el-icon>
</template>
</ElInput>
</template>
</ElPopover>
</template>
<script setup lang="ts">
/** 选项 value 与接口查询字段的对应关系见 `../utils/productMarkQuery.ts`(productMarkList / podCustomizedQuantity / cpCustomizedQuantity)。 */
import { computed } from 'vue'
import { ArrowDown } from '@element-plus/icons-vue'
import type { ProductTypeGroup, ProductTypeValue } from './productTypeFilterTypes'
type Model = ProductTypeValue | ProductTypeValue[] | null | undefined
const props = withDefaults(
defineProps<{
modelValue: Model
options: ProductTypeGroup[]
multiple?: boolean
placeholder?: string
width?: string
}>(),
{
multiple: false,
placeholder: '请选择',
},
)
const emit = defineEmits<{
(e: 'update:modelValue', v: Model): void
}>()
const isMulti = computed(() => props.multiple || Array.isArray(props.modelValue))
const valueArray = computed<ProductTypeValue[]>(() => {
if (!isMulti.value) return []
const v = props.modelValue
return Array.isArray(v) ? v : v == null ? [] : [v]
})
const labelMap = computed(() => {
const map = new Map<ProductTypeValue, string>()
for (const g of props.options) {
map.set(g.value, g.label)
for (const c of g.children) {
map.set(c.value, c.label)
}
}
return map
})
const displayText = computed(() => {
const v = props.modelValue
if (isMulti.value) {
const arr = valueArray.value
if (!arr.length) return ''
return arr.map((x) => labelMap.value.get(x) ?? String(x)).join(',')
}
if (v == null || Array.isArray(v)) return ''
return labelMap.value.get(v) ?? String(v)
})
const isSelected = (v: ProductTypeValue) => {
if (isMulti.value) return valueArray.value.includes(v)
const cur = props.modelValue
return !Array.isArray(cur) && cur != null && cur === v
}
const selectSingle = (v: ProductTypeValue) => {
emit('update:modelValue', v)
}
const handleItemClick = (v: ProductTypeValue) => {
if (!isMulti.value) {
selectSingle(v)
return
}
const next = new Set(valueArray.value)
if (next.has(v)) next.delete(v)
else next.add(v)
emit('update:modelValue', Array.from(next))
}
const isGroupAllChecked = (group: ProductTypeGroup) => {
if (!isMulti.value) return false
const set = new Set(valueArray.value)
return group.children.length > 0 && group.children.every((c) => set.has(c.value))
}
const isGroupIndeterminate = (group: ProductTypeGroup) => {
if (!isMulti.value) return false
const set = new Set(valueArray.value)
const checkedCount = group.children.filter((c) => set.has(c.value)).length
return checkedCount > 0 && checkedCount < group.children.length
}
const toggleGroupAll = (group: ProductTypeGroup) => {
if (!group.children?.length) return
const nextAll = group.children.map((c) => c.value)
if (!isMulti.value) {
emit('update:modelValue', nextAll)
return
}
const set = new Set(valueArray.value)
const allChecked = isGroupAllChecked(group)
for (const c of group.children) {
if (allChecked) set.delete(c.value)
else set.add(c.value)
}
emit('update:modelValue', Array.from(set))
}
const clearValue = () => {
emit('update:modelValue', isMulti.value ? [] : null)
}
</script>
<style scoped lang="scss">
.ptf-panel {
padding: 6px 0;
max-height: 360px;
overflow: auto;
:deep(.el-checkbox) {
margin-right: 2px !important;
}
}
.ptf-group {
border: 1px solid #e5e6eb;
border-top: 0;
}
.ptf-group:first-child {
border-top: 1px solid #e5e6eb;
}
.ptf-group-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
background: #fff;
}
.ptf-group-title {
font-size: 14px;
font-weight: 600;
color: #1f2329;
}
.ptf-group-actions {
display: inline-flex;
align-items: center;
gap: 10px;
color: #1f2329;
user-select: none;
}
.ptf-select-all {
cursor: pointer;
font-size: 13px;
}
.ptf-items {
background: #f5f6f7;
padding: 6px 0;
}
.ptf-item {
display: inline-flex;
align-items: center;
gap: 16px;
padding: 0px 12px;
box-sizing: border-box;
cursor: pointer;
}
.ptf-item-label {
font-size: 14px;
color: #1f2329;
}
.ptf-item.active .ptf-item-label {
color: #409eff;
}
.ptf-trigger :deep(.el-input__wrapper) {
cursor: pointer;
}
.ptf-arrow {
color: #909399;
}
</style>
<style lang="scss">
.product-type-filter-popper {
padding: 0 !important;
}
</style>
<template>
<ElDialog
v-model="visible"
title="挂起订单"
width="500px"
:close-on-click-modal="false"
@close="handleClose"
>
<ElForm ref="formRef" :model="form" :rules="rules" label-width="140px">
<ElFormItem label="挂起原因" prop="pauseReason">
<ElSelect
v-model="form.pauseReason"
placeholder="请选择挂起原因"
style="width: 100%"
clearable
>
<ElOption
v-for="item in suspendReasons"
:key="item"
:label="item.label"
:value="item.value"
/>
</ElSelect>
</ElFormItem>
<ElFormItem label="是否需要客户处理" prop="pauseType">
<ElSelect
v-model="form.pauseType"
placeholder="请选择"
style="width: 100%"
clearable
>
<ElOption
v-for="item in customerHandleOptions"
:key="item"
:label="item.label"
:value="item.value"
/>
</ElSelect>
</ElFormItem>
</ElForm>
<template #footer>
<ElButton @click="visible = false">取消</ElButton>
<ElButton type="primary" :loading="submitLoading" @click="handleSubmit">
确认
</ElButton>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { suspendOrderApi } from '@/api/factoryOrderNew'
const emit = defineEmits<{
success: []
}>()
const visible = ref(false)
const submitLoading = ref(false)
const formRef = ref<FormInstance>()
const orderIds = ref<(number | string)[]>([])
const suspendReasons = [
{ label: '客户拦截', value: 1 },
{ label: '地址异常', value: 2 },
{ label: '素材异常', value: 3 },
{ label: '其他', value: 4 },
]
const customerHandleOptions = [
{ label: '需要客户处理', value: 'CUSTOMER' },
{ label: '无需客户处理', value: 'FACTORY' },
]
const form = reactive({
pauseReason: '',
pauseType: '',
})
const rules: FormRules = {
pauseReason: [{ required: true, message: '请选择挂起原因', trigger: 'change' }],
pauseType: [
{ required: true, message: '请选择是否需要客户处理', trigger: 'change' },
],
}
const open = (ids: (number | string)[]) => {
orderIds.value = ids
form.pauseReason = ''
form.pauseType = ''
visible.value = true
}
const handleClose = () => {
formRef.value?.resetFields()
}
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate()
submitLoading.value = true
try {
const res = await suspendOrderApi(orderIds.value, form.pauseReason, form.pauseType)
if (res.code !== 200) return
ElMessage.success('挂起订单成功')
visible.value = false
emit('success')
} catch (e) {
console.error(e)
} finally {
submitLoading.value = false
}
}
defineExpose({ open })
</script>
<template>
<div class="waiting-restock">
<div class="restock-filter">
<ElForm
:inline="true"
:model="filterForm"
size="default"
class="search-form"
>
<ElFormItem label="库存SKU">
<ElInput
v-model="filterForm.warehouseSku"
placeholder="库存SKU"
clearable
style="width: 150px"
/>
</ElFormItem>
<ElFormItem label="款号">
<ElInput
v-model="filterForm.productNo"
placeholder="款号"
clearable
style="width: 150px"
/>
</ElFormItem>
<ElFormItem>
<ElButton type="primary" @click="handleSearch">查询</ElButton>
<ElButton @click="handleReset">重置</ElButton>
</ElFormItem>
</ElForm>
</div>
<div v-loading="loading" class="restock-table">
<TableView :paginated-data="data" :columns="columns" serial-numberable>
<template #operation="{ row }">
<ElButton
type="primary"
link
size="small"
@click="handleRestockCheck(row)"
>
补货校验
</ElButton>
</template>
</TableView>
</div>
<ElPagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[50, 100, 200, 300]"
background
layout="total, sizes, prev, pager, next, jumper"
:total="total"
style="margin: 10px auto 0"
@size-change="onPageSizeChange"
@current-change="onCurrentPageChange"
/>
</div>
</template>
<script setup lang="tsx">
import { reactive } from 'vue'
import { ElMessage } from 'element-plus'
import { getRestockListApi, restockCheckApi } from '@/api/factoryOrderNew'
import type { RestockData } from '@/types/api/factoryOrderNew'
import TableView from '@/components/TableView.vue'
import usePageList from '@/utils/hooks/usePageList'
const filterForm = reactive({
warehouseSku: '',
productNo: '',
})
const {
loading,
currentPage,
pageSize,
total,
data,
onCurrentPageChange,
onPageSizeChange,
refresh,
} = usePageList<RestockData>({
initPageSize: 50,
query: async (current, size) => {
const res = await getRestockListApi(
{
warehouseSku: filterForm.warehouseSku || undefined,
productNo: filterForm.productNo || undefined,
},
current,
size,
)
return res.data
},
})
const columns = [
{
prop: 'warehouseName',
label: '仓库名称',
width: 160,
},
{
label: 'SKU图片',
width: 100,
align: 'center',
render: (row: RestockData) => {
return (
<el-image
src={row.image ?? ''}
style={{ width: '50px', height: '50px' }}
previewSrcList={[row.image ?? '']}
previewTeleported
/>
)
},
},
{
prop: 'skuName',
label: '商品名称',
minWidth: 140,
showOverflowTooltip: true,
},
{ prop: 'productNo', label: '款号', width: 130, align: 'center' },
{
prop: 'warehouseSku',
label: '库存SKU',
width: 200,
align: 'center',
},
{
prop: 'usableInventory',
label: '缺货数量',
width: 90,
align: 'right',
render: (row: RestockData) => {
const v = row.usableInventory ?? 0
return (
<span style="color: #f56c6c; font-weight: bold">{Math.abs(v)}</span>
)
},
},
{
label: '可用数量',
prop: 'usableInventory',
width: 90,
align: 'right',
},
{
prop: 'inventory',
label: '库存数量',
width: 90,
align: 'right',
},
{
prop: 'occupyInventory',
label: '占用数量',
width: 90,
align: 'right',
},
{
key: 'operation',
label: '操作',
width: 120,
align: 'center',
fixed: 'right',
slot: 'operation',
},
]
const handleSearch = () => {
refresh()
}
const handleReset = () => {
filterForm.warehouseSku = ''
filterForm.productNo = ''
refresh()
}
const handleRestockCheck = async (row: RestockData) => {
try {
await ElMessageBox.confirm('确定补货校验吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
} catch {
return
}
try {
const res = await restockCheckApi(
row.id,
row.warehouseSku ?? '',
row.warehouseId,
)
if (res.code !== 200) return
ElMessage.success('补货校验成功')
refresh()
} catch (e) {
console.error(e)
}
}
defineExpose({ refresh })
</script>
<style scoped lang="scss">
.waiting-restock {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.restock-filter {
flex-shrink: 0;
padding-bottom: 10px;
}
.restock-table {
flex: 1;
overflow: auto;
}
.search-form {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0;
min-width: 0;
:deep(.el-form-item) {
margin-right: 10px;
margin-bottom: 10px;
}
:deep(.el-form-item__content) {
min-width: 0;
}
}
</style>
export type ProductTypeValue = string | number
export interface ProductTypeItem {
label: string
value: ProductTypeValue
}
export interface ProductTypeGroup {
label: string
/** 单选时允许直接选择分组本身 */
value: ProductTypeValue
children: ProductTypeItem[]
}
import { ElLoading, ElMessage, ElMessageBox } from 'element-plus'
import type { BaseRespData } from '@/types/api'
interface BatchActionOptions<T = unknown> {
getIds: () => (number | string)[]
api: (ids: (number | string)[]) => Promise<BaseRespData<T>>
confirmText?: string
successText?: string
refreshTree?: boolean
onSuccess?: (res: BaseRespData<T>) => void | Promise<void>
onAfter?: () => void | Promise<void>
}
interface UseOrderBatchActionsOptions {
getIds: () => (number | string)[]
refreshCurrentView: (options?: { isRefreshTree?: boolean }) => void | Promise<void>
}
export function useOrderBatchActions(options: UseOrderBatchActionsOptions) {
const { getIds, refreshCurrentView } = options
const ensureSelection = (msg = '请先选择订单'): boolean => {
const ids = getIds()
if (!ids.length) {
ElMessage.warning(msg)
return false
}
return true
}
const executeBatchAction = async <T = unknown>(action: BatchActionOptions<T>) => {
const ids = action.getIds()
if (!ids.length) {
ElMessage.warning('请先选择订单')
return
}
if (action.confirmText) {
try {
await ElMessageBox.confirm(action.confirmText, '提示', { type: 'warning' })
} catch {
return
}
}
const loading = ElLoading.service({
fullscreen: true,
text: '操作中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
const res = await action.api(ids)
if (res.code !== 200) return
if (action.successText) ElMessage.success(action.successText)
if (action.onSuccess) await action.onSuccess(res)
await refreshCurrentView({ isRefreshTree: !!action.refreshTree })
if (action.onAfter) await action.onAfter()
} catch (e) {
console.error(e)
} finally {
loading.close()
}
}
return {
ensureSelection,
executeBatchAction,
}
}
import { ref } from 'vue'
import { getListCraftApi, allErpCodeListApi } from '@/api/podCnOrder'
import { getUserMarkList, loadWarehouseListApi } from '@/api/common'
import { getAllCountryApi } from '@/api/logistics'
import type { ProductTypeGroup } from '../component/productTypeFilterTypes'
import {
PRODUCT_TYPE_CUSTOM_PART_MULTIPLE,
PRODUCT_TYPE_CUSTOM_PART_SINGLE,
PRODUCT_TYPE_POD_MULTIPLE,
PRODUCT_TYPE_POD_SINGLE,
} from '../utils/productMarkQuery'
import type { IAllList } from '@/types/api/podUsOrder'
import type { CraftListData } from '@/types/api/podCnOrder'
import type { WarehouseListData } from '@/types'
import { getCustomTagListPodOrderApi } from '@/api/factoryOrderNew'
interface LogisticsCodeItem {
code: string
basicsName: string
apiData: unknown
}
const processTypeMap: Record<string, string> = {
TH: '烫画',
ZP: '直喷',
CX: '刺绣',
DK: '雕刻',
BP: '白胚',
QT: '其他',
}
export function useOrderDictionaries() {
const userMarkList = ref<string[]>([])
const receiverCountryList = ref<{ countryCode: string; nameCn: string }[]>([])
const customTagList = ref<{ id: string; name: string }[]>([])
const allCodelist = ref<LogisticsCodeItem[]>([])
const craftList = ref<IAllList[]>([])
const warehouseList = ref<WarehouseListData[]>([])
const sourceList = [
{ name: 'erp推送', id: 'jomall-erp' },
{ name: '第三方推送', id: 'third-party' },
]
const sizes = ['FS', 'XS', 'S', 'M', 'L', 'XL', 'XXL', '3XL', '4XL', '5XL']
const productTypeGroups = ref<ProductTypeGroup[]>([
{
label: '普品',
value: 'normal',
children: [{ label: '普品', value: 'normal' }],
},
{
label: '胚衣',
value: 'custom_normal',
children: [{ label: '胚衣', value: 'custom_normal' }],
},
{
label: 'POD商品',
value: 'pod',
children: [
{ label: '单面', value: PRODUCT_TYPE_POD_SINGLE },
{ label: '多面', value: PRODUCT_TYPE_POD_MULTIPLE },
],
},
{
label: '一件定制局部印',
value: 'custom_part',
children: [
{ label: '单面', value: PRODUCT_TYPE_CUSTOM_PART_SINGLE },
{ label: '多面', value: PRODUCT_TYPE_CUSTOM_PART_MULTIPLE },
],
},
])
const getUserMark = async () => {
try {
const res = await getUserMarkList()
userMarkList.value = res.data
} catch (_e) {
/* empty */
}
}
const getReceiverCountryList = async () => {
try {
const res = await getAllCountryApi()
if (res.code !== 200) return
receiverCountryList.value = res.data
} catch (_e) {
/* empty */
}
}
const getCustomTagList = async () => {
try {
const res = await getCustomTagListPodOrderApi()
if (res.code !== 200) return
customTagList.value = res.data
} catch (_e) {
/* empty */
}
}
const getLogisticsCompanyAllCodelist = async () => {
try {
const res = await allErpCodeListApi()
if (res.code !== 200) return
allCodelist.value = res.data
} catch (_e) {
/* empty */
}
}
const loadCraftList = async () => {
try {
const res = await getListCraftApi()
if (res.code !== 200) return
const data: CraftListData[] = res.data
craftList.value = data.map((item) => ({
id: item.craftCode,
name: item.craftName,
warehouseName: processTypeMap[item.craftType] ?? '其他',
}))
} catch (_e) {
/* empty */
}
}
const loadWarehouseList = async () => {
try {
const res = await loadWarehouseListApi()
if (res.code !== 200) return
warehouseList.value = res.data
} catch (e) {
console.error(e)
}
}
const loadAllDictionaries = async () => {
await Promise.all([
loadCraftList(),
getUserMark(),
getCustomTagList(),
getLogisticsCompanyAllCodelist(),
getReceiverCountryList(),
loadWarehouseList(),
])
}
return {
userMarkList,
receiverCountryList,
customTagList,
allCodelist,
craftList,
warehouseList,
sourceList,
sizes,
productTypeGroups,
loadCraftList,
getUserMark,
getCustomTagList,
getLogisticsCompanyAllCodelist,
getReceiverCountryList,
loadWarehouseList,
loadAllDictionaries,
}
}
import { nextTick, ref, watch, type Ref } from 'vue'
import type { TabsPaneContext } from 'element-plus'
import usePageList from '@/utils/hooks/usePageList'
import {
getFactoryOrderNewDetailApi,
getFactoryOrderNewListApi,
getFactoryOrderNewLogApi,
} from '@/api/factoryOrderNew'
import type { SearchForm } from '@/types/api/factoryOrderNew'
import type {
FactoryOrderNewListData,
LogListData,
ProductListData,
operateOrderListData,
} from '@/types/api/order/factoryOrderNew'
interface UseOrderListAndDetailOptions {
status: Ref<string>
isCardLayout: Ref<boolean>
isTableLayout: Ref<boolean>
getQueryPayload: () => Record<string, unknown>
getListPageAcceptedSubStatus: () => number | undefined
suspendedSubTab: Ref<number>
}
export function useOrderListAndDetail(options: UseOrderListAndDetailOptions) {
const {
status,
isCardLayout,
isTableLayout,
getQueryPayload,
getListPageAcceptedSubStatus,
suspendedSubTab,
} = options
const subLoading = ref(false)
const activeTab = ref<'product' | 'log'>('product')
const productList = ref<ProductListData[]>([])
const logList = ref<LogListData[]>([])
const tableRef = ref()
const currentRow = ref<FactoryOrderNewListData | null>(null)
const selectedRowIds = ref<(number | string)[]>([])
const selectedRows = ref<FactoryOrderNewListData[]>([])
const cardSelectList = ref<operateOrderListData[]>([])
const listSortProp = ref<string | null>(null)
const listSortOrder = ref<'asc' | 'desc' | null>(null)
const buildListQueryBody = (): SearchForm => {
const base = { ...getQueryPayload() } as SearchForm
if (listSortProp.value && listSortOrder.value) {
return {
...base,
prop: listSortProp.value,
order: listSortOrder.value,
}
}
return base
}
const {
loading,
currentPage,
pageSize,
total,
data: tableData,
onCurrentPageChange,
onPageSizeChange,
refresh: refreshTableList,
} = usePageList<FactoryOrderNewListData>({
query: (page, size) => {
const isSuspend = status.value === 'SUSPEND'
return getFactoryOrderNewListApi(
buildListQueryBody(),
page,
size,
status.value === 'ALL' ? undefined : status.value,
getListPageAcceptedSubStatus(),
isSuspend ? suspendedSubTab.value : undefined,
).then(async (res) => {
const records = res.data.records || []
await nextTick(() => {
tableRef.value.setCurrentRow(records[0])
currentRow.value = (records[0] as never) || null
})
return res.data
})
},
})
const getSelectedIds = (): (number | string)[] => {
if (isCardLayout.value) return cardSelectList.value.map((item) => item.id)
return selectedRowIds.value
}
const clearTableState = () => {
selectedRowIds.value = []
selectedRows.value = []
cardSelectList.value = []
productList.value = []
logList.value = []
currentRow.value = null
listSortProp.value = null
listSortOrder.value = null
}
const SORTABLE_COLUMN_PROPS = [
'createTime',
'startStockingTime',
'finishTime',
] as const
/** 表格列 prop → 后端 list_page 排序字段(如 MyBatis 列别名) */
const LIST_SORT_PROP_BY_COLUMN: Record<
(typeof SORTABLE_COLUMN_PROPS)[number],
string
> = {
createTime: 'po.create_time',
startStockingTime: 'po.start_stocking_time',
finishTime: 'po.finish_time',
}
const handleMainTableSortChange = (data: {
prop?: string
order: string | null
}) => {
const { prop, order } = data
if (
!prop ||
!SORTABLE_COLUMN_PROPS.includes(
prop as (typeof SORTABLE_COLUMN_PROPS)[number],
)
) {
return
}
const sortProp = LIST_SORT_PROP_BY_COLUMN[prop as keyof typeof LIST_SORT_PROP_BY_COLUMN]
if (order === 'ascending') {
listSortProp.value = sortProp
listSortOrder.value = 'asc'
} else if (order === 'descending') {
listSortProp.value = sortProp
listSortOrder.value = 'desc'
} else {
listSortProp.value = null
listSortOrder.value = null
}
refreshTableList()
}
const handleMainSelectionChange = (rows: FactoryOrderNewListData[]) => {
selectedRowIds.value = rows.map((row) => row.id)
selectedRows.value = rows
}
const handleCardSelectionChange = (items: operateOrderListData[]) => {
cardSelectList.value = items
}
const getOrderDetailsById = async (tabName?: 'product' | 'log') => {
if (!currentRow.value) {
productList.value = []
logList.value = []
return
}
const id = currentRow.value.id
// const isSuspend = status.value === 'SUSPEND'
const effectiveTab = tabName ?? activeTab.value
subLoading.value = true
try {
if (effectiveTab === 'product') {
productList.value = []
// const productRes = isSuspend
// ? await getSuspendDetailApi(id)
// : await getFactoryOrderNewDetailApi(id)
const productRes = await getFactoryOrderNewDetailApi(id)
if (productRes.code !== 200) return
productList.value = Array.isArray(productRes.data)
? productRes.data
: []
} else {
logList.value = []
// const logRes = isSuspend
// ? await getSuspendLogApi(id)
// : await getFactoryOrderNewLogApi(id)
const logRes = await getFactoryOrderNewLogApi(id)
if (logRes.code !== 200) return
logList.value = Array.isArray(logRes.data) ? logRes.data : []
}
} catch (e) {
console.error(e)
} finally {
subLoading.value = false
}
}
watch(currentRow, (row) => {
if (!row) {
productList.value = []
logList.value = []
return
}
if (!isTableLayout.value) return
void getOrderDetailsById()
})
const handleRowClick = (row: FactoryOrderNewListData) => {
const isSameRow = currentRow.value?.id === row.id
currentRow.value = row
if (isSameRow) {
getOrderDetailsById()
}
}
const handleTabClick = (tab: TabsPaneContext) => {
if (!currentRow.value) return
const name = tab?.props?.name as 'product' | 'log' | undefined
void getOrderDetailsById(name)
}
return {
subLoading,
activeTab,
productList,
logList,
tableRef,
currentRow,
selectedRowIds,
selectedRows,
cardSelectList,
loading,
currentPage,
pageSize,
total,
tableData,
onCurrentPageChange,
onPageSizeChange,
refreshTableList,
getSelectedIds,
clearTableState,
handleMainSelectionChange,
handleCardSelectionChange,
getOrderDetailsById,
handleRowClick,
handleTabClick,
handleMainTableSortChange,
}
}
import { nextTick, ref } from 'vue'
import type { SearchForm } from '@/types/api/order/factoryOrderNew'
import { normalizeProductMarkListForQuery } from '../utils/productMarkQuery'
export function useOrderSearchForm(refreshCurrentView: () => void) {
const searchForm = ref<SearchForm>({})
const dateRange = ref<string[]>([])
const searchVisible = ref(false)
const pickerOptions = {
shortcuts: [
{
text: '今日',
value: () => {
const start = new Date()
start.setHours(0, 0, 0, 0)
const end = new Date()
end.setHours(23, 59, 59, 999)
return [start, end]
},
},
{
text: '最近7天',
value: () => {
const end = new Date()
end.setHours(23, 59, 59, 999)
const start = new Date()
start.setDate(start.getDate() - 6)
start.setHours(0, 0, 0, 0)
return [start, end]
},
},
{
text: '最近30天',
value: () => {
const end = new Date()
end.setHours(23, 59, 59, 999)
const start = new Date()
start.setDate(start.getDate() - 29)
start.setHours(0, 0, 0, 0)
return [start, end]
},
},
],
}
const toggleMulti = (val: boolean) => {
if (searchForm.value.multi === val) {
setTimeout(() => {
if (searchForm.value.multi === val) {
searchForm.value.multi = undefined
}
}, 0)
}
}
const changeReplaceShipment = () => {
searchForm.value.shipmentType = ''
}
const getQueryPayload = () => {
const { productMarkList: rawProductMarks, tagsIdArr, ...rest } =
searchForm.value
const markQuery = normalizeProductMarkListForQuery(rawProductMarks)
const tagsId =
Array.isArray(tagsIdArr) && tagsIdArr.length > 0
? tagsIdArr.join(',')
: undefined
return {
...rest,
...markQuery,
...(tagsId !== undefined ? { tagsId } : {}),
startTime: dateRange.value?.[0] || null,
endTime: dateRange.value?.[1] || null,
}
}
const reset = () => {
searchForm.value = {}
dateRange.value = []
nextTick(() => {
refreshCurrentView()
})
}
return {
searchForm,
dateRange,
searchVisible,
pickerOptions,
toggleMulti,
changeReplaceShipment,
getQueryPayload,
reset,
}
}
import { computed, ref, nextTick } from 'vue'
import {
getPodOrderAcceptedStatisticsApi,
getPodOrderStateGroupListApi,
getSuspendStatisticsApi,
} from '@/api/factoryOrderNew'
import type { StatusTreeNode } from '@/types/api/order/factoryOrderNew'
interface UseOrderStatusTreeOptions {
getQueryPayload: () => Record<string, unknown>
currentPage: { value: number }
pageSize: { value: number }
onClearTableState: () => void
onRefreshCurrentView: () => void
}
export function useOrderStatusTree(options: UseOrderStatusTreeOptions) {
const {
getQueryPayload,
currentPage,
pageSize,
onClearTableState,
onRefreshCurrentView,
} = options
const cardLayoutStatuses = [
'PENDING_SCHEDULE',
'PENDING_PICK',
'PENDING_REPLENISH',
'IN_PRODUCTION',
'PENDING_PACKING',
]
const specialLayoutStatuses = ['BATCH_MANAGE', 'AWAITING_RESTOCK']
const statusTree = ref<StatusTreeNode[]>()
const status = ref<string>('PENDING_RECEIVE')
const pendingAcceptSubTab = ref<
'PENDING_RECEIVE' | 'ACCEPT_FAIL_OUT_OF_STOCK'
>('PENDING_RECEIVE')
const pendingAcceptCounts = ref<{
acceptedOutOfStockCount?: number
pendingCount?: number
totalCount?: number
}>({
pendingCount: 0,
acceptedOutOfStockCount: 0,
})
const suspendedTabs = ref([
{ label: '客户拦截', key: 'customerInterceptCount', value: 1, count: 0 },
{ label: '地址异常', key: 'addressExceptionCount', value: 2, count: 0 },
{ label: '素材异常', key: 'materialExceptionCount', value: 3, count: 0 },
{ label: '其他', key: 'otherReasonCount', value: 4, count: 0 },
])
const suspendedSubTab = ref(1)
const treeRef = ref()
const isCardLayout = computed(() => cardLayoutStatuses.includes(status.value))
const isSpecialLayout = computed(() =>
specialLayoutStatuses.includes(status.value),
)
const isTableLayout = computed(
() => !isCardLayout.value && !isSpecialLayout.value,
)
const getListPageAcceptedSubStatus = () =>
status.value === 'PENDING_RECEIVE'
? pendingAcceptSubTab.value === 'PENDING_RECEIVE'
? 0
: 2
: undefined
const getPendingReceiveCounts = async () => {
try {
const res = await getPodOrderAcceptedStatisticsApi(
getQueryPayload(),
currentPage.value,
pageSize.value,
status.value,
getListPageAcceptedSubStatus(),
)
if (res.code !== 200) return
pendingAcceptCounts.value = res.data || {
pendingCount: 0,
acceptedOutOfStockCount: 0,
}
} catch (e) {
console.error(e)
}
}
const getSuspendCounts = async () => {
try {
const res = await getSuspendStatisticsApi(
getQueryPayload(),
currentPage.value,
pageSize.value,
suspendedSubTab.value,
)
if (res.code !== 200 || !res.data) return
const data = res.data || {}
suspendedTabs.value = suspendedTabs.value.map((tab) => ({
...tab,
count: data[String(tab.key)] ?? 0,
}))
} catch (e) {
console.error(e)
}
}
const loadStatusTreeCounts = async () => {
try {
const res = await getPodOrderStateGroupListApi()
statusTree.value = [
{ status: 'ALL', statusName: '全部', quantity: 0, children: res.data },
]
nextTick(() => {
treeRef.value?.setCurrentKey(status.value, true)
})
} catch (e) {
console.error(e)
}
}
const handleStatusNodeClick = (node: StatusTreeNode) => {
status.value = node.status
if (node.status !== 'PENDING_RECEIVE') {
pendingAcceptSubTab.value = 'PENDING_RECEIVE'
}
if (node.status !== 'SUSPEND') {
suspendedSubTab.value = 1
}
onClearTableState()
if (!isSpecialLayout.value) {
onRefreshCurrentView()
}
if (node.status === 'SUSPEND') {
void getSuspendCounts()
}
}
const handlePendingAcceptTabClick = (
tab: 'PENDING_RECEIVE' | 'ACCEPT_FAIL_OUT_OF_STOCK',
refreshTableList: () => void,
) => {
if (pendingAcceptSubTab.value === tab) return
pendingAcceptSubTab.value = tab
refreshTableList()
}
const toggleExpand = (node: { expanded?: boolean }) => {
node.expanded = !node.expanded
}
return {
cardLayoutStatuses,
specialLayoutStatuses,
statusTree,
status,
pendingAcceptSubTab,
pendingAcceptCounts,
suspendedTabs,
suspendedSubTab,
treeRef,
isCardLayout,
isSpecialLayout,
isTableLayout,
getListPageAcceptedSubStatus,
getPendingReceiveCounts,
getSuspendCounts,
loadStatusTreeCounts,
handleStatusNodeClick,
handlePendingAcceptTabClick,
toggleExpand,
}
}
<template>
<div class="page card h-100 flex-gap-10 overflow-hidden flex">
<div class="order-status">
<ElTree
ref="treeRef"
default-expand-all
:expand-on-click-node="false"
:default-expanded-keys="['ALL']"
:highlight-current="true"
node-key="status"
:data="statusTree"
:props="{ children: 'children', label: 'statusName' }"
class="status-tree"
@node-click="handleStatusNodeClick"
>
<template #default="{ node, data }">
<div class="tree-node">
<div class="tree-node-label-wrapper">
<span class="tree-node-label">{{ data.statusName }}</span>
<span
v-if="
data.status !== 'ALL' &&
(data.quantity || data.quantity === 0)
"
class="tree-node-count"
>
{{ `(${data.quantity})` }}
</span>
</div>
<span
v-if="data.children && data.children.length"
class="tree-node-expand"
@click.stop="toggleExpand(node)"
>
<el-icon
><ArrowUp v-if="node.expanded" /><ArrowDown v-else
/></el-icon>
</span>
</div>
</template>
</ElTree>
</div>
<div class="order-content flex-1 flex-column overflow-hidden">
<div v-if="!isSpecialLayout" class="header">
<div class="header-filter-form">
<ElForm
class="search-form"
:model="searchForm"
:inline="true"
label-width="70px"
>
<ElFormItem label="平台">
<ElSelect
v-model="searchForm.platform"
placeholder="请选择"
clearable
filterable
popper-class="customize-select-style"
style="width: 140px"
>
<ElOption
v-for="(item, index) in platformJson"
:key="index"
:label="item.type"
:value="item.type"
style="width: 160px"
>
<img
:src="`/images/icon/${item.icon.split('/').pop()}`"
style="height: 20px; margin: 5px 10px 0 0"
/>
<span :title="item.type">{{ item.type }}</span>
</ElOption>
</ElSelect>
</ElFormItem>
<ElFormItem label="工艺">
<LogisticsWaySelect
v-model="searchForm.craftCode"
:company-list="craftList"
:start-width="'150px'"
search-placeholder="搜索工艺名称"
start-placeholder="请选择工艺名称"
></LogisticsWaySelect>
</ElFormItem>
<ElFormItem label="库存SKU">
<ElInput
v-model.trim="searchForm.thirdSkuCode"
placeholder="库存SKU"
clearable
style="width: 140px"
/>
</ElFormItem>
<ElFormItem label="店铺单号">
<ElInput
v-model.trim="searchForm.shopNumber"
placeholder="店铺单号"
clearable
style="width: 150px"
/>
</ElFormItem>
<ElFormItem
v-if="cardLayoutStatuses.includes(status)"
label="批次号"
>
<ElInput
v-model.trim="searchForm.batchArrangeNumber"
placeholder="批次号"
clearable
style="width: 140px"
/>
</ElFormItem>
<ElFormItem
v-if="cardLayoutStatuses.includes(status)"
label="操作单号"
>
<ElInput
v-model.trim="searchForm.operationNo"
placeholder="操作单号"
clearable
style="width: 140px"
/>
</ElFormItem>
<ElFormItem label="商品类型">
<ProductTypeFilter
v-model="searchForm.productMarkList"
:options="productTypeGroups"
:multiple="true"
placeholder="请选择商品类型"
width="140px"
/>
</ElFormItem>
<ElFormItem label="数量">
<el-radio-group v-model="searchForm.multi">
<el-radio-button :value="false" @click="toggleMulti(false)"
>单件</el-radio-button
>
<el-radio-button :value="true" @click="toggleMulti(true)"
>多件</el-radio-button
>
</el-radio-group>
</ElFormItem>
<ElFormItem>
<ElPopover placement="bottom" width="600" trigger="click">
<ElForm
ref="searchFormPopoverRef"
:model="searchForm"
size="default"
inline
label-width="100px"
>
<ElFormItem style="width: 100%; padding-right: 40px">
<div style="width: 100%; display: flex; flex-wrap: nowrap">
<el-select
v-model="searchForm.timeType"
clearable
:teleported="false"
placeholder="时间类型"
style="flex: 1; margin-right: 10px"
>
<el-option :value="1" label="创建时间"></el-option>
<el-option :value="2" label="确认时间"></el-option>
<el-option :value="3" label="完成时间"></el-option>
</el-select>
<el-date-picker
v-model="dateRange"
:teleported="false"
:default-time="[
new Date(0, 0, 0, 0, 0, 0),
new Date(0, 0, 0, 23, 59, 59),
]"
value-format="YYYY-MM-DD HH:mm:ss"
type="datetimerange"
style="width: 280px"
:shortcuts="pickerOptions.shortcuts"
start-placeholder="开始时间"
end-placeholder="结束时间"
clearable
/>
</div>
</ElFormItem>
<ElFormItem label="订单号">
<ElInput
v-model.trim="searchForm.factoryOrderNumber"
placeholder="订单号"
clearable
style="width: 150px"
/>
</ElFormItem>
<ElFormItem label="客户">
<el-select
v-model="searchForm.userMark"
clearable
filterable
:teleported="false"
style="width: 150px"
placeholder="客户"
>
<el-option
v-for="item in userMarkList"
:key="item"
:value="item"
:label="item"
></el-option>
</el-select>
</ElFormItem>
<ElFormItem label="Variant SKU">
<ElInput
v-model.trim="searchForm.sku"
placeholder="Variant SKU"
clearable
style="width: 150px"
/>
</ElFormItem>
<ElFormItem label="款号">
<ElInput
v-model.trim="searchForm.supplierProductNo"
placeholder="款号"
clearable
style="width: 150px"
/>
</ElFormItem>
<ElFormItem label="物流跟踪号">
<ElInput
v-model.trim="searchForm.trackingNumber"
placeholder="物流跟踪号"
clearable
style="width: 150px"
/>
</ElFormItem>
<ElFormItem
v-if="!cardLayoutStatuses.includes(status)"
label="收件国家"
>
<ElSelect
v-model="searchForm.receiverCountry"
placeholder="收件国家"
clearable
multiple
:teleported="false"
style="width: 150px"
filterable
@change="changeReplaceShipment"
>
<ElOption
v-for="item in receiverCountryList"
:key="item.countryCode"
:value="item.countryCode"
:label="item.nameCn + '(' + item.countryCode + ')'"
></ElOption>
</ElSelect>
</ElFormItem>
<ElFormItem label="物流类型">
<ElSelect
v-model="searchForm.shipmentType"
placeholder="物流类型"
clearable
:teleported="false"
style="width: 150px"
>
<ElOption
v-for="(item, index) in [
'自有物流',
'九猫统筹物流',
'自提',
]"
:key="index"
:value="index"
:label="item"
></ElOption>
</ElSelect>
</ElFormItem>
<ElFormItem label="客户标签">
<ElSelect
v-model="searchForm.tagsIdArr"
placeholder="请选择客户标签"
clearable
filterable
multiple
collapse-tags
collapse-tags-tooltip
:teleported="false"
style="width: 150px"
>
<ElOption
v-for="item in customTagList"
:key="item.id"
:value="item.id"
:label="item.name"
></ElOption>
</ElSelect>
</ElFormItem>
<ElFormItem label="自有物流公司">
<ElSelect
v-model="searchForm.logisticsCompanyCode"
placeholder="请选择"
clearable
filterable
:teleported="false"
style="width: 150px"
>
<ElOption
v-for="(item, index) in allCodelist"
:key="index"
:value="item.code"
:label="item.basicsName"
></ElOption>
</ElSelect>
</ElFormItem>
<ElFormItem label="规范素材">
<ElSelect
v-model="searchForm.newStandard"
placeholder="请选择"
clearable
filterable
style="width: 150px"
>
<ElOption
v-for="(_, index) in ['否', '是']"
:key="index"
:value="index"
:label="index === 0 ? '否' : '是'"
></ElOption>
</ElSelect>
</ElFormItem>
</ElForm>
<template #reference>
<el-button
type="warning"
@click="searchVisible = !searchVisible"
>
<el-icon v-if="searchVisible">
<CaretTop />
</el-icon>
<el-icon v-else>
<CaretBottom />
</el-icon>
</el-button>
</template>
</ElPopover>
</ElFormItem>
<ElFormItem>
<ElButton type="primary" @click="() => refreshCurrentView()">
查询
</ElButton>
<ElButton @click="reset"> 重置 </ElButton>
</ElFormItem>
<ElFormItem v-if="status === 'ALL'">
<ElButton type="success" @click="exportData"> 导出 </ElButton>
</ElFormItem>
</ElForm>
</div>
</div>
<!-- ====== 操作按钮栏 ====== -->
<div v-if="!isSpecialLayout" class="operation-list">
<span v-if="status === 'PENDING_RECEIVE'" class="item">
<ElButton type="success" @click="handleConfirmOrder">
{{
pendingAcceptSubTab === 'ACCEPT_FAIL_OUT_OF_STOCK'
? '重新接单'
: '确认接单'
}}
</ElButton>
</span>
<span
v-if="status === 'PENDING_RECEIVE' || status === 'SUSPEND'"
class="item"
>
<ElButton type="danger" @click="handleCancelOrder">取消订单</ElButton>
</span>
<ElDropdown
v-if="status === 'PENDING_CREATE_LOGISTICS'"
trigger="click"
@command="handleLogisticsCommand"
>
<span class="item">
<ElButton type="warning">
物流接口 <el-icon class="el-icon--right"><ArrowDown /></el-icon>
</ElButton>
</span>
<template #dropdown>
<ElDropdownMenu>
<ElDropdownItem
command="createLogistic"
:disabled="
cardSelectList.some((item) => item.shipmentType !== 1)
"
>创建物流订单</ElDropdownItem
>
<ElDropdownItem
command="getTrackingNumber"
:disabled="
cardSelectList.some((item) => item.shipmentType !== 1)
"
>获取跟踪号</ElDropdownItem
>
<ElDropdownItem
command="getPrintOrder"
:disabled="
cardSelectList.some((item) => item.shipmentType !== 1)
"
>获取打印面单</ElDropdownItem
>
<ElDropdownItem
command="cancelLogistic"
:disabled="
cardSelectList.some((item) => item.shipmentType !== 1)
"
>取消物流订单</ElDropdownItem
>
</ElDropdownMenu>
</template>
</ElDropdown>
<span v-if="status === 'PENDING_CREATE_LOGISTICS'" class="item">
<ElButton type="success" @click="handleUpdateCustomsInfo"
>更新报关信息</ElButton
>
</span>
<span v-if="status === 'PENDING_CREATE_LOGISTICS'" class="item">
<ElButton type="primary" @click="handleTransferToArrange">
转至待排单
</ElButton>
</span>
<!-- <span v-if="status === 'PENDING_RECEIVE'" class="item">
<ElButton type="warning" @click="handleTransferOldFlow"
>转旧流程</ElButton
>
</span> -->
<span v-if="status === 'PENDING_SCHEDULE'" class="item">
<ElButton type="primary" @click="handleArrange">排单</ElButton>
</span>
<ElDropdown
v-if="
status === 'PENDING_PICK' ||
status === 'PENDING_REPLENISH' ||
status === 'IN_PRODUCTION'
"
trigger="click"
@command="handleDtfCommand"
>
<span class="item">
<ElButton type="primary">
DTF排版 <el-icon class="el-icon--right"><ArrowDown /></el-icon>
</ElButton>
</span>
<template #dropdown>
<ElDropdownMenu>
<!-- <ElDropdownItem command="tiff_42">TIF(40+2cm)</ElDropdownItem>
<ElDropdownItem command="tiff_60">TIF(60cm)</ElDropdownItem> -->
<ElDropdownItem command="png_42">PNG(40+2cm)</ElDropdownItem>
<ElDropdownItem command="png_60">PNG(60cm)</ElDropdownItem>
</ElDropdownMenu>
</template>
</ElDropdown>
<span
v-if="
status === 'PENDING_PICK' ||
status === 'PENDING_REPLENISH' ||
status === 'IN_PRODUCTION'
"
class="item"
>
<ElButton type="success" @click="handlePrintProductionOrder"
>打印生产单</ElButton
>
</span>
<span v-if="status === 'PENDING_PICK'" class="item">
<ElButton type="warning" @click="handlePrintPickOrder"
>打印拣货单</ElButton
>
</span>
<span v-if="status === 'PENDING_PICK'" class="item">
<ElButton type="success" @click="handlePickComplete"
>拣胚完成</ElButton
>
</span>
<span v-if="status === 'PENDING_PICK'" class="item">
<ElButton type="danger" @click="handlePickFail">拣胚失败</ElButton>
</span>
<span v-if="status === 'PENDING_REPLENISH'" class="item">
<ElButton type="success" @click="handleReplenishComplete"
>补胚完成</ElButton
>
</span>
<span v-if="status === 'PENDING_REPLENISH'" class="item">
<ElButton type="danger" @click="handleReplenishFail"
>补胚失败</ElButton
>
</span>
<span v-if="status === 'PENDING_PACKING'" class="item">
<ElButton type="success" @click="handleSeedingWall"
>播种墙配货</ElButton
>
</span>
<span v-if="status === 'PENDING_PACKING'" class="item">
<ElButton type="primary" @click="handleSinglePrint"
>单件打单</ElButton
>
</span>
<span
v-if="status === 'IN_PRODUCTION' || status === 'PENDING_PACKING'"
class="item"
>
<ElButton type="warning" @click="handleApplyReplenish"
>申请补胚</ElButton
>
</span>
<span v-if="status === 'IN_PRODUCTION'" class="item">
<ElButton type="success" @click="handleQuickProduction"
>快捷生产</ElButton
>
</span>
<span v-if="status === 'IN_PRODUCTION'" class="item">
<ElButton type="success" @click="handleProductionComplete"
>生产完成</ElButton
>
</span>
<span v-if="status === 'PENDING_DELIVERY'" class="item">
<ElButton type="primary" @click="handleWeightSort">称重分拣</ElButton>
</span>
<!-- 已完成 -->
<template v-if="status === 'COMPLETED'">
<span class="item">
<ElButton type="success" @click="handleArchiveOrder"
>订单归档</ElButton
>
</span>
<span class="item">
<ElButton type="primary" @click="handleGetTrackingNumber"
>获取跟踪号</ElButton
>
</span>
<span class="item">
<ElButton type="warning" @click="handleStatusPush"
>状态推送</ElButton
>
</span>
</template>
<!-- 挂起 -->
<template v-if="status === 'SUSPEND'">
<span class="item">
<ElButton type="warning" @click="() => handleCancelSuspend()"
>取消挂起</ElButton
>
</span>
<span class="item">
<ElButton type="primary" @click="handleSyncAddress"
>同步收货地址</ElButton
>
</span>
</template>
<span
v-if="
[
'PENDING_CREATE_LOGISTICS',
'PENDING_SCHEDULE',
'PENDING_DELIVERY',
'PICKING',
].includes(status)
"
class="item"
>
<ElButton type="danger" @click="handleSuspend">挂起</ElButton>
</span>
<span v-if="status === 'PENDING_PACKING'" class="item">
<ElButton type="success" @click="handlePrintWarehouseSkuTag"
>打印库存SKU标签</ElButton
>
</span>
<span
v-if="
status === 'PENDING_SCHEDULE' ||
status === 'PENDING_REPLENISH' ||
status === 'IN_PRODUCTION' ||
status === 'PENDING_DELIVERY' ||
status === 'SUSPEND' ||
status === 'PENDING_PICK'
"
class="item"
>
<ElButton type="success" @click="handleDownloadMaterial"
>下载素材</ElButton
>
</span>
<span
v-if="
[
'PENDING_RECEIVE',
'PENDING_CREATE_LOGISTICS',
'PENDING_SCHEDULE',
'PENDING_PICK',
'PENDING_REPLENISH',
'IN_PRODUCTION',
'PENDING_PACKING',
].includes(status)
"
class="item"
>
<ElButton type="primary" @click="handleRefreshProductInfo"
>刷新商品信息</ElButton
>
</span>
</div>
<div v-if="status === 'SUSPEND'" class="status-subtabs">
<div
v-for="tab in suspendedTabs"
:key="tab.value"
class="status-subtab"
:class="{ active: suspendedSubTab === tab.value }"
@click="handleSuspendTabClick(tab.value)"
>
{{ tab.label }}({{ tab.count || 0 }})
</div>
</div>
<div v-if="status === 'PENDING_RECEIVE'" class="status-subtabs">
<div
class="status-subtab"
:class="{ active: pendingAcceptSubTab === 'PENDING_RECEIVE' }"
@click="handlePendingAcceptTabClick('PENDING_RECEIVE')"
>
待接单<span> ({{ pendingAcceptCounts.pendingCount }}) </span>
</div>
<div
class="status-subtab"
:class="{
active: pendingAcceptSubTab === 'ACCEPT_FAIL_OUT_OF_STOCK',
}"
@click="handlePendingAcceptTabClick('ACCEPT_FAIL_OUT_OF_STOCK')"
>
接单失败-缺货<span>
({{ pendingAcceptCounts?.acceptedOutOfStockCount || 0 }})
</span>
</div>
</div>
<!-- 批次管理 -->
<BatchManageTable v-if="status === 'BATCH_MANAGE'" ref="batchManageRef" />
<!-- 等待补货 -->
<WaitingRestockTable
v-if="status === 'AWAITING_RESTOCK'"
ref="waitingRestockRef"
/>
<!-- 到派单、配货中子状态 -->
<div v-if="isCardLayout" :key="status" class="card-content">
<CardLayout
ref="cardLayoutRef"
:status="status"
:query-payload="getQueryPayload()"
@selection-change="handleCardSelectionChange"
@view-detail="handleViewDetail"
/>
</div>
<!-- 表格布局 -->
<div v-if="isTableLayout" class="table-content">
<splitDiv size="55">
<template #top>
<div
v-loading="loading"
element-loading-text="加载中..."
class="table-list flex-1 overflow-hidden"
>
<TableView
ref="tableRef"
highlight-current-row
:paginated-data="tableData"
:columns="mainColumns"
serial-numberable
selectionable
@selection-change="handleMainSelectionChange"
@sort-change="handleMainTableSortChange"
@row-click="handleRowClick"
>
<template #operation="{ row }">
<div class="main-table-operation">
<ElButton
v-if="status === 'PENDING_RECEIVE'"
type="primary"
link
size="small"
@click.stop="handleSingleConfirmOrder(row)"
>
{{
pendingAcceptSubTab === 'ACCEPT_FAIL_OUT_OF_STOCK'
? '重新接单'
: '接单'
}}
</ElButton>
<ElButton
v-if="showPendingLogisticsUpdateAddress(row)"
type="primary"
link
size="small"
@click.stop="openUpdateReceiverAddress(row)"
>
修改地址
</ElButton>
<ElButton
v-if="status === 'PENDING_CREATE_LOGISTICS'"
type="primary"
link
size="small"
@click.stop="handleRefreshReceiverAddress(row)"
>
刷新地址
</ElButton>
<ElButton
v-if="status === 'SUSPEND'"
type="primary"
link
size="small"
@click.stop="handleCancelSuspend(row)"
>
取消挂起
</ElButton>
</div>
</template>
</TableView>
</div>
<div class="table-pagination-bar">
<div class="selected-count-text">
已选择
<strong style="color: red">{{ selectedRows.length }}</strong>
条数据
</div>
<ElPagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[50, 100, 200, 300]"
background
layout="total, sizes, prev, pager, next, jumper"
:total="total"
style="margin: 0 auto"
@size-change="onPageSizeChange"
@current-change="onCurrentPageChange"
/>
</div>
</template>
<template #bottom>
<el-tabs
v-model="activeTab"
class="tabs-wrapper"
@tab-click="handleTabClick"
>
<el-tab-pane name="product" label="包含商品">
<div
v-loading="subLoading"
element-loading-text="加载中..."
class="sub-table-wrapper"
>
<TableView
:paginated-data="productList"
:columns="productColumns"
serial-numberable
/>
</div>
</el-tab-pane>
<el-tab-pane name="log" label="操作日志">
<div
v-loading="subLoading"
element-loading-text="加载中..."
class="detail-table-content"
>
<LogList style="height: 100%" :log-list="logList" />
<div v-if="!logList.length" class="empty-content">
暂无数据
</div>
</div>
</el-tab-pane>
</el-tabs>
</template>
</splitDiv>
</div>
</div>
<ConfirmOrderDialog
ref="confirmOrderDialogRef"
@success="(data) => openResultInfoDialog(data)"
/>
<CancelOrderDialog
ref="cancelOrderDialogRef"
@success="() => refreshCurrentView({ isRefreshTree: true })"
/>
<SuspendDialog
ref="suspendDialogRef"
@success="() => refreshCurrentView({ isRefreshTree: true })"
/>
<PickCompleteDialog
ref="pickCompleteDialogRef"
@success="() => refreshCurrentView({ isRefreshTree: true })"
/>
<PickFailDialog
ref="pickFailDialogRef"
@success="() => refreshCurrentView({ isRefreshTree: true })"
/>
<CreateLogisticDialog
ref="createLogisticDialogRef"
:new-order-selection="selectedRows"
@show-result="createLogisticsSuccess"
/>
<UpdateCustomDeclarationInfoDialog
ref="updateCustomsDialogRef"
v-model="updateCustomsDialogVisible"
:order-selection="selectedRows"
:is-new-order="true"
@refresh-table="refreshCurrentView"
/>
<WeightDialog
ref="weightDialogRef"
:is-new-order="true"
:list-by-no-api="listByNoPodOrderApi"
:order-weighing-api="orderWeighingPodOrderApi"
:build-order-weighing-payload="
(rows) => ({ podOrderWeighingParams: rows })
"
@update-list="() => refreshCurrentView({ isRefreshTree: true })"
/>
<FastProduction
v-model:detail-visible="detailVisible"
:is-new-order="true"
:detail-data="detailData"
:fast-key="fastKey"
:default-auto-sure="true"
:show-operation-no-row="true"
not-found-message="操作单不存在"
pending-order-label="操作单号"
please-scan-tip="请扫码操作单号"
search-input-audio-tip="请录入操作单号"
history-storage-key="historyFactoryOrderNewData"
tracking-placeholder="扫描枪输入操作单号,录入下一单本单自动生产完成,最后一单扫两次完成生产"
:query-api="getOperationByNo"
:complete-api="completeOperationById"
:download-api="downloadOperationById"
@on-success="handleFastProductionSuccess"
@close="fastClose"
/>
<ArrangeDialog
ref="arrangeDialogRef"
:submit-api="submitArrangeFinishApi"
@success="() => refreshCurrentView({ isRefreshTree: true })"
/>
<OperateDetailsDialog
ref="operateDetailsDialogRef"
title="申请补胚"
@adjust-pick-order-success="adjustPickOrderSuccess"
/>
<PodMakeOrder
v-model="podOrderVisible"
:print-order="printOrder"
:warehouse-list="warehouseList"
:is-new-order="true"
ws-open-code="STARTORDERNEWPOD"
ws-close-code="ENDORDERNEWPOD"
init-url="factory/podOrderPacking/local/getPodBoxOrderDetails"
@set-printer="handlePrinterChange"
@set-warehouse-id="handleWarehouseIdChange"
@refresh="() => refreshCurrentView({ isRefreshTree: true })"
/>
<PodDistributionOrder
v-model="podDistributionOrderVisible"
:is-new-order="true"
:print-order="printOrder"
:warehouse-list="warehouseList"
:query-api="getSingleQueryPodOrderApi"
:submit-complete-api="submitPodPrintOrderComplete"
:print-label-api="getPrintLogisticLabelFactory"
:map-order-param-list-to-submit-items="mapOrderParamListToSubmitItems"
warehouse-session-key="locaclFactorySingel"
@set-printer="handlePrinterChange"
@refresh="() => refreshCurrentView({ isRefreshTree: true })"
/>
<PrintWarehouseSkuTag ref="printWarehouseSkuDialogRef" />
<ResultInfo
ref="resultRefs"
:list="resultInfo"
@confirm="() => refreshCurrentView({ isRefreshTree: true })"
></ResultInfo>
<UpdateAddress
v-if="updateReceiverAddressVisible"
v-model:form="receiverAddressForm"
v-model:visible="updateReceiverAddressVisible"
:country-list="receiverCountryList"
:submit-address-api="submitFactoryOrderReceiverAddress"
@success="() => refreshCurrentView({ isRefreshTree: true })"
/>
<ElDialog
v-model="exportVisible"
title="导出选项"
width="500px"
:close-on-click-modal="false"
>
<el-form :model="exportForm" label-width="80px">
<el-form-item label="" prop="resource">
<el-radio-group v-model="exportForm.resource">
<el-radio :label="0">导出本页</el-radio>
<el-radio :label="1">导出选中</el-radio>
<el-radio :label="2">全部</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="exportVisible = false">取消</el-button>
<el-button type="primary" @click="submitExportForm">确认</el-button>
</span>
</template>
</ElDialog>
</div>
</template>
<script setup lang="tsx">
import {
ArrowDown,
ArrowUp,
CaretTop,
CaretBottom,
Edit,
} from '@element-plus/icons-vue'
import { computed, nextTick, onMounted, ref } from 'vue'
import splitDiv from '@/components/splitDiv/splitDiv.vue'
import TableView from '@/components/TableView.vue'
import ResultInfo from '@/views/order/components/ResultInfo.vue'
import type { BaseRespData } from '@/types/api'
import type {
FactoryOrderNewListData,
ProductListData,
ExportParams,
operateOrderListData,
} from '@/types/api/order/factoryOrderNew'
import type { AddressInfo } from '@/types/api/podCnOrder'
import platformJson from '../../../json/platform.json'
import {
refreshProductInfoApi,
// transferOldFlowApi,
cancelSuspendApi,
archiveOrderApi,
printPickOrderApi,
applyReplenishApi,
getTrackingNumberApi,
getfaceSimplexFileApi,
cancelLogisticsOrderApi,
updateLogisticsToArrangeApi,
downloadMaterialApi,
downloadOperationMaterialApi,
composingNewPodOrderDesignImages,
printNewPodOrderProductionOrderApi,
syncReceiverAddress,
updateReceiverAddressApi,
updateRemarkApi,
completeDeliveryApi,
getByOperationNoLogApi,
listByNoPodOrderApi,
orderWeighingPodOrderApi,
getSingleQueryPodOrderApi,
submitPodPrintOrderCompleteApi,
getFactoryOrderNewOperateDetailApi,
statusPushApi,
arrangeFinishApi,
exportFactoryOrderInfo,
} from '@/api/factoryOrderNew'
import BigNumber from 'bignumber.js'
import { filePath } from '@/api/axios'
import { OrderData } from '@/types/api/podMakeOrder'
import useLodop from '@/utils/hooks/useLodop'
import { printWithLodop } from '@/utils/lodopPrinter'
import LogisticsWaySelect from '@/views/logistics/components/LogisticsWaySelect'
import ProductTypeFilter from './component/ProductTypeFilter.vue'
import ConfirmOrderDialog from './component/ConfirmOrderDialog.vue'
import CancelOrderDialog from './component/CancelOrderDialog.vue'
import SuspendDialog from './component/SuspendDialog.vue'
import PickCompleteDialog from './component/PickCompleteDialog.vue'
import PickFailDialog from './component/PickFailDialog.vue'
import CardLayout from './component/CardLayout.vue'
import BatchManageTable from './component/BatchManageTable.vue'
import WaitingRestockTable from './component/WaitingRestockTable.vue'
import ArrangeDialog from './component/ArrangeDialog.vue'
import OperateDetailsDialog from './component/OperateDetailsDialog.vue'
import CreateLogisticDialog from '@/views/order/components/CreateLogisticDialog.vue'
import UpdateCustomDeclarationInfoDialog from '@/views/order/components/UpdateCustomDeclarationInfoDialog.vue'
import WeightDialog from '@/views/order/components/WeightDialog.vue'
import FastProduction from '@/views/order/components/FastProduction.vue'
import PodMakeOrder from '@/views/order/podUs/PodMakeOrder.vue'
import PodDistributionOrder from '@/views/order/podCN/PodDistributionOrder.vue'
import PrintWarehouseSkuTag from '@/views/order/components/printWarehouseSkuTag.vue'
import UpdateAddress from '@/views/order/podCN/components/updateAddress.vue'
import { ResultInfoDataItem } from '@/types/api/order/common'
import { useOrderDictionaries } from './hooks/useOrderDictionaries'
import { useOrderSearchForm } from './hooks/useOrderSearchForm'
import { useOrderStatusTree } from './hooks/useOrderStatusTree'
import { useOrderListAndDetail } from './hooks/useOrderListAndDetail'
import { useOrderBatchActions } from './hooks/useOrderBatchActions'
import { ElTag } from 'element-plus'
const resultInfo = ref<ResultInfoDataItem[]>([])
const resultRefs = ref()
const batchManageRef = ref<InstanceType<typeof BatchManageTable>>()
const waitingRestockRef = ref<InstanceType<typeof WaitingRestockTable>>()
const cardLayoutRef = ref<InstanceType<typeof CardLayout>>()
const exportVisible = ref(false)
const exportForm = ref({
resource: '',
})
const {
userMarkList,
receiverCountryList,
customTagList,
allCodelist,
craftList,
warehouseList,
productTypeGroups,
loadAllDictionaries,
} = useOrderDictionaries()
const refreshCurrentViewProxy = ref<() => void>(() => {})
const {
searchForm,
dateRange,
searchVisible,
pickerOptions,
toggleMulti,
changeReplaceShipment,
getQueryPayload,
reset,
} = useOrderSearchForm(() => refreshCurrentViewProxy.value())
const statusCurrentPageRef = ref(1)
const statusPageSizeRef = ref(50)
const {
cardLayoutStatuses,
statusTree,
status,
pendingAcceptSubTab,
pendingAcceptCounts,
suspendedTabs,
suspendedSubTab,
treeRef,
isCardLayout,
isSpecialLayout,
isTableLayout,
getListPageAcceptedSubStatus,
getPendingReceiveCounts,
getSuspendCounts,
loadStatusTreeCounts,
handlePendingAcceptTabClick: handlePendingAcceptTabClickRaw,
toggleExpand,
} = useOrderStatusTree({
getQueryPayload,
currentPage: statusCurrentPageRef,
pageSize: statusPageSizeRef,
onClearTableState: () => {},
onRefreshCurrentView: () => {},
})
const {
subLoading,
activeTab,
productList,
logList,
tableRef,
selectedRows,
cardSelectList,
loading,
currentPage,
pageSize,
total,
tableData,
onCurrentPageChange,
onPageSizeChange,
refreshTableList,
getSelectedIds,
clearTableState,
handleMainSelectionChange,
handleCardSelectionChange,
getOrderDetailsById,
handleRowClick,
handleTabClick,
handleMainTableSortChange,
} = useOrderListAndDetail({
status,
isCardLayout,
isTableLayout,
getQueryPayload,
getListPageAcceptedSubStatus,
suspendedSubTab,
})
statusCurrentPageRef.value = currentPage.value
statusPageSizeRef.value = pageSize.value
const exportData = () => {
exportVisible.value = true
exportForm.value.resource = ''
}
const submitExportForm = async () => {
if (exportForm.value.resource === '') {
return ElMessage.error('请选择导出类型')
}
const resourceType = Number(exportForm.value.resource)
const params: ExportParams = {
exportAll: false,
idList: [],
}
const mapIds = (items: FactoryOrderNewListData[]) =>
items.map((el) => Number(el.id))
switch (resourceType) {
case 0:
params.idList = mapIds(tableData.value as FactoryOrderNewListData[])
break
case 1:
if (selectedRows.value.length === 0) {
return ElMessage.error('请选择要导出的订单')
}
params.idList = mapIds(selectedRows.value)
break
case 2:
params.exportAll = true
params.idList = undefined
break
default:
console.error('未知的资源类型:', resourceType)
}
const loading = ElLoading.service({
text: '导出中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
await exportFactoryOrderInfo({
...params,
...(resourceType === 2
? {
...searchForm.value,
}
: {}),
})
ElMessage.success('请求成功,请稍后到右上角[我的下载]中查看')
exportVisible.value = false
} catch (e) {
console.error(e)
} finally {
loading.close()
}
}
const refreshCurrentView = (options?: { isRefreshTree?: boolean }) => {
if (options?.isRefreshTree) void loadStatusTreeCounts()
if (isSpecialLayout.value) {
if (status.value === 'BATCH_MANAGE') batchManageRef.value?.refresh()
if (status.value === 'AWAITING_RESTOCK') waitingRestockRef.value?.refresh()
return
}
if (isCardLayout.value) {
cardLayoutRef.value?.clearSelection()
nextTick(() => {
cardLayoutRef.value?.refresh(true)
})
return
}
if (status.value === 'PENDING_RECEIVE') {
statusCurrentPageRef.value = currentPage.value
statusPageSizeRef.value = pageSize.value
void getPendingReceiveCounts()
}
if (status.value === 'SUSPEND') {
void getSuspendCounts()
}
refreshTableList()
}
refreshCurrentViewProxy.value = () => refreshCurrentView()
const updateReceiverAddressVisible = ref(false)
const receiverAddressForm = ref<AddressInfo>({
receiverName: '',
rfcNumber: '',
receiverPhone: '',
receiverCountry: '',
receiverProvince: '',
receiverCity: '',
receiverDistrict: '',
receiverAddress1: '',
receiverAddress2: '',
receiverPostCode: '',
})
const showPendingLogisticsUpdateAddress = (row: FactoryOrderNewListData) =>
status.value === 'PENDING_CREATE_LOGISTICS' && row.shipmentType === 1
const submitFactoryOrderReceiverAddress = (data: AddressInfo) =>
updateReceiverAddressApi(data)
const openUpdateReceiverAddress = (row: FactoryOrderNewListData) => {
receiverAddressForm.value = {
rfcNumber: '',
receiverName: '',
receiverPhone: '',
receiverCountry: '',
receiverProvince: '',
receiverCity: '',
receiverDistrict: '',
receiverAddress1: '',
receiverAddress2: '',
receiverPostCode: '',
...JSON.parse(JSON.stringify(row)),
} as AddressInfo
updateReceiverAddressVisible.value = true
}
const handleRefreshReceiverAddress = async (row: FactoryOrderNewListData) => {
try {
await ElMessageBox.confirm('确定刷新地址吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
} catch {
return
}
await syncReceiverAddress([row.id])
await ElMessageBox.alert(
'请修改/刷新地址后取消物流或者更换物流再重新创建物流订单、获取跟踪号、获取打印面单',
'提示',
{
type: 'warning',
confirmButtonText: '确定',
cancelButtonText: '取消',
showCancelButton: true,
},
)
refreshCurrentView({ isRefreshTree: true })
}
const handleStatusNodeClick = (node: { status: string }) => {
status.value = node.status
if (node.status !== 'PENDING_RECEIVE') {
pendingAcceptSubTab.value = 'PENDING_RECEIVE'
}
if (node.status !== 'SUSPEND') {
suspendedSubTab.value = 1
}
clearTableState()
if (!isSpecialLayout.value) {
refreshCurrentView()
}
}
const handlePendingAcceptTabClick = (
tab: 'PENDING_RECEIVE' | 'ACCEPT_FAIL_OUT_OF_STOCK',
) => handlePendingAcceptTabClickRaw(tab, refreshTableList)
const handleSuspendTabClick = (value: number) => {
if (suspendedSubTab.value === value) return
suspendedSubTab.value = value
currentPage.value = 1
statusCurrentPageRef.value = 1
refreshTableList()
}
const mainColumns = computed(() => [
{
prop: 'factoryOrderNumber',
label: '订单编号',
minWidth: 160,
align: 'center',
},
{
prop: 'thirdOrderNumber',
label: '客户单号',
minWidth: 160,
align: 'center',
},
{
prop: 'shopNumber',
label: '店铺单号',
minWidth: 240,
},
{
prop: 'statusName',
label: status.value !== 'SUSPEND' ? '订单状态' : '挂起前状态',
minWidth: 120,
align: 'center',
},
{
label: '客户标签',
minWidth: 120,
align: 'center',
render: (row: FactoryOrderNewListData) => {
return (
<div style="display: flex; flex-direction: column; gap: 5px;">
{row?.customTagList?.map((item) => {
return (
<div key={item.id} class="tag-item">
<ElTag type="success">{item.name}</ElTag>
</div>
)
})}
</div>
)
},
},
{
prop: 'logisticsWayName',
label: '物流类型',
width: 140,
align: 'center',
render: (row: FactoryOrderNewListData) => {
return (
<div>
{row.shipmentType === 0 && <span>自有物流</span>}
{row.shipmentType === 1 && <span>九猫统筹物流</span>}
{row.shipmentType === 2 && <span>自提</span>}
</div>
)
},
},
{
prop: 'logisticsWayName',
label: '物流方式',
width: 140,
align: 'center',
},
{
prop: 'trackingNumber',
label: '物流跟踪号',
minWidth: 160,
align: 'center',
},
{ prop: 'weight', label: '总克重(g)', minWidth: 120, align: 'right' },
{
prop: 'productNum',
label: '商品总数量',
minWidth: 120,
align: 'right',
},
{ prop: 'receiverName', label: '收货人', align: 'center', minWidth: 120 },
{
prop: 'receiverPhone',
label: '收货人电话',
minWidth: 140,
align: 'center',
},
{
prop: 'receiverPostCode',
label: '收货人邮编',
minWidth: 140,
align: 'center',
},
{
label: '收货地址',
minWidth: 220,
align: 'left',
render: (row: FactoryOrderNewListData) => {
return (
<div>
<span>{row?.receiverCountry || ''}</span>
<span>{row?.receiverProvince || ''}</span>
<span>{row?.receiverCity || ''}</span>
<span>{row?.receiverDistrict || ''}</span>
</div>
)
},
},
{
prop: 'createTime',
label: '创建时间',
minWidth: 180,
align: 'center',
sortable: 'custom',
},
{
prop: 'startStockingTime',
label: '接单时间',
sortable: 'custom',
minWidth: 180,
align: 'center',
},
{
prop: 'finishTime',
label: '完成时间',
sortable: 'custom',
minWidth: 180,
align: 'center',
},
{
prop: 'operation',
label: '操作',
minWidth: 200,
align: 'center',
slot: 'operation',
fixed: 'right',
},
])
const showProductInventoryColumns = computed(
() =>
status.value == 'PENDING_RECEIVE' &&
pendingAcceptSubTab.value !== 'PENDING_RECEIVE',
)
const baseProductColumns = computed(() => [
{
key: 'variantImage',
prop: 'variantImage',
label: '商品图片',
minWidth: 90,
align: 'center',
render: (row: ProductListData) => {
return (
<div>
<el-image
src={row?.variantImage ?? ''}
previewSrcList={[row?.variantImage ?? '']}
style="width: 50px; height: 50px"
previewTeleported
/>
</div>
)
},
},
{
key: 'productName',
prop: 'productName',
label: '商品名称',
minWidth: 200,
showOverflowTooltip: true,
},
{
key: 'variantSku',
prop: 'variantSku',
label: '变体SKU',
minWidth: 160,
align: 'center',
},
{
key: 'thirdSkuCode',
prop: 'thirdSkuCode',
label: '库存SKU',
minWidth: 160,
align: 'center',
},
{
key: 'craftCode',
prop: 'craftCode',
label: '工艺',
minWidth: 100,
align: 'center',
},
{
prop: 'supplierProductNo',
label: '款号',
minWidth: 140,
align: 'center',
},
{
prop: 'price',
label: '价格',
minWidth: 100,
align: 'right',
render: (row: ProductListData) => {
return (
<span>
{new BigNumber(row.templatePrice ?? 0).plus(row.craftPrice ?? 0).toString()}
</span>
)
},
},
{
prop: 'num',
label: '数量',
minWidth: 100,
align: 'right',
},
{
prop: 'weight',
label: '克重(g)',
minWidth: 100,
align: 'right',
},
{
label: '可用数量',
minWidth: 100,
align: 'right',
showColumn: showProductInventoryColumns.value,
render: (row: ProductListData) => {
const available = new BigNumber(row.inventory ?? 0)
.minus(row.occupyInventory ?? 0)
.minus(row.freezeInventory ?? 0)
.toNumber()
return <span>{available}</span>
},
},
{
prop: 'inventory',
label: '库存数量',
minWidth: 100,
align: 'right',
showColumn: showProductInventoryColumns.value,
render: (row: ProductListData) => {
return <span>{row.inventory || 0}</span>
},
},
{
prop: 'occupyInventory',
label: '占用数量',
minWidth: 100,
align: 'right',
showColumn: showProductInventoryColumns.value,
render: (row: ProductListData) => {
return <span>{row.occupyInventory || 0}</span>
},
},
{
prop: 'freezeInventory',
label: '冻结数量',
minWidth: 100,
align: 'right',
showColumn: showProductInventoryColumns.value,
render: (row: ProductListData) => {
return <span>{row.freezeInventory || 0}</span>
},
},
{
prop: 'customsNameEnglish',
label: '英文报关名称',
width: 160,
align: 'center',
},
{
prop: 'customsNameChinese',
label: '中文报关名称',
width: 160,
align: 'center',
},
{
prop: 'customsWeight',
label: '申报重量(g)',
width: 100,
align: 'right',
},
{
prop: 'customsValue',
label: '申报价值($)',
width: 100,
align: 'right',
},
{
key: 'remark',
prop: 'remark',
label: '备注',
width: 160,
align: 'center',
render: (row: ProductListData) => {
return (
<div style="display:flex;align-items:center;justify-content:center;gap:4px">
<span>{row?.remark ?? ''}</span>
<el-icon
style="cursor:pointer;color:#409eff;flex-shrink:0"
onClick={(e: Event) => {
e.stopPropagation()
handleEditRemark(row)
}}
>
<Edit />
</el-icon>
</div>
)
},
},
])
const productColumns = computed(() => {
if (status.value === 'PENDING_DELIVERY') {
return [
...baseProductColumns.value,
{
key: 'operation',
prop: 'operation',
label: '操作',
width: 160,
align: 'center',
fixed: 'right',
render: (row: ProductListData) => {
return (
<div>
<span class="item">
<el-button
type="primary"
link
size="small"
onClick={(e: Event) => {
e.stopPropagation()
handleSubApplyReplenish(row)
}}
>
申请补胚
</el-button>
</span>
<span class="item">
<el-button
type="primary"
link
size="small"
onClick={(e: Event) => {
e.stopPropagation()
handleDownloadMaterialByProduct(row)
}}
>
下载素材
</el-button>
</span>
</div>
)
},
},
]
}
return baseProductColumns.value
})
const confirmOrderDialogRef = ref<InstanceType<typeof ConfirmOrderDialog>>()
const cancelOrderDialogRef = ref<InstanceType<typeof CancelOrderDialog>>()
const suspendDialogRef = ref<InstanceType<typeof SuspendDialog>>()
const pickCompleteDialogRef = ref<InstanceType<typeof PickCompleteDialog>>()
const pickFailDialogRef = ref<InstanceType<typeof PickFailDialog>>()
const operateDetailsDialogRef = ref()
const arrangeDialogRef = ref<InstanceType<typeof ArrangeDialog>>()
const createLogisticDialogRef = ref()
const printWarehouseSkuDialogRef =
ref<InstanceType<typeof PrintWarehouseSkuTag>>()
const updateCustomsDialogVisible = ref(false)
const weightDialogRef = ref()
const detailVisible = ref(false)
const fastKey = ref('')
const detailData = ref<Record<string, unknown>>({})
const { ensureSelection, executeBatchAction } = useOrderBatchActions({
getIds: getSelectedIds,
refreshCurrentView,
})
const handleEditRemark = async (row: ProductListData) => {
try {
const { value } = await ElMessageBox.prompt('请输入备注', '提示', {
inputValue: row.remark || '',
confirmButtonText: '确认',
cancelButtonText: '取消',
inputPattern: /.+/,
inputErrorMessage: '请输入备注',
})
try {
const res = await updateRemarkApi(row.id, value)
if (res.code !== 200) return
ElMessage.success('备注修改成功')
getOrderDetailsById('product')
} catch (e) {
console.error(e)
}
} catch {
// cancelled
}
}
const handleSubApplyReplenish = (row: ProductListData) => {
operateDetailsDialogRef.value?.open({
row,
ids: [row.id],
url: 'factory/podOrderOperation/getListByPodProductId',
})
}
const handleConfirmOrder = () => {
if (!ensureSelection()) return
confirmOrderDialogRef.value?.open(getSelectedIds())
}
const openResultInfoDialog = (data: ResultInfoDataItem[]) => {
nextTick(() => {
const isSuccess = data.every((item) => item.status)
if (!isSuccess) {
resultInfo.value = data.filter((item) => !item.status)
resultRefs.value?.showDialog()
} else {
ElMessage.success('操作成功')
refreshCurrentView({ isRefreshTree: true })
}
})
}
const handleCancelOrder = () => {
if (!ensureSelection()) return
cancelOrderDialogRef.value?.open(getSelectedIds())
}
const handleRefreshProductInfo = async () => {
if (!ensureSelection()) return
try {
await ElMessageBox.confirm('确定刷新商品信息吗?', '提示', {
type: 'warning',
confirmButtonText: '确认',
cancelButtonText: '取消',
})
} catch {
return
}
const loading = ElLoading.service({
fullscreen: true,
text: '操作中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
const res = await refreshProductInfoApi({
orderIds: !isCardLayout.value ? getSelectedIds().join(',') : undefined,
productIds: isCardLayout.value
? cardSelectList.value
.map((row) => row.podOrderProductId ?? 0)
.join(',')
: undefined,
})
if (res.code !== 200) return
ElMessage.success('刷新商品信息成功')
refreshCurrentView()
} catch (e) {
console.error(e)
} finally {
loading.close()
}
}
const handleLogisticsCommand = async (command: string) => {
if (!ensureSelection()) return
const ids = getSelectedIds()
if (command === 'createLogistic') {
createLogisticDialogRef.value?.showDialog(ids)
return
}
const apiMap: Record<
string,
(data: (number | string)[]) => Promise<BaseRespData<unknown>>
> = {
getTrackingNumber: getTrackingNumberApi,
getPrintOrder: getfaceSimplexFileApi,
cancelLogistic: cancelLogisticsOrderApi,
}
const labelMap: Record<string, string> = {
getTrackingNumber: '获取跟踪号',
getPrintOrder: '获取打印面单',
cancelLogistic: '取消物流订单',
}
const api = apiMap[command]
if (!api) return
try {
await ElMessageBox.confirm(`确定${labelMap[command]}吗?`, '提示', {
type: 'warning',
})
} catch {
return
}
const loading = ElLoading.service({
fullscreen: true,
text: '操作中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
const res = await api(ids)
if (res.code === 200) {
if (Array.isArray(res.data)) {
const isSuccess = res.data.every((item) => item.status)
if (!isSuccess) {
resultInfo.value = res.data.filter((item) => !item.status)
resultRefs.value?.showDialog()
} else {
ElMessage.success('操作成功')
refreshCurrentView()
}
} else if (typeof res.data === 'string') {
window.open(filePath + res.data)
}
}
} catch (e: unknown) {
resultInfo.value = []
console.error(e)
} finally {
loading.close()
}
}
const handleSuspend = () => {
if (!ensureSelection()) return
let ids: (number | string)[]
if (isCardLayout.value) {
ids = cardSelectList.value.map((row) => row.podOrderId as number)
} else {
ids = selectedRows.value.map((row) => row.id)
}
suspendDialogRef.value?.open(ids)
}
const updateCustomsDialogRef = ref()
const handleUpdateCustomsInfo = async () => {
if (!ensureSelection()) return
updateCustomsDialogVisible.value = true
await nextTick()
updateCustomsDialogRef.value?.resetForm()
}
const handleTransferToArrange = async () => {
if (!ensureSelection('请选择订单')) return
try {
await ElMessageBox.confirm('确定转至待排单吗?', '提示', {
type: 'warning',
})
} catch {
return
}
const loading = ElLoading.service({
fullscreen: true,
text: '操作中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
const ids = getSelectedIds().join(',')
const res = await updateLogisticsToArrangeApi({ ids })
if (res.code !== 200) return
if (Array.isArray(res.data)) {
const isSuccess = res.data.every(
(item: { status?: boolean }) => item.status,
)
if (!isSuccess) {
resultInfo.value = res.data.filter(
(item: { status?: boolean }) => !item.status,
)
resultRefs.value?.showDialog()
} else {
ElMessage.success('操作成功')
refreshCurrentView({ isRefreshTree: true })
}
} else {
ElMessage.success('操作成功')
refreshCurrentView({ isRefreshTree: true })
}
} catch (e: unknown) {
resultInfo.value = []
console.error(e)
} finally {
loading.close()
}
}
const handleArrange = async () => {
if (!ensureSelection()) return
const productIdList = getSelectedIds()
.map((v) => Number(v))
.filter((n) => Number.isFinite(n)) as number[]
const productMarks = Array.from(
new Set(
cardSelectList.value
.map((item) => item.productMark)
.filter(Boolean)
.map((mark) =>
mark === 'custom_normal' || mark === 'normal' ? 'normal_group' : mark,
),
),
)
if (productMarks.length > 1) {
return await ElMessageBox.confirm(
'无法排单,你选择的操作单包含多种商品类型,排单不支持多种商品类型混排,请选择同一类型的商品对应的生产单后再试!',
'错误提示',
{
confirmButtonText: '确定',
type: 'warning',
},
)
}
if (productMarks[0] === 'custom_part') {
const craftTypes = Array.from(
new Set(
cardSelectList.value.map((item) => item.craftType).filter(Boolean),
),
)
if (craftTypes.length > 1) {
await ElMessageBox.confirm(
'选中排单的操作单存在多个工艺类型, 是否继续排单?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
},
)
}
arrangeDialogRef.value?.open({
productIdList,
showAutoSwitch: true,
})
return
}
await ElMessageBox.confirm('确认对所选择的操作单进行排单?', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
const loading = ElLoading.service({
fullscreen: true,
text: '操作中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
const res = await submitArrangeFinishApi({ productIdList })
if (res.code !== 200) return
ElMessage.success(res.message)
refreshCurrentView({ isRefreshTree: true })
} catch (e: unknown) {
console.error(e)
} finally {
loading.close()
}
}
const submitArrangeFinishApi = (params: {
productIdList?: number[]
templateWidth?: number
type?: string
}) =>
arrangeFinishApi({
productIdList: params.productIdList || [],
templateWidth: params.templateWidth,
type: params.type,
})
const handleSingleConfirmOrder = (row: FactoryOrderNewListData) => {
confirmOrderDialogRef.value?.open([row.id])
}
const podOrderVisible = ref(false)
const podDistributionOrderVisible = ref(false)
const sheetPrinter = ref('')
const handlePrinterChange = (value: string) => {
sheetPrinter.value = value
localStorage.setItem('sheetPrinter', JSON.stringify(value))
}
const handleWarehouseIdChange = (value: string) => {
localStorage.setItem('localNewWarehouseId', JSON.stringify(value))
}
// 复用 podCN 的单件打单组件:为 factoryOrderNew 注入差异部分
const mapOrderParamListToSubmitItems = (
orderParamList: { id: number; dataVersion?: number }[],
) => orderParamList.map((item) => ({ id: item.id }))
const submitPodPrintOrderComplete = (
data: { id: number; version?: number }[],
warehouseId: number | string,
) =>
submitPodPrintOrderCompleteApi(
{
orderParamList: data.map((item) => ({ id: item.id })),
},
warehouseId,
)
const getPrintLogisticLabelFactory = (id?: number) =>
getfaceSimplexFileApi([id ?? 0])
const { getCLodop } = useLodop()
const handleSeedingWall = () => {
const lodop = getCLodop(null, null)
if (!lodop) return
sheetPrinter.value = lodop.GET_PRINTER_NAME(0)
podOrderVisible.value = true
}
const printOrder = async (
data: OrderData,
callback: (status: boolean) => void,
) => {
await printWithLodop({
getCLodop: () => getCLodop(null, null),
printer: sheetPrinter.value,
data,
callback,
baseUrl: filePath,
})
}
const handleDownloadMaterial = async () => {
if (!ensureSelection()) return
const usePodOrderDownloadStatuses = ['PENDING_DELIVERY', 'SUSPEND']
const usePodOrderDownload = usePodOrderDownloadStatuses.includes(status.value)
const ids = usePodOrderDownload
? selectedRows.value.map((row) => row.id)
: cardSelectList.value.map((row) => row.podOrderProductId)
const loading = ElLoading.service({
fullscreen: true,
text: '操作中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
const res = usePodOrderDownload
? await downloadMaterialApi(ids as number[])
: await downloadOperationMaterialApi(ids as number[])
if (res.code !== 200) return
window.open(filePath + res.message, '_blank')
} catch (e) {
console.error(e)
} finally {
loading.close()
}
}
const handleDownloadMaterialByProduct = async (row: ProductListData) => {
const loading = ElLoading.service({
fullscreen: true,
text: '操作中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
const res = await downloadOperationMaterialApi([row.id])
if (res.code !== 200) return
window.open(filePath + res.message, '_blank')
} catch (e) {
console.error(e)
} finally {
loading.close()
}
}
const handleDtfCommand = async (command: string) => {
if (!ensureSelection()) return
const ids = getSelectedIds()
const [type, widthStr] = command.split('_')
const templateWidth = Number(widthStr)
const loading = ElLoading.service({
fullscreen: true,
text: '操作中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
const res = await composingNewPodOrderDesignImages(
ids as number[],
type,
templateWidth,
)
if (res.message) {
const filePathUrl =
type === 'tiff'
? res.message?.startsWith('/temp')
? `https://factory.jomalls.com/upload/factory` + res.message
: `https://ps.jomalls.com/tiff/` + res.message
: filePath + res.message
window.open(filePathUrl, '_blank')
}
ElMessage.success('DTF排版成功')
} catch (e) {
console.error(e)
} finally {
loading.close()
}
}
const handlePrintProductionOrder = async () => {
if (!ensureSelection()) return
const ids = getSelectedIds()
const loading = ElLoading.service({
fullscreen: true,
text: '操作中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
const res = await printNewPodOrderProductionOrderApi(ids as number[])
if (res.message) {
window.open(filePath + res.message, '_blank')
}
} catch (e) {
console.error(e)
} finally {
loading.close()
}
}
const handlePrintPickOrder = async () => {
if (!ensureSelection()) return
const ids = getSelectedIds()
try {
const res = await printPickOrderApi(ids as number[])
if (res.code !== 200) return
window.open(filePath + res.message, '_blank')
} catch (e) {
console.error(e)
}
}
const handlePickComplete = () => {
if (!ensureSelection()) return
pickCompleteDialogRef.value?.open(getSelectedIds())
}
const handlePickFail = () => {
if (!ensureSelection()) return
pickFailDialogRef.value?.open(getSelectedIds(), {
title: '拣胚失败',
submitType: 'pick',
})
}
const handleApplyReplenish = async () => {
await executeBatchAction({
getIds: getSelectedIds,
api: applyReplenishApi,
confirmText: '确定申请补胚吗?',
successText: '申请补胚成功',
refreshTree: true,
})
}
const adjustPickOrderSuccess = async (
_: unknown,
selectionOperationIds: number[],
) => {
try {
const res = await applyReplenishApi(selectionOperationIds)
if (res.code !== 200) return
ElMessage.success('申请补胚成功')
refreshCurrentView({ isRefreshTree: true })
} catch (e) {
console.error(e)
}
}
const handleProductionComplete = async () => {
await executeBatchAction({
getIds: getSelectedIds,
api: completeDeliveryApi,
confirmText: '确定生产完成吗?',
refreshTree: true,
onSuccess: (res) => {
const data = Array.isArray(res.data)
? (res.data as ResultInfoDataItem[])
: []
const hasFailed = data.some((item) => !item.status)
if (hasFailed) {
resultInfo.value = data.filter((item) => !item.status)
resultRefs.value?.showDialog()
} else {
ElMessage.success('操作成功')
refreshCurrentView({ isRefreshTree: true })
}
},
})
}
const handleWeightSort = () => {
weightDialogRef.value?.open()
}
const handleArchiveOrder = async () => {
await executeBatchAction({
getIds: getSelectedIds,
api: (ids) => archiveOrderApi(ids.join(',')),
confirmText: '确定订单归档吗?',
successText: '订单归档成功',
refreshTree: true,
})
}
const handleCancelSuspend = async (row?: FactoryOrderNewListData) => {
let ids: (number | string)[]
if (row) {
ids = [row.id]
} else {
ids = getSelectedIds()
}
await executeBatchAction({
getIds: () => ids,
api: (ids) => cancelSuspendApi(ids as number[]),
confirmText: '确定取消挂起吗?',
successText: '取消挂起成功',
refreshTree: true,
})
}
const handleSyncAddress = async () => {
await executeBatchAction({
getIds: getSelectedIds,
api: (ids) => syncReceiverAddress(ids as number[]),
confirmText: '确定同步收货地址吗?',
successText: '同步收货地址成功',
refreshTree: true,
})
}
const handleReplenishComplete = () => {
if (!ensureSelection()) return
pickCompleteDialogRef.value?.open(getSelectedIds(), undefined, undefined, {
title: '补胚完成',
submitType: 'replenish',
})
}
const handleReplenishFail = () => {
if (!ensureSelection()) return
pickFailDialogRef.value?.open(getSelectedIds(), {
title: '补胚失败',
submitType: 'replenish',
})
}
const getOperationByNo = (operationNo: string) =>
getByOperationNoLogApi(operationNo) as Promise<{ data?: unknown }>
const completeOperationById = (ids: number[]) => completeDeliveryApi(ids)
const downloadOperationById = (ids: number[]) =>
downloadOperationMaterialApi(ids)
const handleFastProductionSuccess = (data: ResultInfoDataItem[]) => {
const result = data[0]
if (!result.status) {
ElMessageBox.alert(
`操作单号 ${result.factoryOrderNumber} ${result.message}`,
'提示',
{
type: 'error',
},
)
return
}
}
const createLogisticsSuccess = (data: ResultInfoDataItem[]) => {
const isSuccess = data.every((item) => item.status)
if (!isSuccess) {
resultInfo.value = data.filter((item) => !item.status)
resultRefs.value?.showDialog()
} else {
ElMessage.success('操作成功')
refreshCurrentView()
}
}
const fastClose = () => {
detailVisible.value = false
if (fastKey.value === 'detail') return
refreshCurrentView({ isRefreshTree: true })
}
const handleViewDetail = async (item: operateOrderListData) => {
const loading = ElLoading.service({
fullscreen: true,
text: '加载中...',
background: 'rgba(0, 0, 0, 0.3)',
})
try {
const res = await getFactoryOrderNewOperateDetailApi(item.id)
if (res.code !== 200) return
detailData.value = res.data
fastKey.value = 'detail'
detailVisible.value = true
} catch (e) {
console.error(e)
} finally {
loading.close()
}
}
const handleQuickProduction = () => {
detailVisible.value = true
fastKey.value = 'fastProduction'
detailData.value = {}
}
const handleSinglePrint = () => {
podDistributionOrderVisible.value = true
}
const handlePrintWarehouseSkuTag = async () => {
if (!ensureSelection()) return
const ids = cardSelectList.value.map((item) => item.id).join(',')
await printWarehouseSkuDialogRef.value?.open(3, ids)
}
const handleGetTrackingNumber = async () => {
await executeBatchAction({
getIds: getSelectedIds,
api: (ids) => getTrackingNumberApi(ids as number[]),
confirmText: '确定获取跟踪号吗?',
successText: '获取跟踪号成功',
})
}
const handleStatusPush = async () => {
await executeBatchAction({
getIds: getSelectedIds,
api: (ids) => statusPushApi(ids as number[]),
confirmText: '确定推送至ERP吗?',
successText: '状态推送成功',
})
}
onMounted(() => {
loadStatusTreeCounts()
loadAllDictionaries()
// 仅用于消除 TS 插件的 unused-vars 警告:template refs 会绑定这两个变量
void treeRef.value
void tableRef.value
if (status.value === 'PENDING_RECEIVE') {
getPendingReceiveCounts()
}
})
</script>
<style scoped lang="scss">
.page {
display: flex;
}
.order-status {
width: 180px;
:deep(.el-tree-node__content) {
height: 30px;
line-height: 30px;
}
:deep(.el-tree-node__expand-icon) {
display: none;
}
:deep(.status-tree .el-tree-node.is-current > .el-tree-node__content) {
background-color: #ecf5ff !important;
color: #409eff !important;
.tree-node-label,
.tree-node-count {
color: #409eff !important;
}
.el-icon {
color: #409eff !important;
}
}
}
.tree-node {
display: flex;
align-items: center;
gap: 4px;
font-size: 14px;
color: #333;
font-weight: 500;
width: 100%;
justify-content: space-between;
}
.tree-node-label {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
.tree-node-count {
flex-shrink: 0;
}
.tree-node-expand {
flex-shrink: 0;
cursor: pointer;
display: inline-flex;
&:hover {
color: #409eff;
}
}
.status-subtabs {
display: flex;
align-items: center;
gap: 10px;
border-top: 1px solid #e5e6eb;
border-bottom: 1px solid #e5e6eb;
margin-top: 10px;
}
.status-subtab {
font-size: 14px;
line-height: 32px;
padding: 0 12px;
cursor: pointer;
user-select: none;
color: #333;
position: relative;
border-bottom: 2px solid transparent;
box-sizing: border-box;
&.active {
background: #ecf5ff;
color: #409eff;
border-bottom-color: #409eff;
}
}
.operation-list {
display: flex;
align-items: center;
flex-wrap: wrap;
}
.table-content {
flex: 1;
margin-top: 10px;
overflow: hidden;
:deep(#top) {
height: 100%;
}
}
.table-pagination-bar {
margin-top: 10px;
display: flex;
align-items: center;
}
.selected-count-text {
font-size: 14px;
color: #606266;
white-space: nowrap;
}
.card-content {
flex: 1;
margin-top: 10px;
overflow: hidden;
}
.header-filter-form {
width: 100%;
min-width: 0;
}
.search-form {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0;
min-width: 0;
:deep(.el-form-item) {
margin-right: 10px;
margin-bottom: 10px;
}
:deep(.el-form-item__content) {
min-width: 0;
}
}
.tabs-wrapper {
height: 100%;
:deep(.el-tabs__content) {
height: calc(100% - 40px);
}
:deep(.el-tab-pane) {
height: 100%;
display: flex;
flex-direction: column;
}
}
.sub-table-wrapper,
.log-table-wrapper {
flex: 1;
overflow: hidden;
}
.detail-table-content {
height: 100%;
overflow: hidden;
}
</style>
<style lang="scss">
.customize-select-style {
.el-select-dropdown__list {
width: 500px;
display: flex;
flex-wrap: wrap;
}
}
</style>
# 新订单:
## 待接单、待创建物流、配货中、待发货、已完成、已取消、已归档
- 查询条件
- 数量: 当点击同一个字段时,取消选中状态,**例如: 当前状态在多件,再点击一次多件,取消选中状态**
- 主表格操作列
- 待接单状态下添加`排单按钮`,和批量排单的接口一致,区别是这里只能操作一条数据
- 子表格备注列
- 可修改备注,在值的后面加一个编辑图标,如图一所示
- 点击编辑图标后,弹出一个`prompt` 输入框,提示,请输入备注
- 点击确定后,调用`factory/podOrder/updateRemark` 接口,参数为 `id``remark`
- 接口返回成功后,刷新当前行数据
- 待派单状态下的两个tab可以点击
- 点击待接单后,给接口`factory/podOrder/list_page`传入`subStatus``PENDING_RECEIVE`
- 点击接单失败-缺货后,给接口`factory/podOrder/list_page`传入`subStatus``ACCEPT_FAIL_OUT_OF_STOCK`
- `factory/podOrder/list_page`会返回对应状态下的数据,其中`pendingCount`为待接单tab下的数量,`acceptFailOutOfStockCount`为接单失败-缺货tab下的数量
- 点击接单失败-缺货tab后,将`确认接单`按钮修改为`重新接单`,注意:只修改文字,操作列也同步修改
- 待发货子表格加一个操作列
- 操作列添加`申请补胚`按钮
- 点击“申请补胚”按钮后,会调用接口`factory/podOrder/applyForReplenish`,参数为 `id`
- 调用接口成功后,会返回和卡片布局一样的数据结构(这里暂时还不清楚,可占位),然后弹出和待排单、待补胚、生产中、待配货状态下布局一样的卡片布局弹框,如图二所示(页面中紫色字体无需处理)
- 点击确定后,调用接口`factory/podOrder/applyForReplenish`,参数为 `id`
- 接口调用成功后,关闭申请补胚弹框,刷新列表
/** POD 商品子项,与 useOrderDictionaries 中 options 一致 */
export const PRODUCT_TYPE_POD_SINGLE = 'pod_single'
export const PRODUCT_TYPE_POD_MULTIPLE = 'pod_multiple'
/** 一件定制局部印子项 */
export const PRODUCT_TYPE_CUSTOM_PART_SINGLE = 'custom_part_single'
export const PRODUCT_TYPE_CUSTOM_PART_MULTIPLE = 'custom_part_multiple'
export interface ProductMarkQueryResult {
productMarkList?: string[]
podCustomizedQuantity?: string
cpCustomizedQuantity?: string
}
export function normalizeProductMarkListForQuery(
raw: string[] | undefined,
): ProductMarkQueryResult {
if (!raw?.length) {
return {}
}
const set = new Set(raw)
const marks: string[] = []
if (set.has('normal')) marks.push('normal')
if (set.has('custom_normal')) marks.push('custom_normal')
if (
set.has(PRODUCT_TYPE_POD_SINGLE) ||
set.has(PRODUCT_TYPE_POD_MULTIPLE)
) {
marks.push('pod')
}
if (
set.has(PRODUCT_TYPE_CUSTOM_PART_SINGLE) ||
set.has(PRODUCT_TYPE_CUSTOM_PART_MULTIPLE)
) {
marks.push('custom_part')
}
const podS = set.has(PRODUCT_TYPE_POD_SINGLE)
const podM = set.has(PRODUCT_TYPE_POD_MULTIPLE)
const partS = set.has(PRODUCT_TYPE_CUSTOM_PART_SINGLE)
const partM = set.has(PRODUCT_TYPE_CUSTOM_PART_MULTIPLE)
const podQty =
podS && podM ? undefined : podS ? 'single' : podM ? 'multiple' : undefined
const partQty =
partS && partM ? undefined : partS ? 'single' : partM ? 'multiple' : undefined
const result: ProductMarkQueryResult = {}
if (marks.length) result.productMarkList = marks
if (podQty !== undefined) result.podCustomizedQuantity = podQty
if (partQty !== undefined) result.cpCustomizedQuantity = partQty
return result
}
...@@ -75,7 +75,11 @@ ...@@ -75,7 +75,11 @@
ref="testingInputRef" ref="testingInputRef"
v-model="barcode" v-model="barcode"
style="width: 400px" style="width: 400px"
placeholder="请输入生产单号/ 主SKU" :placeholder="
isNewOrder
? '请输入操作单号/ 主SKU'
: '请输入生产单号/ 主SKU'
"
@keyup.enter="barcodeInput" @keyup.enter="barcodeInput"
></el-input> ></el-input>
</el-form-item> </el-form-item>
...@@ -125,7 +129,7 @@ ...@@ -125,7 +129,7 @@
</el-form-item> </el-form-item>
<el-form-item label-width="90px" label="物流公司:"> <el-form-item label-width="90px" label="物流公司:">
<span style="width: 164px; display: inline-block"> <span style="width: 164px; display: inline-block">
{{ testingData.company }} {{ testingData.logisticsCompanyName }}
</span> </span>
</el-form-item> </el-form-item>
<el-form-item label-width="90px" label="物流方式:"> <el-form-item label-width="90px" label="物流方式:">
...@@ -268,12 +272,12 @@ ...@@ -268,12 +272,12 @@
</ElDialog> </ElDialog>
</template> </template>
<script setup lang="ts"> <script setup lang="tsx">
import { computed, nextTick, ref, watch } from 'vue' import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
import useLodop from '@/utils/hooks/useLodop' import useLodop from '@/utils/hooks/useLodop'
import TableView from '@/components/TableView.vue' import TableView from '@/components/TableView.vue'
import { OrderData, ProductList, IorderItem } from '@/types/api/podMakeOrder' import { OrderData, ProductList, IorderItem } from '@/types/api/podMakeOrder'
import { PackingData } from '@/types/api/podCnOrder' // import { PackingData } from '@/types/api/podCnOrder'
import useUserStore from '@/store/user' import useUserStore from '@/store/user'
import { import {
getSingleQueryApi, getSingleQueryApi,
...@@ -283,8 +287,63 @@ import { ...@@ -283,8 +287,63 @@ import {
const { getCLodop } = useLodop() const { getCLodop } = useLodop()
import useOrderStore from '@/store/cnOrder' import useOrderStore from '@/store/cnOrder'
import { WarehouseListData } from '@/types/index' import { WarehouseListData } from '@/types/index'
import { ElTag } from 'element-plus'
const orderStore = useOrderStore() const orderStore = useOrderStore()
const boxList = computed(() => orderStore.podBoxList) const boxList = computed(() => orderStore.podBoxList)
const operationStatusMap: Record<string, string> = {
PENDING_PACKING: '待配货',
PENDING_SCHEDULE: '待排单',
PENDING_REPLENISH: '待补胚',
PENDING_PICK: '待拣胚',
IN_PRODUCTION: '生产中',
PACKING_COMPLETED: '配货完成',
COMPLETED: '已完成',
CANCELLED: '已取消',
ARCHIVE: '已归档',
}
const operationStatusTagMap: Record<
string,
{
type?: 'success' | 'warning' | 'info' | 'danger' | 'primary'
style?: { backgroundColor: string; color: string; borderColor: string }
}
> = {
PENDING_PACKING: { type: 'warning' },
PENDING_SCHEDULE: { type: 'info' },
PENDING_REPLENISH: {
style: {
backgroundColor: '#E6A23C',
color: '#FFFFFF',
borderColor: '#E6A23C',
},
},
PENDING_PICK: {
style: {
backgroundColor: '#409EFF',
color: '#FFFFFF',
borderColor: '#409EFF',
},
},
IN_PRODUCTION: { type: 'primary' },
PACKING_COMPLETED: { type: 'success' },
COMPLETED: { type: 'success' },
CANCELLED: { type: 'danger' },
ARCHIVE: {
style: {
backgroundColor: '#909399',
color: '#FFFFFF',
borderColor: '#909399',
},
},
}
const parseOperationNo = (operationNoWithStatus: string) => {
const match = operationNoWithStatus.match(/^([^_]+)_(.+)$/)
if (!match)
return { operationNo: operationNoWithStatus, status: '', statusText: '' }
const [, operationNo, status] = match
const statusText = operationStatusMap[status] || status
return { operationNo, status, statusText }
}
const podOrderDetailsColumns = computed(() => [ const podOrderDetailsColumns = computed(() => [
{ {
label: '图片', label: '图片',
...@@ -295,10 +354,36 @@ const podOrderDetailsColumns = computed(() => [ ...@@ -295,10 +354,36 @@ const podOrderDetailsColumns = computed(() => [
fixed: 'left', fixed: 'left',
}, },
{ {
label: '生产单号', label: props.isNewOrder ? '操作单号' : '生产单号',
prop: 'podJomallCnNo', prop: props.isNewOrder ? 'operationNos' : 'podJomallCnNo',
width: 200, width: 200,
align: 'center', align: 'center',
render: (row: ProductList) => {
return (
<div>
{props.isNewOrder &&
row.operationNos?.split(',').map((item) => {
const { operationNo, status, statusText } = parseOperationNo(item)
const { type, style } = operationStatusTagMap[status] || {}
return (
<div>
{operationNo}
{statusText && (
<ElTag
size="small"
type={type}
style={{ marginLeft: '6px', ...style }}
>
{statusText}
</ElTag>
)}
</div>
)
})}
{!props.isNewOrder && row.podJomallCnNo}
</div>
)
},
}, },
{ {
label: 'variant SKU', label: 'variant SKU',
...@@ -344,7 +429,7 @@ const podOrderDetailsColumns = computed(() => [ ...@@ -344,7 +429,7 @@ const podOrderDetailsColumns = computed(() => [
// ] // ]
// : []), // : []),
]) ])
const noObj = reactive<Record<string, any>>({}) const noObj = ref<Record<string, OrderData>>({})
const checked = ref<boolean>(false) const checked = ref<boolean>(false)
const autoPrint = ref<boolean>(false) const autoPrint = ref<boolean>(false)
const ttChecked = ref<boolean>(false) const ttChecked = ref<boolean>(false)
...@@ -363,19 +448,31 @@ const props = defineProps<{ ...@@ -363,19 +448,31 @@ const props = defineProps<{
printOrder: (data: OrderData, callback: (status: boolean) => void) => void printOrder: (data: OrderData, callback: (status: boolean) => void) => void
// printOrderOne?: () => void // 单个打印 // printOrderOne?: () => void // 单个打印
warehouseList: WarehouseListData[] warehouseList: WarehouseListData[]
/**
* 可复用注入:单件打单查询接口
* - 默认:CN 的 getSingleQueryApi
*/
queryApi?: (
code: string,
factoryNo: number,
warehouseId: number | string,
) => Promise<{ code: number; data: OrderData }>
submitCompleteApi?: (
data: { id: number; version?: number }[],
warehouseId: number | string,
) => Promise<{ code: number; message?: string }>
printLabelApi?: (id?: number) => Promise<{ code: number; data: unknown }>
mapOrderParamListToSubmitItems?: (
orderParamList: IorderItem[],
) => { id: number; version?: number }[]
warehouseSessionKey?: string
isNewOrder?: boolean
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:modelValue', v: boolean): void (e: 'update:modelValue', v: boolean): void
(e: 'refresh'): void (e: 'refresh'): void
(e: 'set-printer', v: string): void (e: 'set-printer', v: string): void
}>() }>()
onMounted(() => {
window.addEventListener('keydown', keyDown)
})
onUnmounted(() => {
window.removeEventListener('keydown', keyDown)
})
const visible = computed({ const visible = computed({
get() { get() {
return props.modelValue return props.modelValue
...@@ -391,7 +488,8 @@ watch(visible, async (value: boolean) => { ...@@ -391,7 +488,8 @@ watch(visible, async (value: boolean) => {
testingData.value = {} testingData.value = {}
noObj.value = {} noObj.value = {}
checked.value = false checked.value = false
const localRaw = sessionStorage.getItem('locaclCnSingel') const sessionKey = props.warehouseSessionKey ?? 'locaclCnSingel'
const localRaw = sessionStorage.getItem(sessionKey)
const localId = localRaw ? JSON.parse(localRaw) : '' const localId = localRaw ? JSON.parse(localRaw) : ''
/* 先找一次,确认本地值是否存在于列表 */ /* 先找一次,确认本地值是否存在于列表 */
const hit = props.warehouseList.find((w) => w.id === localId) const hit = props.warehouseList.find((w) => w.id === localId)
...@@ -433,11 +531,20 @@ const initPrintDevice = () => { ...@@ -433,11 +531,20 @@ const initPrintDevice = () => {
deviceList.value = arr deviceList.value = arr
} }
const handleWarehouseChange = (value: string) => { const handleWarehouseChange = (value: string) => {
sessionStorage.setItem('locaclCnSingel', JSON.stringify(value)) const sessionKey = props.warehouseSessionKey ?? 'locaclCnSingel'
sessionStorage.setItem(sessionKey, JSON.stringify(value))
} }
function keyDown(e: KeyboardEvent) { function keyDown(e: KeyboardEvent) {
const e1 = e || (window.event as KeyboardEvent) const e1 = e || (window.event as KeyboardEvent)
if (![37, 38, 39, 40].includes(e1.keyCode)) return if (![37, 38, 39, 40].includes(e1.keyCode)) return
const t = e1.target as HTMLElement | null
if (
t?.closest?.(
'input, textarea, select, [contenteditable="true"], [role="textbox"]',
)
) {
return
}
e1.stopPropagation() e1.stopPropagation()
e1.preventDefault() e1.preventDefault()
let idx = boxIndex.value ?? 0 let idx = boxIndex.value ?? 0
...@@ -449,6 +556,23 @@ function keyDown(e: KeyboardEvent) { ...@@ -449,6 +556,23 @@ function keyDown(e: KeyboardEvent) {
if (idx < 1 || idx > len) return if (idx < 1 || idx > len) return
boxIndex.value = idx boxIndex.value = idx
} }
watch(
visible,
(v) => {
if (v) {
window.addEventListener('keydown', keyDown)
} else {
window.removeEventListener('keydown', keyDown)
}
},
{ immediate: true },
)
onUnmounted(() => {
window.removeEventListener('keydown', keyDown)
})
const testingInputRef = ref<HTMLInputElement | null>(null) const testingInputRef = ref<HTMLInputElement | null>(null)
async function inputActive() { async function inputActive() {
await nextTick() await nextTick()
...@@ -473,7 +597,9 @@ const isLock = ref<boolean>(false) ...@@ -473,7 +597,9 @@ const isLock = ref<boolean>(false)
async function barcodeInput() { async function barcodeInput() {
const code = barcode.value.trim() const code = barcode.value.trim()
if (!code) { if (!code) {
ElMessage.warning('请录入生产单号或SKU') ElMessage.warning(
props.isNewOrder ? '请录入操作单号或SKU' : '请录入生产单号或SKU',
)
playAudio('picking_warning') playAudio('picking_warning')
await inputActive() await inputActive()
return return
...@@ -513,12 +639,16 @@ async function submitInspection(objs?: OrderData, callback?: () => void) { ...@@ -513,12 +639,16 @@ async function submitInspection(objs?: OrderData, callback?: () => void) {
if (!factoryNo) { if (!factoryNo) {
return return
} }
const data = (result?.orderParamList ?? []).map((item: IorderItem) => ({ const orderParamList = result?.orderParamList ?? []
const data = props.mapOrderParamListToSubmitItems
? props.mapOrderParamListToSubmitItems(orderParamList as IorderItem[])
: (orderParamList as IorderItem[]).map((item) => ({
id: item.id, id: item.id,
version: item.dataVersion, version: item.dataVersion,
})) }))
try { try {
const res = await submitInspectionApi(data, warehouseId.value) const submitFn = props.submitCompleteApi ?? submitInspectionApi
const res = await submitFn(data, warehouseId.value)
if (res.code !== 200) return if (res.code !== 200) return
ElMessage.success(res.message) ElMessage.success(res.message)
coverImage.value = '' coverImage.value = ''
...@@ -533,19 +663,20 @@ async function submitInspection(objs?: OrderData, callback?: () => void) { ...@@ -533,19 +663,20 @@ async function submitInspection(objs?: OrderData, callback?: () => void) {
const userStore = useUserStore() const userStore = useUserStore()
const productTable = ref() const productTable = ref()
async function getPackingData(code: string) { async function getPackingData(code: string) {
const data: PackingData = {} // const data: PackingData = {}
const arr = code.split('_') const arr = code.split('_')
if (arr.length >= 4) code = arr[3] if (arr.length >= 4) code = arr[3]
if (code.startsWith('PSCD')) data.podProductionNo = code // if (code.startsWith('PSCD')) data.podProductionNo = code
else if (/^\d{16}$/.test(code)) data.jomallCustomNo = code // 16 位数字 // else if (/^\d{16}$/.test(code)) data.jomallCustomNo = code // 16 位数字
else if (code.startsWith('JMSC') || code.startsWith('GCSC')) // else if (code.startsWith('JMSC') || code.startsWith('GCSC'))
data.jomallPsdCustomNo = code // data.jomallPsdCustomNo = code
else if (code.startsWith('S-')) data.jomallCustomNo = code // else if (code.startsWith('S-')) data.jomallCustomNo = code
else if (code.includes('JMPSC') || code.startsWith('GCPS')) { if (code.includes('JMPSC') || code.startsWith('GCPS')) {
code = code.replace(/^([A-Z]+-)*([A-Z]+-[\d-]+)$/, (_, __, p) => p) code = code.replace(/^([A-Z]+-)*([A-Z]+-[\d-]+)$/, (_, __, p) => p)
data.podJomallNo = code }
} else data.sku = code // data.podJomallNo = code
// } else data.sku = code
const loading = ElLoading.service({ background: 'rgba(0, 0, 0, 0.3)' }) const loading = ElLoading.service({ background: 'rgba(0, 0, 0, 0.3)' })
currentCode.value = code currentCode.value = code
...@@ -556,7 +687,8 @@ async function getPackingData(code: string) { ...@@ -556,7 +687,8 @@ async function getPackingData(code: string) {
return return
} }
try { try {
const res = await getSingleQueryApi(code, factoryNo, warehouseId.value) const queryFn = props.queryApi ?? getSingleQueryApi
const res = await queryFn(code, factoryNo, warehouseId.value)
if (res.code === 200) { if (res.code === 200) {
testingData.value = res.data testingData.value = res.data
const { productList = [] } = res.data const { productList = [] } = res.data
...@@ -604,18 +736,27 @@ function printOrderOne(item: OrderData): Promise<void> { ...@@ -604,18 +736,27 @@ function printOrderOne(item: OrderData): Promise<void> {
/* 无文件 → 先拉取再打印 */ /* 无文件 → 先拉取再打印 */
const loading = ElLoading.service({ background: 'rgba(0,0,0,.3)' }) const loading = ElLoading.service({ background: 'rgba(0,0,0,.3)' })
getPrintLogisticLabelApi(item.id) const printFn = props.printLabelApi ?? getPrintLogisticLabelApi
printFn(item.id)
.then((res) => { .then((res) => {
loading.close() loading.close()
const data = res.data ?? {} const payload = res.data ?? {}
if (data.filePath) { if (typeof payload === 'string') {
item.filePath = data.filePath item.filePath = payload
} else if (data.fileData) { } else {
item.fileData = data.fileData const filePayload = payload as {
filePath?: string
fileData?: string
}
if (typeof filePayload.filePath === 'string') {
item.filePath = filePayload.filePath
} else if (typeof filePayload.fileData === 'string') {
item.fileData = filePayload.fileData
} else { } else {
ElMessage.error('获取打印面单失败') ElMessage.error('获取打印面单失败')
return return
} }
}
props.printOrder(item, (v) => { props.printOrder(item, (v) => {
// item.printStatus = v // item.printStatus = v
console.log('printOrder', v) console.log('printOrder', v)
......
<script setup lang="ts"> <script setup lang="ts">
import { defineModel } from 'vue'
import { updateAddressApi } from '@/api/podCnOrder.ts' import { updateAddressApi } from '@/api/podCnOrder.ts'
import {AddressInfo} from '@/types/api/podCnOrder.ts' import type { AddressInfo } from '@/types/api/podCnOrder.ts'
const emits = defineEmits(['success']) const emits = defineEmits(['success'])
defineProps<{ const props = defineProps<{
countryList: { countryCode: string }[] countryList: { countryCode: string; nameCn?: string }[]
submitAddressApi?: (data: AddressInfo) => Promise<unknown>
}>() }>()
const visible = defineModel<boolean>('visible') const visible = defineModel<boolean>('visible')
const form = defineModel<AddressInfo>('form', { const form = defineModel<AddressInfo>('form', {
...@@ -42,7 +42,8 @@ const rules = { ...@@ -42,7 +42,8 @@ const rules = {
const submitForm = async () => { const submitForm = async () => {
formRef?.value.validate(async (valid: boolean) => { formRef?.value.validate(async (valid: boolean) => {
if (valid) { if (valid) {
await updateAddressApi(form.value as never) const save = props.submitAddressApi ?? updateAddressApi
await save(form.value as never)
visible.value = false visible.value = false
emits('success') emits('success')
await ElMessageBox.alert( await ElMessageBox.alert(
...@@ -87,7 +88,7 @@ const submitForm = async () => { ...@@ -87,7 +88,7 @@ const submitForm = async () => {
<el-option <el-option
v-for="it in countryList" v-for="it in countryList"
:key="it.countryCode" :key="it.countryCode"
:label="it.countryCode" :label="it.nameCn || it.countryCode"
:value="it.countryCode" :value="it.countryCode"
></el-option> ></el-option>
</el-select> </el-select>
......
...@@ -1047,8 +1047,8 @@ ...@@ -1047,8 +1047,8 @@
<div class="header-filter-tab"> <div class="header-filter-tab">
<div class="tabs"> <div class="tabs">
<div <div
v-for="item in tabsNav" v-for="(item, tabIndex) in tabsNav"
:key="item.status" :key="`${tabIndex}-${item.status ?? ''}`"
class="tabs-node" class="tabs-node"
:class="item.status === status ? 'tabs-node_active' : ''" :class="item.status === status ? 'tabs-node_active' : ''"
@click="changeTab(item)" @click="changeTab(item)"
...@@ -2079,6 +2079,7 @@ ...@@ -2079,6 +2079,7 @@
</div> </div>
<div <div
v-else v-else
:key="status"
v-loading="loading" v-loading="loading"
element-loading-text="加载中..." element-loading-text="加载中..."
class="card-wrapper flex-1 flex-column overflow-hidden" class="card-wrapper flex-1 flex-column overflow-hidden"
...@@ -2945,7 +2946,7 @@ ...@@ -2945,7 +2946,7 @@
/> />
</template> </template>
<script setup lang="tsx"> <script setup lang="tsx">
import WeightDialog from './components/WeightDialog.vue' import WeightDialog from '@/views/order/components/WeightDialog.vue'
import { getUserMarkList } from '@/api/common' import { getUserMarkList } from '@/api/common'
import LogisticsWaySelect from '../../logistics/components/LogisticsWaySelect.tsx' import LogisticsWaySelect from '../../logistics/components/LogisticsWaySelect.tsx'
import PrintWarehouseSkuTag from '../components/printWarehouseSkuTag.vue' import PrintWarehouseSkuTag from '../components/printWarehouseSkuTag.vue'
...@@ -3019,7 +3020,7 @@ import { loadWarehouseListApi, getEmployeeListApi } from '@/api/common' ...@@ -3019,7 +3020,7 @@ import { loadWarehouseListApi, getEmployeeListApi } from '@/api/common'
// import { logisticsCompanyAllCodelist } from '@/api/logistics.ts' // import { logisticsCompanyAllCodelist } from '@/api/logistics.ts'
import { BaseRespData } from '@/types/api' import { BaseRespData } from '@/types/api'
import ChangeWayDialog from './components/ChangeWayDialog.vue' import ChangeWayDialog from './components/ChangeWayDialog.vue'
import CreateLogisticDialog from './components/CreateLogisticDialog.vue' import CreateLogisticDialog from '../components/CreateLogisticDialog.vue'
import UpdateAddress from './components/updateAddress.vue' import UpdateAddress from './components/updateAddress.vue'
import { useEnterKeyTrigger } from '@/utils/hooks/useEnterKeyTrigger.ts' import { useEnterKeyTrigger } from '@/utils/hooks/useEnterKeyTrigger.ts'
...@@ -3047,16 +3048,16 @@ import { showConfirm } from '@/utils/ui' ...@@ -3047,16 +3048,16 @@ import { showConfirm } from '@/utils/ui'
import { DocumentCopy, EditPen } from '@element-plus/icons-vue' import { DocumentCopy, EditPen } from '@element-plus/icons-vue'
import { Column, ElFormItem, ElMessage } from 'element-plus' import { Column, ElFormItem, ElMessage } from 'element-plus'
import { computed, onMounted, ref, nextTick, reactive } from 'vue' import { computed, onMounted, ref, nextTick, reactive } from 'vue'
import FastProduction from './FastProduction.vue' import FastProduction from '@/views/order/components/FastProduction.vue'
import { filePath } from '@/api/axios' import { filePath } from '@/api/axios'
import PodMakeOrder from './PodMakeOrder.vue' import PodMakeOrder from '@/views/order/components/PodMakeOrder.vue'
import PodDistributionOrder from './PodDistributionOrder.vue' import PodDistributionOrder from './PodDistributionOrder.vue'
import SuperPodMakeOrder from './SuperPodMakeOrder.vue' import SuperPodMakeOrder from './SuperPodMakeOrder.vue'
import { OrderData } from '@/types/api/podMakeOrder' import { OrderData } from '@/types/api/podMakeOrder'
import useLodop, { LODOPObject } from '@/utils/hooks/useLodop' import useLodop, { LODOPObject } from '@/utils/hooks/useLodop'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import RightClickMenu from '@/components/RightClickMenu.vue' import RightClickMenu from '@/components/RightClickMenu.vue'
import ResultInfo from './components/ResultInfo.vue' import ResultInfo from '../components/ResultInfo.vue'
import { isArray, isString } from '@/utils/validate' import { isArray, isString } from '@/utils/validate'
import platformJson from '../../../json/platform.json' import platformJson from '../../../json/platform.json'
// import useUserStore from '@/store/user' // import useUserStore from '@/store/user'
...@@ -3066,7 +3067,7 @@ import { ...@@ -3066,7 +3067,7 @@ import {
type NavigationGuardNext, type NavigationGuardNext,
type RouteLocationNormalized, type RouteLocationNormalized,
} from 'vue-router' } from 'vue-router'
import UpdateCustomDeclarationInfoDialog from './components/UpdateCustomDeclarationInfoDialog.vue' import UpdateCustomDeclarationInfoDialog from '../components/UpdateCustomDeclarationInfoDialog.vue'
import { userData } from '@/types/api/user.ts' import { userData } from '@/types/api/user.ts'
declare global { declare global {
...@@ -4845,9 +4846,9 @@ const cancelOrder = async () => { ...@@ -4845,9 +4846,9 @@ const cancelOrder = async () => {
} }
const cardSelection = ref<ProductList[]>([]) const cardSelection = ref<ProductList[]>([])
const cardClick = (data: ProductList) => { const cardClick = (data: ProductList) => {
const status = isSelectStatused(data) const selected = isSelectStatused(data)
if (status) { if (selected) {
cardSelection.value = cardSelection.value.filter( cardSelection.value = cardSelection.value.filter(
(item: ProductList) => item.id !== data.id, (item: ProductList) => item.id !== data.id,
) )
......
...@@ -64,7 +64,9 @@ ...@@ -64,7 +64,9 @@
<ElInput <ElInput
ref="productionOrderRef" ref="productionOrderRef"
v-model="productionOrder" v-model="productionOrder"
placeholder="请输入生产单号" :placeholder="
props.isNewOrder ? '请输入操作单号' : '请输入生产单号'
"
clearable clearable
style="width: 100%" style="width: 100%"
@keyup.enter="handleSearch" @keyup.enter="handleSearch"
...@@ -155,14 +157,23 @@ ...@@ -155,14 +157,23 @@
<span class="box-top-item-box-index-text">号箱</span> <span class="box-top-item-box-index-text">号箱</span>
<span style="font-size: 30px">放入第</span> <span style="font-size: 30px">放入第</span>
<div class="box-top-item-box-index-number" :style="{color:podOrderDetailsData?.pickingNumber === <div
podOrderDetailsData?.purchaseNumber ? 'rgb(0, 255, 0)' : ''}"> class="box-top-item-box-index-number"
:style="{
color:
podOrderDetailsData?.pickingNumber ===
podOrderDetailsData?.purchaseNumber
? 'rgb(0, 255, 0)'
: '',
}"
>
{{ podOrderDetailsData?.pickingNumber }} {{ podOrderDetailsData?.pickingNumber }}
</div> </div>
<span style="font-size: 30px">件商品</span> <span style="font-size: 30px">件商品</span>
</div> </div>
<div v-else class="box-top-item-box-index-text"> <div v-else class="box-top-item-box-index-text">
单件商品<span style="color: rgb(0, 255, 0)">(配齐)</span>,不占用播种墙 单件商品<span style="color: rgb(0, 255, 0)">(配齐)</span
>,不占用播种墙
</div> </div>
<div class="box-top-item-status"> <div class="box-top-item-status">
<span <span
...@@ -186,11 +197,18 @@ ...@@ -186,11 +197,18 @@
@click="podOrderDetailsData && print(podOrderDetailsData, true)" @click="podOrderDetailsData && print(podOrderDetailsData, true)"
>手动打印</ElButton >手动打印</ElButton
> >
<ElButton type="primary" @click="printNormal">普货拣货 </ElButton> <ElButton v-if="!isNewOrder" type="primary" @click="printNormal"
>普货拣货
</ElButton>
<ElButton type="success" @click="handlePrintFinish" <ElButton type="success" @click="handlePrintFinish"
>打单完成</ElButton >打单完成</ElButton
> >
<ElButton :disabled="boxIndex === 0" type="danger" @click="handleClearBox">清空箱子</ElButton> <ElButton
:disabled="boxIndex === 0"
type="danger"
@click="handleClearBox"
>清空箱子</ElButton
>
</div> </div>
<div <div
v-if=" v-if="
...@@ -245,7 +263,7 @@ ...@@ -245,7 +263,7 @@
</ElDialog> </ElDialog>
</template> </template>
<script setup lang="ts"> <script setup lang="tsx">
import { computed, nextTick, ref, watch } from 'vue' import { computed, nextTick, ref, watch } from 'vue'
import useLodop from '@/utils/hooks/useLodop' import useLodop from '@/utils/hooks/useLodop'
import TableView from '@/components/TableView.vue' import TableView from '@/components/TableView.vue'
...@@ -269,15 +287,27 @@ import { Check, Refresh } from '@element-plus/icons-vue' ...@@ -269,15 +287,27 @@ import { Check, Refresh } from '@element-plus/icons-vue'
import socket from '@/utils/websocket' import socket from '@/utils/websocket'
import { WarehouseListData } from '@/types/index' import { WarehouseListData } from '@/types/index'
import { filePath } from '@/api/axios.ts' import { filePath } from '@/api/axios.ts'
import { ElButton, ElIcon } from 'element-plus' import { ElButton, ElIcon, ElTag } from 'element-plus'
const { getCLodop } = useLodop() const { getCLodop } = useLodop()
const props = defineProps<{ const props = withDefaults(
defineProps<{
modelValue: boolean modelValue: boolean
printOrder: (data: OrderData, callback: (status: boolean) => void) => void printOrder: (data: OrderData, callback: (status: boolean) => void) => void
warehouseList: WarehouseListData[] warehouseList: WarehouseListData[]
}>() isNewOrder?: boolean
wsOpenCode?: string
wsCloseCode?: string
initUrl?: string
}>(),
{
isNewOrder: false,
wsOpenCode: 'STARTORDER',
wsCloseCode: 'ENDORDER',
initUrl: 'factory/podJomallOrderUs/local/getPodBoxOrderDetails',
},
)
const emit = defineEmits([ const emit = defineEmits([
'update:modelValue', 'update:modelValue',
'set-printer', 'set-printer',
...@@ -298,6 +328,60 @@ const sheetPrinter = ref<string>('') ...@@ -298,6 +328,60 @@ const sheetPrinter = ref<string>('')
const productionOrder = ref<string>('') const productionOrder = ref<string>('')
const podOrderDetailsData = ref<OrderData>() const podOrderDetailsData = ref<OrderData>()
const operationStatusMap: Record<string, string> = {
PENDING_PACKING: '待配货',
PENDING_SCHEDULE: '待排单',
PENDING_REPLENISH: '待补胚',
PENDING_PICK: '待拣胚',
IN_PRODUCTION: '生产中',
PACKING_COMPLETED: '配货完成',
COMPLETED: '已完成',
CANCELLED: '已取消',
ARCHIVE: '已归档',
}
const operationStatusTagMap: Record<
string,
{
type?: 'success' | 'warning' | 'info' | 'danger' | 'primary'
style?: { backgroundColor: string; color: string; borderColor: string }
}
> = {
PENDING_PACKING: { type: 'warning' },
PENDING_SCHEDULE: { type: 'info' },
PENDING_REPLENISH: {
style: {
backgroundColor: '#E6A23C',
color: '#FFFFFF',
borderColor: '#E6A23C',
},
},
PENDING_PICK: {
style: {
backgroundColor: '#409EFF',
color: '#FFFFFF',
borderColor: '#409EFF',
},
},
IN_PRODUCTION: { type: 'primary' },
PACKING_COMPLETED: { type: 'success' },
COMPLETED: { type: 'success' },
CANCELLED: { type: 'danger' },
ARCHIVE: {
style: {
backgroundColor: '#909399',
color: '#FFFFFF',
borderColor: '#909399',
},
},
}
const parseOperationNo = (operationNoWithStatus: string) => {
const match = operationNoWithStatus.match(/^([^_]+)_(.+)$/)
if (!match)
return { operationNo: operationNoWithStatus, status: '', statusText: '' }
const [, operationNo, status] = match
const statusText = operationStatusMap[status] || status
return { operationNo, status, statusText }
}
const podOrderDetailsColumns = computed(() => [ const podOrderDetailsColumns = computed(() => [
{ {
label: '图片', label: '图片',
...@@ -307,10 +391,32 @@ const podOrderDetailsColumns = computed(() => [ ...@@ -307,10 +391,32 @@ const podOrderDetailsColumns = computed(() => [
align: 'center', align: 'center',
}, },
{ {
label: '生产单号', label: props.isNewOrder ? '操作单号' : '生产单号',
prop: 'podJomallUsNo', prop: props.isNewOrder ? 'operationNos' : 'podJomallUsNo',
width: 150, width: props.isNewOrder ? 180 : 150,
align: 'center', align: 'center',
render: (row: ProductList) => {
return (
<div>
{props.isNewOrder &&
row.operationNos?.split(',').map((item) => {
const { operationNo, status, statusText } = parseOperationNo(item)
const { type, style } = operationStatusTagMap[status] || {}
return (
<div>
{operationNo}
{statusText && (
<ElTag size="small" type={type} style={{ marginLeft: '6px', ...style }}>
{statusText}
</ElTag>
)}
</div>
)
})}
{!props.isNewOrder && row.podJomallUsNo}
</div>
)
},
}, },
{ {
label: '库存 SKU', label: '库存 SKU',
...@@ -369,7 +475,9 @@ watch(visible, async (value: boolean) => { ...@@ -369,7 +475,9 @@ watch(visible, async (value: boolean) => {
if (value) { if (value) {
podOrderDetailsData.value = {} podOrderDetailsData.value = {}
currentCode = '' currentCode = ''
const localRaw = localStorage.getItem('locaclWarehouseId') const localRaw = props.isNewOrder
? localStorage.getItem('localNewWarehouseId')
: localStorage.getItem('locaclWarehouseId')
const localId = localRaw ? JSON.parse(localRaw) : '' const localId = localRaw ? JSON.parse(localRaw) : ''
/* 先找一次,确认本地值是否存在于列表 */ /* 先找一次,确认本地值是否存在于列表 */
const hit = props.warehouseList.find((w) => w.id == localId) const hit = props.warehouseList.find((w) => w.id == localId)
...@@ -386,7 +494,7 @@ watch(visible, async (value: boolean) => { ...@@ -386,7 +494,7 @@ watch(visible, async (value: boolean) => {
messageChange, messageChange,
) )
socket.send({ socket.send({
code: 'STARTORDER', code: props.wsOpenCode,
factoryNo: userStore.user?.factory.id, factoryNo: userStore.user?.factory.id,
warehouseId: warehouseId.value, warehouseId: warehouseId.value,
}) })
...@@ -394,7 +502,6 @@ watch(visible, async (value: boolean) => { ...@@ -394,7 +502,6 @@ watch(visible, async (value: boolean) => {
console.error(error) console.error(error)
} }
} }
initOrderDetailBox() initOrderDetailBox()
initPrintDevice() initPrintDevice()
const locaclPrinter = localStorage.getItem('sheetPrinter') const locaclPrinter = localStorage.getItem('sheetPrinter')
...@@ -403,7 +510,7 @@ watch(visible, async (value: boolean) => { ...@@ -403,7 +510,7 @@ watch(visible, async (value: boolean) => {
} else { } else {
if (userStore.user?.factory.id) { if (userStore.user?.factory.id) {
socket.send({ socket.send({
code: 'ENDORDER', code: props.wsCloseCode,
factoryNo: userStore.user?.factory.id, factoryNo: userStore.user?.factory.id,
warehouseId: warehouseId.value, warehouseId: warehouseId.value,
}) })
...@@ -423,11 +530,8 @@ watch(boxIndex, (value: number | null) => { ...@@ -423,11 +530,8 @@ watch(boxIndex, (value: number | null) => {
watch( watch(
podBoxList, podBoxList,
(value) => { (value) => {
console.log('podBoxIndex1111', podBoxIndex.value)
if (value) { if (value) {
const item = value.find((item) => item.box === podBoxIndex.value) const item = value.find((item) => item.box === podBoxIndex.value)
console.log('podBoxList', value, podBoxIndex.value, item)
if (item?.data) { if (item?.data) {
renderItemBox(true) renderItemBox(true)
} else { } else {
...@@ -522,11 +626,10 @@ const renderItemBox = (bool: boolean) => { ...@@ -522,11 +626,10 @@ const renderItemBox = (bool: boolean) => {
for (const product of productList) { for (const product of productList) {
if ( if (
product.podJomallUsNo === currentCode || product.podJomallUsNo === currentCode ||
product.thirdSkuCode === currentCode product.thirdSkuCode === currentCode ||
(product.operationNos && product.operationNos.includes(currentCode))
) { ) {
coverImage.value = product.previewImgs?.[0]?.url || '' coverImage.value = product.previewImgs?.[0]?.url || ''
console.log(441, coverImage.value)
nextTick(() => { nextTick(() => {
tableRef.value?.setCurrentRow(product) tableRef.value?.setCurrentRow(product)
}) })
...@@ -537,7 +640,6 @@ const renderItemBox = (bool: boolean) => { ...@@ -537,7 +640,6 @@ const renderItemBox = (bool: boolean) => {
} }
podOrderDetailsData.value = data podOrderDetailsData.value = data
console.log(408, data)
if (productList.every((item) => item.power)) { if (productList.every((item) => item.power)) {
if (userStore.user?.id !== boxItem.fromUser) return if (userStore.user?.id !== boxItem.fromUser) return
...@@ -552,7 +654,7 @@ const messageChange = (data: WebSocketMessage) => { ...@@ -552,7 +654,7 @@ const messageChange = (data: WebSocketMessage) => {
if (!data) return if (!data) return
const { code, ...more } = data const { code, ...more } = data
if (code === 'POD_PRINT_ORDER') { if (code === 'POD_PRINT_ORDER' || code === 'FACTORY_POD_ORDER_PRINT_ORDER') {
try { try {
if (typeof more.txt === 'string') { if (typeof more.txt === 'string') {
console.log( console.log(
...@@ -567,7 +669,10 @@ const messageChange = (data: WebSocketMessage) => { ...@@ -567,7 +669,10 @@ const messageChange = (data: WebSocketMessage) => {
console.log('WebSocketMessage', more) console.log('WebSocketMessage', more)
setPodBoxList(more) setPodBoxList(more)
} else if (code === 'POD_BOX_FLUSH') { } else if (
code === 'POD_BOX_FLUSH' ||
code === 'FACTORY_POD_ORDER_BOX_FLUSH'
) {
initOrderDetailBox() initOrderDetailBox()
} }
} }
...@@ -577,7 +682,10 @@ const setPodBoxList = (data: WebSocketMessage) => { ...@@ -577,7 +682,10 @@ const setPodBoxList = (data: WebSocketMessage) => {
if (obj && typeof obj === 'string') { if (obj && typeof obj === 'string') {
const parsedData = JSON.parse(obj) const parsedData = JSON.parse(obj)
parsedData.fromUser = data.fromUser parsedData.fromUser = data.fromUser
orderStore.setPodBoxList(parsedData) orderStore.setPodBoxList({
url: props.initUrl,
...parsedData,
})
} }
} }
const initPrintDevice = () => { const initPrintDevice = () => {
...@@ -658,15 +766,29 @@ const getPackingData = async (code: string) => { ...@@ -658,15 +766,29 @@ const getPackingData = async (code: string) => {
productionOrder.value = '' productionOrder.value = ''
return return
} }
if(!warehouseId.value){ if (!warehouseId.value) {
return ElMessage.warning('请选择仓库') return ElMessage.warning('请选择仓库')
} }
const res = await getPackingDataApi( const url = props.isNewOrder
code, ? 'factory/podOrderPacking/local/putPackingSafe'
: 'factory/podJomallOrderUs/local/putPackingSafe'
let params = {}
if (props.isNewOrder) {
params = {
podOperationNo: code,
box: boxIndex.value,
factoryNo, factoryNo,
boxIndex.value, warehouseId: warehouseId.value,
warehouseId.value, }
) } else {
params = {
podJomallUsNo: code,
box: boxIndex.value,
factoryNo,
warehouseId: warehouseId.value,
}
}
const res = await getPackingDataApi(url, params)
if (res.code !== 200) { if (res.code !== 200) {
ElMessage.warning(res.message) ElMessage.warning(res.message)
isLock.value = false isLock.value = false
...@@ -718,8 +840,22 @@ const submitInspection = async (callback: () => void) => { ...@@ -718,8 +840,22 @@ const submitInspection = async (callback: () => void) => {
return return
} }
try { try {
const url = props.isNewOrder
? 'factory/podOrderPacking/podPrintOrderComplete'
: 'factory/podJomallOrderUs/podPrintOrderComplete'
let params = {}
if (props.isNewOrder) {
params = {
orderParamList: [{ id: podOrderDetailsData.value?.id }],
}
} else {
params = {
orderId: podOrderDetailsData.value?.id,
}
}
const res = await submitInspectionApi( const res = await submitInspectionApi(
podOrderDetailsData.value?.id, url,
params,
boxIndex.value, boxIndex.value,
warehouseId.value, warehouseId.value,
) )
...@@ -749,12 +885,14 @@ const initOrderDetailBox = async () => { ...@@ -749,12 +885,14 @@ const initOrderDetailBox = async () => {
background: 'rgba(0, 0, 0, 0.3)', background: 'rgba(0, 0, 0, 0.3)',
}) })
try { try {
const res = await getPodBoxListApi(factoryNo, warehouseId.value) const url = props.initUrl
const res = await getPodBoxListApi(url, factoryNo, warehouseId.value)
if (res.code !== 200) { if (res.code !== 200) {
ElMessage.warning(res.message) ElMessage.warning(res.message)
return return
} }
orderStore.setPodBoxList({ orderStore.setPodBoxList({
url,
boxList: res.data, boxList: res.data,
fromUser: userStore.user ? userStore.user.id : 0, fromUser: userStore.user ? userStore.user.id : 0,
factoryNo, factoryNo,
...@@ -910,7 +1048,7 @@ const handleBoxClick = (item: PodMakeOrderData) => { ...@@ -910,7 +1048,7 @@ const handleBoxClick = (item: PodMakeOrderData) => {
} }
const handleClearBox = async () => { const handleClearBox = async () => {
try { try {
if(!boxIndex.value){ if (!boxIndex.value) {
ElMessage.warning('请选择箱子') ElMessage.warning('请选择箱子')
return return
} }
...@@ -927,7 +1065,11 @@ const handleClearBox = async () => { ...@@ -927,7 +1065,11 @@ const handleClearBox = async () => {
} }
try { try {
const url = props.isNewOrder
? 'factory/podOrderPacking/local/delPodBoxOrderDetailsByBox'
: 'factory/podJomallOrderUs/local/delPodBoxOrderDetailsByBox'
const res = await clearBoxApi( const res = await clearBoxApi(
url,
factoryNo, factoryNo,
boxIndex.value || null, boxIndex.value || null,
warehouseId.value, warehouseId.value,
...@@ -937,12 +1079,6 @@ const handleClearBox = async () => { ...@@ -937,12 +1079,6 @@ const handleClearBox = async () => {
return return
} }
ElMessage.success('清空成功') ElMessage.success('清空成功')
// orderStore.setPodBoxList({
// boxList: null,
// factoryNo,
// box: boxIndex.value || undefined,
// warehouseId: warehouseId.value,
// })
boxIndex.value = null boxIndex.value = null
podOrderDetailsData.value = {} podOrderDetailsData.value = {}
coverImage.value = '' coverImage.value = ''
...@@ -1024,6 +1160,7 @@ const print = (data: OrderData, forcePrint = false, callback?: () => void) => { ...@@ -1024,6 +1160,7 @@ const print = (data: OrderData, forcePrint = false, callback?: () => void) => {
const factoryNo = userStore.user?.factory.id const factoryNo = userStore.user?.factory.id
if (!factoryNo) return if (!factoryNo) return
orderStore.setPodBoxList({ orderStore.setPodBoxList({
url: props.initUrl,
fromUser: userStore.user ? userStore.user.id : 0, fromUser: userStore.user ? userStore.user.id : 0,
boxList: item ? (item.data as PodMakeOrderData[]) : null, boxList: item ? (item.data as PodMakeOrderData[]) : null,
factoryNo, factoryNo,
...@@ -1043,16 +1180,15 @@ const clearAllBox = async () => { ...@@ -1043,16 +1180,15 @@ const clearAllBox = async () => {
return return
} }
try { try {
const url = props.isNewOrder
? 'factory/podOrderPacking/local/delPodBoxOrderDetails'
: 'factory/podJomallOrderUs/local/delPodBoxOrderDetails'
const res = await clearAllBoxApi( const res = await clearAllBoxApi(
url,
warehouseId.value, warehouseId.value,
userStore.user?.factory.id, userStore.user?.factory.id,
) )
if (res.code !== 200) return if (res.code !== 200) return
// orderStore.setPodBoxList({
// boxList: res.data,
// factoryNo: userStore.user?.factory.id || '',
// warehouseId: warehouseId.value,
// })
productionOrderRef.value.focus() productionOrderRef.value.focus()
podOrderDetailsData.value = {} podOrderDetailsData.value = {}
coverImage.value = '' coverImage.value = ''
...@@ -1078,7 +1214,7 @@ const handleWarehouseChange = (value: string | number) => { ...@@ -1078,7 +1214,7 @@ const handleWarehouseChange = (value: string | number) => {
if (!value) return if (!value) return
if (_warehouseId.value !== warehouseId.value) { if (_warehouseId.value !== warehouseId.value) {
socket.send({ socket.send({
code: 'ENDORDER', code: props.wsCloseCode,
factoryNo: userStore.user?.factory.id, factoryNo: userStore.user?.factory.id,
warehouseId: _warehouseId.value, warehouseId: _warehouseId.value,
}) })
...@@ -1086,7 +1222,7 @@ const handleWarehouseChange = (value: string | number) => { ...@@ -1086,7 +1222,7 @@ const handleWarehouseChange = (value: string | number) => {
warehouseId.value = value warehouseId.value = value
emit('set-warehouseId', value) emit('set-warehouseId', value)
socket.send({ socket.send({
code: 'STARTORDER', code: props.wsOpenCode,
factoryNo: userStore.user?.factory.id, factoryNo: userStore.user?.factory.id,
warehouseId: warehouseId.value, warehouseId: warehouseId.value,
}) })
...@@ -1131,7 +1267,7 @@ const reconnectWebSocket = async () => { ...@@ -1131,7 +1267,7 @@ const reconnectWebSocket = async () => {
messageChange, messageChange,
) )
socket.send({ socket.send({
code: 'STARTORDER', code: props.wsOpenCode,
factoryNo: userStore.user?.factory.id, factoryNo: userStore.user?.factory.id,
warehouseId: warehouseId.value, warehouseId: warehouseId.value,
}) })
......
<template>
<ElDialog
:model-value="visible"
:title="title"
width="85%"
:close-on-click-modal="false"
@update:model-value="emit('update:visible', $event)"
>
<div class="dialog-form">
<ElForm
ref="editFormRef"
:model="editForm"
:rules="rules"
inline
label-width="90px"
>
<ElFormItem label="入库单号" prop="account">
<ElInput :model-value="editForm.inNo" placeholder="系统自动生成" clearable disabled />
</ElFormItem>
<ElFormItem label="工厂:" prop="factoryCode">
<span>{{ editForm.factoryCode }}</span>
</ElFormItem>
<ElFormItem label="仓库" prop="warehouseId" required>
<ElSelect
v-model="warehouseIdModel"
clearable
:disabled="disableWarehouse"
placeholder="请选择仓库"
style="width: 160px"
@change="emit('warehouse-change', warehouseIdModel)"
>
<ElOption
v-for="item in warehouseList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</ElSelect>
</ElFormItem>
<ElFormItem label="备注" prop="remark" style="width: 45%">
<ElInput v-model="remarkModel" placeholder="请输入备注" clearable />
</ElFormItem>
</ElForm>
<ElTable
size="small"
:data="otherPurchaseData"
height="500px"
border
@selection-change="emit('selection-change', $event)"
>
<ElTableColumn
type="selection"
width="70"
header-align="center"
align="center"
/>
<ElTableColumn
show-overflow-tooltip
width="60"
align="center"
label="序号"
type="index"
/>
<ElTableColumn
show-overflow-tooltip
align="center"
width="100"
label="SKU图片"
prop="skuImage"
>
<template #default="{ row }">
<ImageView :src="row.skuImage" width="40px" height="40px" />
</template>
</ElTableColumn>
<ElTableColumn
show-overflow-tooltip
align="center"
width="140"
label="库存SKU"
prop="warehouseSku"
/>
<ElTableColumn
show-overflow-tooltip
align="center"
width="160"
label="商品名称"
prop="skuName"
/>
<ElTableColumn
show-overflow-tooltip
align="center"
label="款号"
prop="productNo"
/>
<ElTableColumn align="center" label="入库数量" prop="buyStored">
<template #default="{ row }">
<el-input
v-model.number="row.buyStored"
placeholder="入库数量"
style="width: 120px"
clearable
size="small"
@input="emit('set-cost-price', row)"
/>
</template>
</ElTableColumn>
<ElTableColumn
align="center"
width="80"
label="币种"
prop="currencyName"
/>
<ElTableColumn
width="100"
align="center"
label="成本价"
prop="costPrice"
/>
<ElTableColumn
align="center"
width="100"
label="总成本"
prop="totalPrice"
/>
<ElTableColumn align="center" label="库位" prop="locationCode">
<template #default="{ row }">
<ElSelect
v-model="row.locationId"
clearable
placeholder="请输入库位"
style="width: 120px"
filterable
@change="emit('location-change', row.locationId, row)"
>
<ElOption
v-for="item in locationList"
:key="item.locationId"
:label="item.locationCode"
:value="item.locationId"
/>
</ElSelect>
</template>
</ElTableColumn>
<ElTableColumn
align="center"
width="100"
label="所属客户"
prop="userMark"
/>
<ElTableColumn
show-overflow-tooltip
align="center"
width="140"
label="备注"
prop="remark"
>
<template #default="{ row }">
<ElInput v-model.trim="row.remark" clearable size="small" />
</template>
</ElTableColumn>
</ElTable>
</div>
<template #footer>
<div class="product-dialog-footer">
<div style="display: flex; align-items: center">
<span
v-if="showUserMarkFilter"
style="margin-right: 10px; font-style: 13px; color: gray"
>搜索:</span
>
<el-select
v-if="showUserMarkFilter"
:model-value="userMark"
size="small"
style="width: 100px"
@update:model-value="emit('update:userMark', $event)"
>
<el-option
v-for="user in userMarkList"
:key="user.userId"
:label="user.userMark"
:value="user.userId"
/>
</el-select>
<el-input
v-if="showUserMarkFilter"
:model-value="selectSku"
placeholder="库存SKU"
style="width: 200px; margin: 0 10px"
clearable
size="small"
@update:model-value="emit('update:selectSku', $event)"
/>
<el-popover
v-if="showQueryButton"
placement="top-start"
width="1200"
trigger="click"
>
<div v-if="skuData.length > 0" style="height: 50vh">
<ElTable size="small" :data="filterSkuData" height="100%" border>
<ElTableColumn
show-overflow-tooltip
width="60"
align="center"
label="序号"
type="index"
/>
<ElTableColumn
show-overflow-tooltip
align="center"
label="SKU图片"
width="100"
prop="image"
>
<template #default="{ row }">
<ImageView :src="row.skuImage" width="40px" height="40px" />
</template>
</ElTableColumn>
<ElTableColumn
show-overflow-tooltip
align="center"
label="商品名称"
width="200"
prop="productName"
/>
<ElTableColumn
show-overflow-tooltip
align="center"
width="200"
label="库存SKU"
prop="warehouseSku"
/>
<ElTableColumn
show-overflow-tooltip
align="center"
label="款号"
prop="productNo"
/>
<ElTableColumn
show-overflow-tooltip
align="center"
label="币种"
width="80"
prop="currencyName"
/>
<ElTableColumn
show-overflow-tooltip
align="center"
label="成本价"
width="80"
prop="costPrice"
/>
<ElTableColumn
show-overflow-tooltip
align="center"
label="库位"
prop="locationCode"
/>
<ElTableColumn
show-overflow-tooltip
align="center"
label="所属客户"
prop="userMark"
/>
<ElTableColumn
width="80"
align="center"
header-align="center"
label="操作"
>
<template #default="{ row }">
<el-icon :size="32" color="#67C23A" class="cursor-pointer">
<CirclePlusFilled @click="emit('sku-add', row)" />
</el-icon>
</template>
</ElTableColumn>
</ElTable>
</div>
<template #reference>
<el-button
type="primary"
size="small"
style="width: 90px"
@click="emit('query-sku')"
>
查询
</el-button>
</template>
</el-popover>
<el-button
v-if="showBatchAddButton"
style="margin-left: 6px"
type="success"
size="small"
@click="emit('batch-add')"
>
批量新增
</el-button>
<el-button
type="danger"
style="margin-left: 10px"
size="small"
@click="emit('delete')"
>
删除
</el-button>
<el-button
v-if="showImportButton"
type="primary"
style="margin-left: 10px"
size="small"
@click="emit('import')"
>
导入
</el-button>
</div>
<div>
<el-button
size="small"
style="margin-left: 10px"
@click="emit('update:visible', false)"
>
取消
</el-button>
<el-button type="primary" size="small" @click="emit('save')">
保存
</el-button>
</div>
</div>
</template>
</ElDialog>
</template>
<script setup lang="ts">
import { CirclePlusFilled } from '@element-plus/icons-vue'
import { computed, ref } from 'vue'
import ImageView from '@/components/ImageView.vue'
import type {
InterProductList,
InterWarehouseDetail,
InterskuList,
ILocation,
} from '@/types/api/warehouse'
interface WarehouseInfo {
id?: string | number
name: string
}
interface UserMark {
userId: number
userMark: string
}
const props = withDefaults(
defineProps<{
visible: boolean
title: string
disableWarehouse?: boolean
showUserMarkFilter?: boolean
showQueryButton?: boolean
showBatchAddButton?: boolean
showImportButton?: boolean
editForm: InterWarehouseDetail
rules: Record<string, unknown>
warehouseList: WarehouseInfo[]
otherPurchaseData: InterProductList[]
locationList: ILocation[]
userMarkList: UserMark[]
userMark: number
selectSku: string
skuData: InterskuList[]
filterSkuData: InterskuList[]
}>(),
{
disableWarehouse: false,
showUserMarkFilter: true,
showQueryButton: true,
showBatchAddButton: true,
showImportButton: true,
},
)
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'update:userMark', value: number): void
(e: 'update:selectSku', value: string): void
(e: 'update:remark', value: string): void
(e: 'update:warehouseId', value: number | string | undefined): void
(e: 'warehouse-change', value: number | string | undefined): void
(e: 'selection-change', value: InterProductList[]): void
(e: 'set-cost-price', value: InterProductList): void
(
e: 'location-change',
value: number | null | undefined,
row: InterProductList,
): void
(e: 'query-sku'): void
(e: 'sku-add', value: InterskuList): void
(e: 'batch-add'): void
(e: 'delete'): void
(e: 'import'): void
(e: 'save'): void
}>()
const editFormRef = ref()
const warehouseIdModel = computed({
get: () => props.editForm.warehouseId,
set: (value: number | string | undefined) =>
emit('update:warehouseId', value),
})
const remarkModel = computed({
get: () => props.editForm.remark || '',
set: (value: string) => emit('update:remark', value.trim()),
})
const validateForm = async () => {
await editFormRef.value?.validate()
}
defineExpose({
validateForm,
})
</script>
<style lang="scss" scoped>
.cursor-pointer {
cursor: pointer;
}
.product-dialog-footer {
display: flex;
justify-content: space-between;
margin: 8px 0;
}
</style>
import { computed, ref, type Ref } from 'vue'
import BigNumber from 'bignumber.js'
import { ElMessage } from 'element-plus'
import { getBySkuAndUserMarkApi } from '@/api/warehouse'
import type {
InterProductList,
InterWarehouseDetail,
InterskuList,
} from '@/types/api/warehouse'
interface UserMark {
userId: number
userMark: string
userName: string
}
interface UseReceiptProductDialogOptions {
editForm: Ref<InterWarehouseDetail>
otherPurchaseData: Ref<InterProductList[]>
userMark: Ref<number>
batchUserMark: Ref<number>
importUserMark: Ref<number>
selectSku: Ref<string>
userMarkList: Ref<UserMark[]>
}
export function useReceiptProductDialog(options: UseReceiptProductDialogOptions) {
const {
editForm,
otherPurchaseData,
userMark,
batchUserMark,
importUserMark,
selectSku,
userMarkList,
} = options
const skuData = ref<InterskuList[]>([])
const setCostPrice = (item: InterProductList) => {
if (item.costPrice !== 0 && !item.costPrice) {
ElMessage.warning('商品成本价为空,请完善商品成本价')
return
}
const buyStored = item.buyStored ?? 0
const costPrice = item.costPrice ?? 0
const amount = new BigNumber(buyStored).multipliedBy(costPrice).toFixed(2)
item.totalPrice = Number(amount)
}
const getUserMarkText = (
customerId: number,
mode: 'query' | 'create' = 'query',
): string | null => {
const item = userMarkList.value.find((e) => e.userId === customerId)
if (customerId === 0) {
return mode === 'query' ? null : ''
}
return item?.userMark || null
}
const batchAddCommodity = async (
sku: string,
type: '1' | '2' | '3',
): Promise<InterskuList[]> => {
if (!editForm.value.warehouseId) {
ElMessage.error('请选择仓库')
return []
}
try {
let userValue: number = 0
if (type === '1') {
userValue = userMark.value
} else if (type === '2') {
userValue = batchUserMark.value
} else if (type === '3') {
userValue = importUserMark.value
}
const user = getUserMarkText(userValue, 'query')
const res = await getBySkuAndUserMarkApi(editForm.value.warehouseId, sku, user)
const arr: InterskuList[] = res.data || []
const ids: Record<string, boolean> = {}
for (const item of otherPurchaseData.value) {
if (item.warehouseSku !== undefined) {
ids[item.warehouseSku] = true
}
}
return arr.filter((currentItem: InterskuList) => {
return currentItem.sku === undefined || !ids[currentItem.sku]
})
} catch (e) {
console.error(e)
return []
}
}
const selectbySku = async () => {
if (!editForm.value.warehouseId) return ElMessage.error('请选择仓库')
try {
const user = getUserMarkText(userMark.value, 'query')
const res = await getBySkuAndUserMarkApi(
editForm.value.warehouseId,
selectSku.value,
user,
)
skuData.value = res.data || []
} catch (e) {
console.error(e)
}
}
const skudblclick = (val: InterskuList) => {
const {
locationCode = '',
costPrice = null,
productNo = '',
warehouseSku = '',
productName = '',
skuImage = '',
locationId = null,
currencyName = '',
currencyCode = null,
} = val || {}
const lastItem =
otherPurchaseData.value[otherPurchaseData.value.length - 1] || null
if (lastItem && lastItem.currencyName) {
if (!currencyName || currencyName !== lastItem.currencyName) {
ElMessage.error('添加的商品币种需一致')
return
}
}
const item = userMarkList.value.find((e) => e.userId === userMark.value)
otherPurchaseData.value = [
...JSON.parse(JSON.stringify(otherPurchaseData.value)),
{
skuImage,
warehouseSku,
customerId: userMark.value !== 0 ? userMark.value : null,
customerName: userMark.value !== 0 ? item?.userName || '' : null,
userMark: userMark.value !== 0 ? item?.userMark : null,
skuName: productName,
productNo,
locationCode: locationCode ?? '',
locationId: locationId ?? null,
costPrice,
buyStored: null,
totalPrice: null,
currencyName,
currencyCode,
},
]
const skuSet = new Set(
otherPurchaseData.value.map((item: InterProductList) => item.warehouseSku),
)
skuData.value = skuData.value.filter((item: InterskuList) => !skuSet.has(item.sku))
}
const filterSkuData = computed(() => {
const skuList = otherPurchaseData.value.map((el) => el.warehouseSku)
const item = userMarkList.value.find((u) => u.userId === userMark.value)
return skuData.value
.filter((el) => !skuList.includes(el.warehouseSku))
.map((e) => {
return {
...e,
userMark: userMark.value === 0 ? '' : item?.userMark,
}
})
})
return {
skuData,
setCostPrice,
batchAddCommodity,
selectbySku,
skudblclick,
filterSkuData,
}
}
...@@ -490,327 +490,37 @@ ...@@ -490,327 +490,37 @@
</div> </div>
</div> </div>
</ElDialog> </ElDialog>
<ElDialog <ReceiptProductDialog
v-model="newDialogVisible" ref="receiptProductDialogRef"
v-model:visible="newDialogVisible"
v-model:user-mark="userMark"
v-model:select-sku="selectSku"
:title="formId ? '编辑' : '新增'" :title="formId ? '编辑' : '新增'"
width="85%" :disable-warehouse="!!formId"
:close-on-click-modal="false" :edit-form="editForm"
>
<div class="dialog-form">
<ElForm
ref="editFormRef"
:model="editForm"
:rules="rules" :rules="rules"
inline :warehouse-list="warehouseList"
label-width="90px" :other-purchase-data="otherPurchaseData"
> :location-list="locationList"
<ElFormItem label="入库单号" prop="account"> :user-mark-list="userMarkList"
<ElInput v-model.trim="editForm.inNo" clearable disabled /> :sku-data="skuData"
</ElFormItem> :filter-sku-data="filterSkuData"
<ElFormItem label="工厂:" prop="factoryCode"> :show-user-mark-filter="true"
<span>{{ editForm.factoryCode }}</span> :show-batch-add-button="true"
</ElFormItem> :show-import-button="true"
<ElFormItem label="仓库" prop="warehouseId" required> @warehouse-change="handleWarehouseChange"
<ElSelect
v-model="editForm.warehouseId"
clearable
:disabled="formId"
placeholder="请选择仓库"
style="width: 160px"
@change="handleWarehouseChange(editForm.warehouseId)"
>
<ElOption
v-for="item in warehouseList"
:key="item.id"
:label="item.name"
:value="item.id"
></ElOption>
</ElSelect>
</ElFormItem>
<ElFormItem label="备注" prop="remark" style="width: 45%">
<ElInput
v-model.trim="editForm.remark"
placeholder="请输入备注"
clearable
/>
</ElFormItem>
</ElForm>
<ElTable
size="small"
:data="otherPurchaseData"
height="500px"
border
@selection-change="productSelectionChange" @selection-change="productSelectionChange"
> @set-cost-price="setCostPrice"
<ElTableColumn @location-change="handleLocationChange"
type="selection" @query-sku="selectbySku"
width="70" @sku-add="skudblclick"
header-align="center" @batch-add="addPurchase"
align="center" @delete="deleteOtherWarehousing"
></ElTableColumn> @import="importData"
<ElTableColumn @update:remark="updateEditRemark"
show-overflow-tooltip @update:warehouse-id="updateEditWarehouseId"
width="60" @save="addOtherCurrency"
align="center"
label="序号"
type="index"
></ElTableColumn>
<ElTableColumn
show-overflow-tooltip
align="center"
width="100"
label="SKU图片"
prop="skuImage"
>
<template #default="{ row }">
<ImageView :src="row.skuImage" width="40px" height="40px" />
</template>
</ElTableColumn>
<ElTableColumn
show-overflow-tooltip
align="center"
width="140"
label="库存SKU"
prop="warehouseSku"
>
</ElTableColumn>
<ElTableColumn
show-overflow-tooltip
align="center"
width="160"
label="商品名称"
prop="skuName"
/>
<ElTableColumn
show-overflow-tooltip
align="center"
label="款号"
prop="productNo"
/>
<ElTableColumn align="center" label="入库数量" prop="buyStored">
<template #default="{ row }">
<el-input
v-model.number="row.buyStored"
placeholder="入库数量"
style="width: 120px"
clearable
size="small"
@input="setCostPrice(row)"
></el-input>
</template>
</ElTableColumn>
<ElTableColumn
align="center"
width="80"
label="币种"
prop="currencyName"
/> />
<ElTableColumn
width="100"
align="center"
label="成本价"
prop="costPrice"
/>
<ElTableColumn
align="center"
width="100"
label="总成本"
prop="totalPrice"
/>
<ElTableColumn align="center" label="库位" prop="locationCode">
<template #default="{ row }">
<!-- <span v-if="formId&&row.locationCode">{{ row.locationCode }}</span> -->
<ElSelect
v-model="row.locationId"
clearable
placeholder="请输入库位"
style="width: 120px"
filterable
@change="handleLocationChange(row.locationId, row)"
>
<ElOption
v-for="item in locationList"
:key="item.locationId"
:label="item.locationCode"
:value="item.locationId"
></ElOption>
</ElSelect>
</template>
</ElTableColumn>
<ElTableColumn
align="center"
width="100"
label="所属客户"
prop="userMark"
/>
<ElTableColumn
show-overflow-tooltip
align="center"
width="140"
label="备注"
prop="remark"
>
<template #default="{ row }">
<ElInput v-model.trim="row.remark" clearable size="small" />
</template>
</ElTableColumn>
</ElTable>
</div>
<template #footer>
<div class="product-dialog-footer">
<div style="display: flex; align-items: center">
<span style="margin-right: 10px; font-style: 13px; color: gray"
>搜索:</span
>
<el-select v-model="userMark" size="small" style="width: 100px">
<el-option
v-for="user in userMarkList"
:key="user.userId"
:label="user.userMark"
:value="user.userId"
></el-option>
</el-select>
<el-input
v-model.trim="selectSku"
placeholder="库存SKU"
style="width: 200px; margin: 0 10px"
clearable
size="small"
></el-input>
<el-popover placement="top-start" width="1200" trigger="click">
<div v-if="skuData.length > 0" style="height: 50vh">
<ElTable size="small" :data="filterSkuData" height="100%" border>
<ElTableColumn
show-overflow-tooltip
width="60"
align="center"
label="序号"
type="index"
/>
<ElTableColumn
show-overflow-tooltip
align="center"
label="SKU图片"
width="100"
prop="image"
>
<template #default="{ row }">
<ImageView :src="row.skuImage" width="40px" height="40px" />
</template>
</ElTableColumn>
<ElTableColumn
show-overflow-tooltip
align="center"
label="商品名称"
width="200"
prop="productName"
/>
<ElTableColumn
show-overflow-tooltip
align="center"
width="200"
label="库存SKU"
prop="warehouseSku"
/>
<ElTableColumn
show-overflow-tooltip
align="center"
label="款号"
prop="productNo"
/>
<ElTableColumn
show-overflow-tooltip
align="center"
label="币种"
width="80"
prop="currencyName"
/>
<ElTableColumn
show-overflow-tooltip
align="center"
label="成本价"
width="80"
prop="costPrice"
/>
<ElTableColumn
show-overflow-tooltip
align="center"
label="库位"
prop="locationCode"
/>
<ElTableColumn
show-overflow-tooltip
align="center"
label="所属客户"
prop="userMark"
/>
<ElTableColumn
width="80"
align="center"
header-align="center"
label="操作"
>
<template #default="{ row }">
<el-icon :size="32" color="#67C23A" class="cursor-pointer">
<CirclePlusFilled @click="skudblclick(row)" />
</el-icon>
</template>
</ElTableColumn>
</ElTable>
</div>
<template #reference>
<el-button
type="primary"
size="small"
style="width: 90px"
@click="selectbySku()"
>
查询
</el-button>
</template>
</el-popover>
<el-button
style="margin-left: 6px"
type="success"
size="small"
@click="addPurchase"
>
批量新增
</el-button>
<el-button
type="danger"
style="margin-left: 10px"
size="small"
@click="deleteOtherWarehousing()"
>
删除
</el-button>
<el-button
type="primary"
style="margin-left: 10px"
size="small"
@click="importData"
>
导入
</el-button>
</div>
<div>
<el-button
size="small"
style="margin-left: 10px"
@click="newDialogVisible = false"
>
取消
</el-button>
<el-button type="primary" size="small" @click="addOtherCurrency()">
保存
</el-button>
</div>
</div>
</template>
</ElDialog>
<ElDialog <ElDialog
v-model="exportVisible" v-model="exportVisible"
title="导出选项" title="导出选项"
...@@ -920,12 +630,13 @@ ...@@ -920,12 +630,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { ElMessage, ElRadioGroup, ElTree } from 'element-plus' import { ElMessage, ElRadioGroup, ElTree } from 'element-plus'
import { CirclePlusFilled } from '@element-plus/icons-vue'
import splitDiv from '@/components/splitDiv/splitDiv.vue' import splitDiv from '@/components/splitDiv/splitDiv.vue'
import { ElTable } from 'element-plus' import { ElTable } from 'element-plus'
import ReceiptProductDialog from './components/ReceiptProductDialog.vue'
import usePageList from '@/utils/hooks/usePageList' import usePageList from '@/utils/hooks/usePageList'
import { checkUpdateParams, AnyObject } from '@/utils/hooks/commonUtil' import { checkUpdateParams, AnyObject } from '@/utils/hooks/commonUtil'
import { useValue } from '@/utils/hooks/useValue' import { useValue } from '@/utils/hooks/useValue'
import { useReceiptProductDialog } from './hooks/useReceiptProductDialog'
import { import {
getInRecordStatusTree, getInRecordStatusTree,
warehouseInRecordListPageApi, warehouseInRecordListPageApi,
...@@ -942,9 +653,7 @@ import { ...@@ -942,9 +653,7 @@ import {
warehouseInfo, warehouseInfo,
InRecordBatchCheckPrintApi, InRecordBatchCheckPrintApi,
factoryWarehouseInventoryPrint, factoryWarehouseInventoryPrint,
warehouseInRecordExport, warehouseInRecordExport, getLocalFactoryList,
getLocalFactoryList,
getBySkuAndUserMarkApi,
} from '@/api/warehouse' } from '@/api/warehouse'
import { filePath } from '@/api/axios.ts' import { filePath } from '@/api/axios.ts'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
...@@ -955,7 +664,6 @@ import { ...@@ -955,7 +664,6 @@ import {
InterWarehousePage, InterWarehousePage,
InterWarehouseTree, InterWarehouseTree,
InterProductList, InterProductList,
InterskuList,
ILocation, ILocation,
InterWarehouseDetail, InterWarehouseDetail,
} from '@/types/api/warehouse' } from '@/types/api/warehouse'
...@@ -1140,18 +848,6 @@ const { ...@@ -1140,18 +848,6 @@ const {
).then((res) => res.data), ).then((res) => res.data),
}) })
const setCostPrice = (item: InterProductList) => {
if (item.costPrice !== 0 && !item.costPrice) {
ElMessage.warning('商品成本价为空,请完善商品成本价')
return
}
if (item) {
const buyStored = item.buyStored ?? 0
const costPrice = item.costPrice ?? 0
const amount = new BigNumber(buyStored).multipliedBy(costPrice).toFixed(2)
item.totalPrice = Number(amount)
}
}
const getTreeNum = async () => { const getTreeNum = async () => {
try { try {
const res = await getInRecordStatusTree() const res = await getInRecordStatusTree()
...@@ -1214,48 +910,6 @@ async function handlePrintProductTag() { ...@@ -1214,48 +910,6 @@ async function handlePrintProductTag() {
window.open(filePath + res.message, '_blank') window.open(filePath + res.message, '_blank')
} }
const batchAddCommodity = async (
sku: string,
type: string,
): Promise<InterskuList[]> => {
if (!editForm.value.warehouseId) {
ElMessage.error('请选择仓库')
return []
}
try {
let userValue: string | number = ''
if (type === '1') {
userValue = userMark.value
} else if (type === '2') {
userValue = batchUserMark.value
} else if (type === '3') {
userValue = importUserMark.value
}
const item = userMarkList.value.find((e) => e.userId === userValue)
const user = userValue === 0 ? null : item?.userMark
const res = await getBySkuAndUserMarkApi(
editForm.value.warehouseId,
sku,
user,
)
const arr: InterskuList[] = res.data || []
const ids: Record<string, boolean> = {}
// 过滤掉商品列表已经加了的
for (const item of otherPurchaseData.value) {
if (item.warehouseSku !== undefined) {
ids[item.warehouseSku] = true
}
}
// 使用 filter 方法过滤掉已经存在的 SKU
const filteredArr = arr.filter((currentItem: InterskuList) => {
return currentItem.sku === undefined || !ids[currentItem.sku]
})
return filteredArr
} catch (e) {
console.error(e)
return []
}
}
interface InterImportData { interface InterImportData {
warehouseSku: string warehouseSku: string
...@@ -1436,74 +1090,6 @@ const rowClick = (row: InterWarehousePage) => { ...@@ -1436,74 +1090,6 @@ const rowClick = (row: InterWarehousePage) => {
currentRow.value = row currentRow.value = row
tabsClick() tabsClick()
} }
const skuData = ref<InterskuList[]>([])
const selectbySku = async () => {
if (!editForm.value.warehouseId) return ElMessage.error('请选择仓库')
try {
const item = userMarkList.value.find((e) => e.userId === userMark.value)
const user = userMark.value === 0 ? null : item?.userMark
const res = await getBySkuAndUserMarkApi(
editForm.value.warehouseId,
selectSku.value,
user,
)
skuData.value = res.data || []
} catch (e) {
console.error(e)
}
}
const skudblclick = (val: InterskuList) => {
// 使用可选链和空值合并运算符处理可能的null值
const {
locationCode = '',
costPrice = null,
productNo = '',
warehouseSku = '',
productName = '',
skuImage = '',
locationId = null,
currencyName = '',
currencyCode = null,
} = val || {}
// 币种一致性校验
const lastItem =
otherPurchaseData.value[otherPurchaseData.value.length - 1] || null
if (lastItem && lastItem.currencyName) {
if (!currencyName || currencyName !== lastItem.currencyName) {
ElMessage.error(`添加的商品币种需一致`)
return
}
}
const item = userMarkList.value.find((e) => e.userId === userMark.value)
otherPurchaseData.value = [
...JSON.parse(JSON.stringify(otherPurchaseData.value)),
{
skuImage,
warehouseSku,
customerId: userMark.value !== 0 ? userMark.value : null,
customerName: userMark.value !== 0 ? item?.userName || '' : null,
userMark: userMark.value !== 0 ? item?.userMark : null,
skuName: productName,
productNo,
locationCode: locationCode ?? '', // 确保空值处理
locationId: locationId ?? null, // 确保空值处理
costPrice,
buyStored: null,
totalPrice: null,
currencyName,
currencyCode,
},
]
// 使用filter代替forEach+splice,时间复杂度从O(n^2)降到O(n)
const skuSet = new Set(
otherPurchaseData.value.map((item: InterProductList) => item.warehouseSku),
)
skuData.value = skuData.value.filter(
(item: InterskuList) => !skuSet.has(item.sku),
)
}
const tabsClick = async () => { const tabsClick = async () => {
if (!currentRow.value) { if (!currentRow.value) {
detailList.value = [] detailList.value = []
...@@ -1526,11 +1112,35 @@ const [editForm, resetEditForm] = useValue<InterWarehouseDetail>({ ...@@ -1526,11 +1112,35 @@ const [editForm, resetEditForm] = useValue<InterWarehouseDetail>({
factoryId: 0, factoryId: 0,
productList: [], productList: [],
}) })
const updateEditRemark = (value: string) => {
editForm.value.remark = value
}
const updateEditWarehouseId = (value: number | string | undefined) => {
editForm.value.warehouseId = value
}
const newDialogVisible = ref(false) const newDialogVisible = ref(false)
const editFormRef = ref() const receiptProductDialogRef = ref<{
validateForm: () => Promise<void>
} | null>(null)
const editForm2 = ref({}) const editForm2 = ref({})
const formId = ref<number | undefined>(undefined) const formId = ref<number | undefined>(undefined)
const otherPurchaseData = ref<InterProductList[]>([]) const otherPurchaseData = ref<InterProductList[]>([])
const {
skuData,
setCostPrice,
batchAddCommodity,
selectbySku,
skudblclick,
filterSkuData,
} = useReceiptProductDialog({
editForm,
otherPurchaseData,
userMark,
batchUserMark,
importUserMark,
selectSku,
userMarkList,
})
const getUserMark = async () => { const getUserMark = async () => {
const { data } = await getLocalFactoryList() const { data } = await getLocalFactoryList()
...@@ -1572,7 +1182,6 @@ const addDialog = async (i: number, v: InterWarehousePage | null) => { ...@@ -1572,7 +1182,6 @@ const addDialog = async (i: number, v: InterWarehousePage | null) => {
otherPurchaseData.value = [] otherPurchaseData.value = []
formId.value = undefined formId.value = undefined
} }
fetchLocationList('')
selectSku.value = '' selectSku.value = ''
newDialogVisible.value = true newDialogVisible.value = true
} }
...@@ -1699,8 +1308,6 @@ watch( ...@@ -1699,8 +1308,6 @@ watch(
}, },
) )
const addOtherCurrency = async () => { const addOtherCurrency = async () => {
console.log(11111)
const loading = ElLoading.service({ const loading = ElLoading.service({
fullscreen: true, fullscreen: true,
text: '操作中...', text: '操作中...',
...@@ -1708,7 +1315,7 @@ const addOtherCurrency = async () => { ...@@ -1708,7 +1315,7 @@ const addOtherCurrency = async () => {
}) })
try { try {
try { try {
await editFormRef.value?.validate() await receiptProductDialogRef.value?.validateForm()
} catch { } catch {
return return
} }
...@@ -1746,19 +1353,6 @@ const addOtherCurrency = async () => { ...@@ -1746,19 +1353,6 @@ const addOtherCurrency = async () => {
} }
} }
const filterSkuData = computed(() => {
const skuList = otherPurchaseData.value.map((el) => el.warehouseSku)
// console.log(skuList, skuData.value)
const item = userMarkList.value.find((u) => u.userId === userMark.value)
return skuData.value
.filter((el) => !skuList.includes(el.warehouseSku))
.map((e) => {
return {
...e,
userMark: userMark.value === 0 ? '' : item?.userMark,
}
})
})
const addSection = async () => { const addSection = async () => {
const params = { ...editForm.value } const params = { ...editForm.value }
params.productList = otherPurchaseData.value params.productList = otherPurchaseData.value
...@@ -1974,7 +1568,10 @@ const fetchLocationList = async (query: string) => { ...@@ -1974,7 +1568,10 @@ const fetchLocationList = async (query: string) => {
} }
// 输入2秒后再调用接口(节流) // 输入2秒后再调用接口(节流)
// const handleLocationSearch = debounce(fetchLocationList, 2000) // const handleLocationSearch = debounce(fetchLocationList, 2000)
const handleLocationChange = (val: number, row: InterProductList) => { const handleLocationChange = (
val: number | null | undefined,
row: InterProductList,
) => {
const found = locationList.value.find( const found = locationList.value.find(
(item: InterProductList) => item.locationId === val, (item: InterProductList) => item.locationId === val,
) )
...@@ -2016,10 +1613,6 @@ onMounted(() => { ...@@ -2016,10 +1613,6 @@ onMounted(() => {
text-align: center; text-align: center;
} }
.cursor-pointer {
cursor: pointer;
}
.header-filter-form { .header-filter-form {
:deep(.el-form-item) { :deep(.el-form-item) {
margin-right: 14px; margin-right: 14px;
...@@ -2027,12 +1620,6 @@ onMounted(() => { ...@@ -2027,12 +1620,6 @@ onMounted(() => {
} }
} }
.product-dialog-footer {
display: flex;
justify-content: space-between;
margin: 8px 0;
}
$border: solid 1px #ddd; $border: solid 1px #ddd;
.send-order-list { .send-order-list {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment