Compare commits
69
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7382154a8d | ||
|
|
b861487d9d | ||
|
|
6273ec0f19 | ||
|
|
4825cd13f2 | ||
|
|
036b331e9a | ||
|
|
5557819e64 | ||
|
|
69608b92d7 | ||
|
|
c6084e8b2c | ||
|
|
cc2d0218b5 | ||
|
|
ec57e43b25 | ||
|
|
f084afafcc | ||
|
|
a1caee64f7 | ||
|
|
0710e7cccf | ||
|
|
81618150ab | ||
|
|
7b11fc60c8 | ||
|
|
2764b27118 | ||
|
|
3ea99ce92f | ||
|
|
f56c40bb6d | ||
|
|
2903972e55 | ||
|
|
917d028ca0 | ||
|
|
7cf50486ef | ||
|
|
51222f608a | ||
|
|
fc6a514076 | ||
|
|
92b89f145c | ||
|
|
9e70b4a1f3 | ||
|
|
8243a82aa7 | ||
|
|
8f42cea11b | ||
|
|
b259e0c2e7 | ||
|
|
30e20853df | ||
|
|
6688fb3867 | ||
|
|
d9d1458b58 | ||
|
|
4faf1bf41a | ||
|
|
2347b84a8a | ||
|
|
8ec38126e6 | ||
|
|
55f13bb47b | ||
|
|
e785a99b7d | ||
|
|
470a474d70 | ||
|
|
9fffe228d5 | ||
|
|
e7827c4714 | ||
|
|
fc6e9915a6 | ||
|
|
ab9d199fe3 | ||
|
|
863f0fe850 | ||
|
|
ce5f5a956f | ||
|
|
c94a009caf | ||
|
|
12187283cc | ||
|
|
0e105c78e1 | ||
|
|
e1ac017a3c | ||
|
|
066f1c623c | ||
|
|
d660d6fcf1 | ||
|
|
02f192cac6 | ||
|
|
530ca0c3ad | ||
|
|
b3ecd77fb2 | ||
|
|
36c0f9a3a0 | ||
|
|
084bb235f8 | ||
|
|
567da03aea | ||
|
|
d5b2e19658 | ||
|
|
cff4debea4 | ||
|
|
ebfa5bf2a0 | ||
|
|
dc6e2ece54 | ||
|
|
e0b00cc62b | ||
|
|
9272ed0e23 | ||
|
|
9e4be6db92 | ||
|
|
03024c395a | ||
|
|
7043a49206 | ||
|
|
8a9bb09c18 | ||
|
|
f63d202327 | ||
|
|
aa186a3f82 | ||
|
|
216d7c6ab4 | ||
|
|
cfc3a24e6e |
@@ -0,0 +1,5 @@
|
||||
NODE_ENV = production
|
||||
VUE_APP_ENV_NAME = zhoushang环境
|
||||
VUE_APP_UPGRADE_NAME = erp_pda_zhoushang
|
||||
VUE_APP_PRODUCTION_API_URL = https://zsfz.yushangfangzhi.com/hcscm/pda/v1
|
||||
VUE_APP_DEV_API_URL = https://zsfz.yushangfangzhi.com/hcscm/pda/v1
|
||||
@@ -0,0 +1,5 @@
|
||||
NODE_ENV = development
|
||||
VUE_APP_ENV_NAME = 本地环境
|
||||
VUE_APP_UPGRADE_NAME = erp_pda_dev
|
||||
VUE_APP_PRODUCTION_API_URL = http://192.168.1.66:7010/hcscm_weave/pda/v1
|
||||
VUE_APP_DEV_API_URL = https://192.168.1.66:7010/hcscm_weave/pda/v1
|
||||
@@ -0,0 +1,5 @@
|
||||
NODE_ENV = development
|
||||
VUE_APP_ENV_NAME = 本地穿透环境
|
||||
VUE_APP_UPGRADE_NAME = erp_pda_dev
|
||||
VUE_APP_PRODUCTION_API_URL = https://2yd52gx88533.vicp.fun/hcscm/pda/v1
|
||||
VUE_APP_DEV_API_URL = https://2yd52gx88533.vicp.fun/hcscm/pda/v1
|
||||
@@ -0,0 +1,5 @@
|
||||
NODE_ENV = development
|
||||
VUE_APP_ENV_NAME = mes环境
|
||||
VUE_APP_UPGRADE_NAME = erp_pda
|
||||
VUE_APP_PRODUCTION_API_URL = https://www.haotopai.com/hcscm_weave/mes/server/pda/v1
|
||||
VUE_APP_DEV_API_URL = https://www.haotopai.com/hcscm_weave/mes/server/pda/v1
|
||||
@@ -0,0 +1,5 @@
|
||||
NODE_ENV = development
|
||||
VUE_APP_ENV_NAME = zhisheng环境
|
||||
VUE_APP_UPGRADE_NAME = erp_pda_pre
|
||||
VUE_APP_PRODUCTION_API_URL = https://hcscmpre.zzfzyc.com/hcscm/pda/v1
|
||||
VUE_APP_DEV_API_URL = https://hcscmpre.zzfzyc.com/hcscm/pda/v1
|
||||
@@ -0,0 +1,5 @@
|
||||
NODE_ENV = production
|
||||
VUE_APP_ENV_NAME = zhoushang环境
|
||||
VUE_APP_UPGRADE_NAME = erp_pda_zhoushang
|
||||
VUE_APP_PRODUCTION_API_URL = https://zsfz.yushangfangzhi.com/hcscm/pda/v1
|
||||
VUE_APP_DEV_API_URL = https://zsfz.yushangfangzhi.com/hcscm/pda/v1
|
||||
@@ -1,3 +1,20 @@
|
||||
# 不使用 pda项目,以后的新需求或者修改 使用 本项目
|
||||
|
||||
打包正式环境发布:
|
||||
|
||||
先修改 .env 文件,修改为目标请求路径
|
||||
|
||||
NODE_ENV = development
|
||||
VUE_APP_ENV_NAME = 默认环境
|
||||
VUE_APP_UPGRADE_NAME = erp_pda
|
||||
VUE_APP_PRODUCTION_API_URL = https://www.haotopai.com/hcscm/mes/server/pda/v1
|
||||
VUE_APP_DEV_API_URL = https://www.haotopai.com/hcscm/mes/server/pda/v1
|
||||
|
||||
|
||||
HBuilderX中 点击 生成本地打包APP资源
|
||||
|
||||
点击云打包 选择自有证书 输入证书库密码和证书私钥密码:123456 点击快速安心打包
|
||||
|
||||
# pda-cli
|
||||
|
||||
## Project setup
|
||||
@@ -17,3 +34,105 @@ yarn build
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
### 本地电脑window 使用命令行云打包:
|
||||
cli命令需要配置环境变量:[cli配置环境变量](https://hx.dcloud.net.cn/cli/README)
|
||||
cli云打包使用:
|
||||
```
|
||||
cli pack --config D:/work_xuan/pda-cli/pack.json
|
||||
```
|
||||
|
||||
pack.json内容如下:详情可访问 [CLI uni-app发行 - Android/iOS云打包](https://hx.dcloud.net.cn/cli/pack) 了解更多
|
||||
|
||||
以下的证书信息是我本地电脑的本地证书:生成证书可参考 [证书生成](https://ask.dcloud.net.cn/article/35777)
|
||||
|
||||
本地使用Android Studio 离线打包教程如下: [Android 打包发行](https://nativesupport.dcloud.net.cn/AppDocs/package/android.html)
|
||||
```
|
||||
{
|
||||
//项目名字或项目绝对路径
|
||||
"project": "pda",
|
||||
//打包平台 默认值android 值有"android","ios" 如果要打多个逗号隔开打包平台
|
||||
"platform":"android",
|
||||
//是否使用自定义基座 默认值false true自定义基座 false自定义证书
|
||||
"iscustom": false,
|
||||
//打包方式是否为安心打包默认值false,true安心打包,false传统打包
|
||||
"safemode": false,
|
||||
//android打包参数
|
||||
"android": {
|
||||
//安卓包名
|
||||
"packagename":"uni.UNI_F79F300",
|
||||
//安卓打包类型 默认值0 0 使用自有证书 1 使用公共证书 2 使用老版证书 3 使用云端证书
|
||||
"androidpacktype":"0",
|
||||
//安卓使用自有证书自有打包证书参数
|
||||
//安卓打包证书别名,自有证书打包填写的参数
|
||||
"certalias":"testalias",
|
||||
//安卓打包证书文件路径,自有证书打包填写的参数
|
||||
"certfile":"C:\\Users\\13597\\test.keystore",
|
||||
//安卓打包证书密码,自有证书打包填写的参数
|
||||
"certpassword":"123456",
|
||||
//安卓打包证书库密码(HBuilderx4.41支持),自有证书打包填写的参数
|
||||
"storePassword": "123456",
|
||||
//安卓平台要打的渠道包 取值有"google","yyb","360","huawei","xiaomi","oppo","vivo",如果要打多个逗号隔开
|
||||
"channels":""
|
||||
},
|
||||
//ios打包参数
|
||||
"ios": {
|
||||
//ios appid
|
||||
"bundle":"com.test.ios",
|
||||
//ios打包支持的设备类型 默认值iPhone 值有"iPhone","iPad" 如果要打多个逗号隔开打包平台
|
||||
"supporteddevice":"iPhone,iPad",
|
||||
//iOS使用自定义证书打包的profile文件路径
|
||||
"profile":"",
|
||||
//iOS使用自定义证书打包的p12文件路径
|
||||
"certfile":"",
|
||||
//iOS使用自定义证书打包的证书密码
|
||||
"certpassword":"123"
|
||||
},
|
||||
//是否混淆 true混淆 false关闭
|
||||
"isconfusion":false,
|
||||
//开屏广告 true打开 false关闭
|
||||
"splashads":false,
|
||||
//悬浮红包广告true打开 false关闭
|
||||
"rpads":false,
|
||||
//push广告 true打开 false关闭
|
||||
"pushads":false,
|
||||
//加入换量联盟 true加入 false不加入
|
||||
"exchange":false
|
||||
}
|
||||
```
|
||||
### 云服务器走CICD 打包:
|
||||
需要下载 [Linux HBuilderX Cli](https://ask.dcloud.net.cn/article/41469)
|
||||
|
||||
### 上传 PDA 安装包
|
||||
|
||||
云打包得到 APK 后,用命令行上传,等价于 ERP「开发者资源上传」里的安装包上传。周尚环境的登录和签名走 `https://zsfz.yushangfangzhi.com/hcscm`,安装包进入 `zhoushang` 空间。
|
||||
|
||||
先把 `src/manifest.json` 的 `versionName`、`versionCode` 改成新版本,再打包。安装包里的版本号必须和上传文件名一致,否则 PDA 会反复提示升级。
|
||||
|
||||
```
|
||||
npm run upload:apk -- --file D:\path\你的.apk --target test
|
||||
npm run upload:apk -- --file D:\path\你的.apk --target prod
|
||||
```
|
||||
|
||||
`npm` 后面的 `--` 只是分隔符,用来把 `--file`、`--target` 传给脚本。也可以不经过 npm:
|
||||
|
||||
```
|
||||
node scripts/upload-apk.js --file D:\path\你的.apk --target test
|
||||
```
|
||||
|
||||
运行后输入 ERP 手机号和密码获取 token。账号下有多个账套时,再按序号选一次。
|
||||
|
||||
`--target` 只决定 APK 放到哪套 CDN,没有别的差别。生产环境的 PDA 也可以从测试 CDN 下载并安装。
|
||||
|
||||
| 参数 | 说明 |
|
||||
|---|---|
|
||||
| `--target test` | 周尚环境固定上传到 `https://zsfzcdn.yushangfangzhi.com` |
|
||||
| `--target prod` | 其他环境的正式 CDN:`https://hcscm.cdn.zzfzyc.com` |
|
||||
| `--mode` | 默认 `zhoushang`。可选 `zhoushang`、`zhisheng`、`mes`、`cjt` |
|
||||
| `--version` | 可选。不传则用 `src/manifest.json` 的 `versionName` |
|
||||
|
||||
场景值取对应 `.env.{mode}` 里的 `VUE_APP_UPGRADE_NAME`。保存路径为 `/version/{场景值}/{版本号}.apk`。版本包上传成功后,同一份安装包会再传到 `/version/{场景值}/latest.apk`。本分支默认场景是 `.env.zhoushang` 的 `erp_pda_zhoushang`,上传成功后会打印完整下载地址,例如:
|
||||
|
||||
```
|
||||
https://zsfzcdn.yushangfangzhi.com/version/erp_pda_zhoushang/1.1.9.apk
|
||||
https://zsfzcdn.yushangfangzhi.com/version/erp_pda_zhoushang/latest.apk
|
||||
```
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
//项目名字或项目绝对路径
|
||||
"project": "pda",
|
||||
//打包平台 默认值android 值有"android","ios" 如果要打多个逗号隔开打包平台
|
||||
"platform":"android",
|
||||
//是否使用自定义基座 默认值false true自定义基座 false自定义证书
|
||||
"iscustom": false,
|
||||
//打包方式是否为安心打包默认值false,true安心打包,false传统打包
|
||||
"safemode": false,
|
||||
//android打包参数
|
||||
"android": {
|
||||
//安卓包名
|
||||
"packagename":"uni.UNI_F79F300",
|
||||
//安卓打包类型 默认值0 0 使用自有证书 1 使用公共证书 2 使用老版证书 3 使用云端证书
|
||||
"androidpacktype":"0",
|
||||
//安卓使用自有证书自有打包证书参数
|
||||
//安卓打包证书别名,自有证书打包填写的参数
|
||||
"certalias":"testalias",
|
||||
//安卓打包证书文件路径,自有证书打包填写的参数
|
||||
"certfile":"C:\\Users\\13597\\test.keystore",
|
||||
//安卓打包证书密码,自有证书打包填写的参数
|
||||
"certpassword":"123456",
|
||||
//安卓打包证书库密码(HBuilderx4.41支持),自有证书打包填写的参数
|
||||
"storePassword": "123456",
|
||||
//安卓平台要打的渠道包 取值有"google","yyb","360","huawei","xiaomi","oppo","vivo",如果要打多个逗号隔开
|
||||
"channels":""
|
||||
},
|
||||
//ios打包参数
|
||||
"ios": {
|
||||
//ios appid
|
||||
"bundle":"com.test.ios",
|
||||
//ios打包支持的设备类型 默认值iPhone 值有"iPhone","iPad" 如果要打多个逗号隔开打包平台
|
||||
"supporteddevice":"iPhone,iPad",
|
||||
//iOS使用自定义证书打包的profile文件路径
|
||||
"profile":"",
|
||||
//iOS使用自定义证书打包的p12文件路径
|
||||
"certfile":"",
|
||||
//iOS使用自定义证书打包的证书密码
|
||||
"certpassword":"123"
|
||||
},
|
||||
//是否混淆 true混淆 false关闭
|
||||
"isconfusion":false,
|
||||
//开屏广告 true打开 false关闭
|
||||
"splashads":false,
|
||||
//悬浮红包广告true打开 false关闭
|
||||
"rpads":false,
|
||||
//push广告 true打开 false关闭
|
||||
"pushads":false,
|
||||
//加入换量联盟 true加入 false不加入
|
||||
"exchange":false
|
||||
}
|
||||
+62
-45
@@ -7,6 +7,10 @@
|
||||
"serve": "npm run dev:h5",
|
||||
"build": "npm run build:h5",
|
||||
"build:app-plus": "cross-env NODE_ENV=production UNI_PLATFORM=app-plus vue-cli-service uni-build",
|
||||
"build:app-plus:zhisheng": "cross-env NODE_ENV=production UNI_PLATFORM=app-plus vue-cli-service uni-build --mode zhisheng",
|
||||
"build:app-plus:zhoushang": "cross-env NODE_ENV=production UNI_PLATFORM=app-plus vue-cli-service uni-build --mode zhoushang",
|
||||
"build:app-plus:mes": "cross-env NODE_ENV=production UNI_PLATFORM=app-plus vue-cli-service uni-build --mode mes",
|
||||
"build:app-plus:devlocal": "cross-env NODE_ENV=production UNI_PLATFORM=app-plus vue-cli-service uni-build --mode devlocal",
|
||||
"build:custom": "cross-env NODE_ENV=production uniapp-cli custom",
|
||||
"build:h5": "cross-env NODE_ENV=production UNI_PLATFORM=h5 vue-cli-service uni-build",
|
||||
"build:mp-360": "cross-env NODE_ENV=production UNI_PLATFORM=mp-360 vue-cli-service uni-build",
|
||||
@@ -25,6 +29,9 @@
|
||||
"build:quickapp-webview-huawei": "cross-env NODE_ENV=production UNI_PLATFORM=quickapp-webview-huawei vue-cli-service uni-build",
|
||||
"build:quickapp-webview-union": "cross-env NODE_ENV=production UNI_PLATFORM=quickapp-webview-union vue-cli-service uni-build",
|
||||
"dev:app-plus": "cross-env NODE_ENV=development UNI_PLATFORM=app-plus vue-cli-service uni-build --watch",
|
||||
"dev:app-plus:zhisheng": "cross-env NODE_ENV=development UNI_PLATFORM=app-plus vue-cli-service uni-build --watch --mode zhisheng",
|
||||
"dev:app-plus:mes": "cross-env NODE_ENV=development UNI_PLATFORM=app-plus vue-cli-service uni-build --watch --mode mes",
|
||||
"dev:app-plus:devlocal": "cross-env NODE_ENV=development UNI_PLATFORM=app-plus vue-cli-service uni-build --watch --mode devlocal",
|
||||
"dev:custom": "cross-env NODE_ENV=development uniapp-cli custom",
|
||||
"dev:h5": "cross-env NODE_ENV=development UNI_PLATFORM=h5 vue-cli-service uni-serve",
|
||||
"dev:mp-360": "cross-env NODE_ENV=development UNI_PLATFORM=mp-360 vue-cli-service uni-build --watch",
|
||||
@@ -43,6 +50,7 @@
|
||||
"dev:quickapp-webview-huawei": "cross-env NODE_ENV=development UNI_PLATFORM=quickapp-webview-huawei vue-cli-service uni-build --watch",
|
||||
"dev:quickapp-webview-union": "cross-env NODE_ENV=development UNI_PLATFORM=quickapp-webview-union vue-cli-service uni-build --watch",
|
||||
"info": "node node_modules/@dcloudio/vue-cli-plugin-uni/commands/info.js",
|
||||
"upload:apk": "node scripts/upload-apk.js",
|
||||
"serve:quickapp-native": "node node_modules/@dcloudio/uni-quickapp-native/bin/serve.js",
|
||||
"test:android": "cross-env UNI_PLATFORM=app-plus UNI_OS_NAME=android jest -i",
|
||||
"test:h5": "cross-env UNI_PLATFORM=h5 jest -i",
|
||||
@@ -55,60 +63,69 @@
|
||||
"浩拓移动端"
|
||||
],
|
||||
"dependencies": {
|
||||
"@dcloudio/uni-app": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-app-plus": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-h5": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-i18n": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-mp-360": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-mp-alipay": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-mp-baidu": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-mp-harmony": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-mp-jd": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-mp-kuaishou": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-mp-lark": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-mp-qq": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-mp-toutiao": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-mp-vue": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-mp-weixin": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-mp-xhs": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-quickapp-native": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-quickapp-webview": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-stacktracey": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-stat": "^2.0.2-4070520250711001",
|
||||
"@vue/shared": "^3.0.0",
|
||||
"core-js": "^3.8.3",
|
||||
"flyio": "^0.6.2",
|
||||
"vue": ">= 2.6.14 < 2.7",
|
||||
"vuex": "^3.2.0",
|
||||
"@icon-park/vue": "^1.3.5",
|
||||
"@dcloudio/uni-app": "2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-app-plus": "2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-h5": "2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-i18n": "2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-mp-360": "2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-mp-alipay": "2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-mp-baidu": "2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-mp-harmony": "2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-mp-jd": "2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-mp-kuaishou": "2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-mp-lark": "2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-mp-qq": "2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-mp-toutiao": "2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-mp-vue": "2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-mp-weixin": "2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-mp-xhs": "2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-quickapp-native": "2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-quickapp-webview": "2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-stacktracey": "2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-stat": "2.0.2-4070520250711001",
|
||||
"@icon-park/vue": "^1.4.2",
|
||||
"@vue/shared": "^3.5.18",
|
||||
"core-js": "^3.45.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"vue-i18n": "^8.20.0"
|
||||
"dotenv": "^17.2.1",
|
||||
"flyio": "^0.6.14",
|
||||
"uview-ui": "1.8.8",
|
||||
"vue": "^2.6.14",
|
||||
"vue-i18n": "^8.28.2",
|
||||
"vuex": "^3.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dcloudio/types": "^3.3.2",
|
||||
"@dcloudio/uni-automator": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-cli-i18n": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-cli-shared": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-helper-json": "*",
|
||||
"@dcloudio/uni-migration": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-template-compiler": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/vue-cli-plugin-hbuilderx": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/vue-cli-plugin-uni": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/vue-cli-plugin-uni-optimize": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/webpack-uni-mp-loader": "^2.0.2-4070520250711001",
|
||||
"@dcloudio/webpack-uni-pages-loader": "^2.0.2-4070520250711001",
|
||||
"@vue/cli-plugin-babel": "~5.0.0",
|
||||
"@vue/cli-service": "~5.0.0",
|
||||
"babel-plugin-import": "^1.11.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"jest": "^25.4.0",
|
||||
"@dcloudio/types": "^3.4.19",
|
||||
"@dcloudio/uni-automator": "2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-cli-i18n": "2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-cli-shared": "2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-helper-json": "^1.0.13",
|
||||
"@dcloudio/uni-migration": "2.0.2-4070520250711001",
|
||||
"@dcloudio/uni-template-compiler": "2.0.2-4070520250711001",
|
||||
"@dcloudio/vue-cli-plugin-hbuilderx": "2.0.2-4070520250711001",
|
||||
"@dcloudio/vue-cli-plugin-uni": "2.0.2-4070520250711001",
|
||||
"@dcloudio/vue-cli-plugin-uni-optimize": "2.0.2-4070520250711001",
|
||||
"@dcloudio/webpack-uni-mp-loader": "2.0.2-4070520250711001",
|
||||
"@dcloudio/webpack-uni-pages-loader": "2.0.2-4070520250711001",
|
||||
"@vue/cli-plugin-babel": "~5.0.8",
|
||||
"@vue/cli-service": "~5.0.8",
|
||||
"babel-plugin-import": "^1.13.8",
|
||||
"cross-env": "^7.0.3",
|
||||
"jest": "^25.5.4",
|
||||
"postcss-comment": "^2.0.0",
|
||||
"vue-template-compiler": ">= 2.6.14 < 2.7"
|
||||
"sass": "1.26.2",
|
||||
"sass-loader": "8.0.2",
|
||||
"vue-template-compiler": "^2.6.14"
|
||||
},
|
||||
"browserslist": [
|
||||
"Android >= 4.4",
|
||||
"ios >= 9"
|
||||
],
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"postcss-urlrewrite": "0.3.0"
|
||||
}
|
||||
},
|
||||
"uni-app": {
|
||||
"scripts": {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,334 @@
|
||||
/**
|
||||
* 上传已打包的 PDA APK,等价于 ERP「开发者资源上传」里的安装包上传。
|
||||
*
|
||||
* 用法:
|
||||
* npm run upload:apk -- --file D:\path\app.apk --target test
|
||||
* npm run upload:apk -- --file D:\path\app.apk --target prod
|
||||
* npm run upload:apk -- --file D:\path\app.apk --target test --mode zhisheng
|
||||
*
|
||||
* --target 只决定 APK 放到哪套 CDN:
|
||||
* test 测试 CDN
|
||||
* prod 正式 CDN
|
||||
* 两者没有别的差别。生产环境的 PDA 也可以从测试 CDN 下载并安装。
|
||||
*
|
||||
* 版本号取 src/manifest.json 的 versionName。
|
||||
* 场景值取对应 .env 的 VUE_APP_UPGRADE_NAME,拼成 /version/{upgradeName}/{version}.apk。
|
||||
* 版本包上传成功后,再把同一份安装包上传为 /version/{upgradeName}/latest.apk。
|
||||
* 运行时输入 ERP 手机号和密码获取 token。多个账套时再选择一次。
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const readline = require('readline');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '..');
|
||||
const UPYUN_API = 'https://v0.api.upyun.com';
|
||||
const DEFAULT_API_BASE = 'https://hcscmtest.zzfzyc.com/hcscm';
|
||||
const DEFAULT_CDN_HOST = {
|
||||
test: 'https://testhcscm.cdn.zzfzyc.com',
|
||||
prod: 'https://hcscm.cdn.zzfzyc.com',
|
||||
};
|
||||
/** 周尚环境的安装包签到 zhoushang 空间,下载域名是 zsfzcdn */
|
||||
const MODE_UPLOAD = {
|
||||
zhoushang: {
|
||||
apiBase: 'https://zsfz.yushangfangzhi.com/hcscm',
|
||||
cdnHost: 'https://zsfzcdn.yushangfangzhi.com',
|
||||
/** 该环境只有 status=1 返回了 zhoushang 空间,正式状态没有可用 bucket */
|
||||
status: 1,
|
||||
},
|
||||
};
|
||||
|
||||
const MODE_ENV = {
|
||||
zhoushang: '.env.zhoushang',
|
||||
zhisheng: '.env.zhisheng',
|
||||
mes: '.env.mes',
|
||||
cjt: '.env.cjt',
|
||||
};
|
||||
|
||||
function parseArgs(argv) {
|
||||
const args = { mode: 'zhoushang' };
|
||||
for (let i = 0; i < argv.length; i += 1) {
|
||||
const key = argv[i];
|
||||
const value = argv[i + 1];
|
||||
if (key === '--file' || key === '--target' || key === '--mode' || key === '--version' || key === '--phone' || key === '--password' || key === '--tenant') {
|
||||
args[key.slice(2)] = value;
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
function readEnvValue(filePath, name) {
|
||||
const text = fs.readFileSync(filePath, 'utf8');
|
||||
const line = text.split(/\r?\n/).find((item) => item.trim().startsWith(`${name}`));
|
||||
if (!line) return '';
|
||||
const raw = line.slice(line.indexOf('=') + 1).trim();
|
||||
return raw.replace(/^['"]|['"]$/g, '');
|
||||
}
|
||||
|
||||
function readManifestVersion() {
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(ROOT, 'src/manifest.json'), 'utf8'));
|
||||
return String(manifest.versionName || '').trim();
|
||||
}
|
||||
|
||||
function ask(question) {
|
||||
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
||||
return new Promise((resolve) => {
|
||||
rl.question(question, (answer) => {
|
||||
rl.close();
|
||||
resolve(String(answer || '').trim());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function askPassword(question) {
|
||||
return new Promise((resolve) => {
|
||||
const stdin = process.stdin;
|
||||
const stdout = process.stdout;
|
||||
if (!stdin.isTTY || typeof stdin.setRawMode !== 'function') {
|
||||
ask(question).then(resolve);
|
||||
return;
|
||||
}
|
||||
stdout.write(question);
|
||||
stdin.setRawMode(true);
|
||||
stdin.resume();
|
||||
stdin.setEncoding('utf8');
|
||||
let value = '';
|
||||
const onData = (char) => {
|
||||
switch (char) {
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\u0004':
|
||||
stdin.setRawMode(false);
|
||||
stdin.pause();
|
||||
stdin.removeListener('data', onData);
|
||||
stdout.write('\n');
|
||||
resolve(value);
|
||||
break;
|
||||
case '\u0003':
|
||||
process.exit();
|
||||
break;
|
||||
case '\u007f':
|
||||
case '\b':
|
||||
value = value.slice(0, -1);
|
||||
break;
|
||||
default:
|
||||
value += char;
|
||||
break;
|
||||
}
|
||||
};
|
||||
stdin.on('data', onData);
|
||||
});
|
||||
}
|
||||
|
||||
async function apiJson(apiBase, urlPath, { method = 'GET', token, body } = {}) {
|
||||
const headers = { Platform: '1' };
|
||||
if (token) headers.Authorization = token;
|
||||
if (body) headers['Content-Type'] = 'application/json';
|
||||
const res = await fetch(`${apiBase}${urlPath}`, {
|
||||
method,
|
||||
headers,
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok || data.code !== 0) {
|
||||
throw new Error(data.msg || `请求失败(HTTP ${res.status})`);
|
||||
}
|
||||
return data.data || {};
|
||||
}
|
||||
|
||||
async function login(apiBase, { phone: phoneArg, password: passwordArg, tenant: tenantArg } = {}) {
|
||||
const phone = phoneArg || await ask('手机号: ');
|
||||
const password = passwordArg || await askPassword('密码: ');
|
||||
if (!phone || !password) {
|
||||
throw new Error('请输入手机号和密码');
|
||||
}
|
||||
console.log('正在登录...');
|
||||
const first = await apiJson(apiBase, '/admin/v1/login', {
|
||||
method: 'POST',
|
||||
body: { phone, password },
|
||||
});
|
||||
const tempToken = first.token;
|
||||
if (!tempToken) throw new Error('登录未返回 token');
|
||||
|
||||
const dropdown = await apiJson(apiBase, '/admin/v1/tenantManagement/dropdownList', {
|
||||
token: tempToken,
|
||||
});
|
||||
const tenants = dropdown.list || [];
|
||||
if (!tenants.length) return tempToken;
|
||||
|
||||
let tenant = tenants[0];
|
||||
if (tenants.length > 1) {
|
||||
if (tenantArg) {
|
||||
const picked = Number(tenantArg);
|
||||
tenant = tenants[picked - 1];
|
||||
if (!tenant) throw new Error('账套序号无效');
|
||||
} else {
|
||||
console.log('请选择账套:');
|
||||
tenants.forEach((item, index) => {
|
||||
const name = item.company_name || item.database_name || item.tenant_contacts || item.tenant_management_id;
|
||||
console.log(` ${index + 1}. ${name}`);
|
||||
});
|
||||
const picked = Number(await ask('序号: '));
|
||||
tenant = tenants[picked - 1];
|
||||
if (!tenant) throw new Error('账套序号无效');
|
||||
}
|
||||
}
|
||||
|
||||
const second = await apiJson(apiBase, '/admin/v1/login', {
|
||||
method: 'POST',
|
||||
token: tempToken,
|
||||
body: {
|
||||
token: tempToken,
|
||||
tenant_management_id: tenant.tenant_management_id,
|
||||
},
|
||||
});
|
||||
if (!second.token) throw new Error('选择账套后未返回 token');
|
||||
return second.token;
|
||||
}
|
||||
|
||||
async function getCdnSign({ apiBase, token, saveKey, status }) {
|
||||
const url = new URL(`${apiBase}/admin/v1/cdn/token`);
|
||||
url.searchParams.set('method', 'post');
|
||||
url.searchParams.set('save_key', saveKey);
|
||||
url.searchParams.set('status', String(status));
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
Authorization: token,
|
||||
Platform: '1',
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
if (!res.ok || body.code !== 0) {
|
||||
throw new Error(body.msg || `获取签名失败(HTTP ${res.status})`);
|
||||
}
|
||||
return body.data || {};
|
||||
}
|
||||
|
||||
async function uploadToUpyun({ bucket, authorization, policy, filePath, fileName }) {
|
||||
const buf = fs.readFileSync(filePath);
|
||||
const form = new FormData();
|
||||
form.append('authorization', authorization);
|
||||
form.append('policy', policy);
|
||||
form.append('file', new Blob([buf], { type: 'application/vnd.android.package-archive' }), fileName);
|
||||
const res = await fetch(`${UPYUN_API}/${bucket}`, {
|
||||
method: 'POST',
|
||||
body: form,
|
||||
});
|
||||
const text = await res.text();
|
||||
let body = {};
|
||||
try {
|
||||
body = JSON.parse(text);
|
||||
} catch (e) {
|
||||
body = { raw: text };
|
||||
}
|
||||
if (!res.ok || (body.code != null && Number(body.code) !== 200)) {
|
||||
throw new Error(body.message || body.msg || body.raw || `上传失败(HTTP ${res.status})`);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
function toPublicUrl(cdnHost, saveKey, uploaded) {
|
||||
if (uploaded.url && String(uploaded.url).startsWith('http')) return uploaded.url;
|
||||
return `${cdnHost}${saveKey}`;
|
||||
}
|
||||
|
||||
async function uploadApkFile({ apiBase, token, apkPath, saveKey, fileName, status }) {
|
||||
console.log(`场景路径: ${saveKey}`);
|
||||
console.log('正在获取签名...');
|
||||
const sign = await getCdnSign({
|
||||
apiBase,
|
||||
token,
|
||||
saveKey,
|
||||
status,
|
||||
});
|
||||
if (!sign.bucket || !sign.authorization || !sign.policy) {
|
||||
throw new Error('签名返回缺少 bucket、authorization 或 policy');
|
||||
}
|
||||
console.log(`空间: ${sign.bucket}`);
|
||||
console.log(`正在上传 ${fileName}...`);
|
||||
const uploaded = await uploadToUpyun({
|
||||
bucket: sign.bucket,
|
||||
authorization: sign.authorization,
|
||||
policy: sign.policy,
|
||||
filePath: apkPath,
|
||||
fileName,
|
||||
});
|
||||
console.log('上传成功');
|
||||
return uploaded;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
if (!args.file || !args.target) {
|
||||
console.error('用法: npm run upload:apk -- --file <apk路径> --target test|prod [--mode zhoushang]');
|
||||
console.error('--target 只切换 APK 的 CDN:test 测试 CDN,prod 正式 CDN。生产环境也可以从测试 CDN 下载安装。');
|
||||
process.exit(1);
|
||||
}
|
||||
if (args.target !== 'test' && args.target !== 'prod') {
|
||||
console.error('--target 只能是 test 或 prod。两者只是 APK 的 CDN 环境不同,生产环境也可以从测试 CDN 下载安装。');
|
||||
process.exit(1);
|
||||
}
|
||||
const envFile = MODE_ENV[args.mode];
|
||||
if (!envFile) {
|
||||
console.error(`不支持的 --mode:${args.mode},可选 zhoushang、zhisheng、mes、cjt`);
|
||||
process.exit(1);
|
||||
}
|
||||
const apkPath = path.resolve(args.file);
|
||||
if (!fs.existsSync(apkPath)) {
|
||||
console.error(`找不到 APK:${apkPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const version = args.version || readManifestVersion();
|
||||
if (!version) {
|
||||
console.error('manifest.json 里没有 versionName');
|
||||
process.exit(1);
|
||||
}
|
||||
const upgradeName = readEnvValue(path.join(ROOT, envFile), 'VUE_APP_UPGRADE_NAME');
|
||||
if (!upgradeName) {
|
||||
console.error(`${envFile} 里没有 VUE_APP_UPGRADE_NAME`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const modeUpload = MODE_UPLOAD[args.mode];
|
||||
const status = modeUpload ? modeUpload.status : (args.target === 'prod' ? 2 : 1);
|
||||
const fileName = `${version}.apk`;
|
||||
const saveKey = `/version/${upgradeName}/${fileName}`;
|
||||
const latestFileName = 'latest.apk';
|
||||
const latestSaveKey = `/version/${upgradeName}/${latestFileName}`;
|
||||
const apiBase = modeUpload ? modeUpload.apiBase : DEFAULT_API_BASE;
|
||||
const cdnHost = modeUpload ? modeUpload.cdnHost : DEFAULT_CDN_HOST[args.target];
|
||||
const token = await login(apiBase, {
|
||||
phone: args.phone,
|
||||
password: args.password,
|
||||
tenant: args.tenant,
|
||||
});
|
||||
|
||||
console.log(`版本号: ${version}`);
|
||||
console.log(`签名接口: ${apiBase}`);
|
||||
console.log(`CDN: ${cdnHost}`);
|
||||
const uploaded = await uploadApkFile({
|
||||
apiBase,
|
||||
token,
|
||||
apkPath,
|
||||
saveKey,
|
||||
fileName,
|
||||
status,
|
||||
});
|
||||
console.log(toPublicUrl(cdnHost, saveKey, uploaded));
|
||||
|
||||
const latestUploaded = await uploadApkFile({
|
||||
apiBase,
|
||||
token,
|
||||
apkPath,
|
||||
saveKey: latestSaveKey,
|
||||
fileName: latestFileName,
|
||||
status,
|
||||
});
|
||||
console.log(toPublicUrl(cdnHost, latestSaveKey, latestUploaded));
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error.message || error);
|
||||
process.exit(1);
|
||||
});
|
||||
+51
-12
@@ -6,6 +6,8 @@ import { audioManager } from '@/utils/audioManager'
|
||||
|
||||
import util from '@/common/util.js';
|
||||
import md5 from '@/common/md5.js';
|
||||
import { getLoginData, restoreUserSession } from '@/common/userSession.js';
|
||||
import { ensureScanConfigAfterLogin } from '@/common/scanAutoConfig.js';
|
||||
export default {
|
||||
globalData: {
|
||||
AppName: '',
|
||||
@@ -25,11 +27,20 @@ import { audioManager } from '@/utils/audioManager'
|
||||
|
||||
onLaunch() {
|
||||
console.log('App Launch');
|
||||
restoreUserSession();
|
||||
|
||||
// 初始化音频管理器
|
||||
audioManager.init();
|
||||
// 初始化通知系统
|
||||
this.$store.dispatch('initNotification');
|
||||
|
||||
// 已登录且本地无扫码配置时,自动匹配一次
|
||||
const loginData = getLoginData();
|
||||
if (loginData && loginData.token && this.$u && this.$u.api) {
|
||||
ensureScanConfigAfterLogin(this.$u.api).catch((error) => {
|
||||
console.log('启动时自动匹配扫码设置失败', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -58,7 +69,7 @@ import { audioManager } from '@/utils/audioManager'
|
||||
.textCenter {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 15px;
|
||||
font-size: 16px;
|
||||
color: #808080;
|
||||
margin-top: 88rpx;
|
||||
}
|
||||
@@ -105,7 +116,7 @@ import { audioManager } from '@/utils/audioManager'
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 15px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.flex-white-plr26-column {
|
||||
@@ -114,7 +125,7 @@ import { audioManager } from '@/utils/audioManager'
|
||||
padding-right: 26rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 15px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.bdb_f5 {
|
||||
@@ -155,12 +166,12 @@ import { audioManager } from '@/utils/audioManager'
|
||||
|
||||
.redXingh {
|
||||
color: #FF0000;
|
||||
font-size: 14px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.bluecolor {
|
||||
color: #0000FF;
|
||||
font-size: 16px;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.udropdown {
|
||||
@@ -174,7 +185,7 @@ import { audioManager } from '@/utils/audioManager'
|
||||
.width276 {
|
||||
width: 546rpx;
|
||||
color: #c5c5c5;
|
||||
font-size: 14px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.cBlack {
|
||||
@@ -185,7 +196,7 @@ import { audioManager } from '@/utils/audioManager'
|
||||
margin-bottom: -10rpx;
|
||||
margin-top: 16rpx;
|
||||
padding-left: 26rpx;
|
||||
font-size: 30rpx;
|
||||
font-size: 32rpx;
|
||||
color: #555555;
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -193,7 +204,7 @@ import { audioManager } from '@/utils/audioManager'
|
||||
.width242 {
|
||||
width: 484rpx;
|
||||
color: #c5c5c5;
|
||||
font-size: 14px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.myCard {
|
||||
@@ -211,7 +222,7 @@ import { audioManager } from '@/utils/audioManager'
|
||||
.cardRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
font-size: 15px;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
@@ -231,7 +242,7 @@ import { audioManager } from '@/utils/audioManager'
|
||||
}
|
||||
|
||||
.cardTopName {
|
||||
font-size: 16px;
|
||||
font-size: 17px;
|
||||
margin-bottom: 8rpx;
|
||||
color: #000000;
|
||||
overflow: hidden;
|
||||
@@ -265,7 +276,7 @@ import { audioManager } from '@/utils/audioManager'
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
font-size: 15px;
|
||||
font-size: 16px;
|
||||
color: #007AFF;
|
||||
margin-top: 12rpx;
|
||||
margin-bottom: 16rpx;
|
||||
@@ -279,7 +290,7 @@ import { audioManager } from '@/utils/audioManager'
|
||||
position: absolute;
|
||||
right: 26rpx;
|
||||
bottom: 80rpx;
|
||||
font-size: 15px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.fzrLeft {
|
||||
@@ -359,4 +370,32 @@ import { audioManager } from '@/utils/audioManager'
|
||||
height: 32rpx;
|
||||
background-color: #EEEEEE;
|
||||
}
|
||||
|
||||
/* PDA:放大 u-select 确认/取消按钮点击区域 */
|
||||
.u-select__header {
|
||||
height: 110rpx !important;
|
||||
padding: 0 16rpx !important;
|
||||
}
|
||||
|
||||
.u-select__header__btn {
|
||||
font-size: 36rpx !important;
|
||||
line-height: 110rpx !important;
|
||||
padding: 0 40rpx !important;
|
||||
min-width: 140rpx;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.u-select__search,
|
||||
.u-action-sheet-search,
|
||||
.u-dropdown-item__search {
|
||||
height: 88rpx !important;
|
||||
}
|
||||
|
||||
.u-select__search__input,
|
||||
.u-action-sheet-search__input,
|
||||
.u-dropdown-item__search__input {
|
||||
font-size: 32rpx !important;
|
||||
height: 88rpx !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<view>
|
||||
<topDropdown :isSxShow="true" :dropdown1="dropdown1" dropdown2="上架中" :options1="options1" :options2="isSjList" :optionsSx="sxList" searchUrl="./searchAdmin" @dropDownFun1="dropDownFun1" @dropDownFun2="dropDownFun2" @optionSxFun="optionSxFun">
|
||||
</topDropdown>
|
||||
<dataNull v-if="list.length == 0" src="/static/img/chahua/dataNullXz.png" title="暂无相关产品"
|
||||
<dataNull v-if="list.length == 0" src="/src/static/img/chahua/dataNullXz.png" title="暂无相关产品"
|
||||
title1="请添加或者更换查询条件"></dataNull>
|
||||
<scroll-view v-else scroll-y="true" :style="{height: scrollHeight}" @scrolltolower="getChanPinFun"
|
||||
refresher-enabled :refresher-threshold="200" :refresher-triggered="triggered" refresher-background="gray"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<view>
|
||||
<dataNull v-if="list.length == 0" src="/static/img/chahua/dataNullXz.png" title="暂无相关分类"
|
||||
<dataNull v-if="list.length == 0" src="/src/static/img/chahua/dataNullXz.png" title="暂无相关分类"
|
||||
title1="点击添加按钮添加相关分类"></dataNull>
|
||||
<view v-else class="content">
|
||||
<view class="row" v-for="(row, index) in list" :key="index">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<view>
|
||||
<searchView placeholderStr="请输入产品名称" @searchViewClickFun="searchEventsFun($event)"></searchView>
|
||||
<dataNull v-if="list.length == 0" src="/static/img/chahua/dataNullXz.png" title="暂无相关产品"
|
||||
<dataNull v-if="list.length == 0" src="/src/static/img/chahua/dataNullXz.png" title="暂无相关产品"
|
||||
title1="请更换查询条件"></dataNull>
|
||||
<scroll-view v-else scroll-y="true" :style="{height: scrollHeight}" @scrolltolower="getChanPinFun"
|
||||
refresher-enabled :refresher-threshold="200" :refresher-triggered="triggered" refresher-background="gray"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<view>
|
||||
<searchView placeholderStr="请输入产品名称" @searchViewClickFun="searchEventsFun($event)"></searchView>
|
||||
<dataNull v-if="list.length == 0" src="/static/img/chahua/dataNullXz.png" title="暂无相关产品"
|
||||
<dataNull v-if="list.length == 0" src="/src/static/img/chahua/dataNullXz.png" title="暂无相关产品"
|
||||
title1="请添加或者更换查询条件"></dataNull>
|
||||
<scroll-view v-else scroll-y="true" :style="{height: scrollHeight}" @scrolltolower="getChanPinFun"
|
||||
refresher-enabled :refresher-threshold="200" :refresher-triggered="triggered" refresher-background="gray"
|
||||
|
||||
@@ -81,12 +81,12 @@
|
||||
<getMore :isMore="tabBars[tabCurrentIndex].isMore" style="margin-bottom: 16px;"></getMore>
|
||||
<view style="height: 36rpx;"></view>
|
||||
</view>
|
||||
<dataNull v-else src="/static/img/chahua/dataNullXz.png" title="暂无相关产品" title1="请先添加">
|
||||
<dataNull v-else src="/src/static/img/chahua/dataNullXz.png" title="暂无相关产品" title1="请先添加">
|
||||
</dataNull>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<dataNull v-else src="/static/img/chahua/dataNullXz.png" title="暂无相关产品">
|
||||
<dataNull v-else src="/src/static/img/chahua/dataNullXz.png" title="暂无相关产品">
|
||||
</dataNull>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
+8
-12
@@ -6,10 +6,10 @@
|
||||
export const isDevelopment = process.env.NODE_ENV === 'development'
|
||||
|
||||
const config = {
|
||||
upgradeName: 'erp_pda_pre',
|
||||
upgradeName: process.env.VUE_APP_UPGRADE_NAME || 'erp_pda',
|
||||
|
||||
// 产品名称
|
||||
productName: '浩拓技术',
|
||||
productName: '浩拓ERP',
|
||||
|
||||
// 公司名称
|
||||
companyName: '浩拓科技',
|
||||
@@ -17,22 +17,18 @@ const config = {
|
||||
// 版本检查标识
|
||||
appCode: 'android',
|
||||
|
||||
// 内部版本号码
|
||||
appVersion: 1,
|
||||
|
||||
// 管理基础路径
|
||||
adminPath: 'https://hcscmpre.zzfzyc.com/hcscm/pda/v1',
|
||||
|
||||
// 环境判断
|
||||
isDevelopment: isDevelopment,
|
||||
|
||||
// 生产环境API URL
|
||||
productionApiUrl: 'https://hcscmpre.zzfzyc.com/hcscm/pda/v1',
|
||||
// 生产环境API URL(服务名段 hcscm)
|
||||
productionApiUrl: process.env.VUE_APP_PRODUCTION_API_URL,
|
||||
|
||||
// 开发环境API URL选项
|
||||
// 开发环境API URL选项(服务名段 hcscm)
|
||||
devApiUrlOptions: [
|
||||
{ label: '测试环境', value: 'https://hcscmtest.zzfzyc.com/hcscm/pda/v1' },
|
||||
{ label: '正式环境', value: 'https://hcscmpre.zzfzyc.com/hcscm/pda/v1' },
|
||||
{ label: '本地环境', value: 'http://192.168.1.66:7010/hcscm/pda/v1' },
|
||||
{ label: '本地穿透', value: 'https://2yd52gx88533.vicp.fun/hcscm/pda/v1' },
|
||||
{ label: '正式环境', value: process.env.VUE_APP_PRODUCTION_API_URL },
|
||||
{ label: '自定义', value: 'custom' }
|
||||
]
|
||||
|
||||
|
||||
+153
-27
@@ -7,35 +7,163 @@ const install = (Vue, vm) => {
|
||||
// 参数配置对象
|
||||
const config = vm.vuex_config;
|
||||
console.log('install u',config)
|
||||
|
||||
// 接口前缀统一走 hcscm(apiurl 已在 config.js 配置为 hcscm 服务)
|
||||
const getApiUrl = () => String(vm.$store.state.apiurl || '');
|
||||
|
||||
const GFM_OTHER_DELIVERY_PREFIX = '/grey_fabric_manage/gfmOtherDeliveryOrder/';
|
||||
const gfmOtherDeliveryUrl = (name) => getApiUrl() + GFM_OTHER_DELIVERY_PREFIX + name;
|
||||
|
||||
const GFM_WAREHOUSE_PREFIX = '/grey_fabric_manage/gfmWarehouse/';
|
||||
const gfmWarehouseUrl = (name) => getApiUrl() + GFM_WAREHOUSE_PREFIX + name;
|
||||
|
||||
const FPM_PROCESS_IN_PREFIX = '/product/fpmProcessInOrder/';
|
||||
const fpmProcessInUrl = (name) => getApiUrl() + FPM_PROCESS_IN_PREFIX + name;
|
||||
|
||||
const FPM_PROCESS_RETURN_IN_PREFIX = '/product/fpmProcessReturnInOrder/';
|
||||
const fpmProcessReturnInUrl = (name) => getApiUrl() + FPM_PROCESS_RETURN_IN_PREFIX + name;
|
||||
|
||||
const FPM_PROCESS_OUT_PREFIX = '/product/fpmProcessOutOrder/';
|
||||
const fpmProcessOutUrl = (name) => getApiUrl() + FPM_PROCESS_OUT_PREFIX + name;
|
||||
|
||||
const FPM_PRC_IN_PREFIX = '/product/fpmPrcInOrder/';
|
||||
const fpmPrcInUrl = (name) => getApiUrl() + FPM_PRC_IN_PREFIX + name;
|
||||
|
||||
// 将各个定义的接口名称,统一放进对象挂载到vm.$u.api(因为vm就是this,也即this.$u.api)下
|
||||
vm.$u.api = {
|
||||
|
||||
// 坯布出仓单(其他出货单)相关接口
|
||||
gfmOtherDeliveryOrder: {
|
||||
// 获取坯布出仓单列表(query: page,size,order_no,audit_status)
|
||||
list: (params = {}) => vm.$u.get(gfmOtherDeliveryUrl('getGfmOtherDeliveryOrderList'), params),
|
||||
// 获取坯布出仓单详情(query: id)
|
||||
detail: (params = {}) => vm.$u.get(gfmOtherDeliveryUrl('getGfmOtherDeliveryOrder'), params),
|
||||
// 新增坯布出仓单(提交表头信息保存,返回 { id })
|
||||
add: (params = {}) => vm.$u.postJson(gfmOtherDeliveryUrl('addGfmOtherDeliveryOrder'), params),
|
||||
// 扫码新增/删除细码(scan_type: 1新增 2删除;勾选锁定维度时附带 *_lock 字段),返回最新详情
|
||||
scanUpdate: (params = {}) => vm.$u.putJson(gfmOtherDeliveryUrl('updateGfmOtherDeliveryOrder'), params),
|
||||
// 审核
|
||||
statusPass: (params = {}) => vm.$u.putJson(gfmOtherDeliveryUrl('updateGfmOtherDeliveryOrderStatusPass'), params),
|
||||
// 消审
|
||||
statusWait: (params = {}) => vm.$u.putJson(gfmOtherDeliveryUrl('updateGfmOtherDeliveryOrderStatusWait'), params),
|
||||
// 驳回
|
||||
statusReject: (params = {}) => vm.$u.putJson(gfmOtherDeliveryUrl('updateGfmOtherDeliveryOrderStatusReject'), params),
|
||||
// 作废
|
||||
statusCancel: (params = {}) => vm.$u.putJson(gfmOtherDeliveryUrl('updateGfmOtherDeliveryOrderStatusCancel'), params),
|
||||
},
|
||||
|
||||
// 坯布库存相关接口
|
||||
gfmWarehouse: {
|
||||
groupSummaryList: (params = {}) => vm.$u.get(gfmWarehouseUrl('getGfmWarehouseSummaryWeavingFactoryStockGroupList'), params),
|
||||
stockSummaryList: (params = {}) => vm.$u.get(gfmWarehouseUrl('getGfmWarehouseSummaryWeavingFactoryStockList'), params),
|
||||
detailList: (params = {}) => vm.$u.get(gfmWarehouseUrl('getGfmWarehouseList'), params),
|
||||
},
|
||||
|
||||
// 加工进仓单
|
||||
fpmProcessInOrder: {
|
||||
list: (params = {}) => vm.$u.get(fpmProcessInUrl('getFpmProcessInOrderList'), params),
|
||||
detail: (params = {}) => vm.$u.get(fpmProcessInUrl('getFpmProcessInOrder'), params),
|
||||
add: (params = {}) => vm.$u.postJson(fpmProcessInUrl('addFpmProcessInOrder'), params),
|
||||
scanUpdate: (params = {}) => vm.$u.putJson(fpmProcessInUrl('updateFpmProcessInOrder'), params),
|
||||
statusPass: (params = {}) => vm.$u.putJson(fpmProcessInUrl('updateFpmProcessInOrderStatusPass'), params),
|
||||
statusWait: (params = {}) => vm.$u.putJson(fpmProcessInUrl('updateFpmProcessInOrderStatusWait'), params),
|
||||
statusReject: (params = {}) => vm.$u.putJson(fpmProcessInUrl('updateFpmProcessInOrderStatusReject'), params),
|
||||
statusCancel: (params = {}) => vm.$u.putJson(fpmProcessInUrl('updateFpmProcessInOrderStatusCancel'), params),
|
||||
},
|
||||
|
||||
// 加工退货进仓单
|
||||
fpmProcessReturnInOrder: {
|
||||
list: (params = {}) => vm.$u.get(fpmProcessReturnInUrl('getFpmProcessReturnInOrderList'), params),
|
||||
detail: (params = {}) => vm.$u.get(fpmProcessReturnInUrl('getFpmProcessReturnInOrder'), params),
|
||||
add: (params = {}) => vm.$u.postJson(fpmProcessReturnInUrl('addFpmProcessReturnInOrder'), params),
|
||||
scanUpdate: (params = {}) => vm.$u.putJson(fpmProcessReturnInUrl('updateFpmProcessReturnInOrder'), params),
|
||||
statusPass: (params = {}) => vm.$u.putJson(fpmProcessReturnInUrl('updateFpmProcessReturnInOrderStatusPass'), params),
|
||||
statusWait: (params = {}) => vm.$u.putJson(fpmProcessReturnInUrl('updateFpmProcessReturnInOrderStatusWait'), params),
|
||||
statusReject: (params = {}) => vm.$u.putJson(fpmProcessReturnInUrl('updateFpmProcessReturnInOrderStatusReject'), params),
|
||||
statusCancel: (params = {}) => vm.$u.putJson(fpmProcessReturnInUrl('updateFpmProcessReturnInOrderStatusCancel'), params),
|
||||
},
|
||||
|
||||
// 加工出仓单
|
||||
fpmProcessOutOrder: {
|
||||
list: (params = {}) => vm.$u.get(fpmProcessOutUrl('getFpmProcessOutOrderList'), params),
|
||||
detail: (params = {}) => vm.$u.get(fpmProcessOutUrl('getFpmProcessOutOrder'), params),
|
||||
add: (params = {}) => vm.$u.postJson(fpmProcessOutUrl('addFpmProcessOutOrder'), params),
|
||||
scanUpdate: (params = {}) => vm.$u.putJson(fpmProcessOutUrl('updateFpmProcessOutOrder'), params),
|
||||
statusPass: (params = {}) => vm.$u.putJson(fpmProcessOutUrl('updateFpmProcessOutOrderStatusPass'), params),
|
||||
statusWait: (params = {}) => vm.$u.putJson(fpmProcessOutUrl('updateFpmProcessOutOrderStatusWait'), params),
|
||||
statusReject: (params = {}) => vm.$u.putJson(fpmProcessOutUrl('updateFpmProcessOutOrderStatusReject'), params),
|
||||
statusCancel: (params = {}) => vm.$u.putJson(fpmProcessOutUrl('updateFpmProcessOutOrderStatusCancel'), params),
|
||||
/** 加工出仓类型枚举:正常加工出仓 / 回修加工出仓 */
|
||||
getOutTypeEnum: (params = {}) => vm.$u.get(getApiUrl() + '/product/enum/getProcessOutTypeTypeEnum', params),
|
||||
},
|
||||
|
||||
// 成品采购进仓单
|
||||
fpmPrcInOrder: {
|
||||
list: (params = {}) => vm.$u.get(fpmPrcInUrl('getFpmPrcInOrderList'), params),
|
||||
detail: (params = {}) => vm.$u.get(fpmPrcInUrl('getFpmPrcInOrder'), params),
|
||||
add: (params = {}) => vm.$u.postJson(fpmPrcInUrl('addFpmPrcInOrder'), params),
|
||||
scanUpdate: (params = {}) => vm.$u.putJson(fpmPrcInUrl('updateFpmPrcInOrder'), params),
|
||||
statusPass: (params = {}) => vm.$u.putJson(fpmPrcInUrl('updateFpmPrcInOrderStatusPass'), params),
|
||||
statusWait: (params = {}) => vm.$u.putJson(fpmPrcInUrl('updateFpmPrcInOrderStatusWait'), params),
|
||||
statusReject: (params = {}) => vm.$u.putJson(fpmPrcInUrl('updateFpmPrcInOrderStatusReject'), params),
|
||||
statusCancel: (params = {}) => vm.$u.putJson(fpmPrcInUrl('updateFpmPrcInOrderStatusCancel'), params),
|
||||
},
|
||||
|
||||
// 物理仓库下拉
|
||||
physicalWarehouse: {
|
||||
getDropdownList: (params = {}) => vm.$u.get('/warehouse/physicalWarehouse/getPhysicalWarehouseDropdownList', params),
|
||||
},
|
||||
|
||||
// 仓位查询(Request: qr_code / warehouse_id / name;Response: GetPhysicalWarehouseBinData)
|
||||
warehouseBin: {
|
||||
getByQrCode: (params = {}) => vm.$u.get('/warehouse/warehouseBin/getWarehouseBin', params),
|
||||
},
|
||||
|
||||
// 营销体系下拉
|
||||
saleSystem: {
|
||||
getDropdownList: (params = {}) => vm.$u.get(getApiUrl() + '/saleSystem/getSaleSystemDropdownList', params),
|
||||
},
|
||||
|
||||
// 往来单位列表
|
||||
businessUnit: {
|
||||
list: (params = {}) => vm.$u.get('/business_unit/list', params),
|
||||
},
|
||||
|
||||
// 字典明细枚举(路径相对 apiurl:.../pda/v1/dictionaryDetail/getDictionaryDetailEnumList)
|
||||
dictionary: {
|
||||
getDetailEnumList: (params = {}) => vm.$u.get('/dictionaryDetail/getDictionaryDetailEnumList', params),
|
||||
},
|
||||
|
||||
// 基础服务:登录登出、身份信息、菜单授权、切换系统、字典数据等
|
||||
lang: (params = {}) => vm.$u.get('/lang/'+params.lang),
|
||||
index: (params = {}) => vm.$u.get(config.adminPath+'/mobile/index', params),
|
||||
getUserInfo: (params = {}) => vm.$u.get(config.adminPath+'/mobile/user/getUserInfo', params),
|
||||
login: (params = {}) => vm.$u.post(config.adminPath+'/mobile/login/loginByPassword', params),
|
||||
index: (params = {}) => vm.$u.get(config.productionApiUrl+'/mobile/index', params),
|
||||
getUserInfo: (params = {}) => vm.$u.get(config.productionApiUrl+'/mobile/user/getUserInfo', params),
|
||||
login: (params = {}) => vm.$u.post(config.productionApiUrl+'/mobile/login/loginByPassword', params),
|
||||
// PDA登录接口
|
||||
pdaLogin: (params = {}) => vm.$u.postJson('/login', params),
|
||||
pdaLogout: (params = {}) => vm.$u.postJson('/logout', params),
|
||||
// 修改密码:PUT pda/v1/user/updatePassword
|
||||
updatePassword: (params = {}) => vm.$u.putJson('/user/updatePassword', params),
|
||||
// 登录后获取当前用户信息、菜单与按钮权限
|
||||
pdaGetInformation: (params = {}) => vm.$u.get('/information', params),
|
||||
// 获取配布单列表
|
||||
getFpmArrangeOrderList: (params = {}) => vm.$u.get('/product/fpmArrangeOrder/getFpmArrangeOrderList', params),
|
||||
// 获取成品配布单详情
|
||||
getFpmArrangeOrder: (params = {}) => vm.$u.get('/product/fpmArrangeOrder/getFpmArrangeOrder', params),
|
||||
// 生成出仓单
|
||||
outFpmArrangeOrder: (params = {}) => vm.$u.put('/product/fpmArrangeOrder/outFpmArrangeOrder', params),
|
||||
sendCode: (params = {}) => vm.$u.post(config.adminPath+'/mobile/login/sendCode', params),
|
||||
registerUser: (params = {}) => vm.$u.post(config.adminPath+'/mobile/user/registerUser', params),
|
||||
outFpmArrangeOrder: (params = {}) => vm.$u.putJson('/product/fpmArrangeOrder/outFpmArrangeOrder', params),
|
||||
sendCode: (params = {}) => vm.$u.post(config.productionApiUrl+'/mobile/login/sendCode', params),
|
||||
registerUser: (params = {}) => vm.$u.post(config.productionApiUrl+'/mobile/user/registerUser', params),
|
||||
//首页相关api
|
||||
getIndexCardInfo: (params = {}) => vm.$u.get(config.adminPath+'/mobile/index/getIndexCardInfo', params),
|
||||
getM2mOrderFlowList: (params = {}) => vm.$u.get(config.adminPath+'/mobile/index/getM2mOrderFlowList', params),
|
||||
getIndexCardInfo: (params = {}) => vm.$u.get(config.productionApiUrl+'/mobile/index/getIndexCardInfo', params),
|
||||
getM2mOrderFlowList: (params = {}) => vm.$u.get(config.productionApiUrl+'/mobile/index/getM2mOrderFlowList', params),
|
||||
//获取卡可购买套餐包
|
||||
getM2mOrderPackageList: (params = {}) => vm.$u.get(config.adminPath+'/mobile/index/getM2mOrderPackageList', params),
|
||||
getM2mOrderPackageList: (params = {}) => vm.$u.get(config.productionApiUrl+'/mobile/index/getM2mOrderPackageList', params),
|
||||
|
||||
logout: (params = {}) => vm.$u.get(config.adminPath+'/mobile/login/logout', params),
|
||||
authInfo: (params = {}) => vm.$u.get(config.adminPath+'/authInfo', params),
|
||||
menuTree: (params = {}) => vm.$u.get(config.adminPath+'/menuTree', params),
|
||||
switchSys: (params = {}) => vm.$u.get(config.adminPath+'/switch/'+params.sysCode),
|
||||
logout: (params = {}) => vm.$u.get(config.productionApiUrl+'/mobile/login/logout', params),
|
||||
authInfo: (params = {}) => vm.$u.get(config.productionApiUrl+'/authInfo', params),
|
||||
menuTree: (params = {}) => vm.$u.get(config.productionApiUrl+'/menuTree', params),
|
||||
switchSys: (params = {}) => vm.$u.get(config.productionApiUrl+'/switch/'+params.sysCode),
|
||||
dictData: (params = {}) => vm.$u.get(config.a取dminPath+'/system/dict/data/type/'+params.dictType),
|
||||
|
||||
// 账号服务:验证码接口、忘记密码接口、注册账号接口等
|
||||
@@ -46,36 +174,34 @@ const install = (Vue, vm) => {
|
||||
saveRegByValidCode: (params = {}) => vm.$u.post('/account/saveRegByValidCode', params),
|
||||
|
||||
// APP公共服务
|
||||
upgradeCheck: (params = {}) => vm.$u.put(vm.$u.http.config.baseUrl.replace('/pda/v1', '') + '/version/check', params, {
|
||||
'content-type': 'application/json'
|
||||
}),
|
||||
upgradeCheck: (params = {}) => vm.$u.putJson('/version/check', params),
|
||||
commentSave: (params = {}) => vm.$u.post('/app/comment/save', params),
|
||||
|
||||
// 个人信息修改
|
||||
user: {
|
||||
saveUserInfo: (params = {}) => vm.$u.post(config.adminPath+'/mobile/user/saveUserInfo', params),
|
||||
infoSavePwd: (params = {}) => vm.$u.put(config.adminPath+'/system/user/profile/updatePwd', params),
|
||||
infoSavePqa: (params = {}) => vm.$u.post(config.adminPath+'/sys/user/infoSavePqa', params),
|
||||
saveUserInfo: (params = {}) => vm.$u.post(config.productionApiUrl+'/mobile/user/saveUserInfo', params),
|
||||
infoSavePwd: (params = {}) => vm.$u.put(config.productionApiUrl+'/system/user/profile/updatePwd', params),
|
||||
infoSavePqa: (params = {}) => vm.$u.post(config.productionApiUrl+'/sys/user/infoSavePqa', params),
|
||||
},
|
||||
|
||||
// 员工用户查询
|
||||
empUser: {
|
||||
listData: (params = {}) => vm.$u.get(config.adminPath+'/sys/empUser/listData', params),
|
||||
listData: (params = {}) => vm.$u.get(config.productionApiUrl+'/sys/empUser/listData', params),
|
||||
},
|
||||
|
||||
// 组织机构查询
|
||||
office: {
|
||||
treeData: (params = {}) => vm.$u.get(config.adminPath+'/sys/office/treeData', params),
|
||||
treeData: (params = {}) => vm.$u.get(config.productionApiUrl+'/sys/office/treeData', params),
|
||||
},
|
||||
|
||||
// 增删改查例子
|
||||
testData: {
|
||||
form: (params = {}) => vm.$u.post(config.adminPath+'/test/testData/form', params),
|
||||
list: (params = {}) => vm.$u.post(config.adminPath+'/test/testData/listData', params),
|
||||
save: (params = {}) => vm.$u.postJson(config.adminPath+'/test/testData/save', params),
|
||||
disable: (params = {}) => vm.$u.post(config.adminPath+'/test/testData/disable', params),
|
||||
enable: (params = {}) => vm.$u.post(config.adminPath+'/test/testData/enable', params),
|
||||
delete: (params = {}) => vm.$u.post(config.adminPath+'/test/testData/delete', params),
|
||||
form: (params = {}) => vm.$u.post(config.productionApiUrl+'/test/testData/form', params),
|
||||
list: (params = {}) => vm.$u.post(config.productionApiUrl+'/test/testData/listData', params),
|
||||
save: (params = {}) => vm.$u.postJson(config.productionApiUrl+'/test/testData/save', params),
|
||||
disable: (params = {}) => vm.$u.post(config.productionApiUrl+'/test/testData/disable', params),
|
||||
enable: (params = {}) => vm.$u.post(config.productionApiUrl+'/test/testData/enable', params),
|
||||
delete: (params = {}) => vm.$u.post(config.productionApiUrl+'/test/testData/delete', params),
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
* Copyright (c) 2013-Now http://aidex.vip All rights reserved.
|
||||
*/
|
||||
// 此处第二个参数vm,就是我们在页面使用的this,你可以通过vm获取vuex等操作
|
||||
import { clearUserSession } from '@/common/userSession.js';
|
||||
|
||||
const install = (Vue, vm) => {
|
||||
// 通用请求头设定
|
||||
const ajaxHeader = 'x-ajax';
|
||||
@@ -22,6 +24,20 @@ const install = (Vue, vm) => {
|
||||
|
||||
// 请求拦截,配置Token等参数
|
||||
Vue.prototype.$u.http.interceptor.request = (req) => {
|
||||
const reqUrl = req && req.url ? String(req.url) : '';
|
||||
const isAbsolute = /^https?:\/\//i.test(reqUrl);
|
||||
const fullUrl = isAbsolute ? reqUrl : (vm.$store.state.apiurl + reqUrl);
|
||||
// 用 JSON 字符串输出,避免 HBuilderX 控制台对象被截断、点击 URL 跳浏览器
|
||||
console.log('request => ' + JSON.stringify({
|
||||
url: fullUrl,
|
||||
method: req.method,
|
||||
data: req.data,
|
||||
header: req.header
|
||||
}, null, 2));
|
||||
// #ifdef APP-PLUS
|
||||
// Android 5.x 等老 PDA 不信任 Let's Encrypt 新证书链,关闭校验证书
|
||||
req.sslVerify = false;
|
||||
// #endif
|
||||
if (!req.header){
|
||||
req.header = [];
|
||||
}
|
||||
@@ -33,8 +49,9 @@ const install = (Vue, vm) => {
|
||||
}
|
||||
|
||||
// 设定传递 Token 认证参数 aidex
|
||||
if (!req.header[sessionIdHeader] && vm.vuex_token){
|
||||
req.header[sessionIdHeader] = vm.vuex_token;
|
||||
if (!req.header[sessionIdHeader]){
|
||||
const loginData = uni.getStorageSync('RemoteTokenData');
|
||||
req.header[sessionIdHeader] = vm.vuex_token || (loginData && loginData.token) || '';
|
||||
}
|
||||
|
||||
// 为节省流量,记住我数据不是每次都发送的,当会话失效后,尝试重试登录 aidex
|
||||
@@ -42,31 +59,41 @@ const install = (Vue, vm) => {
|
||||
req.header[rememberMeHeader] = vm.vuex_remember;
|
||||
req.remember = false;
|
||||
}
|
||||
console.log('request', req);
|
||||
return req;
|
||||
}
|
||||
|
||||
// 响应拦截,判断状态码是否通过
|
||||
Vue.prototype.$u.http.interceptor.response = async (res, req) => {
|
||||
console.log('response', res);
|
||||
console.log('response statusCode', res?.statusCode);
|
||||
console.log('response data', res?.data);
|
||||
console.log('response errMsg', res?.errMsg);
|
||||
|
||||
// 处理401未授权状态码
|
||||
if (res.statusCode === 401) {
|
||||
vm.$u.toast('登录已过期,请重新登录');
|
||||
// 清除token和用户信息
|
||||
uni.removeStorageSync('token');
|
||||
vm.$u.vuex('vuex_token', '');
|
||||
vm.$u.vuex('vuex_user', {});
|
||||
// 跳转到登录页面
|
||||
clearUserSession();
|
||||
uni.reLaunch({
|
||||
url: '/pages/sys/login/index'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
// 非业务 JSON(如 404 page not found)
|
||||
if (res.statusCode && res.statusCode !== 200) {
|
||||
const msg = (res.data && res.data.msg) || `请求失败(${res.statusCode})`;
|
||||
vm.$u.toast(msg);
|
||||
return Promise.reject(new Error(msg));
|
||||
}
|
||||
console.log('res',res)
|
||||
if (!(res.data)){
|
||||
vm.$u.toast('未连接到服务器')
|
||||
return Promise.reject(new Error('未连接到服务器'));
|
||||
}
|
||||
if (typeof res.data !== 'object') {
|
||||
const msg = '接口返回异常';
|
||||
vm.$u.toast(msg);
|
||||
return Promise.reject(new Error(msg));
|
||||
}
|
||||
|
||||
if(res.data.code !== 0) {
|
||||
vm.$u.toast(res.data.msg)
|
||||
@@ -125,7 +152,7 @@ const install = (Vue, vm) => {
|
||||
|
||||
// 封装 post json 请求
|
||||
vm.$u.postJson = (url, data = {}, header = {}) => {
|
||||
console.log('header', header, url, vm.$u)
|
||||
console.log('header', header, url, vm.$store.state.apiurl)
|
||||
header['content-type'] = 'application/json';
|
||||
return vm.$u.http.request({
|
||||
url,
|
||||
@@ -134,7 +161,49 @@ const install = (Vue, vm) => {
|
||||
data
|
||||
})
|
||||
}
|
||||
// 封装 post json 请求
|
||||
vm.$u.putJson = (url, data = {}, header = {}) => {
|
||||
console.log('header', header, url, vm.$u)
|
||||
header['content-type'] = 'application/json';
|
||||
return vm.$u.http.request({
|
||||
url,
|
||||
method: 'PUT',
|
||||
header,
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 封装不使用 baseUrl 的请求(用于完整URL)
|
||||
vm.$u.putJsonFullUrl = (url, data = {}, header = {}) => {
|
||||
header['content-type'] = 'application/json';
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
url,
|
||||
method: 'PUT',
|
||||
// #ifdef APP-PLUS
|
||||
sslVerify: false,
|
||||
// #endif
|
||||
header: {
|
||||
...header,
|
||||
'Platform': 2,
|
||||
'x-requested-with': 'XMLHttpRequest',
|
||||
'Authorization': vm.vuex_token || ''
|
||||
},
|
||||
data,
|
||||
success: (res) => {
|
||||
console.log('res',res)
|
||||
if (res.data && res.data.code === 0) {
|
||||
resolve(res.data.data);
|
||||
} else {
|
||||
reject(res.data);
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
@import '@/common/pda-typography.scss';
|
||||
|
||||
page { background-color: #F8F8F8; }
|
||||
.filterBar { display: flex; align-items: center; padding: 16rpx 20rpx; background: #fff; gap: 16rpx; }
|
||||
.filterBtn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 88rpx;
|
||||
min-height: 76rpx;
|
||||
padding: 8rpx 10rpx;
|
||||
border: 2rpx solid transparent;
|
||||
border-radius: 12rpx;
|
||||
box-sizing: border-box;
|
||||
font-size: $pda-font-nav-sub;
|
||||
line-height: 1.2;
|
||||
color: #666;
|
||||
background: #f5f6f8;
|
||||
}
|
||||
.filter-active {
|
||||
color: #2979ff !important;
|
||||
background: #d6e8ff !important;
|
||||
border-color: #2979ff !important;
|
||||
}
|
||||
.filterBtn__icon {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.filterBtn__badge {
|
||||
position: absolute;
|
||||
top: -8rpx;
|
||||
right: -10rpx;
|
||||
width: 18rpx;
|
||||
height: 18rpx;
|
||||
border: 4rpx solid #fff;
|
||||
border-radius: 50%;
|
||||
background: #ff4d4f;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
.filterPanel {
|
||||
min-height: 100%;
|
||||
padding: 32rpx 28rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.filterTitle {
|
||||
margin-bottom: 30rpx;
|
||||
font-size: $pda-font-popup-title;
|
||||
font-weight: bold;
|
||||
color: #202328;
|
||||
}
|
||||
.filterItem { margin-bottom: 28rpx; }
|
||||
.filterLabel {
|
||||
display: block;
|
||||
margin-bottom: 14rpx;
|
||||
font-size: $pda-font-body;
|
||||
line-height: 1.35;
|
||||
color: #555b66;
|
||||
}
|
||||
.filterInput {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: $pda-row-min-height;
|
||||
padding: 0 18rpx;
|
||||
background: #f5f6f8;
|
||||
border-radius: 8rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
::v-deep .filterInput .u-input__input {
|
||||
font-size: $pda-font-body !important;
|
||||
color: #202328;
|
||||
}
|
||||
::v-deep .filterInput .u-input__input::placeholder {
|
||||
font-size: $pda-font-placeholder;
|
||||
color: #aeb4bf;
|
||||
}
|
||||
.filterValue {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: $pda-row-min-height;
|
||||
padding: 0 18rpx;
|
||||
background: #f5f6f8;
|
||||
border-radius: 8rpx;
|
||||
box-sizing: border-box;
|
||||
font-size: $pda-font-body;
|
||||
color: #202328;
|
||||
}
|
||||
.dateRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
}
|
||||
.datePicker {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.dateInput {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: $pda-row-min-height;
|
||||
padding: 0 8rpx;
|
||||
background: #f5f6f8;
|
||||
border-radius: 8rpx;
|
||||
box-sizing: border-box;
|
||||
font-size: $pda-font-body;
|
||||
color: #202328;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.dateInput--placeholder {
|
||||
font-size: $pda-font-placeholder;
|
||||
color: #7d8490;
|
||||
}
|
||||
.dateSeparator {
|
||||
flex-shrink: 0;
|
||||
font-size: $pda-font-body;
|
||||
color: #555b66;
|
||||
}
|
||||
.filterActions {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
.filterActions .u-btn {
|
||||
flex: 1;
|
||||
height: $pda-button-height !important;
|
||||
line-height: $pda-button-height !important;
|
||||
padding: 0 !important;
|
||||
font-size: $pda-font-button !important;
|
||||
}
|
||||
::v-deep .pdaSelect .u-select__header {
|
||||
height: $pda-row-min-height;
|
||||
}
|
||||
::v-deep .pdaSelect .u-select__header__btn,
|
||||
::v-deep .pdaSelect .u-select__header__title,
|
||||
::v-deep .pdaSelect .u-select__body__picker-view__item {
|
||||
font-size: $pda-font-popup-item !important;
|
||||
}
|
||||
::v-deep .pdaSelect .u-select__search__input {
|
||||
font-size: $pda-font-body !important;
|
||||
}
|
||||
::v-deep .pdaSelect .u-select__loading,
|
||||
::v-deep .pdaSelect .u-select__empty {
|
||||
font-size: $pda-font-popup-sub !important;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
export function hasFilterValue(value) {
|
||||
if (value === '' || value === null || value === undefined) return false;
|
||||
if (typeof value === 'string') return value.trim() !== '';
|
||||
return true;
|
||||
}
|
||||
|
||||
export function normalizeAuditStatus(value) {
|
||||
if (!hasFilterValue(value) && value !== 0) return '';
|
||||
const status = Number(value);
|
||||
return Number.isNaN(status) ? '' : status;
|
||||
}
|
||||
|
||||
export function isActiveFilterState(filters) {
|
||||
if (!filters) return false;
|
||||
return Object.keys(filters).some((key) => {
|
||||
if (/_name$/.test(key)) return false;
|
||||
return hasFilterValue(filters[key]);
|
||||
});
|
||||
}
|
||||
|
||||
export function normalizeFilterState(value, defaults) {
|
||||
const filters = { ...defaults };
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) return filters;
|
||||
|
||||
Object.keys(filters).forEach((key) => {
|
||||
if (key === 'audit_status') return;
|
||||
if (typeof value[key] === 'string' || typeof value[key] === 'number') {
|
||||
filters[key] = value[key];
|
||||
}
|
||||
});
|
||||
Object.keys(filters).forEach((key) => {
|
||||
if (!key.endsWith('_id') || hasFilterValue(filters[key])) return;
|
||||
const nameKey = `${key.slice(0, -3)}_name`;
|
||||
if (nameKey in filters) filters[nameKey] = '';
|
||||
});
|
||||
if (Object.prototype.hasOwnProperty.call(value, 'audit_status')) {
|
||||
filters.audit_status = normalizeAuditStatus(value.audit_status);
|
||||
}
|
||||
return filters;
|
||||
}
|
||||
|
||||
export function withSelectedOption(list, selectedValue, selectedLabel) {
|
||||
const rows = Array.isArray(list) ? list.slice() : [];
|
||||
const exists = rows.some((item) => String(item.value) === String(selectedValue));
|
||||
if (hasFilterValue(selectedValue) && !exists) {
|
||||
rows.unshift({ value: selectedValue, label: selectedLabel || String(selectedValue) });
|
||||
}
|
||||
return [{ value: '', label: '全部' }].concat(rows);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* PDA 小屏字号标准(以手动进仓页为准)
|
||||
* 业务页请优先引用这些变量,避免字号再次变小。
|
||||
*/
|
||||
|
||||
$pda-font-body: 36rpx;
|
||||
$pda-font-placeholder: 34rpx;
|
||||
$pda-font-action: 34rpx;
|
||||
$pda-font-unit: 32rpx;
|
||||
$pda-font-nav-sub: 32rpx;
|
||||
$pda-font-button: 36rpx;
|
||||
$pda-font-popup-title: 36rpx;
|
||||
$pda-font-popup-item: 36rpx;
|
||||
$pda-font-popup-sub: 34rpx;
|
||||
|
||||
$pda-row-min-height: 100rpx;
|
||||
$pda-scan-input-height: 100rpx;
|
||||
$pda-button-height: 92rpx;
|
||||
$pda-label-width: 200rpx;
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 从往来单位等列表接口中取出数组。
|
||||
* 兼容拦截器解包后的 { list, total },以及完整响应
|
||||
* { data: { code, data: { list, total } } }。
|
||||
*/
|
||||
export function extractList(data, depth = 0) {
|
||||
if (!data || depth > 6) return [];
|
||||
if (Array.isArray(data)) return data;
|
||||
if (Array.isArray(data.list)) return data.list;
|
||||
if (Array.isArray(data.items)) return data.items;
|
||||
if (Array.isArray(data.records)) return data.records;
|
||||
if (data.data && typeof data.data === 'object') return extractList(data.data, depth + 1);
|
||||
return [];
|
||||
}
|
||||
|
||||
/** 将下拉/列表接口响应转为 u-select 所需的 { value, label } */
|
||||
export function mapToSelectOptions(data) {
|
||||
return extractList(data).filter((item) => item && typeof item === 'object').map((item) => ({
|
||||
value: item.id != null ? item.id : item.value,
|
||||
label: item.name || item.label || item.code || (item.id != null ? String(item.id) : ''),
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 下拉远程搜索:把关键字写入接口常用的 name 参数。
|
||||
* 空关键字时不带 name,让后端返回默认列表。
|
||||
*/
|
||||
export function withNameParam(params, keyword) {
|
||||
const next = Object.assign({}, params || {});
|
||||
const name = String(keyword || '').trim();
|
||||
if (name) next.name = name;
|
||||
else delete next.name;
|
||||
return next;
|
||||
}
|
||||
|
||||
export const REMOTE_SELECT_TYPES = [
|
||||
'染厂',
|
||||
'加工单位',
|
||||
'供应商',
|
||||
'仓库名称',
|
||||
'营销体系',
|
||||
];
|
||||
|
||||
export function isRemoteSelectType(type) {
|
||||
return REMOTE_SELECT_TYPES.indexOf(type) !== -1;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { isRemoteSelectType } from '@/common/remoteSelect';
|
||||
|
||||
/**
|
||||
* 表单页远程下拉搜索。
|
||||
* 使用方需实现 fetchRemoteSelectOptions(type, keyword),返回 { value, label }[]。
|
||||
*
|
||||
* 注意:不要用 `_` / `$` 前缀的 data 字段名——Vue2 不会代理到 this,
|
||||
* `++this._xxx` 会得到 NaN,导致列表不更新、loading 不关。
|
||||
*/
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
selectSearching: false,
|
||||
selectSearchSeq: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isRemoteSelect() {
|
||||
return isRemoteSelectType(this.selectType);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isRemoteSelectType,
|
||||
openSelectPicker(type, cachedList) {
|
||||
this.selectType = type;
|
||||
this.selectList = Array.isArray(cachedList) ? cachedList.slice() : [];
|
||||
this.selectShow = true;
|
||||
},
|
||||
onSelectSearch(keyword) {
|
||||
if (!isRemoteSelectType(this.selectType)) return;
|
||||
if (typeof this.fetchRemoteSelectOptions !== 'function') return;
|
||||
const seq = ++this.selectSearchSeq;
|
||||
this.selectSearching = true;
|
||||
const finish = () => {
|
||||
if (seq === this.selectSearchSeq) this.selectSearching = false;
|
||||
};
|
||||
Promise.resolve()
|
||||
.then(() => this.fetchRemoteSelectOptions(this.selectType, keyword))
|
||||
.then((list) => {
|
||||
if (seq !== this.selectSearchSeq) return;
|
||||
this.selectList = Array.isArray(list) ? list.slice() : [];
|
||||
})
|
||||
.catch((e) => {
|
||||
if (seq !== this.selectSearchSeq) return;
|
||||
if (typeof this.showError === 'function') {
|
||||
this.showError((e && e.message) || '搜索失败');
|
||||
} else {
|
||||
uni.showToast({ title: (e && e.message) || '搜索失败', icon: 'none' });
|
||||
}
|
||||
})
|
||||
.then(finish, finish);
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 登录后 / 冷启动:拉取扫码字典并在本地无配置时自动匹配写入
|
||||
*/
|
||||
import {
|
||||
DICTIONARY_TYPE_SCANNER,
|
||||
autoConfigureScanSettings,
|
||||
} from '@/common/scanConfig.js';
|
||||
|
||||
function extractDictionaryList(res) {
|
||||
if (Array.isArray(res)) return res;
|
||||
if (res && Array.isArray(res.list)) return res.list;
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} api - this.$u.api
|
||||
* @returns {Promise<{saved:boolean,skipped:boolean,config:object|null,reason?:string}>}
|
||||
*/
|
||||
export async function ensureScanConfigAfterLogin(api) {
|
||||
if (!api || !api.dictionary || !api.dictionary.getDetailEnumList) {
|
||||
return autoConfigureScanSettings({ remoteConfigs: [] });
|
||||
}
|
||||
|
||||
let remoteConfigs = [];
|
||||
try {
|
||||
const res = await api.dictionary.getDetailEnumList({
|
||||
dictionary_id: DICTIONARY_TYPE_SCANNER,
|
||||
});
|
||||
remoteConfigs = extractDictionaryList(res);
|
||||
} catch (error) {
|
||||
console.log('拉取扫码字典失败,改用内置配置自动匹配', error);
|
||||
}
|
||||
|
||||
const result = autoConfigureScanSettings({ remoteConfigs });
|
||||
if (result.skipped) {
|
||||
console.log('本地已有扫码设置,跳过自动匹配', result.config && result.config.name);
|
||||
} else if (result.saved) {
|
||||
console.log('已自动写入扫码设置', result.config && result.config.name);
|
||||
} else {
|
||||
console.log('自动匹配扫码设置未写入', result.reason);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
export const STORAGE_KEY = 'pda_scan_config';
|
||||
|
||||
export const DICTIONARY_TYPE_SCANNER = 10046;
|
||||
|
||||
export const PHONE_SCAN_BRAND = 'phone';
|
||||
|
||||
export const PHONE_SCAN_CONFIG = {
|
||||
brand: PHONE_SCAN_BRAND,
|
||||
name: '手机扫码',
|
||||
isPhoneScan: true,
|
||||
};
|
||||
|
||||
/** 内置扫码配置:补 needSetup,并在字典拉不到时兜底 */
|
||||
export const BUILTIN_SCANNER_CONFIGS = [
|
||||
{
|
||||
brand: 'newland',
|
||||
name: '东大集成',
|
||||
action: 'com.android.server.scannerservice.broadcast',
|
||||
dataKey: 'scannerdata',
|
||||
needSetup: true,
|
||||
setupAction: 'com.android.scanner.service_settings',
|
||||
setupParams: {
|
||||
action_barcode_broadcast: 'com.android.server.scannerservice.broadcast',
|
||||
key_barcode_broadcast: 'scannerdata',
|
||||
endchar: 'ENTER',
|
||||
},
|
||||
},
|
||||
{
|
||||
brand: 'sunmi',
|
||||
name: '商米',
|
||||
action: 'com.sunmi.scanner.ACTION_DATA_CODE_RECEIVED',
|
||||
dataKey: 'data',
|
||||
needSetup: false,
|
||||
},
|
||||
{
|
||||
brand: 'nlscan',
|
||||
name: '新大陆',
|
||||
action: 'nlscan.action.SCANNER_RESULT',
|
||||
dataKey: 'SCAN_BARCODE1',
|
||||
needSetup: false,
|
||||
},
|
||||
{
|
||||
brand: 'urovo',
|
||||
name: '优博讯',
|
||||
action: 'android.intent.ACTION_DECODE_DATA',
|
||||
dataKey: 'barcode_string',
|
||||
needSetup: false,
|
||||
},
|
||||
{
|
||||
brand: 'idata',
|
||||
name: '盈达',
|
||||
action: 'android.intent.action.SCANRESULT',
|
||||
dataKey: 'value',
|
||||
needSetup: false,
|
||||
},
|
||||
{
|
||||
brand: 'iData',
|
||||
name: '海雅达',
|
||||
action: 'com.android.scanner.broadcast',
|
||||
dataKey: 'scandata',
|
||||
needSetup: false,
|
||||
},
|
||||
{ ...PHONE_SCAN_CONFIG },
|
||||
];
|
||||
|
||||
/** 设备关键词 / 常见扫码相关包名,用于自动识别 */
|
||||
const DETECT_RULES = [
|
||||
{
|
||||
matchNames: ['商米'],
|
||||
matchActions: ['com.sunmi.scanner.ACTION_DATA_CODE_RECEIVED'],
|
||||
keywords: ['sunmi'],
|
||||
packages: ['com.sunmi.scanner', 'woyou.aidlservice.jiuiv5'],
|
||||
},
|
||||
{
|
||||
matchNames: ['新大陆'],
|
||||
matchActions: ['nlscan.action.SCANNER_RESULT'],
|
||||
keywords: ['newland', 'nls', 'nlscan'],
|
||||
packages: ['com.nlscan.barcodescanner', 'com.newland'],
|
||||
},
|
||||
{
|
||||
matchNames: ['优博讯'],
|
||||
matchActions: ['android.intent.ACTION_DECODE_DATA'],
|
||||
keywords: ['urovo'],
|
||||
packages: ['com.urovo', 'com.ubx'],
|
||||
},
|
||||
{
|
||||
matchNames: ['盈达'],
|
||||
matchActions: ['android.intent.action.SCANRESULT'],
|
||||
keywords: ['idata', 'yingda'],
|
||||
packages: ['com.idata', 'com.android.scanner'],
|
||||
},
|
||||
{
|
||||
matchNames: ['海雅达'],
|
||||
matchActions: ['com.android.scanner.broadcast'],
|
||||
keywords: ['iData', 'seui', 'haiyada'],
|
||||
packages: ['com.android.scanner'],
|
||||
},
|
||||
{
|
||||
matchNames: ['东大集成', '东集'],
|
||||
matchActions: ['com.android.server.scannerservice.broadcast'],
|
||||
keywords: ['seuic', 'eastcom', 'newland', 'nls'],
|
||||
packages: ['com.android.scanner', 'com.seuic.scanner'],
|
||||
},
|
||||
];
|
||||
|
||||
const PDA_DEVICE_KEYWORDS = [
|
||||
'newland', 'nls', 'sunmi', 'urovo', 'honeywell', 'idata', 'seuic', 'zebra', 'ubx',
|
||||
];
|
||||
|
||||
const NO_SETUP_HINT_KEY = 'pda_scan_config_hint_shown';
|
||||
|
||||
export function isPhoneScanConfig(config) {
|
||||
return !!(config && (config.isPhoneScan || config.brand === PHONE_SCAN_BRAND));
|
||||
}
|
||||
|
||||
function enrichFromBuiltinConfig(config, builtinConfigs = []) {
|
||||
if (!config || !builtinConfigs.length) {
|
||||
return config;
|
||||
}
|
||||
|
||||
const builtin = builtinConfigs.find((item) => {
|
||||
return item.action === config.action
|
||||
|| item.name === config.name
|
||||
|| item.brand === config.brand;
|
||||
});
|
||||
|
||||
if (!builtin) {
|
||||
return config;
|
||||
}
|
||||
|
||||
return {
|
||||
...config,
|
||||
needSetup: config.needSetup != null ? config.needSetup : builtin.needSetup,
|
||||
setupAction: config.setupAction || builtin.setupAction || '',
|
||||
setupParams: config.setupParams || builtin.setupParams || null,
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeConfig(raw, builtinConfigs = BUILTIN_SCANNER_CONFIGS) {
|
||||
if (!raw || typeof raw !== 'object') return null;
|
||||
|
||||
const brand = String(raw.brand || raw.id || raw.dictionary_detail_id || raw.name || '');
|
||||
const isPhoneScan = raw.isPhoneScan === true || brand === PHONE_SCAN_BRAND;
|
||||
|
||||
if (isPhoneScan) {
|
||||
return { ...PHONE_SCAN_CONFIG };
|
||||
}
|
||||
|
||||
const config = enrichFromBuiltinConfig({
|
||||
brand,
|
||||
name: raw.name || '',
|
||||
action: raw.action || raw.code || '',
|
||||
dataKey: raw.dataKey || raw.data_key || raw.remark || '',
|
||||
needSetup: raw.needSetup != null ? raw.needSetup : !!raw.need_setup,
|
||||
setupAction: raw.setupAction || raw.setup_action || '',
|
||||
setupParams: raw.setupParams || raw.setup_params || null,
|
||||
}, builtinConfigs);
|
||||
|
||||
if (!config.brand || !config.action || !config.dataKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
export function mapDictionaryToScannerConfig(raw, builtinConfigs = BUILTIN_SCANNER_CONFIGS) {
|
||||
return normalizeConfig(raw, builtinConfigs);
|
||||
}
|
||||
|
||||
export function getScanConfig(builtinConfigs = BUILTIN_SCANNER_CONFIGS) {
|
||||
try {
|
||||
const data = uni.getStorageSync(STORAGE_KEY);
|
||||
return normalizeConfig(data, builtinConfigs);
|
||||
} catch (e) {
|
||||
console.log('读取扫码枪配置失败', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function hasLocalScanConfig(builtinConfigs = BUILTIN_SCANNER_CONFIGS) {
|
||||
return !!getScanConfig(builtinConfigs);
|
||||
}
|
||||
|
||||
export function saveScanConfig(config) {
|
||||
const normalized = normalizeConfig(config);
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
uni.setStorageSync(STORAGE_KEY, normalized);
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.log('保存扫码枪配置失败', e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function hasShownNoConfigHint() {
|
||||
try {
|
||||
return !!uni.getStorageSync(NO_SETUP_HINT_KEY);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function markNoConfigHintShown() {
|
||||
try {
|
||||
uni.setStorageSync(NO_SETUP_HINT_KEY, true);
|
||||
} catch (e) {
|
||||
console.log('记录扫码枪提示状态失败', e);
|
||||
}
|
||||
}
|
||||
|
||||
export function mergeScannerConfigs(remoteConfigs = []) {
|
||||
const configMap = new Map();
|
||||
BUILTIN_SCANNER_CONFIGS.forEach((item) => {
|
||||
if (!isPhoneScanConfig(item)) {
|
||||
configMap.set(item.action || item.brand, item);
|
||||
}
|
||||
});
|
||||
(remoteConfigs || []).forEach((raw) => {
|
||||
const normalized = mapDictionaryToScannerConfig(raw);
|
||||
if (!normalized || isPhoneScanConfig(normalized)) return;
|
||||
configMap.set(normalized.action || normalized.brand, normalized);
|
||||
});
|
||||
const list = Array.from(configMap.values());
|
||||
list.push({ ...PHONE_SCAN_CONFIG });
|
||||
return list;
|
||||
}
|
||||
|
||||
export function getDeviceIdentity() {
|
||||
// #ifdef APP-PLUS
|
||||
try {
|
||||
const Build = plus.android.importClass('android.os.Build');
|
||||
return {
|
||||
manufacturer: String(Build.MANUFACTURER || '').toLowerCase(),
|
||||
model: String(Build.MODEL || '').toLowerCase(),
|
||||
brand: String(Build.BRAND || '').toLowerCase(),
|
||||
};
|
||||
} catch (e) {
|
||||
console.log('读取设备信息失败', e);
|
||||
}
|
||||
// #endif
|
||||
return { manufacturer: '', model: '', brand: '' };
|
||||
}
|
||||
|
||||
export function isPdaDevice() {
|
||||
const identity = getDeviceIdentity();
|
||||
const text = `${identity.manufacturer} ${identity.model} ${identity.brand}`;
|
||||
return PDA_DEVICE_KEYWORDS.some((keyword) => text.includes(keyword.toLowerCase()));
|
||||
}
|
||||
|
||||
function findDetectRule(config) {
|
||||
return DETECT_RULES.find((rule) => {
|
||||
if (rule.matchNames.includes(config.name)) return true;
|
||||
if (rule.matchActions.includes(config.action)) return true;
|
||||
return false;
|
||||
}) || null;
|
||||
}
|
||||
|
||||
function isPackageInstalled(packageName) {
|
||||
// #ifdef APP-PLUS
|
||||
try {
|
||||
const main = plus.android.runtimeMainActivity();
|
||||
const pm = main.getPackageManager();
|
||||
plus.android.importClass(pm);
|
||||
pm.getPackageInfo(packageName, 0);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
// #endif
|
||||
return false;
|
||||
}
|
||||
|
||||
function canResolveBroadcast(action) {
|
||||
if (!action) return false;
|
||||
// #ifdef APP-PLUS
|
||||
try {
|
||||
const main = plus.android.runtimeMainActivity();
|
||||
const Intent = plus.android.importClass('android.content.Intent');
|
||||
const pm = main.getPackageManager();
|
||||
plus.android.importClass(pm);
|
||||
const intent = new Intent(action);
|
||||
const list = pm.queryBroadcastReceivers(intent, 0);
|
||||
return !!(list && list.size && list.size() > 0);
|
||||
} catch (e) {
|
||||
console.log('探测广播接收方失败', action, e);
|
||||
return false;
|
||||
}
|
||||
// #endif
|
||||
return false;
|
||||
}
|
||||
|
||||
function scoreScannerConfig(config, identity) {
|
||||
if (!config || isPhoneScanConfig(config)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let score = 0;
|
||||
const rule = findDetectRule(config);
|
||||
const deviceText = `${identity.manufacturer} ${identity.model} ${identity.brand}`;
|
||||
|
||||
if (canResolveBroadcast(config.action)) {
|
||||
score += 100;
|
||||
}
|
||||
|
||||
if (rule) {
|
||||
if ((rule.packages || []).some((pkg) => isPackageInstalled(pkg))) {
|
||||
score += 60;
|
||||
}
|
||||
if ((rule.keywords || []).some((keyword) => deviceText.includes(String(keyword).toLowerCase()))) {
|
||||
score += 40;
|
||||
}
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在候选配置中选出最匹配当前设备的一项;非 PDA 且无命中则回落手机扫码
|
||||
*/
|
||||
export function matchBestScanConfig(candidateConfigs = []) {
|
||||
const configs = (candidateConfigs || [])
|
||||
.map((item) => normalizeConfig(item))
|
||||
.filter(Boolean);
|
||||
const pdaConfigs = configs.filter((item) => !isPhoneScanConfig(item));
|
||||
const identity = getDeviceIdentity();
|
||||
|
||||
let best = null;
|
||||
let bestScore = 0;
|
||||
pdaConfigs.forEach((config) => {
|
||||
const score = scoreScannerConfig(config, identity);
|
||||
if (score > bestScore) {
|
||||
bestScore = score;
|
||||
best = config;
|
||||
}
|
||||
});
|
||||
|
||||
if (best && bestScore > 0) {
|
||||
return best;
|
||||
}
|
||||
|
||||
if (!isPdaDevice()) {
|
||||
return { ...PHONE_SCAN_CONFIG };
|
||||
}
|
||||
|
||||
return pdaConfigs[0] || { ...PHONE_SCAN_CONFIG };
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅本地没有有效配置时,自动匹配并写入扫描设置
|
||||
* @returns {{ saved: boolean, config: object|null, skipped: boolean, reason?: string }}
|
||||
*/
|
||||
export function autoConfigureScanSettings({ remoteConfigs = [] } = {}) {
|
||||
const existing = getScanConfig();
|
||||
if (existing) {
|
||||
return {
|
||||
saved: false,
|
||||
skipped: true,
|
||||
config: existing,
|
||||
reason: 'local_exists',
|
||||
};
|
||||
}
|
||||
|
||||
const candidates = mergeScannerConfigs(remoteConfigs);
|
||||
const matched = matchBestScanConfig(candidates);
|
||||
if (!matched) {
|
||||
return {
|
||||
saved: false,
|
||||
skipped: false,
|
||||
config: null,
|
||||
reason: 'no_match',
|
||||
};
|
||||
}
|
||||
|
||||
const saved = saveScanConfig(matched);
|
||||
return {
|
||||
saved,
|
||||
skipped: false,
|
||||
config: matched,
|
||||
reason: saved ? 'saved' : 'save_failed',
|
||||
};
|
||||
}
|
||||
+247
-116
@@ -1,149 +1,271 @@
|
||||
// common/scanMixin.js
|
||||
// 多厂商PDA扫码器兼容方案 - 策略模式 + 配置表驱动
|
||||
import {
|
||||
BUILTIN_SCANNER_CONFIGS,
|
||||
getScanConfig,
|
||||
hasShownNoConfigHint,
|
||||
isPdaDevice as detectPdaDevice,
|
||||
isPhoneScanConfig,
|
||||
markNoConfigHintShown,
|
||||
normalizeConfig,
|
||||
PHONE_SCAN_CONFIG,
|
||||
} from '@/common/scanConfig.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
scanReceiver: null,
|
||||
isPageActive: false
|
||||
scanReceivers: [],
|
||||
isPageActive: false,
|
||||
registeredBrands: [],
|
||||
remoteScannerConfigs: [],
|
||||
usePhoneScanFallback: false,
|
||||
phoneScanPanelOpen: false,
|
||||
_scanCallback: null,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 注册扫码广播(兼容多种设备)
|
||||
registerScanBroadcast(scanCallback) {
|
||||
try {
|
||||
// 先尝试注册商米扫码头
|
||||
this.registerSunmiBroadcast(scanCallback)
|
||||
.then(() => {
|
||||
console.log('商米扫码头注册成功');
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('商米扫码头注册失败,尝试新大陆扫码头:', error);
|
||||
// 如果商米扫码头注册失败,尝试新大陆扫码头
|
||||
this.registerNewlandBroadcast(scanCallback);
|
||||
getScannerConfigs() {
|
||||
return BUILTIN_SCANNER_CONFIGS.map((item) => ({ ...item }));
|
||||
},
|
||||
|
||||
getPhoneScanConfig() {
|
||||
return { ...PHONE_SCAN_CONFIG };
|
||||
},
|
||||
|
||||
isPdaDevice() {
|
||||
return detectPdaDevice();
|
||||
},
|
||||
|
||||
shouldUsePhoneScan() {
|
||||
const builtinConfigs = this.getScannerConfigs();
|
||||
const activeConfig = getScanConfig(builtinConfigs);
|
||||
if (activeConfig && isPhoneScanConfig(activeConfig)) {
|
||||
return true;
|
||||
}
|
||||
if (!activeConfig && !this.isPdaDevice()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
getAllScannerConfigs() {
|
||||
const builtinConfigs = this.getScannerConfigs();
|
||||
if (!this.remoteScannerConfigs.length) {
|
||||
return builtinConfigs;
|
||||
}
|
||||
|
||||
const configMap = new Map();
|
||||
builtinConfigs.forEach((item) => configMap.set(item.brand, item));
|
||||
this.remoteScannerConfigs.forEach((item) => configMap.set(item.brand, item));
|
||||
if (!configMap.has(PHONE_SCAN_CONFIG.brand)) {
|
||||
configMap.set(PHONE_SCAN_CONFIG.brand, { ...PHONE_SCAN_CONFIG });
|
||||
}
|
||||
return Array.from(configMap.values());
|
||||
},
|
||||
|
||||
getActiveScanConfig() {
|
||||
return getScanConfig(this.getScannerConfigs());
|
||||
},
|
||||
|
||||
getDefaultScanConfig() {
|
||||
const configs = this.getAllScannerConfigs().filter((item) => !isPhoneScanConfig(item));
|
||||
return configs.length ? configs[0] : null;
|
||||
},
|
||||
|
||||
closePhoneScanPanel() {
|
||||
this.phoneScanPanelOpen = false;
|
||||
},
|
||||
|
||||
/** 半屏连续扫:再次点击同一入口则收起 */
|
||||
togglePhoneScanPanel() {
|
||||
this.phoneScanPanelOpen = !this.phoneScanPanelOpen;
|
||||
},
|
||||
|
||||
openPhoneScan(options = {}) {
|
||||
const callback = options.callback || this._scanCallback;
|
||||
// #ifdef APP-PLUS || MP-WEIXIN
|
||||
uni.scanCode({
|
||||
scanType: options.scanType || ['qrCode', 'barCode'],
|
||||
success: (res) => {
|
||||
this.handleScanResult(res.result, callback);
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('手机扫码失败:', err);
|
||||
if (options.fail) {
|
||||
options.fail(err);
|
||||
return;
|
||||
}
|
||||
uni.showToast({
|
||||
title: '扫码失败',
|
||||
icon: 'none',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("注册扫码广播失败:", error);
|
||||
},
|
||||
});
|
||||
return;
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
uni.showToast({
|
||||
title: 'H5环境不支持扫码功能',
|
||||
icon: 'none',
|
||||
});
|
||||
// #endif
|
||||
},
|
||||
|
||||
triggerScan() {
|
||||
if (this.usePhoneScanFallback) {
|
||||
this.openPhoneScan();
|
||||
}
|
||||
},
|
||||
|
||||
registerScanBroadcast(scanCallback) {
|
||||
this._scanCallback = scanCallback;
|
||||
|
||||
// #ifndef APP-PLUS
|
||||
this.usePhoneScanFallback = true;
|
||||
return true;
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
if (this.shouldUsePhoneScan()) {
|
||||
this.usePhoneScanFallback = true;
|
||||
console.log('使用手机扫码模式');
|
||||
return true;
|
||||
}
|
||||
|
||||
this.usePhoneScanFallback = false;
|
||||
const activeConfig = this.getActiveScanConfig();
|
||||
|
||||
if (activeConfig && !isPhoneScanConfig(activeConfig)) {
|
||||
return this.registerScanBroadcastByConfig(activeConfig, scanCallback);
|
||||
}
|
||||
|
||||
if (!hasShownNoConfigHint()) {
|
||||
markNoConfigHintShown();
|
||||
uni.showToast({
|
||||
title: '请先在「我的-扫描设置」配置扫码枪',
|
||||
icon: 'none',
|
||||
duration: 2500,
|
||||
});
|
||||
}
|
||||
|
||||
const defaultConfig = this.getDefaultScanConfig();
|
||||
if (!defaultConfig) {
|
||||
console.error('未找到默认可用的扫码枪配置');
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.registerScanBroadcastByConfig(defaultConfig, scanCallback);
|
||||
// #endif
|
||||
},
|
||||
|
||||
// 注册商米扫码头广播接收器
|
||||
registerSunmiBroadcast(scanCallback) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const main = plus.android.runtimeMainActivity();
|
||||
const IntentFilter = plus.android.importClass("android.content.IntentFilter");
|
||||
registerScanBroadcastByBrand(brand, scanCallback) {
|
||||
// #ifdef APP-PLUS
|
||||
const configs = this.getAllScannerConfigs();
|
||||
const config = configs.find(c => c.brand === brand);
|
||||
|
||||
if (!config) {
|
||||
console.error(`未找到厂商配置: ${brand}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isPhoneScanConfig(config)) {
|
||||
this._scanCallback = scanCallback;
|
||||
this.usePhoneScanFallback = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.registerScanBroadcastByConfig(config, scanCallback);
|
||||
// #endif
|
||||
},
|
||||
|
||||
registerScanBroadcastByConfig(config, scanCallback) {
|
||||
// #ifdef APP-PLUS
|
||||
const normalizedConfig = normalizeConfig(config, this.getScannerConfigs());
|
||||
if (!normalizedConfig) {
|
||||
console.error('扫码枪配置无效');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isPhoneScanConfig(normalizedConfig)) {
|
||||
this._scanCallback = scanCallback;
|
||||
this.usePhoneScanFallback = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
this.registerSingleBroadcast(normalizedConfig, scanCallback);
|
||||
this.registeredBrands.push(normalizedConfig.brand);
|
||||
this.usePhoneScanFallback = false;
|
||||
console.log(`${normalizedConfig.name}扫码广播注册成功`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`${normalizedConfig.name}扫码广播注册失败:`, error);
|
||||
return false;
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
|
||||
registerSingleBroadcast(config, scanCallback) {
|
||||
const main = plus.android.runtimeMainActivity();
|
||||
|
||||
if (config.needSetup && config.setupAction) {
|
||||
try {
|
||||
const Intent = plus.android.importClass('android.content.Intent');
|
||||
const setupIntent = new Intent(config.setupAction);
|
||||
|
||||
Object.keys(config.setupParams || {}).forEach(key => {
|
||||
setupIntent.putExtra(key, config.setupParams[key]);
|
||||
});
|
||||
|
||||
main.sendBroadcast(setupIntent);
|
||||
console.log(`${config.name}扫码配置完成`);
|
||||
} catch (error) {
|
||||
console.error(`${config.name}扫码配置失败:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
const IntentFilter = plus.android.importClass('android.content.IntentFilter');
|
||||
const filter = new IntentFilter();
|
||||
filter.addAction("com.sunmi.scanner.ACTION_DATA_CODE_RECEIVED");
|
||||
filter.addAction(config.action);
|
||||
|
||||
const self = this;
|
||||
const receiver = plus.android.implements(
|
||||
"io.dcloud.feature.internal.reflect.BroadcastReceiver",
|
||||
'io.dcloud.feature.internal.reflect.BroadcastReceiver',
|
||||
{
|
||||
onReceive: (context, intent) => {
|
||||
console.log('商米扫码广播接收,isPageActive:', self.isPageActive);
|
||||
// 只有当页面活动时才处理广播
|
||||
console.log(`${config.name}扫码广播接收,isPageActive:`, self.isPageActive);
|
||||
|
||||
if (!self.isPageActive) return;
|
||||
|
||||
try {
|
||||
// 导入 Intent 类以使用其方法
|
||||
const Intent = plus.android.importClass("android.content.Intent");
|
||||
// 广播回调里的 intent 需要按实例导入,否则 getStringExtra 不可用
|
||||
plus.android.importClass(intent);
|
||||
const scanResult = intent.getStringExtra(config.dataKey);
|
||||
|
||||
const scanResult = intent.getStringExtra("data");
|
||||
const sourceBytes = intent.getByteArrayExtra("source_byte");
|
||||
|
||||
console.log('商米扫码结果:', scanResult, sourceBytes);
|
||||
console.log(`${config.name}扫码结果:`, scanResult);
|
||||
|
||||
if (scanResult) {
|
||||
self.handleScanResult(scanResult, scanCallback);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('处理商米广播数据时出错:', error);
|
||||
console.error(`处理${config.name}广播数据出错:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 注册广播接收器
|
||||
main.registerReceiver(receiver, filter);
|
||||
this.scanReceiver = receiver;
|
||||
console.log('商米扫码广播接收器注册成功');
|
||||
resolve();
|
||||
} catch (error) {
|
||||
console.error('注册商米广播接收器失败:', error);
|
||||
reject(error);
|
||||
}
|
||||
|
||||
this.scanReceivers.push({
|
||||
brand: config.brand,
|
||||
receiver: receiver
|
||||
});
|
||||
},
|
||||
|
||||
// 注册新大陆扫码头广播接收器
|
||||
registerNewlandBroadcast(scanCallback) {
|
||||
try {
|
||||
const main = plus.android.runtimeMainActivity();
|
||||
|
||||
// 先配置扫码枪广播设置
|
||||
try {
|
||||
const Intent = plus.android.importClass("android.content.Intent");
|
||||
const intent = new Intent("com.android.scanner.service_settings");
|
||||
intent.putExtra(
|
||||
"action_barcode_broadcast",
|
||||
"com.android.server.scannerservice.broadcast"
|
||||
);
|
||||
intent.putExtra("key_barcode_broadcast", "scannerdata");
|
||||
main.sendBroadcast(intent);
|
||||
console.log('新大陆扫码枪广播配置完成');
|
||||
} catch (error) {
|
||||
console.error("配置新大陆扫码枪广播失败:", error);
|
||||
}
|
||||
|
||||
// 注册广播接收器
|
||||
const IntentFilter = plus.android.importClass(
|
||||
"android.content.IntentFilter"
|
||||
);
|
||||
const filter = new IntentFilter();
|
||||
filter.addAction("com.android.server.scannerservice.broadcast");
|
||||
console.log("添加新大陆广播action完成");
|
||||
|
||||
const self = this;
|
||||
const receiver = plus.android.implements(
|
||||
"io.dcloud.feature.internal.reflect.BroadcastReceiver",
|
||||
{
|
||||
onReceive: (context, intent) => {
|
||||
console.log('新大陆扫码广播接收,isPageActive:', self.isPageActive);
|
||||
// 只有当页面活动时才处理广播
|
||||
if (!self.isPageActive) return;
|
||||
|
||||
try {
|
||||
// 导入 Intent 类以使用其方法
|
||||
const Intent = plus.android.importClass("android.content.Intent");
|
||||
|
||||
const scanResult = intent.getStringExtra("scannerdata");
|
||||
console.log("新大陆扫码结果:", scanResult);
|
||||
if (scanResult) {
|
||||
self.handleScanResult(scanResult, scanCallback);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("处理新大陆广播数据时出错:", error);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// 注册广播接收器
|
||||
main.registerReceiver(receiver, filter);
|
||||
this.scanReceiver = receiver;
|
||||
console.log("新大陆扫码广播注册成功,等待扫码...");
|
||||
} catch (error) {
|
||||
console.error("注册新大陆扫码广播失败:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// 处理扫码结果的统一方法
|
||||
handleScanResult(scanResult, scanCallback) {
|
||||
try {
|
||||
// 数据清理:去除空格、换行符等
|
||||
let cleanCode = scanResult.replace(/\s+/g, '').replace(/[\r\n]/g, '');
|
||||
|
||||
console.log("扫码结果:", cleanCode);
|
||||
@@ -156,21 +278,31 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
// 取消扫码广播监听
|
||||
unregisterScanBroadcast() {
|
||||
if (this.scanReceiver) {
|
||||
// #ifdef APP-PLUS
|
||||
if (this.scanReceivers.length === 0) return;
|
||||
|
||||
try {
|
||||
const main = plus.android.runtimeMainActivity();
|
||||
main.unregisterReceiver(this.scanReceiver);
|
||||
this.scanReceiver = null;
|
||||
console.log("扫码广播注销成功");
|
||||
|
||||
this.scanReceivers.forEach(item => {
|
||||
try {
|
||||
main.unregisterReceiver(item.receiver);
|
||||
console.log(`${item.brand}扫码广播注销成功`);
|
||||
} catch (err) {
|
||||
console.error("注销扫码广播失败:", err);
|
||||
console.error(`${item.brand}扫码广播注销失败:`, err);
|
||||
}
|
||||
});
|
||||
|
||||
this.scanReceivers = [];
|
||||
this.registeredBrands = [];
|
||||
console.log('所有扫码广播注销完成');
|
||||
} catch (err) {
|
||||
console.error('注销扫码广播失败:', err);
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
|
||||
// 通用的商品扫描处理方法
|
||||
handleGoodsScan(scanCode, successCallback, errorCallback) {
|
||||
const trimmedCode = scanCode ? scanCode.toString().trim() : "";
|
||||
|
||||
@@ -180,17 +312,14 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据业务类型处理不同的扫码逻辑
|
||||
if (this.processScanCode) {
|
||||
this.processScanCode(trimmedCode, successCallback, errorCallback);
|
||||
} else {
|
||||
// 默认处理方式
|
||||
successCallback && successCallback(trimmedCode);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 页面激活状态管理
|
||||
onShow() {
|
||||
this.isPageActive = true;
|
||||
console.log('页面显示,设置 isPageActive 为 true');
|
||||
@@ -198,10 +327,12 @@ export default {
|
||||
|
||||
onHide() {
|
||||
this.isPageActive = false;
|
||||
this.closePhoneScanPanel();
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
this.isPageActive = false;
|
||||
this.closePhoneScanPanel();
|
||||
this.unregisterScanBroadcast();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
@import '@/common/pda-typography.scss';
|
||||
|
||||
.statusTag {
|
||||
flex-shrink: 0;
|
||||
font-size: $pda-font-body;
|
||||
line-height: 1.25;
|
||||
padding: 10rpx 22rpx;
|
||||
border-radius: 10rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
.status-0 { background: #d9d9d9; color: #434343; }
|
||||
.status-1 { background: #fa8c16; color: #fff; }
|
||||
.status-2 { background: #52c41a; color: #fff; }
|
||||
.status-3 { background: #f5222d; color: #fff; }
|
||||
.status-4 { background: #8c8c8c; color: #fff; }
|
||||
@@ -0,0 +1,595 @@
|
||||
/**
|
||||
* 坯布出仓单(其他出货单)常量与数据工具
|
||||
*
|
||||
* 仅存放与接口无关的常量 / 状态映射 / 字段规范化 / 单位换算等纯工具,
|
||||
* 真实接口请求统一注册在 src/common/http.api.js 的 vm.$u.api.gfmOtherDeliveryOrder 下。
|
||||
*/
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
// 审核状态(对应 openapi common.OrderStatus)
|
||||
export const AUDIT_STATUS = {
|
||||
PENDING: 1, // 待审核
|
||||
APPROVED: 2, // 已审核
|
||||
REJECTED: 3, // 已驳回
|
||||
VOIDED: 4, // 已作废
|
||||
};
|
||||
|
||||
/** 前端草稿态(未保存) */
|
||||
export const DRAFT_AUDIT_STATUS = 0;
|
||||
export const DRAFT_AUDIT_STATUS_NAME = '待保存';
|
||||
|
||||
/** 将接口状态统一为数字,避免字符串导致按钮判断失效 */
|
||||
export function toAuditStatus(status) {
|
||||
const n = Number(status);
|
||||
return Number.isFinite(n) ? n : DRAFT_AUDIT_STATUS;
|
||||
}
|
||||
|
||||
|
||||
export const AUDIT_STATUS_MAP = {
|
||||
1: '待审核',
|
||||
2: '已审核',
|
||||
3: '已驳回',
|
||||
4: '已作废',
|
||||
};
|
||||
|
||||
/** 坯布资料弹窗字段 */
|
||||
export const FABRIC_INFO_FIELDS = [
|
||||
{ label: '坯布编号', key: 'grey_fabric_code' },
|
||||
{ label: '名称', key: 'grey_fabric_name' },
|
||||
{ label: '客户', key: 'customer_name' },
|
||||
{ label: '幅宽', key: 'grey_fabric_width' },
|
||||
{ label: '克重', key: 'grey_fabric_gram_weight' },
|
||||
{ label: '纱批', key: 'yarn_batch' },
|
||||
{ label: '针寸数', key: 'needle_size' },
|
||||
{ label: '原料纱名称', key: 'raw_material_yarn_name' },
|
||||
{ label: '原料批号', key: 'raw_material_batch_num' },
|
||||
{ label: '原料品牌', key: 'raw_material_batch_brand' },
|
||||
{ label: '颜色', key: 'gray_fabric_color_name' },
|
||||
{ label: '等级', key: 'grey_fabric_level_name' },
|
||||
{ label: '机台号', key: 'machine_number' },
|
||||
{ label: '织造工艺', key: 'weaving_process' },
|
||||
{ label: '坯布备注', key: 'grey_fabric_remark' },
|
||||
];
|
||||
|
||||
/** 细码表格列(不含操作列) */
|
||||
export const FINE_CODE_TABLE_COLUMNS = [
|
||||
{ label: '序号', key: 'seq', width: 80 },
|
||||
{ label: '卷号', key: 'roll_no', width: 110 },
|
||||
{ label: '外部卷号', key: 'extern_volume_number', width: 140 },
|
||||
{ label: '匹数', key: 'roll', width: 100 },
|
||||
{ label: '数量(重量)', key: 'weight', width: 140 },
|
||||
{ label: '条码', key: 'bar_code', width: 280 },
|
||||
{ label: '仓位', key: 'position', width: 130 },
|
||||
];
|
||||
|
||||
/** 审核状态变更操作配置(apiKey 对应 gfmOtherDeliveryOrder 下的方法名) */
|
||||
export const STATUS_ACTION_CONFIG = {
|
||||
approve: { status: AUDIT_STATUS.APPROVED, msg: '确认审核该单据?', apiKey: 'statusPass' },
|
||||
void: { status: AUDIT_STATUS.VOIDED, msg: '确认作废该单据?', apiKey: 'statusCancel' },
|
||||
unapprove: { status: AUDIT_STATUS.PENDING, msg: '确认消审该单据?', apiKey: 'statusWait' },
|
||||
};
|
||||
|
||||
// 出仓类型:当前后端仅提供「坯布其他出货单」一种类型的接口
|
||||
export const BILL_TYPES = [
|
||||
{ value: 'other', label: '坯布其他出货单' },
|
||||
];
|
||||
|
||||
/** 列表分页拉取每页条数 */
|
||||
export const LIST_PAGE_SIZE = 100;
|
||||
|
||||
/** 往来单位类型:染厂(染厂下拉筛选) */
|
||||
export const BUSINESS_UNIT_TYPE_DYE_FACTORY = 12;
|
||||
|
||||
export function getStatusOptions() {
|
||||
return Object.keys(AUDIT_STATUS_MAP).map((value) => ({
|
||||
value,
|
||||
label: AUDIT_STATUS_MAP[value],
|
||||
}));
|
||||
}
|
||||
|
||||
export { mapToSelectOptions } from '@/common/remoteSelect';
|
||||
|
||||
// 匹数:后端以 0.01 匹存储(乘以100的整数),展示需除以100
|
||||
export function formatRoll(value) {
|
||||
const n = Number(value || 0) / 100;
|
||||
return Number.isInteger(n) ? n : Number(n.toFixed(2));
|
||||
}
|
||||
|
||||
// 重量:后端整数存储(×10000),展示需除以 10000 为 KG
|
||||
export function formatWeightKg(value) {
|
||||
const n = Number(value || 0) / 10000;
|
||||
return Number(n.toFixed(2));
|
||||
}
|
||||
|
||||
/** 从详情对象取展示用总重量(优先用原始值换算) */
|
||||
export function getOrderDisplayWeight(order) {
|
||||
if (!order) return 0;
|
||||
if (order._raw?.total_weight != null) return formatWeightKg(order._raw.total_weight);
|
||||
return order.total_weight ?? 0;
|
||||
}
|
||||
|
||||
/** 从细码取展示用重量(优先用原始值换算) */
|
||||
export function getFineCodeDisplayWeight(fineCode) {
|
||||
if (!fineCode) return 0;
|
||||
if (fineCode.raw_weight != null) return formatWeightKg(fineCode.raw_weight);
|
||||
return fineCode.weight || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将接口详情数据规范化为页面使用的结构
|
||||
*/
|
||||
export function normalizeOrder(order) {
|
||||
if (!order) return null;
|
||||
const itemData = groupItemData((order.item_data || []).map((item) => normalizeItem(item)));
|
||||
return {
|
||||
id: order.id,
|
||||
order_no: order.order_no || '',
|
||||
audit_status: toAuditStatus(order.audit_status),
|
||||
audit_status_name: order.audit_status_name || AUDIT_STATUS_MAP[toAuditStatus(order.audit_status)] || '',
|
||||
// 表头
|
||||
sale_system_id: order.sale_system_id || '',
|
||||
sale_system_name: order.sale_system_name || '',
|
||||
delivery_unit_id: order.delivery_unit_id || '',
|
||||
delivery_unit_name: order.delivery_unit_name || '',
|
||||
business_unit_id: order.business_unit_id || '',
|
||||
business_unit_name: order.business_unit_name || '',
|
||||
delivery_time: (order.delivery_time || '').slice(0, 10),
|
||||
remark: order.remark || '',
|
||||
// 人员/时间
|
||||
creator_name: order.creator_name || '',
|
||||
create_time: order.create_time || '',
|
||||
auditer_name: order.auditer_name || '',
|
||||
audit_time: order.audit_time || '',
|
||||
// 汇总
|
||||
total_roll: formatRoll(order.total_roll),
|
||||
total_weight: formatWeightKg(order.total_weight),
|
||||
item_data: itemData,
|
||||
_raw: order,
|
||||
};
|
||||
}
|
||||
|
||||
/** 扫码详情区表格列 */
|
||||
export const SCAN_DETAIL_TABLE_COLUMNS = [
|
||||
{ label: '坯布编号', key: 'grey_fabric_code', width: 160 },
|
||||
{ label: '坯布名称', key: 'grey_fabric_name', width: 160 },
|
||||
{ label: '纱批', key: 'yarn_batch', width: 130 },
|
||||
{ label: '机号', key: 'machine_number', width: 110 },
|
||||
{ label: '条数', key: 'roll', width: 90 },
|
||||
{ label: '重量', key: 'weight', width: 110 },
|
||||
];
|
||||
|
||||
/** 扫码详情区可勾选锁定维度(对应 PDAScanGfmOtherDeliveryOrderParam 的 *_lock;勾选后参与表格分组并随扫码请求传给后端) */
|
||||
export const SCAN_GROUP_OPTIONS = [
|
||||
{ key: 'produce_order_no', label: '合同' },
|
||||
{ key: 'grey_fabric_code', label: '布编' },
|
||||
{ key: 'yarn_batch', label: '纱批' },
|
||||
{ key: 'machine_number', label: '机号' },
|
||||
{ key: 'grey_fabric_level_name', label: '等级' },
|
||||
];
|
||||
|
||||
/** 勾选维度对应扫码接口锁定字段名(PDAScanGfmOtherDeliveryOrderParam) */
|
||||
export const SCAN_MERGE_FIELD_MAP = {
|
||||
produce_order_no: 'produce_order_no_lock',
|
||||
grey_fabric_code: 'grey_fabric_code_lock',
|
||||
yarn_batch: 'yarn_batch_lock',
|
||||
machine_number: 'machine_number_lock',
|
||||
grey_fabric_level_name: 'grey_fabric_level_id_lock',
|
||||
};
|
||||
|
||||
/** 扫码锁定维度不匹配时的语音文案(按匹配优先级排列) */
|
||||
const SCAN_MISMATCH_VOICE_RULES = [
|
||||
{ patterns: ['机台', '机号', 'machine_number'], voice: '机台不同' },
|
||||
{ patterns: ['纱批', 'yarn_batch'], voice: '纱批不同' },
|
||||
{ patterns: ['合同号', '合同', '生产通知单', 'produce_order'], voice: '合同号不同' },
|
||||
{ patterns: ['布编', 'grey_fabric_code'], voice: '布编不同' },
|
||||
{ patterns: ['等级', 'grey_fabric_level', 'level'], voice: '等级不同' },
|
||||
];
|
||||
|
||||
/**
|
||||
* 根据后端错误信息解析锁定维度不匹配的播报文案
|
||||
* @param {string} message
|
||||
* @returns {string} 如「机台不同」;无法识别则返回空串
|
||||
*/
|
||||
export function resolveScanMismatchVoice(message) {
|
||||
const msg = String(message || '');
|
||||
if (!msg) return '';
|
||||
for (let i = 0; i < SCAN_MISMATCH_VOICE_RULES.length; i++) {
|
||||
const rule = SCAN_MISMATCH_VOICE_RULES[i];
|
||||
if (rule.patterns.some((p) => msg.indexOf(p) !== -1)) {
|
||||
return rule.voice;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/** 构建扫码新增/删除请求参数(勾选锁定维度时附带当前详情区字段值) */
|
||||
export function buildScanUpdateParams({ id, scanCode, scanType, groupKeys = [], detail = {} }) {
|
||||
const params = {
|
||||
id: Number(id),
|
||||
scan_code: scanCode,
|
||||
scan_type: scanType,
|
||||
};
|
||||
(groupKeys || []).forEach((key) => {
|
||||
const apiKey = SCAN_MERGE_FIELD_MAP[key];
|
||||
if (!apiKey) return;
|
||||
if (apiKey === 'grey_fabric_level_id_lock') {
|
||||
const levelIds = detail.grey_fabric_level_id;
|
||||
params.grey_fabric_level_id_lock = Array.isArray(levelIds) ? levelIds : [];
|
||||
return;
|
||||
}
|
||||
const value = detail[key];
|
||||
params[apiKey] = value != null && value !== '' ? value : '';
|
||||
});
|
||||
return params;
|
||||
}
|
||||
|
||||
export function getEmptyScanDetail() {
|
||||
return {
|
||||
customer_id: '',
|
||||
customer_name: '',
|
||||
produce_order_no: '',
|
||||
grey_fabric_code: '',
|
||||
grey_fabric_name: '',
|
||||
yarn_batch: '',
|
||||
machine_number: '',
|
||||
volume_number: '',
|
||||
extern_volume_number: '',
|
||||
grey_fabric_level_id: [],
|
||||
grey_fabric_level_name: '',
|
||||
weight: 0,
|
||||
scanned_roll: 0,
|
||||
machine_stock: 0,
|
||||
total_roll: 0,
|
||||
total_weight: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/** 扫码接口响应是否包含条码详情字段 */
|
||||
export function hasScanDetailFields(raw) {
|
||||
if (!raw) return false;
|
||||
return raw.scanned_total_roll != null
|
||||
|| raw.summary_stock_roll != null
|
||||
|| !!raw.customer_name
|
||||
|| !!raw.grey_fabric_code
|
||||
|| !!raw.produce_order_no;
|
||||
}
|
||||
|
||||
/** 从扫码接口返回的详情构建扫码详情区数据 */
|
||||
export function buildScanDetailFromOrderResponse(order) {
|
||||
const raw = order?._raw || order;
|
||||
if (!hasScanDetailFields(raw)) return null;
|
||||
const detail = {
|
||||
customer_id: raw.customer_id || '',
|
||||
customer_name: raw.customer_name || '',
|
||||
produce_order_no: raw.produce_order_no || '',
|
||||
grey_fabric_code: raw.grey_fabric_code || '',
|
||||
grey_fabric_name: raw.grey_fabric_name || '',
|
||||
yarn_batch: raw.yarn_batch || '',
|
||||
machine_number: raw.machine_number || '',
|
||||
volume_number: raw.volume_number || '',
|
||||
extern_volume_number: raw.extern_volume_number != null && raw.extern_volume_number !== ''
|
||||
? String(raw.extern_volume_number)
|
||||
: '',
|
||||
grey_fabric_level_id: raw.grey_fabric_level_id || [],
|
||||
grey_fabric_level_name: raw.grey_fabric_level_name || '',
|
||||
weight: 0,
|
||||
scanned_roll: formatRoll(raw.scanned_total_roll),
|
||||
machine_stock: raw.summary_stock_roll != null ? formatRoll(raw.summary_stock_roll) : 0,
|
||||
total_roll: order?.total_roll ?? 0,
|
||||
total_weight: getOrderDisplayWeight(order),
|
||||
};
|
||||
return detail;
|
||||
}
|
||||
|
||||
/** 在单据明细中定位本次扫描的坯布行与细码 */
|
||||
export function findScanContext(itemData, scanCode) {
|
||||
const code = (scanCode || '').trim();
|
||||
if (!code) return null;
|
||||
for (const item of itemData || []) {
|
||||
for (const fc of item.fine_codes || []) {
|
||||
const candidates = [
|
||||
fc.fabric_piece_code,
|
||||
fc.bar_code,
|
||||
fc.roll_no,
|
||||
fc.scan_code,
|
||||
].filter(Boolean).map(String);
|
||||
if (candidates.includes(code)) {
|
||||
return { item, fineCode: fc };
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 根据扫码上下文构建详情区展示数据 */
|
||||
export function buildScanDetailFromContext(context, order) {
|
||||
if (!context) return getEmptyScanDetail();
|
||||
const { item, fineCode } = context;
|
||||
const groupKey = getItemGroupKey(item);
|
||||
let scannedRoll = 0;
|
||||
(order.item_data || []).forEach((row) => {
|
||||
if (getItemGroupKey(row) !== groupKey) return;
|
||||
(row.fine_codes || []).forEach((fc) => {
|
||||
scannedRoll += Number(fc.roll || 0);
|
||||
});
|
||||
});
|
||||
return {
|
||||
customer_name: item.customer_name || '',
|
||||
produce_order_no: item.produce_order_no || item.contract_no || '',
|
||||
grey_fabric_code: item.grey_fabric_code || '',
|
||||
grey_fabric_name: item.grey_fabric_name || '',
|
||||
yarn_batch: item.yarn_batch || '',
|
||||
machine_number: item.machine_number || '',
|
||||
volume_number: fineCode.fabric_piece_code || fineCode.roll_no || fineCode.bar_code || '',
|
||||
extern_volume_number: fineCode.extern_volume_number || '',
|
||||
grey_fabric_level_name: item.grey_fabric_level_name || '',
|
||||
weight: getFineCodeDisplayWeight(fineCode),
|
||||
scanned_roll: Number(scannedRoll.toFixed(2)),
|
||||
machine_stock: item.machine_stock ?? 0,
|
||||
total_roll: order.total_roll ?? 0,
|
||||
total_weight: getOrderDisplayWeight(order),
|
||||
};
|
||||
}
|
||||
|
||||
function aggregateScanTableRows(rows, keys) {
|
||||
const map = new Map();
|
||||
rows.forEach((row) => {
|
||||
const groupId = keys.map((k) => row[k] || '').join('|');
|
||||
const existing = map.get(groupId);
|
||||
if (!existing) {
|
||||
map.set(groupId, { ...row, roll: 0, raw_weight: 0 });
|
||||
}
|
||||
const agg = map.get(groupId);
|
||||
agg.roll = Number((agg.roll + Number(row.roll || 0)).toFixed(2));
|
||||
agg.raw_weight = Number(agg.raw_weight || 0) + Number(row.raw_weight || 0);
|
||||
});
|
||||
return Array.from(map.values()).map((row) => ({
|
||||
...row,
|
||||
weight: formatWeightKg(row.raw_weight),
|
||||
}));
|
||||
}
|
||||
|
||||
/** 构建扫码详情区汇总表格数据 */
|
||||
export function buildScanDetailTableRows(itemData, groupKeys = []) {
|
||||
const rows = [];
|
||||
(itemData || []).forEach((item) => {
|
||||
(item.fine_codes || []).forEach((fc) => {
|
||||
rows.push({
|
||||
customer_name: item.customer_name || '',
|
||||
produce_order_no: item.produce_order_no || item.contract_no || '',
|
||||
grey_fabric_code: item.grey_fabric_code || '',
|
||||
grey_fabric_name: item.grey_fabric_name || '',
|
||||
yarn_batch: item.yarn_batch || '',
|
||||
machine_number: item.machine_number || '',
|
||||
volume_number: fc.roll_no || fc.fabric_piece_code || '',
|
||||
grey_fabric_level_name: item.grey_fabric_level_name || '',
|
||||
roll: fc.roll || 0,
|
||||
raw_weight: fc.raw_weight != null ? fc.raw_weight : 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
if (!rows.length) return [];
|
||||
const keys = (groupKeys || []).filter(Boolean);
|
||||
if (!keys.length) {
|
||||
return aggregateScanTableRows(rows, ['grey_fabric_code', 'grey_fabric_name', 'yarn_batch', 'machine_number']);
|
||||
}
|
||||
return aggregateScanTableRows(rows, keys);
|
||||
}
|
||||
|
||||
export function getScanDetailTableWidth(columns) {
|
||||
const total = (columns || []).reduce((sum, col) => sum + (col.width || 120), 0);
|
||||
return `${total}rpx`;
|
||||
}
|
||||
|
||||
export function normalizeItem(item) {
|
||||
const machineStockRaw = item.machine_stock_roll ?? item.machine_stock ?? item.machine_inventory_roll;
|
||||
return {
|
||||
id: item.id,
|
||||
grey_fabric_id: item.grey_fabric_id || '',
|
||||
grey_fabric_code: item.grey_fabric_code || '',
|
||||
grey_fabric_name: item.grey_fabric_name || '',
|
||||
customer_name: item.customer_name || '',
|
||||
produce_order_no: item.produce_order_no || item.contract_no || item.sale_contract_no || item.grey_fabric_contract_no || '',
|
||||
machine_stock: machineStockRaw != null ? formatRoll(machineStockRaw) : 0,
|
||||
grey_fabric_width: item.grey_fabric_width_and_unit_name || item.grey_fabric_width || '',
|
||||
grey_fabric_gram_weight: item.grey_fabric_gram_weight_and_unit_name || item.grey_fabric_gram_weight || '',
|
||||
yarn_batch: item.yarn_batch || '',
|
||||
needle_size: item.needle_size || '',
|
||||
raw_material_yarn_name: item.raw_material_yarn_name || '',
|
||||
raw_material_batch_num: item.raw_material_batch_num || '',
|
||||
raw_material_batch_brand: item.raw_material_batch_brand || '',
|
||||
gray_fabric_color_name: item.gray_fabric_color_name || '',
|
||||
grey_fabric_level_name: item.grey_fabric_level_name || '',
|
||||
machine_number: item.machine_number || '',
|
||||
weaving_process: item.weaving_process || '',
|
||||
grey_fabric_remark: item.grey_fabric_remark || '',
|
||||
roll: formatRoll(item.roll),
|
||||
weight: formatWeightKg(item.total_weight),
|
||||
fine_codes: (item.item_fc_data || []).map((fc) => ({
|
||||
id: fc.id,
|
||||
roll_no: fc.volume_number || '',
|
||||
extern_volume_number: fc.extern_volume_number != null && fc.extern_volume_number !== ''
|
||||
? String(fc.extern_volume_number)
|
||||
: '',
|
||||
fabric_piece_code: fc.fabric_piece_code || '',
|
||||
bar_code: fc.fabric_piece_code || fc.bar_code || '',
|
||||
position: fc.position || '',
|
||||
raw_weight: fc.weight,
|
||||
weight: formatWeightKg(fc.weight),
|
||||
roll: formatRoll(fc.roll),
|
||||
// 删除细码走 scanUpdate,优先 fabric_piece_code,其次卷号
|
||||
scan_code: fc.fabric_piece_code || fc.volume_number || fc.bar_code || '',
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
/** 细码扫码/删除时传给 scanUpdate 的 scan_code(fabric_piece_code 或卷号) */
|
||||
export function getFineCodeScanCode(fineCode) {
|
||||
if (!fineCode) return '';
|
||||
return fineCode.fabric_piece_code
|
||||
|| fineCode.scan_code
|
||||
|| fineCode.bar_code
|
||||
|| fineCode.roll_no
|
||||
|| '';
|
||||
}
|
||||
|
||||
/** 坯布明细归类 key:优先 grey_fabric_id,否则 code + name */
|
||||
export function getItemGroupKey(item) {
|
||||
if (item.grey_fabric_id) return `id:${item.grey_fabric_id}`;
|
||||
return `code:${item.grey_fabric_code}|${item.grey_fabric_name}`;
|
||||
}
|
||||
|
||||
/** 将相同坯布的 item_data 合并为一行,细码 item_fc_data 汇总展示 */
|
||||
export function groupItemData(items) {
|
||||
const grouped = new Map();
|
||||
(items || []).forEach((item) => {
|
||||
const groupKey = getItemGroupKey(item);
|
||||
const existing = grouped.get(groupKey);
|
||||
if (!existing) {
|
||||
grouped.set(groupKey, {
|
||||
...item,
|
||||
group_key: groupKey,
|
||||
source_ids: [item.id],
|
||||
fine_codes: [...(item.fine_codes || [])],
|
||||
});
|
||||
return;
|
||||
}
|
||||
existing.source_ids.push(item.id);
|
||||
existing.fine_codes.push(...(item.fine_codes || []));
|
||||
existing.roll = Number((existing.roll + item.roll).toFixed(2));
|
||||
existing.weight = Number((existing.weight + item.weight).toFixed(2));
|
||||
});
|
||||
return Array.from(grouped.values()).map((row) => ({
|
||||
...row,
|
||||
id: row.grey_fabric_id || row.group_key,
|
||||
}));
|
||||
}
|
||||
|
||||
// 构造审核状态变更接口的入参(id 为字符串)
|
||||
export function buildStatusParam(id, auditStatus) {
|
||||
return { id: String(id), audit_status: auditStatus };
|
||||
}
|
||||
|
||||
export function formatDisplayTime(value, format = 'YYYY-MM-DD') {
|
||||
if (!value) return '';
|
||||
const parsed = dayjs(value);
|
||||
return parsed.isValid() ? parsed.format(format) : String(value).slice(0, 10);
|
||||
}
|
||||
|
||||
export function canScanAtStatus(status) {
|
||||
const s = toAuditStatus(status);
|
||||
return s === AUDIT_STATUS.PENDING || s === AUDIT_STATUS.REJECTED;
|
||||
}
|
||||
|
||||
export function isLockedOrderStatus(status) {
|
||||
const s = toAuditStatus(status);
|
||||
return s === AUDIT_STATUS.APPROVED || s === AUDIT_STATUS.VOIDED;
|
||||
}
|
||||
|
||||
/** 分页拉取全部列表(直至末页) */
|
||||
export function fetchAllOrderList(listFn, params = {}, pageSize = LIST_PAGE_SIZE) {
|
||||
const loadPage = (page, acc) => listFn({ ...params, page, size: pageSize })
|
||||
.then((res) => {
|
||||
const batch = Array.isArray(res) ? res : (res && res.list) || [];
|
||||
const merged = acc.concat(batch);
|
||||
if (batch.length < pageSize) return merged;
|
||||
return loadPage(page + 1, merged);
|
||||
});
|
||||
return loadPage(1, []);
|
||||
}
|
||||
|
||||
/** 新增/编辑页底部:审核、作废 */
|
||||
export function getFormStatusActionButtons(headerSaved, orderId, status) {
|
||||
if (!headerSaved || !orderId) return [];
|
||||
const s = toAuditStatus(status);
|
||||
if (s === AUDIT_STATUS.APPROVED) {
|
||||
return [{ label: '消审', action: 'unapprove' }];
|
||||
}
|
||||
if (!canScanAtStatus(s)) return [];
|
||||
return [
|
||||
{ label: '审核', action: 'approve' },
|
||||
{ label: '作废', action: 'void' },
|
||||
];
|
||||
}
|
||||
|
||||
/** 查看页底部:编辑、审核、作废 / 消审 */
|
||||
export function getViewStatusActionButtons(status) {
|
||||
const s = toAuditStatus(status);
|
||||
if (s === AUDIT_STATUS.PENDING || s === AUDIT_STATUS.REJECTED) {
|
||||
return [
|
||||
{ label: '编辑', action: 'edit' },
|
||||
{ label: '审核', action: 'approve' },
|
||||
{ label: '作废', action: 'void' },
|
||||
];
|
||||
}
|
||||
if (s === AUDIT_STATUS.APPROVED) {
|
||||
return [{ label: '消审', action: 'unapprove' }];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/** 将 normalizeOrder 结果映射为表单字段 */
|
||||
export function orderToFormFields(order) {
|
||||
return {
|
||||
order_no: order.order_no,
|
||||
business_unit_id: order.business_unit_id,
|
||||
business_unit_name: order.business_unit_name,
|
||||
delivery_time: order.delivery_time,
|
||||
remark: order.remark,
|
||||
item_data: order.item_data,
|
||||
};
|
||||
}
|
||||
|
||||
export function getFineCodeTableColumns(readonly) {
|
||||
const cols = [...FINE_CODE_TABLE_COLUMNS];
|
||||
if (!readonly) cols.push({ label: '操作', key: 'action', width: 100 });
|
||||
return cols;
|
||||
}
|
||||
|
||||
export function mapFineCodeTableRows(list, readonly) {
|
||||
return (list || []).map((item, index) => ({
|
||||
seq: index + 1,
|
||||
roll_no: item.roll_no || '-',
|
||||
extern_volume_number: item.extern_volume_number || '-',
|
||||
roll: item.roll != null && item.roll !== '' ? item.roll : '-',
|
||||
weight: item.weight != null && item.weight !== '' ? `${item.weight}` : '-',
|
||||
bar_code: item.bar_code || '-',
|
||||
position: item.position || '-',
|
||||
action: readonly ? '' : '删除',
|
||||
}));
|
||||
}
|
||||
|
||||
export function getFineCodeTableWidth(columns) {
|
||||
const total = (columns || []).reduce((sum, col) => sum + (col.width || 120), 0);
|
||||
return `${total}rpx`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认并执行审核状态变更(新增/编辑/查看页共用)
|
||||
* @param {Vue} vm 页面实例,需有 $u.api 与 showError
|
||||
* @param {{ orderId: number, action: string, onSuccess?: Function }} options
|
||||
*/
|
||||
export function confirmOrderStatusAction(vm, { orderId, action, onSuccess }) {
|
||||
const cfg = STATUS_ACTION_CONFIG[action];
|
||||
if (!cfg || !orderId) return;
|
||||
const api = vm.$u.api.gfmOtherDeliveryOrder;
|
||||
const requestFn = api[cfg.apiKey];
|
||||
if (typeof requestFn !== 'function') return;
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: cfg.msg,
|
||||
success: (res) => {
|
||||
if (!res.confirm) return;
|
||||
uni.showLoading({ title: '处理中...' });
|
||||
requestFn(buildStatusParam(orderId, cfg.status))
|
||||
.then(() => {
|
||||
if (typeof onSuccess === 'function') return onSuccess();
|
||||
uni.hideLoading();
|
||||
})
|
||||
.catch((e) => {
|
||||
uni.hideLoading();
|
||||
if (vm.showError) vm.showError(e.message || '操作失败');
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
/**
|
||||
* 坯布库存查询:常量、格式化、查询参数组装
|
||||
*/
|
||||
import {
|
||||
formatRoll,
|
||||
formatWeightKg,
|
||||
fetchAllOrderList,
|
||||
LIST_PAGE_SIZE,
|
||||
} from '@/common/storeFabricBusinessOut';
|
||||
|
||||
export { formatRoll, formatWeightKg, fetchAllOrderList, LIST_PAGE_SIZE };
|
||||
|
||||
/** 第一维度:坯布汇总表格列 */
|
||||
export const GROUP_TABLE_COLUMNS = [
|
||||
{ label: '坯布', key: 'fabric_label', width: 300 },
|
||||
{ label: '仓库名称', key: 'warehouse_name', width: 180 },
|
||||
{ label: '库存匹数', key: 'stock_roll_display', width: 130 },
|
||||
{ label: '库存数量', key: 'stock_weight_display', width: 160 },
|
||||
];
|
||||
|
||||
/** 第二维度:排除上期/本期字段后的表格列 */
|
||||
export const STOCK_TABLE_COLUMNS = [
|
||||
{ label: '颜色', key: 'gray_fabric_color_name', width: 120 },
|
||||
{ label: '坯布编号', key: 'grey_fabric_code', width: 140 },
|
||||
{ label: '坯布名称', key: 'grey_fabric_name', width: 160 },
|
||||
{ label: '等级', key: 'grey_fabric_level_name', width: 100 },
|
||||
{ label: '幅宽', key: 'grey_fabric_width_and_unit_name', width: 120 },
|
||||
{ label: '克重', key: 'grey_fabric_gram_weight_and_unit_name', width: 120 },
|
||||
{ label: '成分', key: 'component', width: 120 },
|
||||
{ label: '客户', key: 'customer_name', width: 120 },
|
||||
{ label: '织厂', key: 'supplier_name', width: 140 },
|
||||
{ label: '机台号', key: 'machine_number', width: 100 },
|
||||
{ label: '针寸数', key: 'needle_size', width: 100 },
|
||||
{ label: '纱批', key: 'yarn_batch', width: 120 },
|
||||
{ label: '原料纱名', key: 'raw_material_yarn_name', width: 140 },
|
||||
{ label: '原料批号', key: 'raw_material_batch_num', width: 120 },
|
||||
{ label: '原料品牌', key: 'raw_material_batch_brand', width: 120 },
|
||||
{ label: '仓库', key: 'warehouse_name', width: 140 },
|
||||
{ label: '收货单位', key: 'receiver_name', width: 140 },
|
||||
{ label: '工厂备注', key: 'warehouse_remark', width: 140 },
|
||||
{ label: '染厂用坯单号', key: 'dye_unit_use_order_no', width: 150 },
|
||||
{ label: '生产通知单号', key: 'produce_order_no', width: 150 },
|
||||
{ label: '出坯单号', key: 'source_code', width: 140 },
|
||||
{ label: '出坯日期', key: 'source_time', width: 120 },
|
||||
{ label: '坯布备注', key: 'source_remark', width: 140 },
|
||||
{ label: '预约匹数', key: 'book_roll_display', width: 110 },
|
||||
{ label: '库存匹数', key: 'stock_roll_display', width: 110 },
|
||||
{ label: '库存数量', key: 'stock_weight_display', width: 140 },
|
||||
{ label: '库存成本', key: 'stock_cost_display', width: 110 },
|
||||
{ label: '单价', key: 'single_price_display', width: 100 },
|
||||
{ label: '毛重成本', key: 'buoyant_weight_price_display', width: 110 },
|
||||
];
|
||||
|
||||
/** 细码明细表格列 */
|
||||
export const WAREHOUSE_DETAIL_COLUMNS = [
|
||||
{ label: '序号', key: 'seq', width: 80 },
|
||||
{ label: '卷号', key: 'volume_number', width: 110 },
|
||||
{ label: '外部卷号', key: 'extern_volume_number', width: 140 },
|
||||
{ label: '匹数', key: 'roll', width: 100 },
|
||||
{ label: '数量(KG)', key: 'weight', width: 130 },
|
||||
{ label: '条码', key: 'bar_code', width: 280 },
|
||||
{ label: '仓位', key: 'position', width: 120 },
|
||||
{ label: '颜色', key: 'gray_fabric_color_name', width: 120 },
|
||||
{ label: '织厂', key: 'supplier_name', width: 140 },
|
||||
{ label: '机台', key: 'machine_number', width: 100 },
|
||||
{ label: '库龄(天)', key: 'days_of_age', width: 110 },
|
||||
];
|
||||
|
||||
export function formatStockWeightWithUnit(weight, unitName) {
|
||||
const val = formatWeightKg(weight);
|
||||
if (val === 0 && (weight === 0 || weight === '0')) return unitName ? `0 ${unitName}` : '0';
|
||||
if (!weight && weight !== 0) return '-';
|
||||
return unitName ? `${val} ${unitName}` : `${val}`;
|
||||
}
|
||||
|
||||
export function formatDisplayCell(value) {
|
||||
if (value === null || value === undefined || value === '') return '-';
|
||||
return String(value);
|
||||
}
|
||||
|
||||
export function toQueryString(params = {}) {
|
||||
return Object.keys(params)
|
||||
.filter((key) => {
|
||||
const val = params[key];
|
||||
return val !== '' && val !== null && val !== undefined;
|
||||
})
|
||||
.map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
|
||||
.join('&');
|
||||
}
|
||||
|
||||
export function parsePageQuery(options = {}) {
|
||||
const parsed = { ...options };
|
||||
['grey_fabric_id', 'warehouse_id', 'supplier_id', 'gray_fabric_color_id', 'warehouse_sum_id'].forEach((key) => {
|
||||
if (parsed[key] !== undefined && parsed[key] !== '') {
|
||||
const num = Number(parsed[key]);
|
||||
if (!Number.isNaN(num)) parsed[key] = num;
|
||||
}
|
||||
});
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/** 第一维度接口筛选:仅坯布编号、坯布名称 */
|
||||
export function buildGroupListQuery(filters = {}) {
|
||||
const params = {};
|
||||
if (filters.grey_fabric_code) params.grey_fabric_code = filters.grey_fabric_code;
|
||||
if (filters.grey_fabric_name) params.grey_fabric_name = filters.grey_fabric_name;
|
||||
return params;
|
||||
}
|
||||
|
||||
/** 第二维度接口筛选:坯布 id + 机台号 */
|
||||
export function buildStockListQuery(filters = {}, context = {}) {
|
||||
const params = {};
|
||||
if (context.grey_fabric_id) params.grey_fabric_id = context.grey_fabric_id;
|
||||
if (filters.machine_number) params.machine_number = filters.machine_number;
|
||||
return params;
|
||||
}
|
||||
|
||||
export function buildDetailQuery(row = {}, context = {}) {
|
||||
const params = {};
|
||||
const greyFabricId = row.grey_fabric_id || context.grey_fabric_id;
|
||||
if (greyFabricId) params.grey_fabric_id = greyFabricId;
|
||||
if (row.warehouse_id || context.warehouse_id) {
|
||||
params.warehouse_id = row.warehouse_id || context.warehouse_id;
|
||||
}
|
||||
if (row.supplier_id) params.supplier_id = row.supplier_id;
|
||||
if (row.gray_fabric_color_id) params.gray_fabric_color_id = row.gray_fabric_color_id;
|
||||
if (row.machine_number) params.machine_number = row.machine_number;
|
||||
if (row.yarn_batch) params.yarn_batch = row.yarn_batch;
|
||||
if (row.produce_order_no) params.produce_order_no = row.produce_order_no;
|
||||
if (row.source_code) params.source_code = row.source_code;
|
||||
if (row.id) params.warehouse_sum_id = row.id;
|
||||
if (row.warehouse_sum_id) params.warehouse_sum_id = row.warehouse_sum_id;
|
||||
return params;
|
||||
}
|
||||
|
||||
export function buildStockListNavigateQuery(item) {
|
||||
return {
|
||||
grey_fabric_id: item.grey_fabric_id,
|
||||
grey_fabric_code: item.grey_fabric_code || '',
|
||||
grey_fabric_name: item.grey_fabric_name || '',
|
||||
warehouse_id: item.warehouse_id || '',
|
||||
};
|
||||
}
|
||||
|
||||
export function buildDetailNavigateQuery(row, context = {}) {
|
||||
const detailQuery = buildDetailQuery(row, context);
|
||||
return {
|
||||
...detailQuery,
|
||||
grey_fabric_code: row.grey_fabric_code || context.grey_fabric_code || '',
|
||||
grey_fabric_name: row.grey_fabric_name || context.grey_fabric_name || '',
|
||||
gray_fabric_color_name: row.gray_fabric_color_name || '',
|
||||
supplier_name: row.supplier_name || '',
|
||||
machine_number: row.machine_number || '',
|
||||
yarn_batch: row.yarn_batch || '',
|
||||
warehouse_name: row.warehouse_name || '',
|
||||
stock_roll: row.stock_roll_display || '',
|
||||
stock_weight: row.stock_weight_display || '',
|
||||
};
|
||||
}
|
||||
|
||||
export function mapGroupTableRow(row) {
|
||||
const code = row.grey_fabric_code || '';
|
||||
const name = row.grey_fabric_name || '';
|
||||
return {
|
||||
...row,
|
||||
_key: `${row.grey_fabric_id || ''}_${row.warehouse_id || ''}`,
|
||||
fabric_label: code && name ? `${code}#${name}` : (code || name || '-'),
|
||||
warehouse_name: formatDisplayCell(row.warehouse_name || row.receiver_name),
|
||||
stock_roll_display: formatRoll(row.stock_roll),
|
||||
stock_weight_display: formatStockWeightWithUnit(row.stock_weight, row.unit_name),
|
||||
};
|
||||
}
|
||||
|
||||
export function mapStockTableRow(row) {
|
||||
const unitName = row.unit_name || '';
|
||||
return {
|
||||
...row,
|
||||
_key: row.id || `${row.grey_fabric_id}_${row.gray_fabric_color_id}_${row.supplier_id}_${row.machine_number}`,
|
||||
gray_fabric_color_name: formatDisplayCell(row.gray_fabric_color_name),
|
||||
grey_fabric_code: formatDisplayCell(row.grey_fabric_code),
|
||||
grey_fabric_name: formatDisplayCell(row.grey_fabric_name),
|
||||
grey_fabric_level_name: formatDisplayCell(row.grey_fabric_level_name),
|
||||
grey_fabric_width_and_unit_name: formatDisplayCell(row.grey_fabric_width_and_unit_name),
|
||||
grey_fabric_gram_weight_and_unit_name: formatDisplayCell(row.grey_fabric_gram_weight_and_unit_name),
|
||||
component: formatDisplayCell(row.component),
|
||||
customer_name: formatDisplayCell(row.customer_name),
|
||||
supplier_name: formatDisplayCell(row.supplier_name),
|
||||
machine_number: formatDisplayCell(row.machine_number),
|
||||
needle_size: formatDisplayCell(row.needle_size),
|
||||
yarn_batch: formatDisplayCell(row.yarn_batch),
|
||||
raw_material_yarn_name: formatDisplayCell(row.raw_material_yarn_name),
|
||||
raw_material_batch_num: formatDisplayCell(row.raw_material_batch_num),
|
||||
raw_material_batch_brand: formatDisplayCell(row.raw_material_batch_brand),
|
||||
warehouse_name: formatDisplayCell(row.warehouse_name),
|
||||
receiver_name: formatDisplayCell(row.receiver_name),
|
||||
warehouse_remark: formatDisplayCell(row.warehouse_remark),
|
||||
dye_unit_use_order_no: formatDisplayCell(row.dye_unit_use_order_no),
|
||||
produce_order_no: formatDisplayCell(row.produce_order_no),
|
||||
source_code: formatDisplayCell(row.source_code),
|
||||
source_time: formatDisplayCell((row.source_time || '').slice(0, 10)),
|
||||
source_remark: formatDisplayCell(row.source_remark),
|
||||
book_roll_display: row.book_roll != null && row.book_roll !== '' ? formatRoll(row.book_roll) : '-',
|
||||
stock_roll_display: formatRoll(row.stock_roll),
|
||||
stock_weight_display: formatStockWeightWithUnit(row.stock_weight, unitName),
|
||||
stock_cost_display: row.stock_cost != null && row.stock_cost !== '' ? formatWeightKg(row.stock_cost) : '-',
|
||||
single_price_display: row.single_price != null && row.single_price !== '' ? formatWeightKg(row.single_price) : '-',
|
||||
buoyant_weight_price_display: row.buoyant_weight_price != null && row.buoyant_weight_price !== ''
|
||||
? formatWeightKg(row.buoyant_weight_price) : '-',
|
||||
creator_name: formatDisplayCell(row.creator_name),
|
||||
create_time: formatDisplayCell(row.create_time),
|
||||
update_user_name: formatDisplayCell(row.update_user_name),
|
||||
update_time: formatDisplayCell(row.update_time),
|
||||
};
|
||||
}
|
||||
|
||||
export function sumStockTotals(list = []) {
|
||||
let roll = 0;
|
||||
let weight = 0;
|
||||
(list || []).forEach((item) => {
|
||||
roll += Number(item.stock_roll || 0);
|
||||
weight += Number(item.stock_weight || 0);
|
||||
});
|
||||
return {
|
||||
roll: formatRoll(roll),
|
||||
weight: formatWeightKg(weight),
|
||||
count: list.length,
|
||||
};
|
||||
}
|
||||
|
||||
export function sumDetailTotals(list = []) {
|
||||
let roll = 0;
|
||||
let weight = 0;
|
||||
(list || []).forEach((item) => {
|
||||
roll += Number(item.num || 0);
|
||||
weight += Number(item.weight || 0);
|
||||
});
|
||||
return {
|
||||
roll: formatRoll(roll),
|
||||
weight: formatWeightKg(weight),
|
||||
count: list.length,
|
||||
};
|
||||
}
|
||||
|
||||
export function mapWarehouseDetailRows(list = []) {
|
||||
return (list || []).map((item, index) => ({
|
||||
seq: index + 1,
|
||||
volume_number: item.volume_number || '-',
|
||||
extern_volume_number: item.extern_volume_number || '-',
|
||||
roll: item.num != null && item.num !== '' ? formatRoll(item.num) : '-',
|
||||
weight: item.weight != null && item.weight !== '' ? formatWeightKg(item.weight) : '-',
|
||||
bar_code: item.bar_code || item.fabric_piece_code || '-',
|
||||
position: item.warehouse_bin_Name || '-',
|
||||
gray_fabric_color_name: item.gray_fabric_color_name || '-',
|
||||
supplier_name: item.supplier_name || '-',
|
||||
machine_number: item.machine_number || '-',
|
||||
days_of_age: item.days_of_age != null && item.days_of_age !== '' ? item.days_of_age : '-',
|
||||
}));
|
||||
}
|
||||
|
||||
export function getWarehouseDetailTableWidth(columns = WAREHOUSE_DETAIL_COLUMNS) {
|
||||
const total = columns.reduce((sum, col) => sum + (col.width || 120), 0);
|
||||
return `${total}rpx`;
|
||||
}
|
||||
|
||||
export function fetchAllWarehouseList(listFn, params = {}, pageSize = LIST_PAGE_SIZE) {
|
||||
return fetchAllOrderList(listFn, params, pageSize);
|
||||
}
|
||||
|
||||
export function calcTableHeight(rowCount, options = {}) {
|
||||
const headerPx = options.headerPx || 44;
|
||||
const rowPx = options.rowPx || 44;
|
||||
const minPx = options.minPx || 200;
|
||||
const maxPx = options.maxPx || 520;
|
||||
const total = headerPx + rowCount * rowPx;
|
||||
return `${Math.min(Math.max(total, minPx), maxPx)}px`;
|
||||
}
|
||||
@@ -0,0 +1,472 @@
|
||||
/**
|
||||
* 加工进仓单常量与数据工具
|
||||
*
|
||||
* 真实接口请求统一注册在 src/common/http.api.js 的 vm.$u.api.fpmProcessInOrder 下。
|
||||
*/
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export const AUDIT_STATUS = {
|
||||
PENDING: 1,
|
||||
APPROVED: 2,
|
||||
REJECTED: 3,
|
||||
VOIDED: 4,
|
||||
};
|
||||
|
||||
export const DRAFT_AUDIT_STATUS = 0;
|
||||
export const DRAFT_AUDIT_STATUS_NAME = '待保存';
|
||||
|
||||
/** 将接口状态统一为数字,避免字符串导致按钮判断失效 */
|
||||
export function toAuditStatus(status) {
|
||||
const n = Number(status);
|
||||
return Number.isFinite(n) ? n : DRAFT_AUDIT_STATUS;
|
||||
}
|
||||
|
||||
|
||||
export const AUDIT_STATUS_MAP = {
|
||||
1: '待审核',
|
||||
2: '已审核',
|
||||
3: '已驳回',
|
||||
4: '已作废',
|
||||
};
|
||||
|
||||
/** 操作类型:1录入 3删除 */
|
||||
export const ARRANGE_TYPE = {
|
||||
IN: 1,
|
||||
DEL: 3,
|
||||
};
|
||||
|
||||
/** 入仓类型:加工进仓单 */
|
||||
export const IN_ORDER_TYPE_PROCESS = 5;
|
||||
|
||||
/** 入仓类型:返修进仓 */
|
||||
export const IN_ORDER_TYPE_REPAIR = 6;
|
||||
|
||||
export const IN_ORDER_TYPE_MAP = {
|
||||
[IN_ORDER_TYPE_PROCESS]: '加工进仓单',
|
||||
[IN_ORDER_TYPE_REPAIR]: '返修进仓',
|
||||
};
|
||||
|
||||
export const IN_ORDER_TYPE_OPTIONS = [
|
||||
{ value: IN_ORDER_TYPE_PROCESS, label: IN_ORDER_TYPE_MAP[IN_ORDER_TYPE_PROCESS] },
|
||||
{ value: IN_ORDER_TYPE_REPAIR, label: IN_ORDER_TYPE_MAP[IN_ORDER_TYPE_REPAIR] },
|
||||
];
|
||||
|
||||
/** 默认纸筒重量(公斤,表单展示值) */
|
||||
export const DEFAULT_PAPER_TUBE_WEIGHT = 0.3;
|
||||
|
||||
/** 默认包装重量(公斤,表单展示值) */
|
||||
export const DEFAULT_PACKAGE_WEIGHT = 0.15;
|
||||
|
||||
/** 往来单位类型:加工单位 */
|
||||
export const BUSINESS_UNIT_TYPE_PROCESS = 12;
|
||||
|
||||
/** 成品物理仓类型 */
|
||||
export const WAREHOUSE_TYPE_FINISHED = 103;
|
||||
|
||||
/** 默认仓库名称 */
|
||||
export const DEFAULT_WAREHOUSE_NAME = '成品仓';
|
||||
|
||||
export const LIST_PAGE_SIZE = 100;
|
||||
|
||||
export const STATUS_ACTION_CONFIG = {
|
||||
approve: { status: AUDIT_STATUS.APPROVED, msg: '确认审核该单据?', apiKey: 'statusPass' },
|
||||
void: { status: AUDIT_STATUS.VOIDED, msg: '确认作废该单据?', apiKey: 'statusCancel' },
|
||||
unapprove: { status: AUDIT_STATUS.PENDING, msg: '确认消审该单据?', apiKey: 'statusWait' },
|
||||
};
|
||||
|
||||
export function getStatusOptions() {
|
||||
return Object.keys(AUDIT_STATUS_MAP).map((value) => ({
|
||||
value,
|
||||
label: AUDIT_STATUS_MAP[value],
|
||||
}));
|
||||
}
|
||||
|
||||
export { mapToSelectOptions } from '@/common/remoteSelect';
|
||||
|
||||
/** 匹数:后端 ×100 存储 */
|
||||
export function formatRoll(value) {
|
||||
const n = Number(value || 0) / 100;
|
||||
return Number.isInteger(n) ? n : Number(n.toFixed(2));
|
||||
}
|
||||
|
||||
/** 数量(公斤):后端 ×10000 存储 */
|
||||
export function formatWeightKg(value) {
|
||||
const n = Number(value || 0) / 10000;
|
||||
return Number(n.toFixed(2));
|
||||
}
|
||||
|
||||
/** 公斤展示值转后端存储(×10000) */
|
||||
export function toWeightStorage(value) {
|
||||
return Math.round(Number(value || 0) * 10000);
|
||||
}
|
||||
|
||||
export function normalizeOrder(order) {
|
||||
if (!order) return null;
|
||||
return {
|
||||
id: order.id,
|
||||
order_no: order.order_no || '',
|
||||
audit_status: toAuditStatus(order.audit_status),
|
||||
audit_status_name: order.audit_status_name || AUDIT_STATUS_MAP[toAuditStatus(order.audit_status)] || '',
|
||||
process_unit_id: order.process_unit_id || '',
|
||||
process_name: order.process_name || '',
|
||||
warehouse_id: order.warehouse_id || '',
|
||||
warehouse_name: order.warehouse_name || '',
|
||||
sale_system_id: order.sale_system_id || '',
|
||||
sale_system_name: order.sale_system_name || '',
|
||||
in_order_type: order.in_order_type || IN_ORDER_TYPE_PROCESS,
|
||||
in_order_type_name: order.in_order_type_name
|
||||
|| IN_ORDER_TYPE_MAP[order.in_order_type]
|
||||
|| IN_ORDER_TYPE_MAP[IN_ORDER_TYPE_PROCESS],
|
||||
paper_tube_weight: formatWeightKg(order.paper_tube_weight),
|
||||
package_weight: formatWeightKg(order.package_weight),
|
||||
remark: order.remark || '',
|
||||
warehouse_in_time: (order.warehouse_in_time || '').slice(0, 10),
|
||||
creator_name: order.creator_name || '',
|
||||
create_time: order.create_time || '',
|
||||
auditor_name: order.auditor_name || '',
|
||||
audit_time: order.audit_time || '',
|
||||
total_roll: formatRoll(order.total_roll),
|
||||
total_weight: formatWeightKg(order.total_weight),
|
||||
total_length: formatRoll(order.total_length),
|
||||
item_data: (order.item_data || []).map(normalizeItem),
|
||||
_raw: order,
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeFineCode(fc) {
|
||||
if (!fc) return null;
|
||||
const weightRaw = fc.base_unit_weight != null
|
||||
? fc.base_unit_weight
|
||||
: (fc.settle_weight != null ? fc.settle_weight : fc.weight);
|
||||
const qrCode = fc.qr_code || fc.long_code || '';
|
||||
const barCode = fc.bar_code || fc.digital_code || fc.short_code || '';
|
||||
return {
|
||||
id: fc.id,
|
||||
volume_number: fc.volume_number != null && fc.volume_number !== '' ? String(fc.volume_number) : '',
|
||||
extern_volume_number: fc.extern_volume_number != null && fc.extern_volume_number !== ''
|
||||
? String(fc.extern_volume_number)
|
||||
: '',
|
||||
roll: formatRoll(fc.roll),
|
||||
weight: formatWeightKg(weightRaw),
|
||||
bar_code: barCode,
|
||||
qr_code: qrCode,
|
||||
digital_code: fc.digital_code || '',
|
||||
long_code: fc.long_code || '',
|
||||
short_code: fc.short_code || '',
|
||||
dye_factory_dyelot_number: fc.dye_factory_dyelot_number || fc.dyelot_number || '',
|
||||
warehouse_bin_name: fc.warehouse_bin_name || '',
|
||||
shelf_no: fc.shelf_no || '',
|
||||
scan_code: qrCode || barCode || '',
|
||||
paper_tube_weight: fc.paper_tube_weight != null && fc.paper_tube_weight !== ''
|
||||
? formatWeightKg(fc.paper_tube_weight)
|
||||
: null,
|
||||
package_weight: fc.package_weight != null && fc.package_weight !== ''
|
||||
? formatWeightKg(fc.package_weight)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeItem(item) {
|
||||
return {
|
||||
id: item.id,
|
||||
quote_order_no: item.quote_order_no || '',
|
||||
product_code: item.product_code || '',
|
||||
product_name: item.product_name || '',
|
||||
product_color_code: item.product_color_code || '',
|
||||
product_color_name: item.product_color_name || '',
|
||||
dye_factory_dyelot_number: item.dye_factory_dyelot_number || '',
|
||||
dye_factory_color_code: item.dye_factory_color_code || '',
|
||||
in_roll: formatRoll(item.in_roll),
|
||||
in_weight: formatWeightKg(item.in_weight),
|
||||
item_fc_data: (item.item_fc_data || []).map(normalizeFineCode).filter(Boolean),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 明细分组:成品编号 → 成品色号 → 细码
|
||||
*/
|
||||
export function buildItemGroups(items) {
|
||||
const productMap = new Map();
|
||||
(items || []).forEach((item) => {
|
||||
const productKey = item.product_code || '-';
|
||||
if (!productMap.has(productKey)) {
|
||||
productMap.set(productKey, {
|
||||
group_key: `p:${productKey}`,
|
||||
product_code: item.product_code || '',
|
||||
product_name: item.product_name || '',
|
||||
in_roll: 0,
|
||||
in_weight: 0,
|
||||
colors: new Map(),
|
||||
});
|
||||
}
|
||||
const productGroup = productMap.get(productKey);
|
||||
if (!productGroup.product_name && item.product_name) {
|
||||
productGroup.product_name = item.product_name;
|
||||
}
|
||||
|
||||
const colorKey = item.product_color_code || item.product_color_name || '-';
|
||||
if (!productGroup.colors.has(colorKey)) {
|
||||
productGroup.colors.set(colorKey, {
|
||||
group_key: `p:${productKey}|c:${colorKey}`,
|
||||
product_color_code: item.product_color_code || '',
|
||||
product_color_name: item.product_color_name || '',
|
||||
quote_order_no: item.quote_order_no || '',
|
||||
dye_factory_dyelot_number: item.dye_factory_dyelot_number || '',
|
||||
in_roll: 0,
|
||||
in_weight: 0,
|
||||
fine_codes: [],
|
||||
});
|
||||
}
|
||||
const colorGroup = productGroup.colors.get(colorKey);
|
||||
productGroup.in_roll = Number((productGroup.in_roll + Number(item.in_roll || 0)).toFixed(2));
|
||||
productGroup.in_weight = Number((productGroup.in_weight + Number(item.in_weight || 0)).toFixed(2));
|
||||
colorGroup.in_roll = Number((colorGroup.in_roll + Number(item.in_roll || 0)).toFixed(2));
|
||||
colorGroup.in_weight = Number((colorGroup.in_weight + Number(item.in_weight || 0)).toFixed(2));
|
||||
if (item.quote_order_no) colorGroup.quote_order_no = item.quote_order_no;
|
||||
if (item.dye_factory_dyelot_number) {
|
||||
colorGroup.dye_factory_dyelot_number = item.dye_factory_dyelot_number;
|
||||
}
|
||||
if (!colorGroup.product_color_name && item.product_color_name) {
|
||||
colorGroup.product_color_name = item.product_color_name;
|
||||
}
|
||||
colorGroup.fine_codes.push(...(item.item_fc_data || []));
|
||||
});
|
||||
|
||||
return Array.from(productMap.values()).map((product) => ({
|
||||
group_key: product.group_key,
|
||||
product_code: product.product_code,
|
||||
product_name: product.product_name,
|
||||
in_roll: product.in_roll,
|
||||
in_weight: product.in_weight,
|
||||
colors: Array.from(product.colors.values()),
|
||||
}));
|
||||
}
|
||||
|
||||
export function getEmptyScanDetail() {
|
||||
return {
|
||||
quote_order_no: '',
|
||||
dyelot_number: '',
|
||||
volume_number: '',
|
||||
extern_volume_number: '',
|
||||
product_code: '',
|
||||
product_color_code: '',
|
||||
order_roll: 0,
|
||||
item_roll: 0,
|
||||
order_weight: 0,
|
||||
item_weight: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/** 刚扫细码的数量(公斤存储值):优先细码 weight */
|
||||
function pickScannedFineWeight(res) {
|
||||
if (!res) return 0;
|
||||
if (res.weight != null && res.weight !== '') return res.weight;
|
||||
if (res.base_unit_weight != null && res.base_unit_weight !== '') return res.base_unit_weight;
|
||||
if (res.settle_weight != null && res.settle_weight !== '') return res.settle_weight;
|
||||
if (res.actually_weight != null && res.actually_weight !== '') return res.actually_weight;
|
||||
return res.item_weight;
|
||||
}
|
||||
|
||||
/** 从扫码接口返回构建详情区(匹数÷100,数量÷10000) */
|
||||
export function buildScanDetailFromScanResponse(res) {
|
||||
if (!res) return getEmptyScanDetail();
|
||||
return {
|
||||
quote_order_no: res.quote_order_no || '',
|
||||
dyelot_number: res.dyelot_number || '',
|
||||
volume_number: res.volume_number != null && res.volume_number !== '' ? String(res.volume_number) : '',
|
||||
extern_volume_number: res.extern_volume_number != null && res.extern_volume_number !== ''
|
||||
? String(res.extern_volume_number)
|
||||
: '',
|
||||
product_code: res.product_code || '',
|
||||
product_color_code: res.product_color_code || '',
|
||||
// 本单匹数/数量:整单合计(扫码后会再以详情 total_* 覆盖)
|
||||
order_roll: formatRoll(res.order_roll != null ? res.order_roll : res.total_roll),
|
||||
item_roll: formatRoll(res.item_roll != null ? res.item_roll : res.roll),
|
||||
order_weight: formatWeightKg(res.order_weight != null ? res.order_weight : res.total_weight),
|
||||
// 本行数量:刚扫这条细码的重量(weight),不是明细行合计
|
||||
item_weight: formatWeightKg(pickScannedFineWeight(res)),
|
||||
};
|
||||
}
|
||||
|
||||
/** 校验纸筒/包装重量(扫码时提交) */
|
||||
export function validateScanWeights(paperTubeWeight, packageWeight) {
|
||||
if (paperTubeWeight === '' || paperTubeWeight == null) {
|
||||
return '请输入纸筒重量';
|
||||
}
|
||||
if (Number.isNaN(Number(paperTubeWeight))) return '纸筒重量格式不正确';
|
||||
if (packageWeight === '' || packageWeight == null) {
|
||||
return '请输入包装重量';
|
||||
}
|
||||
if (Number.isNaN(Number(packageWeight))) return '包装重量格式不正确';
|
||||
return '';
|
||||
}
|
||||
|
||||
/** 构建扫码请求参数(纸筒/包装重量在扫码时提交,公斤值需 ×10000) */
|
||||
export function buildScanUpdateParams({
|
||||
id,
|
||||
scanCode,
|
||||
arrangeType,
|
||||
warehouseBinId,
|
||||
paperTubeWeight,
|
||||
packageWeight,
|
||||
}) {
|
||||
const code = (scanCode || '').trim();
|
||||
const params = {
|
||||
id: Number(id),
|
||||
arrange_type: arrangeType,
|
||||
paper_tube_weight: toWeightStorage(paperTubeWeight),
|
||||
package_weight: toWeightStorage(packageWeight),
|
||||
};
|
||||
if (warehouseBinId) {
|
||||
params.warehouse_bin_id = Number(warehouseBinId);
|
||||
}
|
||||
// 长度大于25走二维码,否则走条码
|
||||
if (code.length > 25) {
|
||||
params.qr_code = code;
|
||||
} else {
|
||||
params.bar_code = code;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
export function buildStatusParam(id) {
|
||||
return { id: String(id) };
|
||||
}
|
||||
|
||||
export function formatDisplayTime(value, format = 'YYYY-MM-DD') {
|
||||
if (!value) return '';
|
||||
const parsed = dayjs(value);
|
||||
return parsed.isValid() ? parsed.format(format) : String(value).slice(0, 10);
|
||||
}
|
||||
|
||||
export function canScanAtStatus(status) {
|
||||
const s = toAuditStatus(status);
|
||||
return s === AUDIT_STATUS.PENDING || s === AUDIT_STATUS.REJECTED;
|
||||
}
|
||||
|
||||
export function isLockedOrderStatus(status) {
|
||||
const s = toAuditStatus(status);
|
||||
return s === AUDIT_STATUS.APPROVED || s === AUDIT_STATUS.VOIDED;
|
||||
}
|
||||
|
||||
export function fetchAllOrderList(listFn, params = {}, pageSize = LIST_PAGE_SIZE) {
|
||||
const pickBatch = (res) => {
|
||||
if (Array.isArray(res)) return res;
|
||||
if (!res || typeof res !== 'object') return [];
|
||||
return res.list || res.records || res.items || [];
|
||||
};
|
||||
const loadPage = (page, acc) => listFn({ ...params, page, size: pageSize })
|
||||
.then((res) => {
|
||||
const batch = pickBatch(res);
|
||||
if (!batch.length) return acc;
|
||||
// 一次返回超过 pageSize:后端忽略分页,视为全量,避免死循环翻页
|
||||
if (batch.length > pageSize) return acc.concat(batch);
|
||||
// 翻页后与首页首条相同:后端忽略 page,避免死循环
|
||||
if (
|
||||
page > 1
|
||||
&& acc.length
|
||||
&& batch[0]
|
||||
&& acc[0]
|
||||
&& String(batch[0].id) === String(acc[0].id)
|
||||
) {
|
||||
return acc;
|
||||
}
|
||||
const merged = acc.concat(batch);
|
||||
const total = !Array.isArray(res) && res != null ? Number(res.total) : NaN;
|
||||
if (!Number.isNaN(total) && merged.length >= total) return merged;
|
||||
if (batch.length < pageSize) return merged;
|
||||
return loadPage(page + 1, merged);
|
||||
});
|
||||
return loadPage(1, []);
|
||||
}
|
||||
|
||||
export function getFormStatusActionButtons(headerSaved, orderId, status) {
|
||||
if (!headerSaved || !orderId) return [];
|
||||
const s = toAuditStatus(status);
|
||||
if (s === AUDIT_STATUS.APPROVED) {
|
||||
return [{ label: '消审', action: 'unapprove' }];
|
||||
}
|
||||
if (!canScanAtStatus(s)) return [];
|
||||
return [
|
||||
{ label: '审核', action: 'approve' },
|
||||
{ label: '作废', action: 'void' },
|
||||
];
|
||||
}
|
||||
|
||||
export function getViewStatusActionButtons(status) {
|
||||
const s = toAuditStatus(status);
|
||||
if (s === AUDIT_STATUS.PENDING || s === AUDIT_STATUS.REJECTED) {
|
||||
return [
|
||||
{ label: '编辑', action: 'edit' },
|
||||
{ label: '审核', action: 'approve' },
|
||||
{ label: '作废', action: 'void' },
|
||||
];
|
||||
}
|
||||
if (s === AUDIT_STATUS.APPROVED) {
|
||||
return [{ label: '消审', action: 'unapprove' }];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function orderToFormFields(order) {
|
||||
return {
|
||||
order_no: order.order_no,
|
||||
process_unit_id: order.process_unit_id,
|
||||
process_name: order.process_name,
|
||||
warehouse_id: order.warehouse_id,
|
||||
warehouse_name: order.warehouse_name,
|
||||
sale_system_id: order.sale_system_id,
|
||||
sale_system_name: order.sale_system_name,
|
||||
in_order_type: order.in_order_type || IN_ORDER_TYPE_PROCESS,
|
||||
in_order_type_name: order.in_order_type_name
|
||||
|| IN_ORDER_TYPE_MAP[order.in_order_type]
|
||||
|| IN_ORDER_TYPE_MAP[IN_ORDER_TYPE_PROCESS],
|
||||
paper_tube_weight: String(DEFAULT_PAPER_TUBE_WEIGHT),
|
||||
package_weight: String(DEFAULT_PACKAGE_WEIGHT),
|
||||
remark: order.remark,
|
||||
item_data: order.item_data,
|
||||
};
|
||||
}
|
||||
|
||||
/** 从仓库下拉中选出默认成品仓 */
|
||||
export function pickDefaultWarehouse(options, loginWarehouseId, loginWarehouseName) {
|
||||
const list = options || [];
|
||||
const byName = list.find((item) => item.label === DEFAULT_WAREHOUSE_NAME);
|
||||
if (byName) return byName;
|
||||
if (loginWarehouseId) {
|
||||
const byId = list.find((item) => String(item.value) === String(loginWarehouseId));
|
||||
if (byId) return byId;
|
||||
if (loginWarehouseName) {
|
||||
return { value: loginWarehouseId, label: loginWarehouseName };
|
||||
}
|
||||
}
|
||||
return list[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认并执行审核状态变更
|
||||
*/
|
||||
export function confirmOrderStatusAction(vm, { orderId, action, onSuccess }) {
|
||||
const cfg = STATUS_ACTION_CONFIG[action];
|
||||
if (!cfg || !orderId) return;
|
||||
const api = vm.$u.api.fpmProcessInOrder;
|
||||
const requestFn = api[cfg.apiKey];
|
||||
if (typeof requestFn !== 'function') return;
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: cfg.msg,
|
||||
success: (res) => {
|
||||
if (!res.confirm) return;
|
||||
uni.showLoading({ title: '处理中...' });
|
||||
requestFn(buildStatusParam(orderId))
|
||||
.then(() => {
|
||||
if (typeof onSuccess === 'function') return onSuccess();
|
||||
uni.hideLoading();
|
||||
})
|
||||
.catch((e) => {
|
||||
uni.hideLoading();
|
||||
if (vm.showError) vm.showError(e.message || '操作失败');
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
/**
|
||||
* 加工出仓单常量与数据工具
|
||||
*
|
||||
* 真实接口请求统一注册在 src/common/http.api.js 的 vm.$u.api.fpmProcessOutOrder 下。
|
||||
*/
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export const AUDIT_STATUS = {
|
||||
PENDING: 1,
|
||||
APPROVED: 2,
|
||||
REJECTED: 3,
|
||||
VOIDED: 4,
|
||||
};
|
||||
|
||||
export const DRAFT_AUDIT_STATUS = 0;
|
||||
export const DRAFT_AUDIT_STATUS_NAME = '待保存';
|
||||
|
||||
/** 将接口状态统一为数字,避免字符串导致按钮判断失效 */
|
||||
export function toAuditStatus(status) {
|
||||
const n = Number(status);
|
||||
return Number.isFinite(n) ? n : DRAFT_AUDIT_STATUS;
|
||||
}
|
||||
|
||||
|
||||
export const AUDIT_STATUS_MAP = {
|
||||
1: '待审核',
|
||||
2: '已审核',
|
||||
3: '已驳回',
|
||||
4: '已作废',
|
||||
};
|
||||
|
||||
/** 操作类型:1录入 3删除 */
|
||||
export const ARRANGE_TYPE = {
|
||||
IN: 1,
|
||||
DEL: 3,
|
||||
};
|
||||
|
||||
/** 出仓类型:正常加工出仓 WarehouseGoodOutTypeProcess = 5 */
|
||||
export const OUT_ORDER_TYPE_PROCESS = 5;
|
||||
export const OUT_ORDER_TYPE_PROCESS_NAME = '正常加工出仓';
|
||||
/** 出仓类型:回修加工出仓 WarehouseGoodOutTypeProcessRepair = 8 */
|
||||
export const OUT_ORDER_TYPE_REPAIR = 8;
|
||||
export const OUT_ORDER_TYPE_REPAIR_NAME = '回修加工出仓';
|
||||
|
||||
/** 往来单位类型:加工单位 */
|
||||
export const BUSINESS_UNIT_TYPE_PROCESS = 12;
|
||||
|
||||
/** 成品物理仓类型 */
|
||||
export const WAREHOUSE_TYPE_FINISHED = 103;
|
||||
|
||||
/** 默认仓库名称 */
|
||||
export const DEFAULT_WAREHOUSE_NAME = '成品仓';
|
||||
|
||||
export const LIST_PAGE_SIZE = 100;
|
||||
|
||||
export const STATUS_ACTION_CONFIG = {
|
||||
approve: { status: AUDIT_STATUS.APPROVED, msg: '确认审核该单据?', apiKey: 'statusPass' },
|
||||
void: { status: AUDIT_STATUS.VOIDED, msg: '确认作废该单据?', apiKey: 'statusCancel' },
|
||||
unapprove: { status: AUDIT_STATUS.PENDING, msg: '确认消审该单据?', apiKey: 'statusWait' },
|
||||
};
|
||||
|
||||
export function getStatusOptions() {
|
||||
return Object.keys(AUDIT_STATUS_MAP).map((value) => ({
|
||||
value,
|
||||
label: AUDIT_STATUS_MAP[value],
|
||||
}));
|
||||
}
|
||||
|
||||
export { mapToSelectOptions } from '@/common/remoteSelect';
|
||||
|
||||
/** 匹数:后端 ×100 存储 */
|
||||
export function formatRoll(value) {
|
||||
const n = Number(value || 0) / 100;
|
||||
return Number.isInteger(n) ? n : Number(n.toFixed(2));
|
||||
}
|
||||
|
||||
/** 数量(公斤):后端 ×10000 存储 */
|
||||
export function formatWeightKg(value) {
|
||||
const n = Number(value || 0) / 10000;
|
||||
return Number(n.toFixed(2));
|
||||
}
|
||||
|
||||
export function normalizeOrder(order) {
|
||||
if (!order) return null;
|
||||
return {
|
||||
id: order.id,
|
||||
order_no: order.order_no || '',
|
||||
audit_status: toAuditStatus(order.audit_status),
|
||||
audit_status_name: order.audit_status_name || AUDIT_STATUS_MAP[toAuditStatus(order.audit_status)] || '',
|
||||
process_unit_id: order.process_unit_id || '',
|
||||
process_name: order.process_name || '',
|
||||
warehouse_id: order.warehouse_id || '',
|
||||
warehouse_name: order.warehouse_name || '',
|
||||
sale_system_id: order.sale_system_id || '',
|
||||
sale_system_name: order.sale_system_name || '',
|
||||
out_order_type: order.out_order_type || OUT_ORDER_TYPE_PROCESS,
|
||||
out_order_type_name: order.out_order_type_name || OUT_ORDER_TYPE_PROCESS_NAME,
|
||||
remark: order.remark || '',
|
||||
warehouse_out_time: (order.warehouse_out_time || '').slice(0, 10),
|
||||
creator_name: order.creator_name || '',
|
||||
create_time: order.create_time || '',
|
||||
auditor_name: order.auditor_name || '',
|
||||
audit_time: order.audit_time || '',
|
||||
total_roll: formatRoll(order.total_roll),
|
||||
total_weight: formatWeightKg(order.total_weight),
|
||||
total_length: formatRoll(order.total_length),
|
||||
item_data: (order.item_data || []).map(normalizeItem),
|
||||
_raw: order,
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeFineCode(fc) {
|
||||
if (!fc) return null;
|
||||
// 出仓细码数量:优先基本单位公斤,其次结算数量
|
||||
const weightRaw = fc.base_unit_weight != null
|
||||
? fc.base_unit_weight
|
||||
: (fc.settle_weight != null ? fc.settle_weight : fc.weight);
|
||||
const qrCode = fc.qr_code || '';
|
||||
const digitalCode = fc.digital_code || '';
|
||||
const barCode = fc.bar_code || digitalCode || '';
|
||||
return {
|
||||
id: fc.id,
|
||||
volume_number: fc.volume_number != null && fc.volume_number !== '' ? String(fc.volume_number) : '',
|
||||
extern_volume_number: fc.extern_volume_number != null && fc.extern_volume_number !== ''
|
||||
? String(fc.extern_volume_number)
|
||||
: '',
|
||||
roll: formatRoll(fc.roll),
|
||||
weight: formatWeightKg(weightRaw),
|
||||
bar_code: barCode,
|
||||
qr_code: qrCode,
|
||||
digital_code: digitalCode,
|
||||
dye_factory_dyelot_number: fc.dye_factory_dyelot_number || fc.dyelot_number || '',
|
||||
warehouse_bin_name: fc.warehouse_bin_name || '',
|
||||
shelf_no: fc.shelf_no || '',
|
||||
// 删除扫码优先二维码
|
||||
scan_code: qrCode || barCode || digitalCode || '',
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeItem(item) {
|
||||
// 明细数量字段:OpenAPI 为 total_weight / settle_weight(×10000),匹数为 out_roll(×100)
|
||||
const weightRaw = item.total_weight != null
|
||||
? item.total_weight
|
||||
: (item.settle_weight != null ? item.settle_weight : item.out_weight);
|
||||
return {
|
||||
id: item.id,
|
||||
quote_order_no: item.quote_order_no || '',
|
||||
product_code: item.product_code || '',
|
||||
product_name: item.product_name || '',
|
||||
product_color_code: item.product_color_code || '',
|
||||
product_color_name: item.product_color_name || '',
|
||||
dye_factory_dyelot_number: item.dye_factory_dyelot_number || '',
|
||||
dye_factory_color_code: item.dye_factory_color_code || '',
|
||||
out_roll: formatRoll(item.out_roll),
|
||||
out_weight: formatWeightKg(weightRaw),
|
||||
item_fc_data: (item.item_fc_data || []).map(normalizeFineCode).filter(Boolean),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 明细分组:成品编号 → 成品色号 → 细码
|
||||
*/
|
||||
export function buildItemGroups(items) {
|
||||
const productMap = new Map();
|
||||
(items || []).forEach((item) => {
|
||||
const productKey = item.product_code || '-';
|
||||
if (!productMap.has(productKey)) {
|
||||
productMap.set(productKey, {
|
||||
group_key: `p:${productKey}`,
|
||||
product_code: item.product_code || '',
|
||||
product_name: item.product_name || '',
|
||||
out_roll: 0,
|
||||
out_weight: 0,
|
||||
colors: new Map(),
|
||||
});
|
||||
}
|
||||
const productGroup = productMap.get(productKey);
|
||||
if (!productGroup.product_name && item.product_name) {
|
||||
productGroup.product_name = item.product_name;
|
||||
}
|
||||
|
||||
const colorKey = item.product_color_code || item.product_color_name || '-';
|
||||
if (!productGroup.colors.has(colorKey)) {
|
||||
productGroup.colors.set(colorKey, {
|
||||
group_key: `p:${productKey}|c:${colorKey}`,
|
||||
product_color_code: item.product_color_code || '',
|
||||
product_color_name: item.product_color_name || '',
|
||||
quote_order_no: item.quote_order_no || '',
|
||||
dye_factory_dyelot_number: item.dye_factory_dyelot_number || '',
|
||||
out_roll: 0,
|
||||
out_weight: 0,
|
||||
fine_codes: [],
|
||||
});
|
||||
}
|
||||
const colorGroup = productGroup.colors.get(colorKey);
|
||||
productGroup.out_roll = Number((productGroup.out_roll + Number(item.out_roll || 0)).toFixed(2));
|
||||
productGroup.out_weight = Number((productGroup.out_weight + Number(item.out_weight || 0)).toFixed(2));
|
||||
colorGroup.out_roll = Number((colorGroup.out_roll + Number(item.out_roll || 0)).toFixed(2));
|
||||
colorGroup.out_weight = Number((colorGroup.out_weight + Number(item.out_weight || 0)).toFixed(2));
|
||||
if (item.quote_order_no) colorGroup.quote_order_no = item.quote_order_no;
|
||||
if (item.dye_factory_dyelot_number) {
|
||||
colorGroup.dye_factory_dyelot_number = item.dye_factory_dyelot_number;
|
||||
}
|
||||
if (!colorGroup.product_color_name && item.product_color_name) {
|
||||
colorGroup.product_color_name = item.product_color_name;
|
||||
}
|
||||
colorGroup.fine_codes.push(...(item.item_fc_data || []));
|
||||
});
|
||||
|
||||
return Array.from(productMap.values()).map((product) => ({
|
||||
group_key: product.group_key,
|
||||
product_code: product.product_code,
|
||||
product_name: product.product_name,
|
||||
out_roll: product.out_roll,
|
||||
out_weight: product.out_weight,
|
||||
colors: Array.from(product.colors.values()),
|
||||
}));
|
||||
}
|
||||
|
||||
export function getEmptyScanDetail() {
|
||||
return {
|
||||
quote_order_no: '',
|
||||
dyelot_number: '',
|
||||
volume_number: '',
|
||||
extern_volume_number: '',
|
||||
product_code: '',
|
||||
product_color_code: '',
|
||||
order_roll: 0,
|
||||
item_roll: 0,
|
||||
order_weight: 0,
|
||||
item_weight: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/** 从扫码接口返回构建详情区(匹数÷100,数量÷10000) */
|
||||
export function buildScanDetailFromScanResponse(res) {
|
||||
if (!res) return getEmptyScanDetail();
|
||||
return {
|
||||
quote_order_no: res.quote_order_no || '',
|
||||
dyelot_number: res.dyelot_number || '',
|
||||
volume_number: res.volume_number != null && res.volume_number !== '' ? String(res.volume_number) : '',
|
||||
extern_volume_number: res.extern_volume_number != null && res.extern_volume_number !== ''
|
||||
? String(res.extern_volume_number)
|
||||
: '',
|
||||
product_code: res.product_code || '',
|
||||
product_color_code: res.product_color_code || '',
|
||||
// 本单匹数/数量:整单合计(扫码后会再以详情 total_* 覆盖)
|
||||
order_roll: formatRoll(res.order_roll != null ? res.order_roll : res.total_roll),
|
||||
item_roll: formatRoll(res.item_roll != null ? res.item_roll : res.roll),
|
||||
order_weight: formatWeightKg(res.order_weight != null ? res.order_weight : res.total_weight),
|
||||
item_weight: formatWeightKg(res.item_weight != null ? res.item_weight : res.weight),
|
||||
};
|
||||
}
|
||||
|
||||
/** 构建扫码请求参数 */
|
||||
export function buildScanUpdateParams({ id, scanCode, arrangeType, warehouseBinId }) {
|
||||
const code = (scanCode || '').trim();
|
||||
const params = {
|
||||
id: Number(id),
|
||||
arrange_type: arrangeType,
|
||||
};
|
||||
if (warehouseBinId) {
|
||||
params.warehouse_bin_id = Number(warehouseBinId);
|
||||
}
|
||||
// 长度大于25走二维码,否则走条码
|
||||
if (code.length > 25) {
|
||||
params.qr_code = code;
|
||||
} else {
|
||||
params.bar_code = code;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
export function buildStatusParam(id) {
|
||||
return { id: String(id) };
|
||||
}
|
||||
|
||||
export function formatDisplayTime(value, format = 'YYYY-MM-DD') {
|
||||
if (!value) return '';
|
||||
const parsed = dayjs(value);
|
||||
return parsed.isValid() ? parsed.format(format) : String(value).slice(0, 10);
|
||||
}
|
||||
|
||||
export function canScanAtStatus(status) {
|
||||
const s = toAuditStatus(status);
|
||||
return s === AUDIT_STATUS.PENDING || s === AUDIT_STATUS.REJECTED;
|
||||
}
|
||||
|
||||
export function isLockedOrderStatus(status) {
|
||||
const s = toAuditStatus(status);
|
||||
return s === AUDIT_STATUS.APPROVED || s === AUDIT_STATUS.VOIDED;
|
||||
}
|
||||
|
||||
export function fetchAllOrderList(listFn, params = {}, pageSize = LIST_PAGE_SIZE) {
|
||||
const pickBatch = (res) => {
|
||||
if (Array.isArray(res)) return res;
|
||||
if (!res || typeof res !== 'object') return [];
|
||||
return res.list || res.records || res.items || [];
|
||||
};
|
||||
const loadPage = (page, acc) => listFn({ ...params, page, size: pageSize })
|
||||
.then((res) => {
|
||||
const batch = pickBatch(res);
|
||||
if (!batch.length) return acc;
|
||||
// 一次返回超过 pageSize:后端忽略分页,视为全量,避免死循环翻页
|
||||
if (batch.length > pageSize) return acc.concat(batch);
|
||||
// 翻页后与首页首条相同:后端忽略 page,避免死循环
|
||||
if (
|
||||
page > 1
|
||||
&& acc.length
|
||||
&& batch[0]
|
||||
&& acc[0]
|
||||
&& String(batch[0].id) === String(acc[0].id)
|
||||
) {
|
||||
return acc;
|
||||
}
|
||||
const merged = acc.concat(batch);
|
||||
const total = !Array.isArray(res) && res != null ? Number(res.total) : NaN;
|
||||
if (!Number.isNaN(total) && merged.length >= total) return merged;
|
||||
if (batch.length < pageSize) return merged;
|
||||
return loadPage(page + 1, merged);
|
||||
});
|
||||
return loadPage(1, []);
|
||||
}
|
||||
|
||||
export function getFormStatusActionButtons(headerSaved, orderId, status) {
|
||||
if (!headerSaved || !orderId) return [];
|
||||
const s = toAuditStatus(status);
|
||||
if (s === AUDIT_STATUS.APPROVED) {
|
||||
return [{ label: '消审', action: 'unapprove' }];
|
||||
}
|
||||
if (!canScanAtStatus(s)) return [];
|
||||
return [
|
||||
{ label: '审核', action: 'approve' },
|
||||
{ label: '作废', action: 'void' },
|
||||
];
|
||||
}
|
||||
|
||||
export function getViewStatusActionButtons(status) {
|
||||
const s = toAuditStatus(status);
|
||||
if (s === AUDIT_STATUS.PENDING || s === AUDIT_STATUS.REJECTED) {
|
||||
return [
|
||||
{ label: '编辑', action: 'edit' },
|
||||
{ label: '审核', action: 'approve' },
|
||||
{ label: '作废', action: 'void' },
|
||||
];
|
||||
}
|
||||
if (s === AUDIT_STATUS.APPROVED) {
|
||||
return [{ label: '消审', action: 'unapprove' }];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function orderToFormFields(order) {
|
||||
return {
|
||||
order_no: order.order_no,
|
||||
out_order_type: order.out_order_type || OUT_ORDER_TYPE_PROCESS,
|
||||
out_order_type_name: order.out_order_type_name || OUT_ORDER_TYPE_PROCESS_NAME,
|
||||
process_unit_id: order.process_unit_id,
|
||||
process_name: order.process_name,
|
||||
warehouse_id: order.warehouse_id,
|
||||
warehouse_name: order.warehouse_name,
|
||||
remark: order.remark,
|
||||
item_data: order.item_data,
|
||||
};
|
||||
}
|
||||
|
||||
/** 从仓库下拉中选出默认成品仓 */
|
||||
export function pickDefaultWarehouse(options, loginWarehouseId, loginWarehouseName) {
|
||||
const list = options || [];
|
||||
const byName = list.find((item) => item.label === DEFAULT_WAREHOUSE_NAME);
|
||||
if (byName) return byName;
|
||||
if (loginWarehouseId) {
|
||||
const byId = list.find((item) => String(item.value) === String(loginWarehouseId));
|
||||
if (byId) return byId;
|
||||
if (loginWarehouseName) {
|
||||
return { value: loginWarehouseId, label: loginWarehouseName };
|
||||
}
|
||||
}
|
||||
return list[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认并执行审核状态变更
|
||||
*/
|
||||
export function confirmOrderStatusAction(vm, { orderId, action, onSuccess }) {
|
||||
const cfg = STATUS_ACTION_CONFIG[action];
|
||||
if (!cfg || !orderId) return;
|
||||
const api = vm.$u.api.fpmProcessOutOrder;
|
||||
const requestFn = api[cfg.apiKey];
|
||||
if (typeof requestFn !== 'function') return;
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: cfg.msg,
|
||||
success: (res) => {
|
||||
if (!res.confirm) return;
|
||||
uni.showLoading({ title: '处理中...' });
|
||||
requestFn(buildStatusParam(orderId))
|
||||
.then(() => {
|
||||
if (typeof onSuccess === 'function') return onSuccess();
|
||||
uni.hideLoading();
|
||||
})
|
||||
.catch((e) => {
|
||||
uni.hideLoading();
|
||||
if (vm.showError) vm.showError(e.message || '操作失败');
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,402 @@
|
||||
/**
|
||||
* 加工退货进仓单常量与数据工具
|
||||
*
|
||||
* 真实接口请求统一注册在 src/common/http.api.js 的 vm.$u.api.fpmProcessReturnInOrder 下。
|
||||
*/
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export const AUDIT_STATUS = {
|
||||
PENDING: 1,
|
||||
APPROVED: 2,
|
||||
REJECTED: 3,
|
||||
VOIDED: 4,
|
||||
};
|
||||
|
||||
export const DRAFT_AUDIT_STATUS = 0;
|
||||
export const DRAFT_AUDIT_STATUS_NAME = '待保存';
|
||||
|
||||
/** 将接口状态统一为数字,避免字符串导致按钮判断失效 */
|
||||
export function toAuditStatus(status) {
|
||||
const n = Number(status);
|
||||
return Number.isFinite(n) ? n : DRAFT_AUDIT_STATUS;
|
||||
}
|
||||
|
||||
|
||||
export const AUDIT_STATUS_MAP = {
|
||||
1: '待审核',
|
||||
2: '已审核',
|
||||
3: '已驳回',
|
||||
4: '已作废',
|
||||
};
|
||||
|
||||
/** 操作类型:1录入 3删除 */
|
||||
export const ARRANGE_TYPE = {
|
||||
IN: 1,
|
||||
DEL: 3,
|
||||
};
|
||||
|
||||
/** 入仓类型:加工退货进仓单 */
|
||||
export const IN_ORDER_TYPE_PROCESS_RETURN = 9;
|
||||
|
||||
/** 往来单位类型:加工单位 */
|
||||
export const BUSINESS_UNIT_TYPE_PROCESS = 12;
|
||||
|
||||
/** 成品物理仓类型 */
|
||||
export const WAREHOUSE_TYPE_FINISHED = 103;
|
||||
|
||||
/** 默认仓库名称 */
|
||||
export const DEFAULT_WAREHOUSE_NAME = '成品仓';
|
||||
|
||||
export const LIST_PAGE_SIZE = 100;
|
||||
|
||||
export const STATUS_ACTION_CONFIG = {
|
||||
approve: { status: AUDIT_STATUS.APPROVED, msg: '确认审核该单据?', apiKey: 'statusPass' },
|
||||
void: { status: AUDIT_STATUS.VOIDED, msg: '确认作废该单据?', apiKey: 'statusCancel' },
|
||||
unapprove: { status: AUDIT_STATUS.PENDING, msg: '确认消审该单据?', apiKey: 'statusWait' },
|
||||
};
|
||||
|
||||
export function getStatusOptions() {
|
||||
return Object.keys(AUDIT_STATUS_MAP).map((value) => ({
|
||||
value,
|
||||
label: AUDIT_STATUS_MAP[value],
|
||||
}));
|
||||
}
|
||||
|
||||
export { mapToSelectOptions } from '@/common/remoteSelect';
|
||||
|
||||
/** 匹数:后端 ×100 存储 */
|
||||
export function formatRoll(value) {
|
||||
const n = Number(value || 0) / 100;
|
||||
return Number.isInteger(n) ? n : Number(n.toFixed(2));
|
||||
}
|
||||
|
||||
/** 数量(公斤):后端 ×10000 存储 */
|
||||
export function formatWeightKg(value) {
|
||||
const n = Number(value || 0) / 10000;
|
||||
return Number(n.toFixed(2));
|
||||
}
|
||||
|
||||
export function normalizeOrder(order) {
|
||||
if (!order) return null;
|
||||
return {
|
||||
id: order.id,
|
||||
order_no: order.order_no || '',
|
||||
audit_status: toAuditStatus(order.audit_status),
|
||||
audit_status_name: order.audit_status_name || AUDIT_STATUS_MAP[toAuditStatus(order.audit_status)] || '',
|
||||
process_unit_id: order.process_unit_id || order.biz_unit_id || '',
|
||||
process_name: order.process_name || order.biz_unit_name || '',
|
||||
warehouse_id: order.warehouse_id || '',
|
||||
warehouse_name: order.warehouse_name || '',
|
||||
sale_system_id: order.sale_system_id || '',
|
||||
sale_system_name: order.sale_system_name || '',
|
||||
in_order_type: order.in_order_type || IN_ORDER_TYPE_PROCESS_RETURN,
|
||||
in_order_type_name: order.in_order_type_name || '加工退货进仓单',
|
||||
remark: order.remark || '',
|
||||
warehouse_in_time: (order.warehouse_in_time || '').slice(0, 10),
|
||||
creator_name: order.creator_name || '',
|
||||
create_time: order.create_time || '',
|
||||
auditor_name: order.auditor_name || '',
|
||||
audit_time: order.audit_time || '',
|
||||
total_roll: formatRoll(order.total_roll),
|
||||
total_weight: formatWeightKg(order.total_weight),
|
||||
total_length: formatRoll(order.total_length),
|
||||
item_data: (order.item_data || []).map(normalizeItem),
|
||||
_raw: order,
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeFineCode(fc) {
|
||||
if (!fc) return null;
|
||||
const weightRaw = fc.base_unit_weight != null
|
||||
? fc.base_unit_weight
|
||||
: (fc.settle_weight != null ? fc.settle_weight : fc.weight);
|
||||
return {
|
||||
id: fc.id,
|
||||
volume_number: fc.volume_number != null && fc.volume_number !== '' ? String(fc.volume_number) : '',
|
||||
extern_volume_number: fc.extern_volume_number != null && fc.extern_volume_number !== ''
|
||||
? String(fc.extern_volume_number)
|
||||
: '',
|
||||
roll: formatRoll(fc.roll),
|
||||
weight: formatWeightKg(weightRaw),
|
||||
bar_code: fc.bar_code || '',
|
||||
qr_code: fc.qr_code || '',
|
||||
dye_factory_dyelot_number: fc.dye_factory_dyelot_number || fc.dyelot_number || '',
|
||||
warehouse_bin_name: fc.warehouse_bin_name || '',
|
||||
shelf_no: fc.shelf_no || '',
|
||||
scan_code: fc.qr_code || fc.bar_code || '',
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeItem(item) {
|
||||
return {
|
||||
id: item.id,
|
||||
quote_order_no: item.quote_order_no || '',
|
||||
product_code: item.product_code || '',
|
||||
product_name: item.product_name || '',
|
||||
product_color_code: item.product_color_code || '',
|
||||
product_color_name: item.product_color_name || '',
|
||||
dye_factory_dyelot_number: item.dye_factory_dyelot_number || '',
|
||||
dye_factory_color_code: item.dye_factory_color_code || '',
|
||||
in_roll: formatRoll(item.in_roll),
|
||||
in_weight: formatWeightKg(item.in_weight),
|
||||
item_fc_data: (item.item_fc_data || []).map(normalizeFineCode).filter(Boolean),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 明细分组:成品编号 → 成品色号 → 细码
|
||||
*/
|
||||
export function buildItemGroups(items) {
|
||||
const productMap = new Map();
|
||||
(items || []).forEach((item) => {
|
||||
const productKey = item.product_code || '-';
|
||||
if (!productMap.has(productKey)) {
|
||||
productMap.set(productKey, {
|
||||
group_key: `p:${productKey}`,
|
||||
product_code: item.product_code || '',
|
||||
product_name: item.product_name || '',
|
||||
in_roll: 0,
|
||||
in_weight: 0,
|
||||
colors: new Map(),
|
||||
});
|
||||
}
|
||||
const productGroup = productMap.get(productKey);
|
||||
if (!productGroup.product_name && item.product_name) {
|
||||
productGroup.product_name = item.product_name;
|
||||
}
|
||||
|
||||
const colorKey = item.product_color_code || item.product_color_name || '-';
|
||||
if (!productGroup.colors.has(colorKey)) {
|
||||
productGroup.colors.set(colorKey, {
|
||||
group_key: `p:${productKey}|c:${colorKey}`,
|
||||
product_color_code: item.product_color_code || '',
|
||||
product_color_name: item.product_color_name || '',
|
||||
quote_order_no: item.quote_order_no || '',
|
||||
dye_factory_dyelot_number: item.dye_factory_dyelot_number || '',
|
||||
in_roll: 0,
|
||||
in_weight: 0,
|
||||
fine_codes: [],
|
||||
});
|
||||
}
|
||||
const colorGroup = productGroup.colors.get(colorKey);
|
||||
productGroup.in_roll = Number((productGroup.in_roll + Number(item.in_roll || 0)).toFixed(2));
|
||||
productGroup.in_weight = Number((productGroup.in_weight + Number(item.in_weight || 0)).toFixed(2));
|
||||
colorGroup.in_roll = Number((colorGroup.in_roll + Number(item.in_roll || 0)).toFixed(2));
|
||||
colorGroup.in_weight = Number((colorGroup.in_weight + Number(item.in_weight || 0)).toFixed(2));
|
||||
if (item.quote_order_no) colorGroup.quote_order_no = item.quote_order_no;
|
||||
if (item.dye_factory_dyelot_number) {
|
||||
colorGroup.dye_factory_dyelot_number = item.dye_factory_dyelot_number;
|
||||
}
|
||||
if (!colorGroup.product_color_name && item.product_color_name) {
|
||||
colorGroup.product_color_name = item.product_color_name;
|
||||
}
|
||||
colorGroup.fine_codes.push(...(item.item_fc_data || []));
|
||||
});
|
||||
|
||||
return Array.from(productMap.values()).map((product) => ({
|
||||
group_key: product.group_key,
|
||||
product_code: product.product_code,
|
||||
product_name: product.product_name,
|
||||
in_roll: product.in_roll,
|
||||
in_weight: product.in_weight,
|
||||
colors: Array.from(product.colors.values()),
|
||||
}));
|
||||
}
|
||||
|
||||
export function getEmptyScanDetail() {
|
||||
return {
|
||||
quote_order_no: '',
|
||||
dyelot_number: '',
|
||||
volume_number: '',
|
||||
extern_volume_number: '',
|
||||
product_code: '',
|
||||
product_color_code: '',
|
||||
order_roll: 0,
|
||||
item_roll: 0,
|
||||
order_weight: 0,
|
||||
item_weight: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/** 刚扫细码的数量(公斤存储值):优先细码 weight */
|
||||
function pickScannedFineWeight(res) {
|
||||
if (!res) return 0;
|
||||
if (res.weight != null && res.weight !== '') return res.weight;
|
||||
if (res.base_unit_weight != null && res.base_unit_weight !== '') return res.base_unit_weight;
|
||||
if (res.settle_weight != null && res.settle_weight !== '') return res.settle_weight;
|
||||
if (res.actually_weight != null && res.actually_weight !== '') return res.actually_weight;
|
||||
return res.item_weight;
|
||||
}
|
||||
|
||||
/** 从扫码接口返回构建详情区(匹数÷100,数量÷10000) */
|
||||
export function buildScanDetailFromScanResponse(res) {
|
||||
if (!res) return getEmptyScanDetail();
|
||||
return {
|
||||
quote_order_no: res.quote_order_no || '',
|
||||
dyelot_number: res.dyelot_number || '',
|
||||
volume_number: res.volume_number != null && res.volume_number !== '' ? String(res.volume_number) : '',
|
||||
extern_volume_number: res.extern_volume_number != null && res.extern_volume_number !== ''
|
||||
? String(res.extern_volume_number)
|
||||
: '',
|
||||
product_code: res.product_code || '',
|
||||
product_color_code: res.product_color_code || '',
|
||||
// 本单匹数/数量:整单合计(扫码后会再以详情 total_* 覆盖)
|
||||
order_roll: formatRoll(res.order_roll != null ? res.order_roll : res.total_roll),
|
||||
item_roll: formatRoll(res.item_roll != null ? res.item_roll : res.roll),
|
||||
order_weight: formatWeightKg(res.order_weight != null ? res.order_weight : res.total_weight),
|
||||
item_weight: formatWeightKg(pickScannedFineWeight(res)),
|
||||
};
|
||||
}
|
||||
|
||||
/** 构建扫码请求参数 */
|
||||
export function buildScanUpdateParams({ id, scanCode, arrangeType, warehouseBinId }) {
|
||||
const code = (scanCode || '').trim();
|
||||
const params = {
|
||||
id: Number(id),
|
||||
arrange_type: arrangeType,
|
||||
};
|
||||
if (warehouseBinId) {
|
||||
params.warehouse_bin_id = Number(warehouseBinId);
|
||||
}
|
||||
// 长度大于25走二维码,否则走条码
|
||||
if (code.length > 25) {
|
||||
params.qr_code = code;
|
||||
} else {
|
||||
params.bar_code = code;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
export function buildStatusParam(id) {
|
||||
return { id: String(id) };
|
||||
}
|
||||
|
||||
export function formatDisplayTime(value, format = 'YYYY-MM-DD') {
|
||||
if (!value) return '';
|
||||
const parsed = dayjs(value);
|
||||
return parsed.isValid() ? parsed.format(format) : String(value).slice(0, 10);
|
||||
}
|
||||
|
||||
export function canScanAtStatus(status) {
|
||||
const s = toAuditStatus(status);
|
||||
return s === AUDIT_STATUS.PENDING || s === AUDIT_STATUS.REJECTED;
|
||||
}
|
||||
|
||||
export function isLockedOrderStatus(status) {
|
||||
const s = toAuditStatus(status);
|
||||
return s === AUDIT_STATUS.APPROVED || s === AUDIT_STATUS.VOIDED;
|
||||
}
|
||||
|
||||
export function fetchAllOrderList(listFn, params = {}, pageSize = LIST_PAGE_SIZE) {
|
||||
const pickBatch = (res) => {
|
||||
if (Array.isArray(res)) return res;
|
||||
if (!res || typeof res !== 'object') return [];
|
||||
return res.list || res.records || res.items || [];
|
||||
};
|
||||
const loadPage = (page, acc) => listFn({ ...params, page, size: pageSize })
|
||||
.then((res) => {
|
||||
const batch = pickBatch(res);
|
||||
if (!batch.length) return acc;
|
||||
// 一次返回超过 pageSize:后端忽略分页,视为全量,避免死循环翻页
|
||||
if (batch.length > pageSize) return acc.concat(batch);
|
||||
// 翻页后与首页首条相同:后端忽略 page,避免死循环
|
||||
if (
|
||||
page > 1
|
||||
&& acc.length
|
||||
&& batch[0]
|
||||
&& acc[0]
|
||||
&& String(batch[0].id) === String(acc[0].id)
|
||||
) {
|
||||
return acc;
|
||||
}
|
||||
const merged = acc.concat(batch);
|
||||
const total = !Array.isArray(res) && res != null ? Number(res.total) : NaN;
|
||||
if (!Number.isNaN(total) && merged.length >= total) return merged;
|
||||
if (batch.length < pageSize) return merged;
|
||||
return loadPage(page + 1, merged);
|
||||
});
|
||||
return loadPage(1, []);
|
||||
}
|
||||
|
||||
export function getFormStatusActionButtons(headerSaved, orderId, status) {
|
||||
if (!headerSaved || !orderId) return [];
|
||||
const s = toAuditStatus(status);
|
||||
if (s === AUDIT_STATUS.APPROVED) {
|
||||
return [{ label: '消审', action: 'unapprove' }];
|
||||
}
|
||||
if (!canScanAtStatus(s)) return [];
|
||||
return [
|
||||
{ label: '审核', action: 'approve' },
|
||||
{ label: '作废', action: 'void' },
|
||||
];
|
||||
}
|
||||
|
||||
export function getViewStatusActionButtons(status) {
|
||||
const s = toAuditStatus(status);
|
||||
if (s === AUDIT_STATUS.PENDING || s === AUDIT_STATUS.REJECTED) {
|
||||
return [
|
||||
{ label: '编辑', action: 'edit' },
|
||||
{ label: '审核', action: 'approve' },
|
||||
{ label: '作废', action: 'void' },
|
||||
];
|
||||
}
|
||||
if (s === AUDIT_STATUS.APPROVED) {
|
||||
return [{ label: '消审', action: 'unapprove' }];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function orderToFormFields(order) {
|
||||
return {
|
||||
order_no: order.order_no,
|
||||
process_unit_id: order.process_unit_id,
|
||||
process_name: order.process_name,
|
||||
warehouse_id: order.warehouse_id,
|
||||
warehouse_name: order.warehouse_name,
|
||||
remark: order.remark,
|
||||
item_data: order.item_data,
|
||||
};
|
||||
}
|
||||
|
||||
/** 从仓库下拉中选出默认成品仓 */
|
||||
export function pickDefaultWarehouse(options, loginWarehouseId, loginWarehouseName) {
|
||||
const list = options || [];
|
||||
const byName = list.find((item) => item.label === DEFAULT_WAREHOUSE_NAME);
|
||||
if (byName) return byName;
|
||||
if (loginWarehouseId) {
|
||||
const byId = list.find((item) => String(item.value) === String(loginWarehouseId));
|
||||
if (byId) return byId;
|
||||
if (loginWarehouseName) {
|
||||
return { value: loginWarehouseId, label: loginWarehouseName };
|
||||
}
|
||||
}
|
||||
return list[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认并执行审核状态变更
|
||||
*/
|
||||
export function confirmOrderStatusAction(vm, { orderId, action, onSuccess }) {
|
||||
const cfg = STATUS_ACTION_CONFIG[action];
|
||||
if (!cfg || !orderId) return;
|
||||
const api = vm.$u.api.fpmProcessReturnInOrder;
|
||||
const requestFn = api[cfg.apiKey];
|
||||
if (typeof requestFn !== 'function') return;
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: cfg.msg,
|
||||
success: (res) => {
|
||||
if (!res.confirm) return;
|
||||
uni.showLoading({ title: '处理中...' });
|
||||
requestFn(buildStatusParam(orderId))
|
||||
.then(() => {
|
||||
if (typeof onSuccess === 'function') return onSuccess();
|
||||
uni.hideLoading();
|
||||
})
|
||||
.catch((e) => {
|
||||
uni.hideLoading();
|
||||
if (vm.showError) vm.showError(e.message || '操作失败');
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,490 @@
|
||||
/**
|
||||
* 成品采购进仓单常量与数据工具
|
||||
*
|
||||
* 真实接口请求统一注册在 src/common/http.api.js 的 vm.$u.api.fpmPrcInOrder 下。
|
||||
*/
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export const AUDIT_STATUS = {
|
||||
PENDING: 1,
|
||||
APPROVED: 2,
|
||||
REJECTED: 3,
|
||||
VOIDED: 4,
|
||||
};
|
||||
|
||||
export const DRAFT_AUDIT_STATUS = 0;
|
||||
export const DRAFT_AUDIT_STATUS_NAME = '待保存';
|
||||
|
||||
/** 将接口状态统一为数字,避免字符串导致按钮判断失效 */
|
||||
export function toAuditStatus(status) {
|
||||
const n = Number(status);
|
||||
return Number.isFinite(n) ? n : DRAFT_AUDIT_STATUS;
|
||||
}
|
||||
|
||||
|
||||
export const AUDIT_STATUS_MAP = {
|
||||
1: '待审核',
|
||||
2: '已审核',
|
||||
3: '已驳回',
|
||||
4: '已作废',
|
||||
};
|
||||
|
||||
/** 操作类型:1录入 3删除 */
|
||||
export const ARRANGE_TYPE = {
|
||||
IN: 1,
|
||||
DEL: 3,
|
||||
};
|
||||
|
||||
/** 入仓类型:采购进仓单 */
|
||||
export const IN_ORDER_TYPE_PURCHASE = 4;
|
||||
|
||||
export const IN_ORDER_TYPE_MAP = {
|
||||
[IN_ORDER_TYPE_PURCHASE]: '采购进仓单',
|
||||
};
|
||||
|
||||
/** 订单类型:1大货 2剪板 3客订大货 4客订剪板 */
|
||||
export const SALE_MODE = {
|
||||
BULK: 1,
|
||||
SHEAR: 2,
|
||||
CUSTOMER_BULK: 3,
|
||||
CUSTOMER_SHEAR: 4,
|
||||
};
|
||||
|
||||
export const SALE_MODE_MAP = {
|
||||
[SALE_MODE.BULK]: '大货',
|
||||
[SALE_MODE.SHEAR]: '剪板',
|
||||
[SALE_MODE.CUSTOMER_BULK]: '客订大货',
|
||||
[SALE_MODE.CUSTOMER_SHEAR]: '客订剪板',
|
||||
};
|
||||
|
||||
export const SALE_MODE_OPTIONS = [
|
||||
{ value: SALE_MODE.BULK, label: SALE_MODE_MAP[SALE_MODE.BULK] },
|
||||
{ value: SALE_MODE.SHEAR, label: SALE_MODE_MAP[SALE_MODE.SHEAR] },
|
||||
{ value: SALE_MODE.CUSTOMER_BULK, label: SALE_MODE_MAP[SALE_MODE.CUSTOMER_BULK] },
|
||||
{ value: SALE_MODE.CUSTOMER_SHEAR, label: SALE_MODE_MAP[SALE_MODE.CUSTOMER_SHEAR] },
|
||||
];
|
||||
|
||||
/** 往来单位类型:成品供应商 */
|
||||
export const BUSINESS_UNIT_TYPE_PRODUCT_SUPPLIER = 15;
|
||||
|
||||
/** 成品物理仓类型 */
|
||||
export const WAREHOUSE_TYPE_FINISHED = 103;
|
||||
|
||||
/** 默认仓库名称 */
|
||||
export const DEFAULT_WAREHOUSE_NAME = '成品仓';
|
||||
|
||||
/** 默认纸筒重量(公斤,表单展示值) */
|
||||
export const DEFAULT_PAPER_TUBE_WEIGHT = 0.3;
|
||||
|
||||
/** 默认包装重量(公斤,表单展示值) */
|
||||
export const DEFAULT_PACKAGE_WEIGHT = 0.15;
|
||||
|
||||
export const LIST_PAGE_SIZE = 100;
|
||||
|
||||
export const STATUS_ACTION_CONFIG = {
|
||||
approve: { status: AUDIT_STATUS.APPROVED, msg: '确认审核该单据?', apiKey: 'statusPass' },
|
||||
void: { status: AUDIT_STATUS.VOIDED, msg: '确认作废该单据?', apiKey: 'statusCancel' },
|
||||
unapprove: { status: AUDIT_STATUS.PENDING, msg: '确认消审该单据?', apiKey: 'statusWait' },
|
||||
};
|
||||
|
||||
export function getStatusOptions() {
|
||||
return Object.keys(AUDIT_STATUS_MAP).map((value) => ({
|
||||
value,
|
||||
label: AUDIT_STATUS_MAP[value],
|
||||
}));
|
||||
}
|
||||
|
||||
export { mapToSelectOptions } from '@/common/remoteSelect';
|
||||
|
||||
/** 匹数:后端 ×100 存储 */
|
||||
export function formatRoll(value) {
|
||||
const n = Number(value || 0) / 100;
|
||||
return Number.isInteger(n) ? n : Number(n.toFixed(2));
|
||||
}
|
||||
|
||||
/** 数量(公斤):后端 ×10000 存储 */
|
||||
export function formatWeightKg(value) {
|
||||
const n = Number(value || 0) / 10000;
|
||||
return Number(n.toFixed(2));
|
||||
}
|
||||
|
||||
/** 公斤展示值转后端存储(×10000) */
|
||||
export function toWeightStorage(value) {
|
||||
return Math.round(Number(value || 0) * 10000);
|
||||
}
|
||||
|
||||
export function normalizeOrder(order) {
|
||||
if (!order) return null;
|
||||
return {
|
||||
id: order.id,
|
||||
order_no: order.order_no || '',
|
||||
audit_status: toAuditStatus(order.audit_status),
|
||||
audit_status_name: order.audit_status_name || AUDIT_STATUS_MAP[toAuditStatus(order.audit_status)] || '',
|
||||
biz_unit_id: order.biz_unit_id || '',
|
||||
biz_unit_name: order.biz_unit_name || '',
|
||||
warehouse_id: order.warehouse_id || '',
|
||||
warehouse_name: order.warehouse_name || '',
|
||||
sale_system_id: order.sale_system_id || '',
|
||||
sale_system_name: order.sale_system_name || '',
|
||||
sale_mode: order.sale_mode || '',
|
||||
sale_mode_name: order.sale_mode_name || SALE_MODE_MAP[order.sale_mode] || '',
|
||||
in_order_type: order.in_order_type || IN_ORDER_TYPE_PURCHASE,
|
||||
in_order_type_name: order.in_order_type_name
|
||||
|| IN_ORDER_TYPE_MAP[order.in_order_type]
|
||||
|| IN_ORDER_TYPE_MAP[IN_ORDER_TYPE_PURCHASE],
|
||||
paper_tube_weight: formatWeightKg(order.paper_tube_weight),
|
||||
package_weight: formatWeightKg(order.package_weight),
|
||||
remark: order.remark || '',
|
||||
warehouse_in_time: (order.warehouse_in_time || '').slice(0, 10),
|
||||
creator_name: order.creator_name || '',
|
||||
create_time: order.create_time || '',
|
||||
auditor_name: order.auditor_name || '',
|
||||
audit_time: order.audit_time || '',
|
||||
total_roll: formatRoll(order.total_roll),
|
||||
total_weight: formatWeightKg(order.total_weight),
|
||||
total_length: formatRoll(order.total_length),
|
||||
item_data: (order.item_data || []).map(normalizeItem),
|
||||
_raw: order,
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeFineCode(fc) {
|
||||
if (!fc) return null;
|
||||
const weightRaw = fc.base_unit_weight != null
|
||||
? fc.base_unit_weight
|
||||
: (fc.settle_weight != null ? fc.settle_weight : fc.weight);
|
||||
const qrCode = fc.qr_code || fc.long_code || '';
|
||||
const barCode = fc.bar_code || fc.digital_code || fc.short_code || '';
|
||||
return {
|
||||
id: fc.id,
|
||||
volume_number: fc.volume_number != null && fc.volume_number !== '' ? String(fc.volume_number) : '',
|
||||
extern_volume_number: fc.extern_volume_number != null && fc.extern_volume_number !== ''
|
||||
? String(fc.extern_volume_number)
|
||||
: '',
|
||||
roll: formatRoll(fc.roll),
|
||||
weight: formatWeightKg(weightRaw),
|
||||
bar_code: barCode,
|
||||
qr_code: qrCode,
|
||||
digital_code: fc.digital_code || '',
|
||||
long_code: fc.long_code || '',
|
||||
short_code: fc.short_code || '',
|
||||
dye_factory_dyelot_number: fc.dye_factory_dyelot_number || fc.dyelot_number || '',
|
||||
warehouse_bin_name: fc.warehouse_bin_name || '',
|
||||
shelf_no: fc.shelf_no || '',
|
||||
scan_code: qrCode || barCode || '',
|
||||
paper_tube_weight: fc.paper_tube_weight != null && fc.paper_tube_weight !== ''
|
||||
? formatWeightKg(fc.paper_tube_weight)
|
||||
: null,
|
||||
package_weight: fc.package_weight != null && fc.package_weight !== ''
|
||||
? formatWeightKg(fc.package_weight)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeItem(item) {
|
||||
const weightRaw = item.in_weight != null ? item.in_weight : item.total_weight;
|
||||
return {
|
||||
id: item.id,
|
||||
quote_order_no: item.quote_order_no || '',
|
||||
product_code: item.product_code || '',
|
||||
product_name: item.product_name || '',
|
||||
product_color_code: item.product_color_code || '',
|
||||
product_color_name: item.product_color_name || '',
|
||||
dye_factory_dyelot_number: item.dye_factory_dyelot_number || '',
|
||||
dye_factory_color_code: item.dye_factory_color_code || '',
|
||||
in_roll: formatRoll(item.in_roll),
|
||||
in_weight: formatWeightKg(weightRaw),
|
||||
item_fc_data: (item.item_fc_data || []).map(normalizeFineCode).filter(Boolean),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 明细分组:成品编号 → 成品色号 → 细码
|
||||
*/
|
||||
export function buildItemGroups(items) {
|
||||
const productMap = new Map();
|
||||
(items || []).forEach((item) => {
|
||||
const productKey = item.product_code || '-';
|
||||
if (!productMap.has(productKey)) {
|
||||
productMap.set(productKey, {
|
||||
group_key: `p:${productKey}`,
|
||||
product_code: item.product_code || '',
|
||||
product_name: item.product_name || '',
|
||||
in_roll: 0,
|
||||
in_weight: 0,
|
||||
colors: new Map(),
|
||||
});
|
||||
}
|
||||
const productGroup = productMap.get(productKey);
|
||||
if (!productGroup.product_name && item.product_name) {
|
||||
productGroup.product_name = item.product_name;
|
||||
}
|
||||
|
||||
const colorKey = item.product_color_code || item.product_color_name || '-';
|
||||
if (!productGroup.colors.has(colorKey)) {
|
||||
productGroup.colors.set(colorKey, {
|
||||
group_key: `p:${productKey}|c:${colorKey}`,
|
||||
product_color_code: item.product_color_code || '',
|
||||
product_color_name: item.product_color_name || '',
|
||||
quote_order_no: item.quote_order_no || '',
|
||||
dye_factory_dyelot_number: item.dye_factory_dyelot_number || '',
|
||||
in_roll: 0,
|
||||
in_weight: 0,
|
||||
fine_codes: [],
|
||||
});
|
||||
}
|
||||
const colorGroup = productGroup.colors.get(colorKey);
|
||||
productGroup.in_roll = Number((productGroup.in_roll + Number(item.in_roll || 0)).toFixed(2));
|
||||
productGroup.in_weight = Number((productGroup.in_weight + Number(item.in_weight || 0)).toFixed(2));
|
||||
colorGroup.in_roll = Number((colorGroup.in_roll + Number(item.in_roll || 0)).toFixed(2));
|
||||
colorGroup.in_weight = Number((colorGroup.in_weight + Number(item.in_weight || 0)).toFixed(2));
|
||||
if (item.quote_order_no) colorGroup.quote_order_no = item.quote_order_no;
|
||||
if (item.dye_factory_dyelot_number) {
|
||||
colorGroup.dye_factory_dyelot_number = item.dye_factory_dyelot_number;
|
||||
}
|
||||
if (!colorGroup.product_color_name && item.product_color_name) {
|
||||
colorGroup.product_color_name = item.product_color_name;
|
||||
}
|
||||
colorGroup.fine_codes.push(...(item.item_fc_data || []));
|
||||
});
|
||||
|
||||
return Array.from(productMap.values()).map((product) => ({
|
||||
group_key: product.group_key,
|
||||
product_code: product.product_code,
|
||||
product_name: product.product_name,
|
||||
in_roll: product.in_roll,
|
||||
in_weight: product.in_weight,
|
||||
colors: Array.from(product.colors.values()),
|
||||
}));
|
||||
}
|
||||
|
||||
export function getEmptyScanDetail() {
|
||||
return {
|
||||
quote_order_no: '',
|
||||
dyelot_number: '',
|
||||
volume_number: '',
|
||||
extern_volume_number: '',
|
||||
product_code: '',
|
||||
product_color_code: '',
|
||||
order_roll: 0,
|
||||
item_roll: 0,
|
||||
order_weight: 0,
|
||||
item_weight: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/** 刚扫细码的数量(公斤存储值):优先细码 weight */
|
||||
function pickScannedFineWeight(res) {
|
||||
if (!res) return 0;
|
||||
if (res.weight != null && res.weight !== '') return res.weight;
|
||||
if (res.base_unit_weight != null && res.base_unit_weight !== '') return res.base_unit_weight;
|
||||
if (res.settle_weight != null && res.settle_weight !== '') return res.settle_weight;
|
||||
if (res.actually_weight != null && res.actually_weight !== '') return res.actually_weight;
|
||||
return res.item_weight;
|
||||
}
|
||||
|
||||
/** 从扫码接口返回构建详情区(匹数÷100,数量÷10000) */
|
||||
export function buildScanDetailFromScanResponse(res) {
|
||||
if (!res) return getEmptyScanDetail();
|
||||
return {
|
||||
quote_order_no: res.quote_order_no || '',
|
||||
dyelot_number: res.dyelot_number || '',
|
||||
volume_number: res.volume_number != null && res.volume_number !== '' ? String(res.volume_number) : '',
|
||||
extern_volume_number: res.extern_volume_number != null && res.extern_volume_number !== ''
|
||||
? String(res.extern_volume_number)
|
||||
: '',
|
||||
product_code: res.product_code || '',
|
||||
product_color_code: res.product_color_code || '',
|
||||
// 本单匹数/数量:整单合计(扫码后会再以详情 total_* 覆盖)
|
||||
order_roll: formatRoll(res.order_roll != null ? res.order_roll : res.total_roll),
|
||||
item_roll: formatRoll(res.item_roll != null ? res.item_roll : res.roll),
|
||||
order_weight: formatWeightKg(res.order_weight != null ? res.order_weight : res.total_weight),
|
||||
// 本行数量:刚扫这条细码的重量(weight),不是明细行合计
|
||||
item_weight: formatWeightKg(pickScannedFineWeight(res)),
|
||||
};
|
||||
}
|
||||
|
||||
/** 校验纸筒/包装重量(扫码时提交) */
|
||||
export function validateScanWeights(paperTubeWeight, packageWeight) {
|
||||
if (paperTubeWeight === '' || paperTubeWeight == null) {
|
||||
return '请输入纸筒重量';
|
||||
}
|
||||
if (Number.isNaN(Number(paperTubeWeight))) return '纸筒重量格式不正确';
|
||||
if (packageWeight === '' || packageWeight == null) {
|
||||
return '请输入包装重量';
|
||||
}
|
||||
if (Number.isNaN(Number(packageWeight))) return '包装重量格式不正确';
|
||||
return '';
|
||||
}
|
||||
|
||||
/** 构建扫码请求参数(纸筒/包装重量在扫码时提交,公斤值需 ×10000) */
|
||||
export function buildScanUpdateParams({
|
||||
id,
|
||||
scanCode,
|
||||
arrangeType,
|
||||
warehouseBinId,
|
||||
paperTubeWeight,
|
||||
packageWeight,
|
||||
}) {
|
||||
const code = (scanCode || '').trim();
|
||||
const params = {
|
||||
id: Number(id),
|
||||
arrange_type: arrangeType,
|
||||
paper_tube_weight: toWeightStorage(paperTubeWeight),
|
||||
package_weight: toWeightStorage(packageWeight),
|
||||
};
|
||||
if (warehouseBinId) {
|
||||
params.warehouse_bin_id = Number(warehouseBinId);
|
||||
}
|
||||
// 长度大于25走二维码,否则走条码
|
||||
if (code.length > 25) {
|
||||
params.qr_code = code;
|
||||
} else {
|
||||
params.bar_code = code;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
export function buildStatusParam(id) {
|
||||
return { id: String(id) };
|
||||
}
|
||||
|
||||
export function formatDisplayTime(value, format = 'YYYY-MM-DD') {
|
||||
if (!value) return '';
|
||||
const parsed = dayjs(value);
|
||||
return parsed.isValid() ? parsed.format(format) : String(value).slice(0, 10);
|
||||
}
|
||||
|
||||
export function canScanAtStatus(status) {
|
||||
const s = toAuditStatus(status);
|
||||
return s === AUDIT_STATUS.PENDING || s === AUDIT_STATUS.REJECTED;
|
||||
}
|
||||
|
||||
export function isLockedOrderStatus(status) {
|
||||
const s = toAuditStatus(status);
|
||||
return s === AUDIT_STATUS.APPROVED || s === AUDIT_STATUS.VOIDED;
|
||||
}
|
||||
|
||||
export function fetchAllOrderList(listFn, params = {}, pageSize = LIST_PAGE_SIZE) {
|
||||
const pickBatch = (res) => {
|
||||
if (Array.isArray(res)) return res;
|
||||
if (!res || typeof res !== 'object') return [];
|
||||
return res.list || res.records || res.items || [];
|
||||
};
|
||||
const loadPage = (page, acc) => listFn({ ...params, page, size: pageSize })
|
||||
.then((res) => {
|
||||
const batch = pickBatch(res);
|
||||
if (!batch.length) return acc;
|
||||
// 一次返回超过 pageSize:后端忽略分页,视为全量,避免死循环翻页
|
||||
if (batch.length > pageSize) return acc.concat(batch);
|
||||
// 翻页后与首页首条相同:后端忽略 page,避免死循环
|
||||
if (
|
||||
page > 1
|
||||
&& acc.length
|
||||
&& batch[0]
|
||||
&& acc[0]
|
||||
&& String(batch[0].id) === String(acc[0].id)
|
||||
) {
|
||||
return acc;
|
||||
}
|
||||
const merged = acc.concat(batch);
|
||||
const total = !Array.isArray(res) && res != null ? Number(res.total) : NaN;
|
||||
if (!Number.isNaN(total) && merged.length >= total) return merged;
|
||||
if (batch.length < pageSize) return merged;
|
||||
return loadPage(page + 1, merged);
|
||||
});
|
||||
return loadPage(1, []);
|
||||
}
|
||||
|
||||
export function getFormStatusActionButtons(headerSaved, orderId, status) {
|
||||
if (!headerSaved || !orderId) return [];
|
||||
const s = toAuditStatus(status);
|
||||
if (s === AUDIT_STATUS.APPROVED) {
|
||||
return [{ label: '消审', action: 'unapprove' }];
|
||||
}
|
||||
if (!canScanAtStatus(s)) return [];
|
||||
return [
|
||||
{ label: '审核', action: 'approve' },
|
||||
{ label: '作废', action: 'void' },
|
||||
];
|
||||
}
|
||||
|
||||
export function getViewStatusActionButtons(status) {
|
||||
const s = toAuditStatus(status);
|
||||
if (s === AUDIT_STATUS.PENDING || s === AUDIT_STATUS.REJECTED) {
|
||||
return [
|
||||
{ label: '编辑', action: 'edit' },
|
||||
{ label: '审核', action: 'approve' },
|
||||
{ label: '作废', action: 'void' },
|
||||
];
|
||||
}
|
||||
if (s === AUDIT_STATUS.APPROVED) {
|
||||
return [{ label: '消审', action: 'unapprove' }];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function orderToFormFields(order) {
|
||||
return {
|
||||
order_no: order.order_no,
|
||||
biz_unit_id: order.biz_unit_id,
|
||||
biz_unit_name: order.biz_unit_name,
|
||||
warehouse_id: order.warehouse_id,
|
||||
warehouse_name: order.warehouse_name,
|
||||
sale_system_id: order.sale_system_id,
|
||||
sale_system_name: order.sale_system_name,
|
||||
sale_mode: order.sale_mode || '',
|
||||
sale_mode_name: order.sale_mode_name || SALE_MODE_MAP[order.sale_mode] || '',
|
||||
in_order_type: order.in_order_type || IN_ORDER_TYPE_PURCHASE,
|
||||
in_order_type_name: order.in_order_type_name
|
||||
|| IN_ORDER_TYPE_MAP[order.in_order_type]
|
||||
|| IN_ORDER_TYPE_MAP[IN_ORDER_TYPE_PURCHASE],
|
||||
paper_tube_weight: String(DEFAULT_PAPER_TUBE_WEIGHT),
|
||||
package_weight: String(DEFAULT_PACKAGE_WEIGHT),
|
||||
remark: order.remark,
|
||||
item_data: order.item_data,
|
||||
};
|
||||
}
|
||||
|
||||
/** 从仓库下拉中选出默认成品仓 */
|
||||
export function pickDefaultWarehouse(options, loginWarehouseId, loginWarehouseName) {
|
||||
const list = options || [];
|
||||
const byName = list.find((item) => item.label === DEFAULT_WAREHOUSE_NAME);
|
||||
if (byName) return byName;
|
||||
if (loginWarehouseId) {
|
||||
const byId = list.find((item) => String(item.value) === String(loginWarehouseId));
|
||||
if (byId) return byId;
|
||||
if (loginWarehouseName) {
|
||||
return { value: loginWarehouseId, label: loginWarehouseName };
|
||||
}
|
||||
}
|
||||
return list[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认并执行审核状态变更
|
||||
*/
|
||||
export function confirmOrderStatusAction(vm, { orderId, action, onSuccess }) {
|
||||
const cfg = STATUS_ACTION_CONFIG[action];
|
||||
if (!cfg || !orderId) return;
|
||||
const api = vm.$u.api.fpmPrcInOrder;
|
||||
const requestFn = api[cfg.apiKey];
|
||||
if (typeof requestFn !== 'function') return;
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: cfg.msg,
|
||||
success: (res) => {
|
||||
if (!res.confirm) return;
|
||||
uni.showLoading({ title: '处理中...' });
|
||||
requestFn(buildStatusParam(orderId))
|
||||
.then(() => {
|
||||
if (typeof onSuccess === 'function') return onSuccess();
|
||||
uni.hideLoading();
|
||||
})
|
||||
.catch((e) => {
|
||||
uni.hideLoading();
|
||||
if (vm.showError) vm.showError(e.message || '操作失败');
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Android PDA 语音播报(系统 TextToSpeech)
|
||||
* App 端可用;非 App 环境静默跳过。
|
||||
*/
|
||||
let ttsInstance = null;
|
||||
let ttsReady = false;
|
||||
let ttsInitStarted = false;
|
||||
const pendingQueue = [];
|
||||
|
||||
function flushPending() {
|
||||
if (!ttsReady || !ttsInstance) return;
|
||||
while (pendingQueue.length) {
|
||||
const text = pendingQueue.shift();
|
||||
doSpeak(text);
|
||||
}
|
||||
}
|
||||
|
||||
function doSpeak(text) {
|
||||
if (!ttsInstance || !text) return;
|
||||
try {
|
||||
const TextToSpeech = plus.android.importClass('android.speech.tts.TextToSpeech');
|
||||
// QUEUE_FLUSH:打断当前播报,立即播新内容
|
||||
ttsInstance.speak(String(text), TextToSpeech.QUEUE_FLUSH, null);
|
||||
} catch (e) {
|
||||
console.warn('[tts] speak failed', e);
|
||||
}
|
||||
}
|
||||
|
||||
export function initTts() {
|
||||
// #ifdef APP-PLUS
|
||||
if (ttsInitStarted || typeof plus === 'undefined') return;
|
||||
if (plus.os.name !== 'Android') return;
|
||||
ttsInitStarted = true;
|
||||
try {
|
||||
const main = plus.android.runtimeMainActivity();
|
||||
const TextToSpeech = plus.android.importClass('android.speech.tts.TextToSpeech');
|
||||
const Locale = plus.android.importClass('java.util.Locale');
|
||||
ttsInstance = new TextToSpeech(
|
||||
main,
|
||||
plus.android.implements('android.speech.tts.TextToSpeech$OnInitListener', {
|
||||
onInit(status) {
|
||||
if (status === TextToSpeech.SUCCESS) {
|
||||
try {
|
||||
ttsInstance.setLanguage(Locale.CHINA);
|
||||
ttsInstance.setSpeechRate(1.0);
|
||||
} catch (e) {
|
||||
console.warn('[tts] setLanguage failed', e);
|
||||
}
|
||||
ttsReady = true;
|
||||
flushPending();
|
||||
} else {
|
||||
console.warn('[tts] init status not SUCCESS', status);
|
||||
ttsReady = false;
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
console.warn('[tts] init failed', e);
|
||||
ttsInitStarted = false;
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
|
||||
/**
|
||||
* 播报中文文本;TTS 未就绪时先入队,初始化成功后补播。
|
||||
* @param {string} text
|
||||
*/
|
||||
export function speak(text) {
|
||||
const content = (text || '').trim();
|
||||
if (!content) return;
|
||||
// #ifdef APP-PLUS
|
||||
if (typeof plus === 'undefined' || plus.os.name !== 'Android') return;
|
||||
if (!ttsInitStarted) initTts();
|
||||
if (ttsReady && ttsInstance) {
|
||||
doSpeak(content);
|
||||
return;
|
||||
}
|
||||
pendingQueue.push(content);
|
||||
// 避免异常堆积
|
||||
if (pendingQueue.length > 5) pendingQueue.splice(0, pendingQueue.length - 5);
|
||||
// #endif
|
||||
}
|
||||
|
||||
export function stopSpeak() {
|
||||
// #ifdef APP-PLUS
|
||||
if (!ttsInstance) return;
|
||||
try {
|
||||
ttsInstance.stop();
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* 登录会话:token、用户信息、菜单/按钮权限
|
||||
*/
|
||||
import store from '@/store/index.js';
|
||||
|
||||
const TOKEN_KEY = 'RemoteTokenData';
|
||||
const USER_TOKEN_KEY = 'userToken';
|
||||
const INFORMATION_KEY = 'PdaUserInformation';
|
||||
|
||||
function commitVuex(name, value) {
|
||||
store.commit('$uStore', { name, value });
|
||||
}
|
||||
|
||||
function applyGlobalData(patch) {
|
||||
try {
|
||||
const app = getApp();
|
||||
if (!app || !app.globalData) {
|
||||
return;
|
||||
}
|
||||
Object.keys(patch).forEach((key) => {
|
||||
app.globalData[key] = patch[key];
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('applyGlobalData error', e);
|
||||
}
|
||||
}
|
||||
|
||||
export function getLoginData() {
|
||||
return uni.getStorageSync(TOKEN_KEY) || null;
|
||||
}
|
||||
|
||||
export function getUserInformation() {
|
||||
return uni.getStorageSync(INFORMATION_KEY) || null;
|
||||
}
|
||||
|
||||
export function applyLoginData(loginData) {
|
||||
if (!loginData) {
|
||||
return;
|
||||
}
|
||||
uni.setStorageSync(TOKEN_KEY, loginData);
|
||||
uni.setStorageSync(USER_TOKEN_KEY, { Token: loginData.token });
|
||||
if (loginData.token) {
|
||||
commitVuex('vuex_token', loginData.token);
|
||||
}
|
||||
applyGlobalData({
|
||||
Token: loginData.token || '',
|
||||
EmployeeID: loginData.employee_id != null ? loginData.employee_id : (loginData.user_id || 0),
|
||||
LoginID: loginData.user_id || 0,
|
||||
LoginName: loginData.user_name || '',
|
||||
UserName: loginData.user_name || '',
|
||||
PlanDepartmentID: loginData.default_sale_system_id || 0,
|
||||
PlanDepartmentName: loginData.default_sale_system_name || '',
|
||||
StoreNameID: loginData.default_physical_warehouse_id || 0,
|
||||
StoreName: loginData.default_physical_warehouse_name || '',
|
||||
IsSaleUserStatus: 0,
|
||||
UserGroup: '',
|
||||
StoreTypeNo: ''
|
||||
});
|
||||
}
|
||||
|
||||
export function applyUserInformation(info) {
|
||||
if (!info) {
|
||||
return;
|
||||
}
|
||||
uni.setStorageSync(INFORMATION_KEY, info);
|
||||
const profile = {
|
||||
userName: info.user_name || '',
|
||||
user_id: info.user_id,
|
||||
user_name: info.user_name || '',
|
||||
avatar: info.avatar_url || '',
|
||||
avatar_url: info.avatar_url || '',
|
||||
employee_id: info.employee_id,
|
||||
department_id: info.department_id,
|
||||
department_name: info.department_name || '',
|
||||
phone: info.phone || '',
|
||||
default_sale_system_id: info.default_sale_system_id,
|
||||
default_sale_system_name: info.default_sale_system_name || '',
|
||||
default_customer_id: info.default_customer_id,
|
||||
default_customer_name: info.default_customer_name || '',
|
||||
role_id: info.role_id || []
|
||||
};
|
||||
commitVuex('vuex_user', profile);
|
||||
applyGlobalData({
|
||||
EmployeeID: info.employee_id != null ? info.employee_id : (info.user_id || 0),
|
||||
LoginID: info.user_id || 0,
|
||||
LoginName: info.user_name || '',
|
||||
UserName: info.user_name || '',
|
||||
PlanDepartmentID: info.default_sale_system_id || 0,
|
||||
PlanDepartmentName: info.default_sale_system_name || ''
|
||||
});
|
||||
}
|
||||
|
||||
export function restoreUserSession() {
|
||||
const loginData = getLoginData();
|
||||
if (loginData && loginData.token) {
|
||||
applyLoginData(loginData);
|
||||
}
|
||||
const info = getUserInformation();
|
||||
if (info) {
|
||||
applyUserInformation(info);
|
||||
}
|
||||
}
|
||||
|
||||
export function clearUserSession() {
|
||||
uni.removeStorageSync(TOKEN_KEY);
|
||||
uni.removeStorageSync(USER_TOKEN_KEY);
|
||||
uni.removeStorageSync(INFORMATION_KEY);
|
||||
uni.removeStorageSync('token');
|
||||
commitVuex('vuex_token', '');
|
||||
commitVuex('vuex_user', {});
|
||||
applyGlobalData({
|
||||
Token: '',
|
||||
EmployeeID: 0,
|
||||
LoginID: 0,
|
||||
LoginName: '',
|
||||
UserName: '',
|
||||
PlanDepartmentID: 0,
|
||||
PlanDepartmentName: '',
|
||||
StoreNameID: 0,
|
||||
StoreName: '',
|
||||
IsSaleUserStatus: 0,
|
||||
UserGroup: '',
|
||||
StoreTypeNo: ''
|
||||
});
|
||||
}
|
||||
|
||||
export function hasResourceRouter(name) {
|
||||
if (!name) {
|
||||
return true;
|
||||
}
|
||||
const info = getUserInformation();
|
||||
if (!info) {
|
||||
return true;
|
||||
}
|
||||
const names = info.resource_router_names;
|
||||
if (!Array.isArray(names)) {
|
||||
return true;
|
||||
}
|
||||
return names.indexOf(name) !== -1;
|
||||
}
|
||||
|
||||
export function hasButtonCode(code) {
|
||||
if (!code) {
|
||||
return true;
|
||||
}
|
||||
const info = getUserInformation();
|
||||
if (!info) {
|
||||
return true;
|
||||
}
|
||||
const codes = info.button_codes;
|
||||
if (!Array.isArray(codes)) {
|
||||
return true;
|
||||
}
|
||||
return codes.indexOf(code) !== -1;
|
||||
}
|
||||
+57
-2
@@ -2,6 +2,7 @@
|
||||
* 通用公共js库,主要定义api接口,auth apikey 常量值和通用函数
|
||||
*/
|
||||
import store from '@/store/index.js';
|
||||
import { speak } from './tts.js';
|
||||
|
||||
// var apiurl = 'http://192.168.1.66:50002/hcscm/pda/v1/'; //开发环境 添
|
||||
// var apiurl = 'http://192.168.1.55:50002/hcscm/pda/v1/'; //开发环境 添
|
||||
@@ -23,6 +24,9 @@ export function request(options) {
|
||||
url: apiurl + options.url,
|
||||
method: options.method || 'GET',
|
||||
data: options.data,
|
||||
// #ifdef APP-PLUS
|
||||
sslVerify: false,
|
||||
// #endif
|
||||
header: {
|
||||
'Platform': 2,
|
||||
'Authorization': token,
|
||||
@@ -329,7 +333,56 @@ function createAudio(src) {
|
||||
let successAudio = undefined;
|
||||
let errorAudio = undefined;
|
||||
|
||||
function playSuccessAudio() {
|
||||
/** 审核类操作成功时的固定播报 */
|
||||
function statusSuccessVoice(action) {
|
||||
const map = {
|
||||
approve: '审核成功',
|
||||
void: '作废成功',
|
||||
unapprove: '消审成功',
|
||||
};
|
||||
return map[action] || '操作成功';
|
||||
}
|
||||
|
||||
/** 成功/失败提示音约 0.9 秒,播完再朗读,避免提示音抢走语音 */
|
||||
let speakFeedbackTimer = null;
|
||||
|
||||
function voiceContent(text, fallback) {
|
||||
let content = String(text == null ? '' : text)
|
||||
.replace(/[\r\n\t]/g, '')
|
||||
.replace(/[!!。..??;;,,、]/g, '')
|
||||
.trim();
|
||||
if (!content || content.length > 32) content = fallback;
|
||||
return content;
|
||||
}
|
||||
|
||||
function clearSpeakFeedbackTimer() {
|
||||
if (!speakFeedbackTimer) return;
|
||||
clearTimeout(speakFeedbackTimer);
|
||||
speakFeedbackTimer = null;
|
||||
}
|
||||
|
||||
function speakFeedback(text, fallback) {
|
||||
const content = voiceContent(text, fallback);
|
||||
clearSpeakFeedbackTimer();
|
||||
speakFeedbackTimer = setTimeout(() => {
|
||||
speakFeedbackTimer = null;
|
||||
speak(content);
|
||||
}, 1100);
|
||||
}
|
||||
|
||||
/** 只播人声,不播提示音 */
|
||||
function speakNow(text, fallback) {
|
||||
clearSpeakFeedbackTimer();
|
||||
if (successAudio) successAudio.stop();
|
||||
speak(voiceContent(text, fallback));
|
||||
}
|
||||
|
||||
function playSuccessAudio(text, options) {
|
||||
if (options && options.voiceOnly) {
|
||||
speakNow(text, '成功');
|
||||
return;
|
||||
}
|
||||
speakFeedback(text, '成功');
|
||||
if (!successAudio) {
|
||||
//console.log("create success");
|
||||
successAudio = createAudio('/static/audio/appright.wav');
|
||||
@@ -346,7 +399,8 @@ function playSuccessAudio() {
|
||||
successAudio.play();
|
||||
}
|
||||
|
||||
function playErrorAudio() {
|
||||
function playErrorAudio(text) {
|
||||
speakFeedback(text, '失败');
|
||||
if (!errorAudio) {
|
||||
//console.log("create error");
|
||||
errorAudio = createAudio('/static/audio/apperror.wav');
|
||||
@@ -375,4 +429,5 @@ export default {
|
||||
parGetData,
|
||||
playSuccessAudio,
|
||||
playErrorAudio,
|
||||
statusSuccessVoice,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<view class="myCard">
|
||||
<view class="cardTopName">
|
||||
<text>{{ item.order_no }}</text>
|
||||
<text class="statusTag" :class="'status-' + item.audit_status">{{ item.audit_status_name }}</text>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>出仓日期:</view>
|
||||
<view>{{ formatTime(item.out_date) }}</view>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>出仓类型:</view>
|
||||
<view>{{ item.bill_type_name }}</view>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>染厂:</view>
|
||||
<view>{{ item.business_unit_name || '-' }}</view>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>创 建 人:</view>
|
||||
<view>{{ item.create_user_name }} {{ formatTime(item.create_time, 'YYYY-MM-DD') }}</view>
|
||||
</view>
|
||||
<view v-if="item.auditor_name" class="cardRow">
|
||||
<view>审 核 人:</view>
|
||||
<view>{{ item.auditor_name }} {{ formatTime(item.audit_date, 'YYYY-MM-DD') }}</view>
|
||||
</view>
|
||||
<view v-if="!isDetail && !isSelect" class="lookDetail">
|
||||
<text>查看详情</text>
|
||||
<u-icon name="arrow-right" size="36" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import { formatDisplayTime } from '@/common/storeFabricBusinessOut';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
item: { type: Object, default: () => ({}) },
|
||||
index: { type: Number, default: 0 },
|
||||
isSelect: { type: Boolean, default: false },
|
||||
isDetail: { type: Boolean, default: false },
|
||||
},
|
||||
methods: {
|
||||
formatTime: formatDisplayTime,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/common/status-tag.scss';
|
||||
|
||||
.cardTopName {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
overflow: visible;
|
||||
white-space: normal;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<view class="myCard">
|
||||
<view class="cardTopName">
|
||||
<text>{{ item.grey_fabric_code || '-' }}</text>
|
||||
<text class="stockTag">{{ item.stock_roll_display }} 匹</text>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>坯布名称:</view>
|
||||
<view>{{ item.grey_fabric_name || '-' }}</view>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>仓 库:</view>
|
||||
<view>{{ item.warehouse_name || item.receiver_name || '-' }}</view>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>库存数量:</view>
|
||||
<view>{{ item.stock_weight_display }} KG{{ item.unit_label }}</view>
|
||||
</view>
|
||||
<view v-if="!isDetail" class="lookDetail">
|
||||
<text>查看维度汇总</text>
|
||||
<u-icon name="arrow-right" size="36" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
item: { type: Object, default: () => ({}) },
|
||||
index: { type: Number, default: 0 },
|
||||
isDetail: { type: Boolean, default: false },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cardTopName {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.stockTag {
|
||||
font-size: 24rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 6rpx;
|
||||
font-weight: normal;
|
||||
background: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<view class="myCard">
|
||||
<view class="cardTopName">
|
||||
<text>{{ item.gray_fabric_color_name || '未分色' }}</text>
|
||||
<text class="stockTag">{{ item.stock_roll_display }} 匹</text>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>织 厂:</view>
|
||||
<view>{{ item.supplier_name || '-' }}</view>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>机 台 号:</view>
|
||||
<view>{{ item.machine_number || '-' }}</view>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>纱 批:</view>
|
||||
<view>{{ item.yarn_batch || '-' }}</view>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>仓 库:</view>
|
||||
<view>{{ item.warehouse_name || '-' }}</view>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>幅宽/克重:</view>
|
||||
<view>{{ item.grey_fabric_width_and_unit_name || '-' }} / {{ item.grey_fabric_gram_weight_and_unit_name || '-' }}</view>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>库存数量:</view>
|
||||
<view>{{ item.stock_weight_display }} KG{{ item.unit_label }}</view>
|
||||
</view>
|
||||
<view v-if="!isDetail" class="lookDetail">
|
||||
<text>查看细码明细</text>
|
||||
<u-icon name="arrow-right" size="36" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
item: { type: Object, default: () => ({}) },
|
||||
index: { type: Number, default: 0 },
|
||||
isDetail: { type: Boolean, default: false },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cardTopName {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.stockTag {
|
||||
font-size: 24rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 6rpx;
|
||||
font-weight: normal;
|
||||
background: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<view class="myCard">
|
||||
<view class="cardTopName">
|
||||
<text>{{ item.order_no }}</text>
|
||||
<text class="statusTag" :class="'status-' + item.audit_status">{{ item.audit_status_name }}</text>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>加工单位:</view>
|
||||
<view>{{ item.process_name || '-' }}</view>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>仓库名称:</view>
|
||||
<view>{{ item.warehouse_name || '-' }}</view>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>匹数总计:</view>
|
||||
<view>{{ item.total_roll != null ? item.total_roll : '-' }}</view>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>创 建 人:</view>
|
||||
<view>{{ item.create_user_name }} {{ formatTime(item.create_time, 'YYYY-MM-DD') }}</view>
|
||||
</view>
|
||||
<view v-if="item.auditor_name" class="cardRow">
|
||||
<view>审 核 人:</view>
|
||||
<view>{{ item.auditor_name }} {{ formatTime(item.audit_date, 'YYYY-MM-DD') }}</view>
|
||||
</view>
|
||||
<view v-if="!isDetail && !isSelect" class="lookDetail">
|
||||
<text>查看详情</text>
|
||||
<u-icon name="arrow-right" size="36" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { formatDisplayTime } from '@/common/storeGoodsProcessIn';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
item: { type: Object, default: () => ({}) },
|
||||
index: { type: Number, default: 0 },
|
||||
isSelect: { type: Boolean, default: false },
|
||||
isDetail: { type: Boolean, default: false },
|
||||
},
|
||||
methods: {
|
||||
formatTime: formatDisplayTime,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/common/status-tag.scss';
|
||||
|
||||
.cardTopName {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
overflow: visible;
|
||||
white-space: normal;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<view class="myCard">
|
||||
<view class="cardTopName">
|
||||
<text>{{ item.order_no }}</text>
|
||||
<text class="statusTag" :class="'status-' + item.audit_status">{{ item.audit_status_name }}</text>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>出仓类型:</view>
|
||||
<view>{{ item.out_order_type_name || '-' }}</view>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>加工单位:</view>
|
||||
<view>{{ item.process_name || '-' }}</view>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>仓库名称:</view>
|
||||
<view>{{ item.warehouse_name || '-' }}</view>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>匹数总计:</view>
|
||||
<view>{{ item.total_roll != null ? item.total_roll : '-' }}</view>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>创 建 人:</view>
|
||||
<view>{{ item.create_user_name }} {{ formatTime(item.create_time, 'YYYY-MM-DD') }}</view>
|
||||
</view>
|
||||
<view v-if="item.auditor_name" class="cardRow">
|
||||
<view>审 核 人:</view>
|
||||
<view>{{ item.auditor_name }} {{ formatTime(item.audit_date, 'YYYY-MM-DD') }}</view>
|
||||
</view>
|
||||
<view v-if="!isDetail && !isSelect" class="lookDetail">
|
||||
<text>查看详情</text>
|
||||
<u-icon name="arrow-right" size="36" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { formatDisplayTime } from '@/common/storeGoodsProcessOut';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
item: { type: Object, default: () => ({}) },
|
||||
index: { type: Number, default: 0 },
|
||||
isSelect: { type: Boolean, default: false },
|
||||
isDetail: { type: Boolean, default: false },
|
||||
},
|
||||
methods: {
|
||||
formatTime: formatDisplayTime,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/common/status-tag.scss';
|
||||
|
||||
.cardTopName {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
overflow: visible;
|
||||
white-space: normal;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<view class="myCard">
|
||||
<view class="cardTopName">
|
||||
<text>{{ item.order_no }}</text>
|
||||
<text class="statusTag" :class="'status-' + item.audit_status">{{ item.audit_status_name }}</text>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>加工单位:</view>
|
||||
<view>{{ item.process_name || '-' }}</view>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>仓库名称:</view>
|
||||
<view>{{ item.warehouse_name || '-' }}</view>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>匹数总计:</view>
|
||||
<view>{{ item.total_roll != null ? item.total_roll : '-' }}</view>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>创 建 人:</view>
|
||||
<view>{{ item.create_user_name }} {{ formatTime(item.create_time, 'YYYY-MM-DD') }}</view>
|
||||
</view>
|
||||
<view v-if="item.auditor_name" class="cardRow">
|
||||
<view>审 核 人:</view>
|
||||
<view>{{ item.auditor_name }} {{ formatTime(item.audit_date, 'YYYY-MM-DD') }}</view>
|
||||
</view>
|
||||
<view v-if="!isDetail && !isSelect" class="lookDetail">
|
||||
<text>查看详情</text>
|
||||
<u-icon name="arrow-right" size="36" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { formatDisplayTime } from '@/common/storeGoodsProcessReturnIn';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
item: { type: Object, default: () => ({}) },
|
||||
index: { type: Number, default: 0 },
|
||||
isSelect: { type: Boolean, default: false },
|
||||
isDetail: { type: Boolean, default: false },
|
||||
},
|
||||
methods: {
|
||||
formatTime: formatDisplayTime,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/common/status-tag.scss';
|
||||
|
||||
.cardTopName {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
overflow: visible;
|
||||
white-space: normal;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<view class="myCard">
|
||||
<view class="cardTopName">
|
||||
<text>{{ item.order_no }}</text>
|
||||
<text class="statusTag" :class="'status-' + item.audit_status">{{ item.audit_status_name }}</text>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>供 应 商:</view>
|
||||
<view>{{ item.biz_unit_name || '-' }}</view>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>仓库名称:</view>
|
||||
<view>{{ item.warehouse_name || '-' }}</view>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>匹数总计:</view>
|
||||
<view>{{ item.total_roll != null ? item.total_roll : '-' }}</view>
|
||||
</view>
|
||||
<view class="cardRow">
|
||||
<view>创 建 人:</view>
|
||||
<view>{{ item.create_user_name }} {{ formatTime(item.create_time, 'YYYY-MM-DD') }}</view>
|
||||
</view>
|
||||
<view v-if="item.auditor_name" class="cardRow">
|
||||
<view>审 核 人:</view>
|
||||
<view>{{ item.auditor_name }} {{ formatTime(item.audit_date, 'YYYY-MM-DD') }}</view>
|
||||
</view>
|
||||
<view v-if="!isDetail && !isSelect" class="lookDetail">
|
||||
<text>查看详情</text>
|
||||
<u-icon name="arrow-right" size="36" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { formatDisplayTime } from '@/common/storeGoodsPurchaseIn';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
item: { type: Object, default: () => ({}) },
|
||||
index: { type: Number, default: 0 },
|
||||
isSelect: { type: Boolean, default: false },
|
||||
isDetail: { type: Boolean, default: false },
|
||||
},
|
||||
methods: {
|
||||
formatTime: formatDisplayTime,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/common/status-tag.scss';
|
||||
|
||||
.cardTopName {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
overflow: visible;
|
||||
white-space: normal;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,286 @@
|
||||
<template>
|
||||
<view v-show="visible" class="plus-barcode-panel">
|
||||
<view class="plus-barcode-panel__toolbar">
|
||||
<text class="plus-barcode-panel__tip">{{ tipText }}</text>
|
||||
<text class="plus-barcode-panel__close" @click.stop="close">收起</text>
|
||||
</view>
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<view :id="barcodeViewId" class="plus-barcode-panel__holder" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef APP-PLUS -->
|
||||
<view class="plus-barcode-panel__fallback">
|
||||
<text>当前环境不支持半屏扫码,请使用输入框或系统扫码</text>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const DEFAULT_FILTERS = [0, 1, 2, 8, 10]; // QR EAN13 EAN8 CODE39 CODE128
|
||||
let barcodeSeq = 0;
|
||||
|
||||
export default {
|
||||
name: 'PlusBarcodePanel',
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
hintText: { type: String, default: '将条码放入框内,可连续扫' },
|
||||
scanTimeSpace: { type: Number, default: 1200 },
|
||||
filters: { type: Array, default: () => DEFAULT_FILTERS.slice() },
|
||||
},
|
||||
data() {
|
||||
barcodeSeq += 1;
|
||||
return {
|
||||
barcodeViewId: `plusBarcodeView_${barcodeSeq}`,
|
||||
_barcode: null,
|
||||
_restartTimer: null,
|
||||
_active: false,
|
||||
_creating: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
tipText() {
|
||||
return this.hintText || '将条码放入框内,可连续扫';
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
visible(val) {
|
||||
if (val) {
|
||||
this.$nextTick(() => {
|
||||
this.openNative();
|
||||
});
|
||||
} else {
|
||||
this.destroyNative();
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.destroyNative();
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
if (this.visible) {
|
||||
this.$nextTick(() => this.openNative());
|
||||
return;
|
||||
}
|
||||
this.$emit('update:visible', true);
|
||||
},
|
||||
close() {
|
||||
this.destroyNative();
|
||||
if (this.visible) {
|
||||
this.$emit('update:visible', false);
|
||||
}
|
||||
this.$emit('close');
|
||||
},
|
||||
getCurrentWebview() {
|
||||
// #ifdef APP-PLUS
|
||||
try {
|
||||
const pages = getCurrentPages();
|
||||
const page = pages[pages.length - 1];
|
||||
const vm = page && (page.$vm || page);
|
||||
const scope = (vm && vm.$scope) || (page && page.$scope);
|
||||
if (scope && typeof scope.$getAppWebview === 'function') {
|
||||
return scope.$getAppWebview();
|
||||
}
|
||||
if (page && typeof page.$getAppWebview === 'function') {
|
||||
return page.$getAppWebview();
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('PlusBarcodePanel getCurrentWebview failed', e);
|
||||
}
|
||||
// #endif
|
||||
return null;
|
||||
},
|
||||
openNative() {
|
||||
// #ifdef APP-PLUS
|
||||
if (!this.visible || this._creating) return;
|
||||
if (this._barcode) {
|
||||
this.resumeScan();
|
||||
return;
|
||||
}
|
||||
this._creating = true;
|
||||
this._active = true;
|
||||
// 等占位节点挂到页面后再创建,避免 view 层报 Not Found: Page[-1]
|
||||
setTimeout(() => {
|
||||
if (!this.visible || !this._active) {
|
||||
this._creating = false;
|
||||
return;
|
||||
}
|
||||
this.measureHolder(0);
|
||||
}, 200);
|
||||
// #endif
|
||||
},
|
||||
measureHolder(attempt) {
|
||||
// #ifdef APP-PLUS
|
||||
const query = uni.createSelectorQuery().in(this);
|
||||
query.select(`#${this.barcodeViewId}`)
|
||||
.boundingClientRect((rect) => {
|
||||
if (!this.visible || !this._active) {
|
||||
this._creating = false;
|
||||
return;
|
||||
}
|
||||
if (!rect || !rect.width || !rect.height) {
|
||||
if (attempt < 8) {
|
||||
setTimeout(() => this.measureHolder(attempt + 1), 150);
|
||||
return;
|
||||
}
|
||||
const sys = uni.getSystemInfoSync();
|
||||
this._creating = false;
|
||||
this.createBarcode({
|
||||
top: (sys.statusBarHeight || 0) + 44,
|
||||
left: 0,
|
||||
width: sys.windowWidth || 300,
|
||||
height: 220,
|
||||
});
|
||||
return;
|
||||
}
|
||||
this._creating = false;
|
||||
this.createBarcode(rect);
|
||||
})
|
||||
.exec();
|
||||
// #endif
|
||||
},
|
||||
createBarcode(rect) {
|
||||
// #ifdef APP-PLUS
|
||||
const webview = this.getCurrentWebview();
|
||||
if (!webview || typeof plus === 'undefined' || !plus.barcode) {
|
||||
uni.showToast({ title: '当前环境不支持扫码', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
this.destroyNative(false);
|
||||
const width = Math.max(Math.round(rect.width), 1);
|
||||
const height = Math.max(Math.round(rect.height), 1);
|
||||
const top = Math.max(0, Math.round(rect.top || 0));
|
||||
const left = Math.max(0, Math.round(rect.left || 0));
|
||||
try {
|
||||
// 使用独立 id 覆盖在占位区域上,避免和页面节点 id 冲突导致 Not Found
|
||||
const barcode = plus.barcode.create(
|
||||
`plusBarcodeNative_${Date.now()}`,
|
||||
this.filters && this.filters.length ? this.filters : DEFAULT_FILTERS,
|
||||
{
|
||||
top: `${top}px`,
|
||||
left: `${left}px`,
|
||||
width: `${width}px`,
|
||||
height: `${height}px`,
|
||||
position: 'absolute',
|
||||
background: '#000000',
|
||||
frameColor: '#2979ff',
|
||||
scanbarColor: '#2979ff',
|
||||
},
|
||||
);
|
||||
barcode.onmarked = (type, code) => {
|
||||
this.onMarked(type, code);
|
||||
};
|
||||
barcode.onerror = (err) => {
|
||||
console.error('PlusBarcodePanel onerror', err);
|
||||
};
|
||||
webview.append(barcode);
|
||||
this._barcode = barcode;
|
||||
this._active = true;
|
||||
barcode.start({
|
||||
vibrate: true,
|
||||
sound: 'default',
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('PlusBarcodePanel create failed', e);
|
||||
uni.showToast({ title: '打开扫码失败', icon: 'none' });
|
||||
this._barcode = null;
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
onMarked(type, code) {
|
||||
const result = String(code || '').trim();
|
||||
if (!result || !this._active) return;
|
||||
try {
|
||||
if (this._barcode) this._barcode.cancel();
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
this.$emit('success', result, type);
|
||||
clearTimeout(this._restartTimer);
|
||||
const gap = Number(this.scanTimeSpace) > 0 ? Number(this.scanTimeSpace) : 1200;
|
||||
this._restartTimer = setTimeout(() => {
|
||||
this.resumeScan();
|
||||
}, gap);
|
||||
},
|
||||
resumeScan() {
|
||||
// #ifdef APP-PLUS
|
||||
if (!this.visible || !this._active || !this._barcode) return;
|
||||
try {
|
||||
this._barcode.start({
|
||||
vibrate: true,
|
||||
sound: 'default',
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('PlusBarcodePanel resume failed', e);
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
destroyNative(clearActive = true) {
|
||||
clearTimeout(this._restartTimer);
|
||||
this._restartTimer = null;
|
||||
if (clearActive) this._active = false;
|
||||
// #ifdef APP-PLUS
|
||||
const barcode = this._barcode;
|
||||
this._barcode = null;
|
||||
if (!barcode) return;
|
||||
try {
|
||||
barcode.cancel();
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
barcode.close();
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/common/pda-typography.scss';
|
||||
|
||||
.plus-barcode-panel {
|
||||
background: #000;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
.plus-barcode-panel__toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 72rpx;
|
||||
padding: 12rpx 26rpx;
|
||||
background: #1f1f1f;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.plus-barcode-panel__tip {
|
||||
flex: 1;
|
||||
color: #ffffff;
|
||||
font-size: $pda-font-action;
|
||||
padding-right: 16rpx;
|
||||
}
|
||||
.plus-barcode-panel__close {
|
||||
color: #2979ff;
|
||||
font-size: $pda-font-action;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.plus-barcode-panel__holder {
|
||||
width: 100%;
|
||||
height: 420rpx;
|
||||
background: #000;
|
||||
}
|
||||
.plus-barcode-panel__fallback {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 420rpx;
|
||||
padding: 24rpx;
|
||||
box-sizing: border-box;
|
||||
background: #333;
|
||||
color: #fff;
|
||||
font-size: $pda-font-body;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@@ -11,7 +11,7 @@
|
||||
props: {
|
||||
src: {
|
||||
type: String,
|
||||
default: '/static/img/dataNull.png'
|
||||
default: '/src/static/img/dataNull.png'
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<u-popup v-model="visible" mode="center" width="680rpx" border-radius="14" :closeable="true">
|
||||
<view class="popupWrap">
|
||||
<view class="popupTitle">坯布资料</view>
|
||||
<scroll-view scroll-y class="scrollBody">
|
||||
<view class="infoRow" v-for="field in fields" :key="field.key">
|
||||
<text class="label">{{ field.label }}</text>
|
||||
<text class="value">{{ data[field.key] || '-' }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { FABRIC_INFO_FIELDS } from '@/common/storeFabricBusinessOut';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
show: { type: Boolean, default: false },
|
||||
data: { type: Object, default: () => ({}) },
|
||||
},
|
||||
computed: {
|
||||
visible: {
|
||||
get() { return this.show; },
|
||||
set(val) { this.$emit('update:show', val); },
|
||||
},
|
||||
fields() {
|
||||
return FABRIC_INFO_FIELDS;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.popupWrap {
|
||||
padding: 30rpx 30rpx 24rpx;
|
||||
max-height: 80vh;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
.popupTitle {
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
margin-bottom: 20rpx;
|
||||
color: #333;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.scrollBody {
|
||||
flex: 1;
|
||||
height: 62vh;
|
||||
max-height: 62vh;
|
||||
}
|
||||
.infoRow {
|
||||
display: flex;
|
||||
padding: 16rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
.label {
|
||||
width: 180rpx;
|
||||
color: #888;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.value {
|
||||
flex: 1;
|
||||
color: #333;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="summary-title">{{ title }}</view>
|
||||
<slot name="before-list" />
|
||||
<view class="summary-wrap">
|
||||
<scroll-view scroll-x class="summary-scroll">
|
||||
<t-table v-if="list.length" border="1" border-color="#e5e5e5" :style="{ width: tableWidth }">
|
||||
<t-tr class="summary-head" font-size="18" color="#666666" align="left">
|
||||
<t-th v-for="col in columns" :key="col.key" :style="cellStyle(col)">{{ col.label }}</t-th>
|
||||
</t-tr>
|
||||
<t-tr
|
||||
v-for="row in list"
|
||||
:key="row.group_key || row.id"
|
||||
font-size="18"
|
||||
color="#333333"
|
||||
align="left"
|
||||
@click="$emit('fabric-info', row)"
|
||||
>
|
||||
<t-td :style="cellStyle(columns[0])">{{ row.grey_fabric_code || '-' }}</t-td>
|
||||
<t-td :style="cellStyle(columns[1])">{{ row.grey_fabric_name || '-' }}</t-td>
|
||||
<t-td :style="cellStyle(columns[2])">{{ displayNum(row.roll) }}</t-td>
|
||||
<t-td :style="cellStyle(columns[3])">{{ displayNum(row.weight) }}</t-td>
|
||||
<t-td class="summary-action" :style="cellStyle(columns[4])" @click.stop="$emit('fine-code', row)">细码</t-td>
|
||||
</t-tr>
|
||||
<t-tr class="summary-total" font-size="18" color="#333333" align="left">
|
||||
<t-td :style="cellStyle(columns[0])">合计</t-td>
|
||||
<t-td :style="cellStyle(columns[1])"></t-td>
|
||||
<t-td :style="cellStyle(columns[2])">{{ totalRoll }}</t-td>
|
||||
<t-td :style="cellStyle(columns[3])">{{ totalWeight }}</t-td>
|
||||
<t-td :style="cellStyle(columns[4])"></t-td>
|
||||
</t-tr>
|
||||
</t-table>
|
||||
<view v-else class="summary-empty">暂无出仓明细</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import tTable from '@/components/t-table/t-table.vue';
|
||||
import tTh from '@/components/t-table/t-th.vue';
|
||||
import tTr from '@/components/t-table/t-tr.vue';
|
||||
import tTd from '@/components/t-table/t-td.vue';
|
||||
|
||||
const COLUMNS = [
|
||||
{ key: 'grey_fabric_code', label: '坯布编号', width: 220 },
|
||||
{ key: 'grey_fabric_name', label: '坯布名称', width: 260 },
|
||||
{ key: 'roll', label: '匹数', width: 140 },
|
||||
{ key: 'weight', label: '重量', width: 160 },
|
||||
{ key: 'action', label: '操作', width: 140 },
|
||||
];
|
||||
|
||||
export default {
|
||||
components: { tTable, tTh, tTr, tTd },
|
||||
props: {
|
||||
list: { type: Array, default: () => [] },
|
||||
title: { type: String, default: '出仓细码' },
|
||||
},
|
||||
computed: {
|
||||
columns() {
|
||||
return COLUMNS;
|
||||
},
|
||||
tableWidth() {
|
||||
const total = COLUMNS.reduce((sum, col) => sum + col.width, 0);
|
||||
return `${total}rpx`;
|
||||
},
|
||||
totalRoll() {
|
||||
return this.sumField('roll');
|
||||
},
|
||||
totalWeight() {
|
||||
return this.sumField('weight');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
cellStyle(col) {
|
||||
const width = `${col.width}rpx`;
|
||||
return {
|
||||
width,
|
||||
minWidth: width,
|
||||
maxWidth: width,
|
||||
flex: `0 0 ${width}`,
|
||||
};
|
||||
},
|
||||
displayNum(val) {
|
||||
if (val === 0) return '0';
|
||||
return val != null && val !== '' ? val : '-';
|
||||
},
|
||||
sumField(key) {
|
||||
const total = (this.list || []).reduce((sum, row) => sum + Number(row[key] || 0), 0);
|
||||
return Number(total.toFixed(2));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/common/pda-typography.scss';
|
||||
|
||||
.summary-title {
|
||||
font-size: $pda-font-body;
|
||||
font-weight: bold;
|
||||
padding: 24rpx 26rpx 12rpx;
|
||||
border-left: 6rpx solid #007AFF;
|
||||
margin: 20rpx 26rpx 0;
|
||||
}
|
||||
.summary-wrap {
|
||||
margin: 0 26rpx 16rpx;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
.summary-scroll {
|
||||
width: 100%;
|
||||
}
|
||||
::v-deep .t-th,
|
||||
::v-deep .t-td {
|
||||
box-sizing: border-box;
|
||||
min-height: $pda-row-min-height;
|
||||
word-break: break-all;
|
||||
}
|
||||
::v-deep .summary-head {
|
||||
background: #f7f8fa;
|
||||
}
|
||||
::v-deep .summary-total {
|
||||
background: #fff7e6;
|
||||
}
|
||||
::v-deep .summary-action {
|
||||
color: #2979ff;
|
||||
}
|
||||
.summary-empty {
|
||||
padding: 28rpx 16rpx;
|
||||
font-size: $pda-font-placeholder;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<view v-if="buttons.length" class="fabric-out-submitView">
|
||||
<view v-for="btn in buttons" :key="btn.action" class="fabric-out-actionBtnWrap">
|
||||
<u-button type="primary" :class="singleBtnClass" @click="$emit('action', btn.action)">
|
||||
{{ btn.label }}
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
buttons: { type: Array, default: () => [] },
|
||||
single: { type: Boolean, default: false },
|
||||
},
|
||||
computed: {
|
||||
singleBtnClass() {
|
||||
return this.single ? 'fabric-out-actionBtn' : '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/pages/storefabric/storeFabricBusinessOutPage.scss';
|
||||
</style>
|
||||
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<view>
|
||||
<view v-if="canScan" class="flex-white-plr26 ptb20 bdb_f5 fabric-out-scanRow">
|
||||
<text class="mr26">条码资料</text>
|
||||
<input
|
||||
type="text"
|
||||
:value="value"
|
||||
class="fabric-out-scanInput"
|
||||
placeholder="扫描条码"
|
||||
@input="$emit('input', $event.detail.value)"
|
||||
@confirm="$emit('scan')"
|
||||
/>
|
||||
<view
|
||||
v-if="showPhoneScan && scanning"
|
||||
class="fabric-out-scanClose"
|
||||
@click.stop="$emit('phone-scan')"
|
||||
>关闭扫码</view>
|
||||
<u-icon
|
||||
v-else-if="showPhoneScan"
|
||||
class="fabric-out-phoneScanIcon"
|
||||
name="scan"
|
||||
size="44"
|
||||
color="#2979ff"
|
||||
@click="$emit('phone-scan')"
|
||||
/>
|
||||
<checkbox-group @change="$emit('delete-mode-change', $event)">
|
||||
<label class="fabric-out-delCheck">
|
||||
<checkbox :checked="deleteMode" />删除
|
||||
</label>
|
||||
</checkbox-group>
|
||||
</view>
|
||||
<view v-else class="fabric-out-scanTip">{{ disabledTip }}</view>
|
||||
<view v-if="message" class="fabric-out-msgTip">{{ message }}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
canScan: { type: Boolean, default: false },
|
||||
value: { type: String, default: '' },
|
||||
deleteMode: { type: Boolean, default: false },
|
||||
message: { type: String, default: '' },
|
||||
disabledTip: { type: String, default: '当前状态不可扫码' },
|
||||
showPhoneScan: { type: Boolean, default: false },
|
||||
scanning: { type: Boolean, default: false },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/pages/storefabric/storeFabricBusinessOutPage.scss';
|
||||
@import '@/common/pda-typography.scss';
|
||||
|
||||
.fabric-out-phoneScanIcon {
|
||||
margin-left: 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.fabric-out-scanClose {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 8rpx;
|
||||
padding: 0 16rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 8rpx;
|
||||
border: 1rpx solid #2979ff;
|
||||
background: #eaf2ff;
|
||||
color: #2979ff;
|
||||
font-size: $pda-font-action;
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,161 @@
|
||||
<template>
|
||||
<view class="fabric-out-scanDetail">
|
||||
<view class="fabric-out-scanDetailRow fabric-out-scanDetailRow--2col">
|
||||
<text class="fabric-out-scanDetailCell">已扫条数:{{ formatCount(detail.scanned_roll) }}条</text>
|
||||
<text class="fabric-out-scanDetailCell">机台库存:{{ formatRoll(detail.machine_stock) }}条</text>
|
||||
</view>
|
||||
<view class="fabric-out-scanDetailRow fabric-out-scanDetailRow--2col">
|
||||
<text class="fabric-out-scanDetailCell">合计条数:{{ formatRoll(detail.total_roll) }}条</text>
|
||||
<text class="fabric-out-scanDetailCell">合计重量:{{ formatWeight(detail.total_weight) }}KG</text>
|
||||
</view>
|
||||
<view class="fabric-out-scanDetailRow">
|
||||
<text class="fabric-out-scanDetailLabel">客户:</text>
|
||||
<text class="fabric-out-scanDetailValue">{{ detail.customer_name || '' }}</text>
|
||||
</view>
|
||||
<view class="fabric-out-scanDetailRow fabric-out-scanDetailRow--2col">
|
||||
<view class="fabric-out-scanDetailCheck" @click="toggleGroup('produce_order_no')">
|
||||
<checkbox :checked="isGroupChecked('produce_order_no')" />
|
||||
<text class="fabric-out-scanDetailLabel">生产通知单:</text>
|
||||
<text class="fabric-out-scanDetailValue">{{ detail.produce_order_no || '' }}</text>
|
||||
</view>
|
||||
<view class="fabric-out-scanDetailCheck" @click="toggleGroup('grey_fabric_code')">
|
||||
<checkbox :checked="isGroupChecked('grey_fabric_code')" />
|
||||
<text class="fabric-out-scanDetailLabel">布编:</text>
|
||||
<text class="fabric-out-scanDetailValue">{{ detail.grey_fabric_code || '' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="fabric-out-scanDetailRow">
|
||||
<text class="fabric-out-scanDetailLabel">布名:</text>
|
||||
<text class="fabric-out-scanDetailValue">{{ detail.grey_fabric_name || '' }}</text>
|
||||
</view>
|
||||
<view class="fabric-out-scanDetailRow">
|
||||
<view class="fabric-out-scanDetailCheck" @click="toggleGroup('yarn_batch')">
|
||||
<checkbox :checked="isGroupChecked('yarn_batch')" />
|
||||
<text class="fabric-out-scanDetailLabel">纱批:</text>
|
||||
<text class="fabric-out-scanDetailValue">{{ detail.yarn_batch || '' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="fabric-out-scanDetailRow fabric-out-scanDetailRow--2col">
|
||||
<view class="fabric-out-scanDetailCheck" @click="toggleGroup('machine_number')">
|
||||
<checkbox :checked="isGroupChecked('machine_number')" />
|
||||
<text class="fabric-out-scanDetailLabel">机号:</text>
|
||||
<text class="fabric-out-scanDetailValue">{{ detail.machine_number || '' }}</text>
|
||||
</view>
|
||||
<view class="fabric-out-scanDetailCell">
|
||||
<text class="fabric-out-scanDetailLabel">布号:</text>
|
||||
<text class="fabric-out-scanDetailValue">{{ detail.volume_number || '' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="fabric-out-scanDetailRow">
|
||||
<text class="fabric-out-scanDetailLabel">外部卷号:</text>
|
||||
<text class="fabric-out-scanDetailValue">{{ detail.extern_volume_number || '-' }}</text>
|
||||
</view>
|
||||
<view class="fabric-out-scanDetailRow fabric-out-scanDetailRow--2col">
|
||||
<view class="fabric-out-scanDetailCheck" @click="toggleGroup('grey_fabric_level_name')">
|
||||
<checkbox :checked="isGroupChecked('grey_fabric_level_name')" />
|
||||
<text class="fabric-out-scanDetailLabel">等级:</text>
|
||||
<text class="fabric-out-scanDetailValue">{{ detail.grey_fabric_level_name || '' }}</text>
|
||||
</view>
|
||||
<view class="fabric-out-scanDetailCell">
|
||||
<text class="fabric-out-scanDetailLabel">重量:</text>
|
||||
<text class="fabric-out-scanDetailValue">{{ formatWeight(detail.weight) }}Kg</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view scroll-x class="fabric-out-scanDetailTableScroll">
|
||||
<view class="fabric-out-scanDetailTable" :style="{ width: tableWidth }">
|
||||
<view class="fabric-out-scanDetailTableRow fabric-out-scanDetailTableHead">
|
||||
<view
|
||||
v-for="col in columns"
|
||||
:key="col.key"
|
||||
class="fabric-out-scanDetailTableCell"
|
||||
:style="cellStyle(col)"
|
||||
>{{ col.label }}</view>
|
||||
</view>
|
||||
<view v-if="!tableRows.length" class="fabric-out-scanDetailEmpty">暂无数据~~</view>
|
||||
<view
|
||||
v-for="(row, rowIndex) in tableRows"
|
||||
:key="rowIndex"
|
||||
class="fabric-out-scanDetailTableRow"
|
||||
>
|
||||
<view
|
||||
v-for="col in columns"
|
||||
:key="col.key"
|
||||
class="fabric-out-scanDetailTableCell"
|
||||
:style="cellStyle(col)"
|
||||
>{{ formatCell(row, col.key) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
SCAN_DETAIL_TABLE_COLUMNS,
|
||||
SCAN_GROUP_OPTIONS,
|
||||
getScanDetailTableWidth,
|
||||
buildScanDetailTableRows,
|
||||
} from '@/common/storeFabricBusinessOut';
|
||||
|
||||
const LOCKABLE_GROUP_KEYS = SCAN_GROUP_OPTIONS.map((item) => item.key);
|
||||
|
||||
export default {
|
||||
props: {
|
||||
detail: { type: Object, default: () => ({}) },
|
||||
itemData: { type: Array, default: () => [] },
|
||||
groupKeys: { type: Array, default: () => [] },
|
||||
},
|
||||
computed: {
|
||||
columns() {
|
||||
return SCAN_DETAIL_TABLE_COLUMNS;
|
||||
},
|
||||
tableWidth() {
|
||||
return getScanDetailTableWidth(this.columns);
|
||||
},
|
||||
tableRows() {
|
||||
return buildScanDetailTableRows(this.itemData, this.groupKeys);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
cellStyle(col) {
|
||||
return {
|
||||
width: `${col.width}rpx`,
|
||||
minWidth: `${col.width}rpx`,
|
||||
maxWidth: `${col.width}rpx`,
|
||||
};
|
||||
},
|
||||
isGroupChecked(key) {
|
||||
return (this.groupKeys || []).includes(key);
|
||||
},
|
||||
toggleGroup(key) {
|
||||
if (!LOCKABLE_GROUP_KEYS.includes(key)) return;
|
||||
const next = [...(this.groupKeys || [])];
|
||||
const index = next.indexOf(key);
|
||||
if (index >= 0) next.splice(index, 1);
|
||||
else next.push(key);
|
||||
this.$emit('group-change', next);
|
||||
},
|
||||
formatRoll(value) {
|
||||
const n = Number(value || 0);
|
||||
return Number.isInteger(n) ? n : Number(n.toFixed(2));
|
||||
},
|
||||
formatCount(value) {
|
||||
return Number(value || 0);
|
||||
},
|
||||
formatWeight(value) {
|
||||
const n = Number(value || 0);
|
||||
return Number(n.toFixed(2));
|
||||
},
|
||||
formatCell(row, key) {
|
||||
if (key === 'roll') return this.formatRoll(row.roll);
|
||||
if (key === 'weight') return this.formatWeight(row.weight);
|
||||
return row[key] || '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/pages/storefabric/storeFabricBusinessOutPage.scss';
|
||||
</style>
|
||||
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<u-popup v-model="visible" mode="center" width="94%" border-radius="14" :closeable="true">
|
||||
<view class="popupWrap">
|
||||
<view class="popupTitle">细码</view>
|
||||
<view v-if="!list.length" class="emptyTip">暂无细码数据</view>
|
||||
<scroll-view v-else scroll-x class="tableScroll">
|
||||
<view class="fineTable" :style="{ width: tableWidth }">
|
||||
<view class="tableRow tableHead">
|
||||
<view
|
||||
v-for="col in columns"
|
||||
:key="col.key"
|
||||
class="tableCell headCell"
|
||||
:style="cellStyle(col)"
|
||||
>{{ col.label }}</view>
|
||||
</view>
|
||||
<view v-for="(row, rowIndex) in tableRows" :key="rowIndex" class="tableRow">
|
||||
<view
|
||||
v-for="col in columns"
|
||||
:key="col.key"
|
||||
class="tableCell"
|
||||
:class="{ actionCell: col.key === 'action' && !readonly }"
|
||||
:style="cellStyle(col)"
|
||||
@click="onCellTap(col.key, rowIndex)"
|
||||
>{{ row[col.key] }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view v-if="!readonly && list.length" class="tableTip">点击「操作」列删除,将调用扫码删除接口</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getFineCodeTableColumns,
|
||||
getFineCodeTableWidth,
|
||||
mapFineCodeTableRows,
|
||||
} from '@/common/storeFabricBusinessOut';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
show: { type: Boolean, default: false },
|
||||
list: { type: Array, default: () => [] },
|
||||
readonly: { type: Boolean, default: false },
|
||||
},
|
||||
computed: {
|
||||
visible: {
|
||||
get() { return this.show; },
|
||||
set(val) { this.$emit('update:show', val); },
|
||||
},
|
||||
columns() {
|
||||
return getFineCodeTableColumns(this.readonly);
|
||||
},
|
||||
tableRows() {
|
||||
return mapFineCodeTableRows(this.list, this.readonly);
|
||||
},
|
||||
tableWidth() {
|
||||
return getFineCodeTableWidth(this.columns);
|
||||
},
|
||||
tableScrollHeight() {
|
||||
const rowHeight = 68;
|
||||
const headerHeight = 68;
|
||||
const total = headerHeight + this.list.length * rowHeight;
|
||||
return `${Math.min(Math.max(total, 136), 520)}rpx`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
cellStyle(col) {
|
||||
return {
|
||||
width: `${col.width}rpx`,
|
||||
minWidth: `${col.width}rpx`,
|
||||
maxWidth: `${col.width}rpx`,
|
||||
};
|
||||
},
|
||||
onCellTap(key, rowIndex) {
|
||||
if (this.readonly || key !== 'action') return;
|
||||
const item = this.list[rowIndex];
|
||||
if (item) this.$emit('delete', item);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.popupWrap {
|
||||
padding: 24rpx 16rpx 30rpx;
|
||||
max-height: 80vh;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
.popupTitle {
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
margin-bottom: 20rpx;
|
||||
color: #333;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.emptyTip {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
padding: 40rpx 0;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
.tableScroll {
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.fineTable {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
border: 1rpx solid #e1e1e1;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.tableRow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
border-bottom: 1rpx solid #e1e1e1;
|
||||
}
|
||||
.tableRow:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.tableHead {
|
||||
background: #f1f1f1;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
.tableCell {
|
||||
flex-shrink: 0;
|
||||
padding: 16rpx 10rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
border-right: 1rpx solid #e1e1e1;
|
||||
box-sizing: border-box;
|
||||
word-break: break-all;
|
||||
white-space: normal;
|
||||
}
|
||||
.tableCell:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
.headCell {
|
||||
font-weight: bold;
|
||||
color: #3e3e3e;
|
||||
}
|
||||
.actionCell {
|
||||
color: #f5222d;
|
||||
}
|
||||
.tableTip {
|
||||
margin-top: 16rpx;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<view class="order-header-fold">
|
||||
<view class="order-header-fold__bar" @click="toggle">
|
||||
<text class="order-header-fold__title">单据信息</text>
|
||||
<text v-if="summary" class="order-header-fold__summary">{{ summary }}</text>
|
||||
<text class="order-header-fold__action">{{ value ? '展开' : '收起' }}</text>
|
||||
</view>
|
||||
<view v-show="!value">
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: { type: Boolean, default: false },
|
||||
summary: { type: String, default: '' },
|
||||
},
|
||||
methods: {
|
||||
toggle() {
|
||||
this.$emit('input', !this.value);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/common/pda-typography.scss';
|
||||
|
||||
.order-header-fold__bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: $pda-row-min-height;
|
||||
margin: 16rpx 26rpx 0;
|
||||
padding: 0 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
.order-header-fold__title {
|
||||
font-size: $pda-font-body;
|
||||
font-weight: bold;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
.order-header-fold__summary {
|
||||
flex: 1;
|
||||
margin-left: 16rpx;
|
||||
font-size: $pda-font-action;
|
||||
color: #666;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.order-header-fold__action {
|
||||
margin-left: auto;
|
||||
padding-left: 16rpx;
|
||||
font-size: $pda-font-action;
|
||||
color: #2979ff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<view class="statusRow">
|
||||
<text class="statusLabel">订单状态</text>
|
||||
<text class="statusTag" :class="'status-' + status">{{ displayName }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { AUDIT_STATUS_MAP, DRAFT_AUDIT_STATUS, DRAFT_AUDIT_STATUS_NAME } from '@/common/storeFabricBusinessOut';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
status: { type: [Number, String], default: 0 },
|
||||
name: { type: String, default: '' },
|
||||
},
|
||||
computed: {
|
||||
displayName() {
|
||||
if (Number(this.status) === DRAFT_AUDIT_STATUS) return DRAFT_AUDIT_STATUS_NAME;
|
||||
return this.name || AUDIT_STATUS_MAP[Number(this.status)] || '-';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/common/status-tag.scss';
|
||||
|
||||
.statusRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 26rpx;
|
||||
background: #fff;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
}
|
||||
.statusLabel {
|
||||
font-size: $pda-font-body;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<view class="warehouse-table-wrap">
|
||||
<scroll-view scroll-x class="tableScrollX">
|
||||
<wyb-table
|
||||
ref="table"
|
||||
:headers="headers"
|
||||
:contents="contents"
|
||||
:width="tableWidth"
|
||||
:height="height"
|
||||
:first-line-fixed="firstLineFixed"
|
||||
@onCellClick="onCellClick"
|
||||
/>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import wybTable from '@/components/wyb-table/wyb-table.vue';
|
||||
|
||||
export default {
|
||||
components: { wybTable },
|
||||
props: {
|
||||
headers: { type: Array, default: () => [] },
|
||||
contents: { type: Array, default: () => [] },
|
||||
height: { type: String, default: 'auto' },
|
||||
firstLineFixed: { type: Boolean, default: true },
|
||||
},
|
||||
computed: {
|
||||
tableWidth() {
|
||||
const total = (this.headers || []).reduce((sum, col) => sum + (col.width || 160), 0);
|
||||
return `${total}rpx`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onCellClick(event) {
|
||||
if (event && event.lineData) {
|
||||
this.$emit('row-click', event.lineData, event);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.warehouse-table-wrap {
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
}
|
||||
.tableScrollX {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,294 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="summary-title">{{ title }}</view>
|
||||
<slot name="before-list" />
|
||||
<view class="summary-wrap">
|
||||
<scroll-view scroll-x class="summary-scroll">
|
||||
<t-table v-if="rows.length" border="1" border-color="#e5e5e5" :style="{ width: tableWidth }">
|
||||
<t-tr class="summary-head" font-size="18" color="#666666" align="left">
|
||||
<t-th v-for="col in columns" :key="col.key" :style="cellStyle(col)">{{ col.label }}</t-th>
|
||||
</t-tr>
|
||||
<t-tr
|
||||
v-for="row in rows"
|
||||
:key="row.group_key"
|
||||
font-size="18"
|
||||
color="#333333"
|
||||
align="left"
|
||||
:class="{ 'summary-row--open': expandedKey === row.group_key }"
|
||||
@click="toggleRow(row.group_key)"
|
||||
>
|
||||
<t-td v-for="col in columns" :key="col.key" :style="cellStyle(col)">{{ formatCell(row, col.key) }}</t-td>
|
||||
</t-tr>
|
||||
<t-tr class="summary-total" font-size="18" color="#333333" align="left">
|
||||
<t-td v-for="col in columns" :key="col.key" :style="cellStyle(col)">{{ totalCell(col.key) }}</t-td>
|
||||
</t-tr>
|
||||
</t-table>
|
||||
<view v-else class="summary-empty">{{ emptyText }}</view>
|
||||
</scroll-view>
|
||||
|
||||
<view v-if="expandedRow" class="summary-fine">
|
||||
<view class="summary-fineTitle">
|
||||
细码({{ expandedRow.fine_codes.length }})
|
||||
<text class="summary-fineHint">{{ expandedKey ? '点击行收起' : '' }}</text>
|
||||
</view>
|
||||
<view v-if="!expandedRow.fine_codes.length" class="summary-empty">暂无细码</view>
|
||||
<scroll-view v-else scroll-x class="summary-scroll">
|
||||
<t-table border="1" border-color="#e5e5e5" :style="{ width: fineTableWidth }">
|
||||
<t-tr class="summary-head" font-size="18" color="#666666" align="left">
|
||||
<t-th v-for="col in fineCols" :key="col.key" :style="cellStyle(col)">{{ col.label }}</t-th>
|
||||
</t-tr>
|
||||
<t-tr
|
||||
v-for="(fc, fcIndex) in expandedRow.fine_codes"
|
||||
:key="fc.id || fcIndex"
|
||||
font-size="18"
|
||||
color="#333333"
|
||||
align="left"
|
||||
>
|
||||
<t-td
|
||||
v-for="col in fineCols"
|
||||
:key="col.key"
|
||||
:class="{ 'summary-action': col.key === 'action' }"
|
||||
:style="cellStyle(col)"
|
||||
@click="onFineCellTap(col.key, fc)"
|
||||
>{{ formatFineCell(fc, col.key) }}</t-td>
|
||||
</t-tr>
|
||||
</t-table>
|
||||
</scroll-view>
|
||||
<view v-if="deletable && expandedRow.fine_codes.length" class="summary-tip">
|
||||
点击「删除」移除对应细码
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import tTable from '@/components/t-table/t-table.vue';
|
||||
import tTh from '@/components/t-table/t-th.vue';
|
||||
import tTr from '@/components/t-table/t-tr.vue';
|
||||
import tTd from '@/components/t-table/t-td.vue';
|
||||
|
||||
const SUMMARY_COLUMNS = [
|
||||
{ label: '成品编号', key: 'product_code', width: 260 },
|
||||
{ label: '色号', key: 'product_color_code', width: 220 },
|
||||
{ label: '匹数', key: 'roll', width: 140 },
|
||||
{ label: '数量', key: 'weight', width: 160 },
|
||||
{ label: '染整单', key: 'quote_order_no', width: 260 },
|
||||
{ label: '缸号', key: 'dye_factory_dyelot_number', width: 220 },
|
||||
];
|
||||
|
||||
const FINE_COLUMNS = [
|
||||
{ label: '卷号', key: 'volume_number', width: 140 },
|
||||
{ label: '外部卷号', key: 'extern_volume_number', width: 180 },
|
||||
{ label: '匹数', key: 'roll', width: 120 },
|
||||
{ label: '数量', key: 'weight', width: 150 },
|
||||
{ label: '仓位', key: 'warehouse_bin_name', width: 180 },
|
||||
{ label: '缸号', key: 'dye_factory_dyelot_number', width: 200 },
|
||||
];
|
||||
|
||||
const TARE_COLUMNS = [
|
||||
{ label: '纸筒重量', key: 'paper_tube_weight', width: 180 },
|
||||
{ label: '包装重量', key: 'package_weight', width: 180 },
|
||||
];
|
||||
|
||||
export default {
|
||||
components: { tTable, tTh, tTr, tTd },
|
||||
props: {
|
||||
groups: { type: Array, default: () => [] },
|
||||
title: { type: String, default: '明细' },
|
||||
emptyText: { type: String, default: '暂无明细' },
|
||||
rollKey: { type: String, default: 'in_roll' },
|
||||
weightKey: { type: String, default: 'in_weight' },
|
||||
deletable: { type: Boolean, default: false },
|
||||
showTareColumns: { type: Boolean, default: false },
|
||||
showBarCode: { type: Boolean, default: false },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
expandedKey: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
columns() {
|
||||
return SUMMARY_COLUMNS;
|
||||
},
|
||||
rows() {
|
||||
const rows = [];
|
||||
(this.groups || []).forEach((product) => {
|
||||
(product.colors || []).forEach((color) => {
|
||||
rows.push({
|
||||
group_key: color.group_key,
|
||||
product_code: product.product_code || '',
|
||||
product_name: product.product_name || '',
|
||||
product_color_code: color.product_color_code || '',
|
||||
product_color_name: color.product_color_name || '',
|
||||
quote_order_no: color.quote_order_no || '',
|
||||
dye_factory_dyelot_number: color.dye_factory_dyelot_number || '',
|
||||
roll: color[this.rollKey],
|
||||
weight: color[this.weightKey],
|
||||
fine_codes: color.fine_codes || [],
|
||||
});
|
||||
});
|
||||
});
|
||||
return rows;
|
||||
},
|
||||
expandedRow() {
|
||||
return this.rows.find((row) => row.group_key === this.expandedKey) || null;
|
||||
},
|
||||
fineCols() {
|
||||
const cols = [...FINE_COLUMNS];
|
||||
if (this.showBarCode) {
|
||||
const idx = cols.findIndex((col) => col.key === 'weight');
|
||||
cols.splice(idx + 1, 0, { label: '条码', key: 'bar_code', width: 280 });
|
||||
}
|
||||
if (this.showTareColumns) {
|
||||
const idx = cols.findIndex((col) => col.key === 'weight');
|
||||
cols.splice(idx + 1, 0, ...TARE_COLUMNS);
|
||||
}
|
||||
if (this.deletable) {
|
||||
cols.push({ label: '操作', key: 'action', width: 120 });
|
||||
}
|
||||
return cols;
|
||||
},
|
||||
tableWidth() {
|
||||
return this.sumWidth(this.columns);
|
||||
},
|
||||
fineTableWidth() {
|
||||
return this.sumWidth(this.fineCols);
|
||||
},
|
||||
totalRoll() {
|
||||
return this.sumField('roll');
|
||||
},
|
||||
totalWeight() {
|
||||
return this.sumField('weight');
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
rows() {
|
||||
if (this.expandedKey && !this.rows.some((row) => row.group_key === this.expandedKey)) {
|
||||
this.expandedKey = '';
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
sumWidth(cols) {
|
||||
const total = cols.reduce((sum, col) => sum + (col.width || 120), 0);
|
||||
return `${total}rpx`;
|
||||
},
|
||||
sumField(key) {
|
||||
const total = this.rows.reduce((sum, row) => sum + Number(row[key] || 0), 0);
|
||||
return Number(total.toFixed(2));
|
||||
},
|
||||
cellStyle(col) {
|
||||
const width = `${col.width}rpx`;
|
||||
return {
|
||||
width,
|
||||
minWidth: width,
|
||||
maxWidth: width,
|
||||
flex: `0 0 ${width}`,
|
||||
};
|
||||
},
|
||||
joinName(code, name) {
|
||||
const main = code || '-';
|
||||
return name ? `${main} ${name}` : main;
|
||||
},
|
||||
formatCell(row, key) {
|
||||
if (key === 'product_code') return this.joinName(row.product_code, row.product_name);
|
||||
if (key === 'product_color_code') return this.joinName(row.product_color_code, row.product_color_name);
|
||||
if (key === 'roll' || key === 'weight') {
|
||||
const val = row[key];
|
||||
if (val === 0) return '0';
|
||||
return val != null && val !== '' ? val : '-';
|
||||
}
|
||||
const val = row[key];
|
||||
if (val === 0) return '0';
|
||||
return val != null && val !== '' ? val : '-';
|
||||
},
|
||||
totalCell(key) {
|
||||
if (key === 'product_code') return '合计';
|
||||
if (key === 'roll') return this.totalRoll;
|
||||
if (key === 'weight') return this.totalWeight;
|
||||
return '';
|
||||
},
|
||||
toggleRow(key) {
|
||||
this.expandedKey = this.expandedKey === key ? '' : key;
|
||||
},
|
||||
formatFineCell(fc, key) {
|
||||
if (key === 'action') return '删除';
|
||||
const val = fc[key];
|
||||
if (val === 0) return '0';
|
||||
return val != null && val !== '' ? val : '-';
|
||||
},
|
||||
onFineCellTap(key, fc) {
|
||||
if (!this.deletable || key !== 'action') return;
|
||||
this.$emit('delete', fc);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/common/pda-typography.scss';
|
||||
|
||||
.summary-title {
|
||||
font-size: $pda-font-body;
|
||||
font-weight: bold;
|
||||
padding: 24rpx 26rpx 12rpx;
|
||||
border-left: 6rpx solid #007AFF;
|
||||
margin: 20rpx 26rpx 0;
|
||||
}
|
||||
.summary-wrap {
|
||||
margin: 0 26rpx 16rpx;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
.summary-scroll {
|
||||
width: 100%;
|
||||
}
|
||||
::v-deep .t-th,
|
||||
::v-deep .t-td {
|
||||
box-sizing: border-box;
|
||||
min-height: $pda-row-min-height;
|
||||
word-break: break-all;
|
||||
}
|
||||
::v-deep .summary-head {
|
||||
background: #f7f8fa;
|
||||
}
|
||||
::v-deep .summary-row--open {
|
||||
background: #f0f7ff;
|
||||
}
|
||||
::v-deep .summary-total {
|
||||
background: #fff7e6;
|
||||
}
|
||||
::v-deep .summary-action {
|
||||
color: #f5222d;
|
||||
}
|
||||
.summary-empty {
|
||||
padding: 28rpx 16rpx;
|
||||
font-size: $pda-font-placeholder;
|
||||
color: #999;
|
||||
}
|
||||
.summary-fine {
|
||||
padding: 8rpx 0 16rpx;
|
||||
}
|
||||
.summary-fineTitle {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 12rpx 16rpx 8rpx;
|
||||
font-size: $pda-font-action;
|
||||
color: #2979ff;
|
||||
}
|
||||
.summary-fineHint {
|
||||
color: #999;
|
||||
font-size: $pda-font-popup-sub;
|
||||
}
|
||||
.summary-action {
|
||||
color: #f5222d;
|
||||
}
|
||||
.summary-tip {
|
||||
padding: 8rpx 16rpx 0;
|
||||
font-size: $pda-font-popup-sub;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<view>
|
||||
<view v-if="canScan" class="flex-white-plr26 ptb20 bdb_f5 fabric-out-scanRow process-in-binRow">
|
||||
<text class="mr26 process-in-binLabel">仓位<text class="redXingh">*</text></text>
|
||||
<input
|
||||
type="text"
|
||||
:value="code"
|
||||
class="fabric-out-scanInput"
|
||||
placeholder="请先扫描仓位"
|
||||
@input="$emit('code-input', $event.detail.value)"
|
||||
@confirm="$emit('confirm')"
|
||||
/>
|
||||
<text v-if="name" class="process-in-binName">{{ name }}</text>
|
||||
<view
|
||||
v-if="showPhoneScan && scanning"
|
||||
class="fabric-out-scanClose"
|
||||
@click.stop="$emit('phone-scan')"
|
||||
>关闭扫码</view>
|
||||
<u-icon
|
||||
v-else-if="showPhoneScan"
|
||||
class="fabric-out-phoneScanIcon"
|
||||
name="scan"
|
||||
size="44"
|
||||
color="#2979ff"
|
||||
@click="$emit('phone-scan')"
|
||||
/>
|
||||
<text v-if="binId" class="process-in-binClear" @click="$emit('clear')">更换</text>
|
||||
</view>
|
||||
<view v-else-if="showDisabledTip" class="fabric-out-scanTip">{{ disabledTip }}</view>
|
||||
<view v-if="canScan && !binId" class="fabric-out-scanTip">请先扫描仓位,再扫描布匹二维码</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
canScan: { type: Boolean, default: false },
|
||||
binId: { type: [Number, String], default: 0 },
|
||||
code: { type: String, default: '' },
|
||||
name: { type: String, default: '' },
|
||||
showPhoneScan: { type: Boolean, default: false },
|
||||
scanning: { type: Boolean, default: false },
|
||||
showDisabledTip: { type: Boolean, default: true },
|
||||
disabledTip: { type: String, default: '请先提交保存后再扫仓位' },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/pages/storefabric/storeFabricBusinessOutPage.scss';
|
||||
@import '@/common/pda-typography.scss';
|
||||
|
||||
.process-in-binRow {
|
||||
background: #fff;
|
||||
}
|
||||
.process-in-binLabel {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.fabric-out-phoneScanIcon {
|
||||
margin-left: 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.fabric-out-scanClose {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 8rpx;
|
||||
padding: 0 16rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 8rpx;
|
||||
border: 1rpx solid #2979ff;
|
||||
background: #eaf2ff;
|
||||
color: #2979ff;
|
||||
font-size: $pda-font-action;
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.process-in-binName {
|
||||
margin-left: 12rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
max-width: 200rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.process-in-binClear {
|
||||
margin-left: 16rpx;
|
||||
font-size: 28rpx;
|
||||
color: #2979ff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<FineCodeSummaryTable
|
||||
:groups="groups"
|
||||
:title="title"
|
||||
:empty-text="emptyText"
|
||||
roll-key="in_roll"
|
||||
weight-key="in_weight"
|
||||
:deletable="deletable"
|
||||
:show-tare-columns="showTareColumns"
|
||||
@delete="$emit('delete', $event)"
|
||||
>
|
||||
<slot name="before-list" slot="before-list" />
|
||||
</FineCodeSummaryTable>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FineCodeSummaryTable from '@/components/storegoods/FineCodeSummaryTable.vue';
|
||||
import { buildItemGroups } from '@/common/storeGoodsProcessIn';
|
||||
|
||||
export default {
|
||||
components: { FineCodeSummaryTable },
|
||||
props: {
|
||||
list: { type: Array, default: () => [] },
|
||||
title: { type: String, default: '进仓明细' },
|
||||
emptyText: { type: String, default: '暂无进仓明细' },
|
||||
deletable: { type: Boolean, default: false },
|
||||
showTareColumns: { type: Boolean, default: false },
|
||||
},
|
||||
computed: {
|
||||
groups() {
|
||||
return buildItemGroups(this.list);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<view class="fabric-out-scanDetail">
|
||||
<view class="fabric-out-scanDetailRow fabric-out-scanDetailRow--2col">
|
||||
<text class="fabric-out-scanDetailCell">染整单号:{{ detail.quote_order_no || '-' }}</text>
|
||||
<text class="fabric-out-scanDetailCell">缸号:{{ detail.dyelot_number || '-' }}</text>
|
||||
</view>
|
||||
<view class="fabric-out-scanDetailRow fabric-out-scanDetailRow--2col">
|
||||
<text class="fabric-out-scanDetailCell">卷号:{{ detail.volume_number || '-' }}</text>
|
||||
<text class="fabric-out-scanDetailCell">外部卷号:{{ detail.extern_volume_number || '-' }}</text>
|
||||
</view>
|
||||
<view class="fabric-out-scanDetailRow fabric-out-scanDetailRow--2col">
|
||||
<text class="fabric-out-scanDetailCell">成品编号:{{ detail.product_code || '-' }}</text>
|
||||
<text class="fabric-out-scanDetailCell">成品色号:{{ detail.product_color_code || '-' }}</text>
|
||||
</view>
|
||||
<view class="fabric-out-scanDetailRow fabric-out-scanDetailRow--2col">
|
||||
<text class="fabric-out-scanDetailCell">本行数量:{{ detail.item_weight != null ? detail.item_weight : '-' }}</text>
|
||||
<text class="fabric-out-scanDetailCell">本单匹数:{{ detail.order_roll != null ? detail.order_roll : '-' }}</text>
|
||||
</view>
|
||||
<view class="fabric-out-scanDetailRow">
|
||||
<text class="fabric-out-scanDetailLabel">本单数量:</text>
|
||||
<text class="fabric-out-scanDetailValue">{{ detail.order_weight != null ? detail.order_weight : '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
detail: { type: Object, default: () => ({}) },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/pages/storefabric/storeFabricBusinessOutPage.scss';
|
||||
</style>
|
||||
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<FineCodeSummaryTable
|
||||
:groups="groups"
|
||||
:title="title"
|
||||
empty-text="暂无出仓明细"
|
||||
roll-key="out_roll"
|
||||
weight-key="out_weight"
|
||||
:deletable="deletable"
|
||||
show-bar-code
|
||||
@delete="$emit('delete', $event)"
|
||||
>
|
||||
<slot name="before-list" slot="before-list" />
|
||||
</FineCodeSummaryTable>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FineCodeSummaryTable from '@/components/storegoods/FineCodeSummaryTable.vue';
|
||||
import { buildItemGroups } from '@/common/storeGoodsProcessOut';
|
||||
|
||||
export default {
|
||||
components: { FineCodeSummaryTable },
|
||||
props: {
|
||||
list: { type: Array, default: () => [] },
|
||||
title: { type: String, default: '出仓明细' },
|
||||
deletable: { type: Boolean, default: false },
|
||||
},
|
||||
computed: {
|
||||
groups() {
|
||||
return buildItemGroups(this.list);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<view class="fabric-out-scanDetail">
|
||||
<view class="fabric-out-scanDetailRow fabric-out-scanDetailRow--2col">
|
||||
<text class="fabric-out-scanDetailCell">染整单号:{{ detail.quote_order_no || '-' }}</text>
|
||||
<text class="fabric-out-scanDetailCell">缸号:{{ detail.dyelot_number || '-' }}</text>
|
||||
</view>
|
||||
<view class="fabric-out-scanDetailRow fabric-out-scanDetailRow--2col">
|
||||
<text class="fabric-out-scanDetailCell">卷号:{{ detail.volume_number || '-' }}</text>
|
||||
<text class="fabric-out-scanDetailCell">外部卷号:{{ detail.extern_volume_number || '-' }}</text>
|
||||
</view>
|
||||
<view class="fabric-out-scanDetailRow fabric-out-scanDetailRow--2col">
|
||||
<text class="fabric-out-scanDetailCell">成品编号:{{ detail.product_code || '-' }}</text>
|
||||
<text class="fabric-out-scanDetailCell">成品色号:{{ detail.product_color_code || '-' }}</text>
|
||||
</view>
|
||||
<view class="fabric-out-scanDetailRow fabric-out-scanDetailRow--2col">
|
||||
<text class="fabric-out-scanDetailCell">本行匹数:{{ detail.item_roll != null ? detail.item_roll : '-' }}</text>
|
||||
<text class="fabric-out-scanDetailCell">本行数量:{{ detail.item_weight != null ? detail.item_weight : '-' }}</text>
|
||||
</view>
|
||||
<view class="fabric-out-scanDetailRow fabric-out-scanDetailRow--2col">
|
||||
<text class="fabric-out-scanDetailCell">本单匹数:{{ detail.order_roll != null ? detail.order_roll : '-' }}</text>
|
||||
<text class="fabric-out-scanDetailCell">本单数量:{{ detail.order_weight != null ? detail.order_weight : '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
detail: { type: Object, default: () => ({}) },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/pages/storefabric/storeFabricBusinessOutPage.scss';
|
||||
</style>
|
||||
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<view class="t-table" :style="{ 'border-width': border + 'px', 'border-color': borderColor }">
|
||||
<slot />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
border: {
|
||||
type: String,
|
||||
default: '1'
|
||||
},
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: '#d0dee5'
|
||||
},
|
||||
isCheck: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
table: this
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
created() {
|
||||
this.childrens = [];
|
||||
this.index = 0;
|
||||
},
|
||||
methods: {
|
||||
fire(e, index, len) {
|
||||
let childrens = this.childrens;
|
||||
console.log(childrens);
|
||||
// 全选
|
||||
if (index === 0) {
|
||||
childrens.map((vm, index) => {
|
||||
vm.checkboxData.checked = e;
|
||||
return vm;
|
||||
});
|
||||
} else {
|
||||
let isAll = childrens.find((n, ids) => ids !== 0 && !n.checkboxData.checked);
|
||||
childrens[0].checkboxData.checked = isAll ? false : true;
|
||||
}
|
||||
|
||||
let fireArr = [];
|
||||
for (let i = 0; i < childrens.length; i++) {
|
||||
if (childrens[i].checkboxData.checked && i !== 0) {
|
||||
fireArr.push(childrens[i].checkboxData.value - 1);
|
||||
}
|
||||
}
|
||||
this.$emit('change', {
|
||||
detail: fireArr
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.t-table {
|
||||
width: 100%;
|
||||
border: 1px #d0dee5 solid;
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.t-table>>>t-tr {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.t-table>>>t-tr:nth-child(2n) {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
/* #ifdef H5 */
|
||||
.t-table>>>.t-tr:nth-child(2n) {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
/* #endif */
|
||||
</style>
|
||||
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<view class="t-td" :style="{ 'border-width': thBorder + 'px','border-color':borderColor ,'font-size':fontSize+'px' ,'color':color,'justify-content':tdAlignCpd}" @click="$emit('click', $event)">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
align: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
thBorder: '1',
|
||||
borderColor: '#d0dee5',
|
||||
fontSize: '14',
|
||||
color: '#555c60',
|
||||
tdAlign: 'center'
|
||||
};
|
||||
},
|
||||
inject: ['table', 'tr'],
|
||||
|
||||
created() {
|
||||
this.thBorder = this.table.border;
|
||||
this.borderColor = this.table.borderColor;
|
||||
this.fontSize = this.tr.fontSize;
|
||||
this.color = this.tr.color;
|
||||
if (this.align) {
|
||||
this.tdAlign = this.align;
|
||||
} else {
|
||||
this.tdAlign = this.tr.align
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tdAlignCpd() {
|
||||
let nameAlign = '';
|
||||
switch (this.tdAlign) {
|
||||
case 'left':
|
||||
nameAlign = 'flex-start'
|
||||
break;
|
||||
case 'center':
|
||||
nameAlign = 'center'
|
||||
break;
|
||||
case 'right':
|
||||
nameAlign = 'flex-end'
|
||||
break;
|
||||
default:
|
||||
nameAlign = 'center'
|
||||
break;
|
||||
}
|
||||
return nameAlign
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.t-td {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 14upx;
|
||||
border-top: 1px #d0dee5 solid;
|
||||
border-left: 1px #d0dee5 solid;
|
||||
text-align: center;
|
||||
color: #555c60;
|
||||
font-size: 28upx;
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<view class="t-th" :style="{ 'border-width': thBorder + 'px' ,'border-color':borderColor,'font-size':fontSize+'px' ,'color':color,'justify-content':thAlignCpd}">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
align: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
thBorder: '1',
|
||||
borderColor: '#d0dee5',
|
||||
fontSize: '15',
|
||||
color: '#3b4246',
|
||||
thAlign: 'center'
|
||||
};
|
||||
},
|
||||
inject: ['table', 'tr'],
|
||||
|
||||
created() {
|
||||
this.thBorder = this.table.border;
|
||||
this.borderColor = this.table.borderColor;
|
||||
this.fontSize = this.tr.fontSize;
|
||||
this.color = this.tr.color;
|
||||
if (this.align) {
|
||||
this.thAlign = this.align;
|
||||
} else {
|
||||
this.thAlign = this.tr.align
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
thAlignCpd() {
|
||||
let nameAlign = '';
|
||||
switch (this.thAlign) {
|
||||
case 'left':
|
||||
nameAlign = 'flex-start'
|
||||
break;
|
||||
case 'center':
|
||||
nameAlign = 'center'
|
||||
break;
|
||||
case 'right':
|
||||
nameAlign = 'flex-end'
|
||||
break;
|
||||
default:
|
||||
nameAlign = 'center'
|
||||
break;
|
||||
}
|
||||
return nameAlign
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.t-th {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 30upx;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
color: #3b4246;
|
||||
border-left: 1px #d0dee5 solid;
|
||||
border-top: 1px #d0dee5 solid;
|
||||
padding: 15upx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<view class="t-tr" @click="$emit('click', $event)">
|
||||
<view v-if="isCheck" class="t-check-box" :style="{ 'border-width': thBorder + 'px' ,'border-color':borderColor}">
|
||||
<checkbox-group @change="checkboxChange">
|
||||
<checkbox :value="checkboxData.value + ''" :checked="checkboxData.checked" />
|
||||
</checkbox-group>
|
||||
</view>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
fontSize: String,
|
||||
color: String,
|
||||
align: String
|
||||
},
|
||||
inject: ['table'],
|
||||
provide() {
|
||||
return {
|
||||
tr: this
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isCheck: false,
|
||||
checkboxData: {
|
||||
value: 0,
|
||||
checked: false
|
||||
},
|
||||
checked: false,
|
||||
thBorder: '1',
|
||||
borderColor: '#d0dee5'
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.thBorder = this.table.border;
|
||||
this.borderColor = this.table.borderColor;
|
||||
this.table.childrens.push(this);
|
||||
this.checkboxData.value = this.table.index++;
|
||||
this.isCheck = this.table.isCheck;
|
||||
|
||||
},
|
||||
methods: {
|
||||
checkboxChange(e) {
|
||||
this.checkboxData.checked = !this.checkboxData.checked;
|
||||
this.table.childrens[this.checkboxData.value] = this;
|
||||
this.table.fire(e.detail.value[0] ? true : false, this.checkboxData.value, this.table.index);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.t-tr {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.t-tr t-th,
|
||||
.t-tr t-td {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.t-tr .t-check-box {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 80upx;
|
||||
color: #3b4246;
|
||||
border-left: 1px #d0dee5 solid;
|
||||
border-top: 1px #d0dee5 solid;
|
||||
}
|
||||
|
||||
.t-tr .t-check-box checkbox {
|
||||
transform: scale(0.8);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,247 @@
|
||||
<template>
|
||||
<u-popup mode="bottom" :border-radius="borderRadius" :popup="false" v-model="value" :maskCloseAble="maskCloseAble"
|
||||
length="auto" :safeAreaInsetBottom="safeAreaInsetBottom" @close="popupClose" :z-index="uZIndex">
|
||||
<view class="u-tips u-border-bottom" v-if="tips.text" :style="[tipsStyle]">
|
||||
{{tips.text}}
|
||||
</view>
|
||||
<view class="u-action-sheet-search" v-if="showSearch" @tap.stop>
|
||||
<u-icon name="search" size="32" color="#909399"></u-icon>
|
||||
<input
|
||||
class="u-action-sheet-search__input"
|
||||
:value="keyword"
|
||||
:placeholder="searchPlaceholder"
|
||||
confirm-type="search"
|
||||
:adjust-position="true"
|
||||
@input="onSearchInput"
|
||||
/>
|
||||
<u-icon
|
||||
v-if="keyword"
|
||||
name="close-circle-fill"
|
||||
size="28"
|
||||
color="#c0c4cc"
|
||||
@tap.stop="clearSearch"
|
||||
></u-icon>
|
||||
</view>
|
||||
<scroll-view scroll-y class="u-action-sheet-scroll">
|
||||
<view
|
||||
v-for="(item, index) in displayList"
|
||||
:key="item._originIndex"
|
||||
@touchmove.stop.prevent
|
||||
@tap="itemClick(item._originIndex)"
|
||||
:style="[itemStyle(item._originIndex)]"
|
||||
class="u-action-sheet-item u-line-1"
|
||||
:class="[index < displayList.length - 1 ? 'u-border-bottom' : '']"
|
||||
:hover-stay-time="150"
|
||||
>
|
||||
<text>{{item.text}}</text>
|
||||
<text class="u-action-sheet-item__subtext u-line-1" v-if="item.subText">{{item.subText}}</text>
|
||||
</view>
|
||||
<view v-if="!displayList.length" class="u-action-sheet-empty">无匹配选项</view>
|
||||
</scroll-view>
|
||||
<view class="u-gab" v-if="cancelBtn">
|
||||
</view>
|
||||
<view @touchmove.stop.prevent class="u-actionsheet-cancel u-action-sheet-item" hover-class="u-hover-class"
|
||||
:hover-stay-time="150" v-if="cancelBtn" @tap="close">{{cancelText}}</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* actionSheet 操作菜单(本地覆盖:选项支持搜索)
|
||||
* @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。
|
||||
* @tutorial https://www.uviewui.com/components/actionSheet.html
|
||||
* @property {Array<Object>} list 按钮的文字数组,见官方文档示例
|
||||
* @property {Boolean} show-search 是否显示搜索框(默认true)
|
||||
* @property {String} search-placeholder 搜索框占位文字
|
||||
* @event {Function} click 点击ActionSheet列表项时触发,返回原始列表下标
|
||||
* @example <u-action-sheet :list="list" @click="click" v-model="show"></u-action-sheet>
|
||||
*/
|
||||
export default {
|
||||
name: "u-action-sheet",
|
||||
props: {
|
||||
maskCloseAble: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
list: {
|
||||
type: Array,
|
||||
default () {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
tips: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
text: '',
|
||||
color: '',
|
||||
fontSize: '26'
|
||||
}
|
||||
}
|
||||
},
|
||||
cancelBtn: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
borderRadius: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
zIndex: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
},
|
||||
showSearch: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
searchPlaceholder: {
|
||||
type: String,
|
||||
default: '搜索'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
keyword: ''
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
if (val) this.keyword = '';
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tipsStyle() {
|
||||
let style = {};
|
||||
if (this.tips.color) style.color = this.tips.color;
|
||||
if (this.tips.fontSize) style.fontSize = this.tips.fontSize + 'rpx';
|
||||
return style;
|
||||
},
|
||||
itemStyle() {
|
||||
return (index) => {
|
||||
let style = {};
|
||||
const item = this.list[index];
|
||||
if (!item) return style;
|
||||
if (item.color) style.color = item.color;
|
||||
if (item.fontSize) style.fontSize = item.fontSize + 'rpx';
|
||||
if (item.disabled) style.color = '#c0c4cc';
|
||||
return style;
|
||||
}
|
||||
},
|
||||
uZIndex() {
|
||||
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
|
||||
},
|
||||
displayList() {
|
||||
const keyword = (this.keyword || '').trim().toLowerCase();
|
||||
return (this.list || []).reduce((arr, item, index) => {
|
||||
const text = String((item && item.text) || '');
|
||||
const subText = String((item && item.subText) || '');
|
||||
if (!keyword || text.toLowerCase().indexOf(keyword) !== -1 || subText.toLowerCase().indexOf(keyword) !== -1) {
|
||||
arr.push(Object.assign({}, item, { _originIndex: index }));
|
||||
}
|
||||
return arr;
|
||||
}, []);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSearchInput(e) {
|
||||
this.keyword = e && e.detail && e.detail.value != null ? e.detail.value : '';
|
||||
},
|
||||
clearSearch() {
|
||||
this.keyword = '';
|
||||
},
|
||||
close() {
|
||||
this.popupClose();
|
||||
this.$emit('close');
|
||||
},
|
||||
popupClose() {
|
||||
this.keyword = '';
|
||||
this.$emit('input', false);
|
||||
},
|
||||
itemClick(index) {
|
||||
if(this.list[index] && this.list[index].disabled) return;
|
||||
this.$emit('click', index);
|
||||
this.$emit('input', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "~uview-ui/libs/css/style.components.scss";
|
||||
|
||||
.u-tips {
|
||||
font-size: 26rpx;
|
||||
text-align: center;
|
||||
padding: 34rpx 0;
|
||||
line-height: 1;
|
||||
color: $u-tips-color;
|
||||
}
|
||||
|
||||
.u-action-sheet-search {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
margin: 16rpx 24rpx;
|
||||
padding: 0 24rpx;
|
||||
height: 80rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 12rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
&__input {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
margin-left: 12rpx;
|
||||
font-size: 30rpx;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
|
||||
.u-action-sheet-scroll {
|
||||
max-height: 600rpx;
|
||||
}
|
||||
|
||||
.u-action-sheet-empty {
|
||||
text-align: center;
|
||||
padding: 60rpx 0;
|
||||
font-size: 30rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.u-action-sheet-item {
|
||||
@include vue-flex;
|
||||
line-height: 1;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 32rpx;
|
||||
padding: 34rpx 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.u-action-sheet-item__subtext {
|
||||
font-size: 24rpx;
|
||||
color: $u-tips-color;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.u-gab {
|
||||
height: 12rpx;
|
||||
background-color: rgb(234, 234, 236);
|
||||
}
|
||||
|
||||
.u-actionsheet-cancel {
|
||||
color: $u-main-color;
|
||||
}
|
||||
</style>
|
||||
+82
-28
@@ -1,18 +1,37 @@
|
||||
<template>
|
||||
<view class="u-dropdown-item" v-if="active" @touchmove.stop.prevent="() => {}" @tap.stop.prevent="() => {}">
|
||||
<block v-if="!$slots.default && !$slots.$default">
|
||||
<view class="u-dropdown-item__search" v-if="showSearch" @tap.stop>
|
||||
<u-icon name="search" size="28" color="#909399"></u-icon>
|
||||
<input
|
||||
class="u-dropdown-item__search__input"
|
||||
:value="keyword"
|
||||
:placeholder="searchPlaceholder"
|
||||
confirm-type="search"
|
||||
:adjust-position="true"
|
||||
@input="onSearchInput"
|
||||
/>
|
||||
<u-icon
|
||||
v-if="keyword"
|
||||
name="close-circle-fill"
|
||||
size="28"
|
||||
color="#c0c4cc"
|
||||
@tap.stop="clearSearch"
|
||||
></u-icon>
|
||||
</view>
|
||||
<scroll-view scroll-y="true" :style="{
|
||||
height: $u.addUnit(height)
|
||||
}">
|
||||
<view class="u-dropdown-item__options">
|
||||
<u-cell-group>
|
||||
<u-cell-item @click="cellClick(item.value)" :arrow="false" :title="item.label" v-for="(item, index) in options"
|
||||
<u-cell-item @click="cellClick(item.value)" :arrow="false" :title="item.label" v-for="(item, index) in filteredOptions"
|
||||
:key="index" :title-style="{
|
||||
color: value == item.value ? activeColor : inactiveColor
|
||||
}">
|
||||
<u-icon v-if="value == item.value" name="checkbox-mark" :color="activeColor" size="32"></u-icon>
|
||||
</u-cell-item>
|
||||
</u-cell-group>
|
||||
<view v-if="!filteredOptions.length" class="u-dropdown-item__empty">无匹配选项</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</block>
|
||||
@@ -22,102 +41,111 @@
|
||||
|
||||
<script>
|
||||
/**
|
||||
* dropdown-item 下拉菜单
|
||||
* dropdown-item 下拉菜单(本地覆盖:选项支持搜索)
|
||||
* @description 该组件一般用于向下展开菜单,同时可切换多个选项卡的场景
|
||||
* @tutorial http://uviewui.com/components/dropdown.html
|
||||
* @property {String | Number} v-model 双向绑定选项卡选择值
|
||||
* @property {String} title 菜单项标题
|
||||
* @property {Array[Object]} options 选项数据,如果传入了默认slot,此参数无效
|
||||
* @property {Boolean} disabled 是否禁用此选项卡(默认false)
|
||||
* @property {String | Number} duration 选项卡展开和收起的过渡时间,单位ms(默认300)
|
||||
* @property {String | Number} height 弹窗下拉内容的高度(内容超出将会滚动)(默认auto)
|
||||
* @property {Boolean} show-search 是否显示搜索框(默认true)
|
||||
* @property {String} search-placeholder 搜索框占位文字
|
||||
* @example <u-dropdown-item title="标题"></u-dropdown-item>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-dropdown-item',
|
||||
props: {
|
||||
// 当前选中项的value值
|
||||
value: {
|
||||
type: [Number, String, Array],
|
||||
default: ''
|
||||
},
|
||||
// 菜单项标题
|
||||
title: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 选项数据,如果传入了默认slot,此参数无效
|
||||
options: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 是否禁用此菜单项
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 下拉弹窗的高度
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 'auto'
|
||||
},
|
||||
showSearch: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
searchPlaceholder: {
|
||||
type: String,
|
||||
default: '搜索'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
active: false, // 当前项是否处于展开状态
|
||||
activeColor: '#2979ff', // 激活时左边文字和右边对勾图标的颜色
|
||||
inactiveColor: '#606266', // 未激活时左边文字和右边对勾图标的颜色
|
||||
active: false,
|
||||
activeColor: '#2979ff',
|
||||
inactiveColor: '#606266',
|
||||
keyword: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 监听props是否发生了变化,有些值需要传递给父组件u-dropdown,无法双向绑定
|
||||
propsChange() {
|
||||
return `${this.title}-${this.disabled}`;
|
||||
},
|
||||
filteredOptions() {
|
||||
const keyword = (this.keyword || '').trim().toLowerCase();
|
||||
const list = this.options || [];
|
||||
if (!keyword) return list;
|
||||
return list.filter(item => {
|
||||
const label = String(item && item.label != null ? item.label : '');
|
||||
const value = String(item && item.value != null ? item.value : '');
|
||||
return label.toLowerCase().indexOf(keyword) !== -1 || value.toLowerCase().indexOf(keyword) !== -1;
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
propsChange(n) {
|
||||
// 当值变化时,通知父组件重新初始化,让父组件执行每个子组件的init()方法
|
||||
// 将所有子组件数据重新整理一遍
|
||||
propsChange() {
|
||||
if (this.parent) this.parent.init();
|
||||
},
|
||||
active(val) {
|
||||
if (!val) this.keyword = '';
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 父组件的实例
|
||||
this.parent = false;
|
||||
},
|
||||
methods: {
|
||||
onSearchInput(e) {
|
||||
this.keyword = e && e.detail && e.detail.value != null ? e.detail.value : '';
|
||||
},
|
||||
clearSearch() {
|
||||
this.keyword = '';
|
||||
},
|
||||
init() {
|
||||
// 获取父组件u-dropdown
|
||||
let parent = this.$u.$parent.call(this, 'u-dropdown');
|
||||
if (parent) {
|
||||
this.parent = parent;
|
||||
// 将子组件的激活颜色配置为父组件设置的激活和未激活时的颜色
|
||||
this.activeColor = parent.activeColor;
|
||||
this.inactiveColor = parent.inactiveColor;
|
||||
// 将本组件的this,放入到父组件的children数组中,让父组件可以操作本(子)组件的方法和属性
|
||||
// push进去前,显判断是否已经存在了本实例,因为在子组件内部数据变化时,会通过父组件重新初始化子组件
|
||||
let exist = parent.children.find(val => {
|
||||
return this === val;
|
||||
})
|
||||
if (!exist) parent.children.push(this);
|
||||
if (parent.children.length == 1) this.active = true;
|
||||
// 父组件无法监听children的变化,故将子组件的title,传入父组件的menuList数组中
|
||||
parent.menuList.push({
|
||||
title: this.title,
|
||||
disabled: this.disabled
|
||||
});
|
||||
}
|
||||
},
|
||||
// cell被点击
|
||||
cellClick(value) {
|
||||
// 修改通过v-model绑定的值
|
||||
this.$emit('input', value);
|
||||
// 通知父组件(u-dropdown)收起菜单
|
||||
this.parent.close();
|
||||
// 发出事件,抛出当前勾选项的value
|
||||
this.$emit('change', value);
|
||||
}
|
||||
},
|
||||
@@ -128,5 +156,31 @@
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "../../libs/css/style.components.scss";
|
||||
@import "~uview-ui/libs/css/style.components.scss";
|
||||
|
||||
.u-dropdown-item__search {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
margin: 16rpx 24rpx 0;
|
||||
padding: 0 24rpx;
|
||||
height: 72rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 12rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
&__input {
|
||||
flex: 1;
|
||||
height: 72rpx;
|
||||
margin-left: 12rpx;
|
||||
font-size: 28rpx;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
|
||||
.u-dropdown-item__empty {
|
||||
text-align: center;
|
||||
padding: 40rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,589 @@
|
||||
<template>
|
||||
<view class="u-select">
|
||||
<u-popup :maskCloseAble="maskCloseAble" mode="bottom" :popup="false" v-model="value" length="auto" :safeAreaInsetBottom="safeAreaInsetBottom" @close="close" :z-index="uZIndex">
|
||||
<view class="u-select">
|
||||
<view class="u-select__header" @touchmove.stop.prevent="">
|
||||
<view
|
||||
class="u-select__header__cancel u-select__header__btn"
|
||||
:style="{ color: cancelColor }"
|
||||
hover-class="u-hover-class"
|
||||
:hover-stay-time="150"
|
||||
@tap="getResult('cancel')"
|
||||
>
|
||||
{{cancelText}}
|
||||
</view>
|
||||
<view class="u-select__header__title">
|
||||
{{title}}
|
||||
</view>
|
||||
<view
|
||||
class="u-select__header__confirm u-select__header__btn"
|
||||
:style="{ color: moving ? cancelColor : confirmColor }"
|
||||
hover-class="u-hover-class"
|
||||
:hover-stay-time="150"
|
||||
@touchmove.stop=""
|
||||
@tap.stop="getResult('confirm')"
|
||||
>
|
||||
{{confirmText}}
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-select__search" v-if="showSearch" @tap.stop>
|
||||
<u-icon name="search" size="32" color="#909399"></u-icon>
|
||||
<input
|
||||
class="u-select__search__input"
|
||||
:value="keyword"
|
||||
:placeholder="searchPlaceholder"
|
||||
confirm-type="search"
|
||||
:adjust-position="true"
|
||||
@input="onSearchInput"
|
||||
@confirm="onSearchConfirm"
|
||||
/>
|
||||
<u-icon
|
||||
v-if="keyword"
|
||||
name="close-circle-fill"
|
||||
size="28"
|
||||
color="#c0c4cc"
|
||||
@tap.stop="clearSearch"
|
||||
></u-icon>
|
||||
</view>
|
||||
<view v-if="loading" class="u-select__loading">搜索中...</view>
|
||||
<view class="u-select__body" :style="hasCustomItemHeight ? bodyStyle : null">
|
||||
<view v-if="isColumnEmpty && !loading" class="u-select__empty">无匹配选项</view>
|
||||
<picker-view
|
||||
v-else-if="value && !isColumnEmpty"
|
||||
:key="pickerKey"
|
||||
@change="columnChange"
|
||||
class="u-select__body__picker-view"
|
||||
:style="pickerViewStyle"
|
||||
:indicator-style="indicatorStyle"
|
||||
:value="defaultSelector"
|
||||
@pickstart="pickstart"
|
||||
@pickend="pickend"
|
||||
>
|
||||
<picker-view-column v-for="(item, index) in columnData" :key="'col-' + pickerKey + '-' + index">
|
||||
<view
|
||||
class="u-select__body__picker-view__item"
|
||||
:style="hasCustomItemHeight ? itemStyle : null"
|
||||
v-for="(item1, index1) in item"
|
||||
:key="item1[valueName] != null ? item1[valueName] : index1"
|
||||
>
|
||||
<view class="u-line-1" :style="hasCustomItemHeight ? itemStyle : null">{{ item1[labelName] }}</view>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* select 列选择器(本地覆盖:选项支持搜索)
|
||||
* @description 此选择器用于单列,多列,多列联动的选择场景。
|
||||
* @tutorial http://uviewui.com/components/select.html
|
||||
* @property {String} mode 模式选择,"single-column"-单列模式,"mutil-column"-多列模式,"mutil-column-auto"-多列联动模式
|
||||
* @property {Array} list 列数据,数组形式,见官网说明
|
||||
* @property {Boolean} v-model 布尔值变量,用于控制选择器的弹出与收起
|
||||
* @property {Boolean} show-search 是否显示搜索框(默认true)
|
||||
* @property {String} search-placeholder 搜索框占位文字
|
||||
* @event {Function} confirm 点击确定按钮,返回当前选择的值
|
||||
* @example <u-select v-model="show" :list="list"></u-select>
|
||||
*/
|
||||
|
||||
export default {
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
cancelColor: {
|
||||
type: String,
|
||||
default: '#606266'
|
||||
},
|
||||
confirmColor: {
|
||||
type: String,
|
||||
default: '#2979ff'
|
||||
},
|
||||
zIndex: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
maskCloseAble: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
defaultValue: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [0];
|
||||
}
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'single-column'
|
||||
},
|
||||
valueName: {
|
||||
type: String,
|
||||
default: 'value'
|
||||
},
|
||||
labelName: {
|
||||
type: String,
|
||||
default: 'label'
|
||||
},
|
||||
childName: {
|
||||
type: String,
|
||||
default: 'children'
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
},
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: '确认'
|
||||
},
|
||||
showSearch: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
searchPlaceholder: {
|
||||
type: String,
|
||||
default: '搜索'
|
||||
},
|
||||
remote: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
debounce: {
|
||||
type: Number,
|
||||
default: 400
|
||||
},
|
||||
/** 选项行高(rpx)。App 端需与 indicator 同步,避免选中框错位 */
|
||||
itemHeight: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
/** 可见行数,用于计算滚轮高度;需为奇数,选中行才能居中 */
|
||||
visibleItemCount: {
|
||||
type: [Number, String],
|
||||
default: 5
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
defaultSelector: [0],
|
||||
columnData: [],
|
||||
selectValue: [],
|
||||
lastSelectIndex: [],
|
||||
columnNum: 0,
|
||||
moving: false,
|
||||
keyword: '',
|
||||
pickerKey: 0,
|
||||
// 勿用 _ 前缀:Vue2 不会把 data 代理到 this
|
||||
searchTimer: null,
|
||||
lastSearchEmit: '',
|
||||
lastSearchAt: 0
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
if (val) {
|
||||
this.keyword = '';
|
||||
this.applyPickerData();
|
||||
if (this.remote) this.emitRemoteSearch('');
|
||||
} else {
|
||||
this.clearSearchTimer();
|
||||
this.lastSearchEmit = '';
|
||||
this.lastSearchAt = 0;
|
||||
}
|
||||
}
|
||||
},
|
||||
list: {
|
||||
handler() {
|
||||
if (!this.value) return;
|
||||
this.applyPickerData();
|
||||
},
|
||||
},
|
||||
loading(val) {
|
||||
// loading 从 true→false 时再刷一次滚轮,避免原生 picker-view 漏更
|
||||
if (!val && this.value && this.remote) this.applyPickerData();
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.clearSearchTimer();
|
||||
},
|
||||
computed: {
|
||||
uZIndex() {
|
||||
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
|
||||
},
|
||||
isColumnEmpty() {
|
||||
return !this.columnData || !this.columnData.length || !this.columnData[0] || !this.columnData[0].length;
|
||||
},
|
||||
hasCustomItemHeight() {
|
||||
return Number(this.itemHeight) > 0;
|
||||
},
|
||||
itemHeightRpx() {
|
||||
return Number(this.itemHeight) || 70;
|
||||
},
|
||||
itemHeightPx() {
|
||||
return uni.upx2px(this.itemHeightRpx);
|
||||
},
|
||||
visibleCount() {
|
||||
const n = Number(this.visibleItemCount) || 5;
|
||||
return n % 2 === 0 ? n + 1 : n;
|
||||
},
|
||||
indicatorStyle() {
|
||||
if (!this.hasCustomItemHeight) return '';
|
||||
return `height: ${this.itemHeightPx}px;`;
|
||||
},
|
||||
itemStyle() {
|
||||
return {
|
||||
height: `${this.itemHeightPx}px`,
|
||||
lineHeight: `${this.itemHeightPx}px`,
|
||||
};
|
||||
},
|
||||
pickerViewStyle() {
|
||||
if (!this.hasCustomItemHeight) return null;
|
||||
return { height: `${this.itemHeightPx * this.visibleCount}px` };
|
||||
},
|
||||
bodyStyle() {
|
||||
return this.pickerViewStyle;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
pickstart() {
|
||||
// #ifdef MP-WEIXIN
|
||||
this.moving = true;
|
||||
// #endif
|
||||
},
|
||||
pickend() {
|
||||
// #ifdef MP-WEIXIN
|
||||
this.moving = false;
|
||||
// #endif
|
||||
},
|
||||
readInputValue(e) {
|
||||
if (typeof e === 'string' || typeof e === 'number') return String(e);
|
||||
if (e && e.detail && e.detail.value != null) return String(e.detail.value);
|
||||
if (e && e.target && e.target.value != null) return String(e.target.value);
|
||||
return '';
|
||||
},
|
||||
clearSearchTimer() {
|
||||
if (this.searchTimer) {
|
||||
clearTimeout(this.searchTimer);
|
||||
this.searchTimer = null;
|
||||
}
|
||||
},
|
||||
emitRemoteSearch(keyword) {
|
||||
this.clearSearchTimer();
|
||||
const key = keyword == null ? '' : String(keyword).trim();
|
||||
const now = Date.now();
|
||||
if (key === this.lastSearchEmit && now - this.lastSearchAt < 80) return;
|
||||
this.lastSearchEmit = key;
|
||||
this.lastSearchAt = now;
|
||||
this.$emit('search', key);
|
||||
},
|
||||
scheduleRemoteSearch() {
|
||||
this.clearSearchTimer();
|
||||
this.searchTimer = setTimeout(() => {
|
||||
this.emitRemoteSearch(this.keyword);
|
||||
}, this.debounce);
|
||||
},
|
||||
onSearchInput(e) {
|
||||
this.keyword = this.readInputValue(e);
|
||||
if (this.remote) this.scheduleRemoteSearch();
|
||||
else this.applyFilter();
|
||||
},
|
||||
onSearchConfirm(e) {
|
||||
this.keyword = this.readInputValue(e);
|
||||
if (this.remote) this.emitRemoteSearch(this.keyword);
|
||||
else this.applyFilter();
|
||||
},
|
||||
clearSearch() {
|
||||
this.keyword = '';
|
||||
if (this.remote) this.emitRemoteSearch('');
|
||||
else this.applyFilter();
|
||||
},
|
||||
matchKeyword(text) {
|
||||
const keyword = (this.keyword || '').trim().toLowerCase();
|
||||
if (!keyword) return true;
|
||||
return String(text == null ? '' : text).toLowerCase().indexOf(keyword) !== -1;
|
||||
},
|
||||
filterItems(list) {
|
||||
if (!Array.isArray(list)) return [];
|
||||
if (this.remote) return list.slice();
|
||||
const keyword = (this.keyword || '').trim();
|
||||
if (!keyword) return list.slice();
|
||||
return list.filter(item => {
|
||||
return this.matchKeyword(item && item[this.labelName]) || this.matchKeyword(item && item[this.valueName]);
|
||||
});
|
||||
},
|
||||
applyPickerData() {
|
||||
this.init();
|
||||
this.pickerKey += 1;
|
||||
},
|
||||
applyFilter() {
|
||||
this.applyPickerData();
|
||||
},
|
||||
init() {
|
||||
this.setColumnNum();
|
||||
this.setDefaultSelector();
|
||||
this.setColumnData();
|
||||
this.setSelectValue();
|
||||
},
|
||||
setDefaultSelector() {
|
||||
const columnNum = this.columnNum || 1;
|
||||
const defaultValue = Array.isArray(this.defaultValue) ? this.defaultValue.slice() : [];
|
||||
this.defaultSelector = defaultValue.length === columnNum
|
||||
? defaultValue
|
||||
: Array(columnNum).fill(0);
|
||||
this.lastSelectIndex = this.defaultSelector.slice();
|
||||
},
|
||||
setColumnNum() {
|
||||
if(this.mode == 'single-column') this.columnNum = 1;
|
||||
else if(this.mode == 'mutil-column') this.columnNum = this.list.length;
|
||||
else if(this.mode == 'mutil-column-auto') {
|
||||
let num = 1;
|
||||
let column = this.list;
|
||||
while(column[0] && column[0][this.childName]) {
|
||||
column = column[0] ? column[0][this.childName] : {};
|
||||
num ++;
|
||||
}
|
||||
this.columnNum = num;
|
||||
}
|
||||
},
|
||||
setColumnData() {
|
||||
let data = [];
|
||||
this.selectValue = [];
|
||||
if(this.mode == 'mutil-column-auto') {
|
||||
const sourceList = this.filterItems(this.list);
|
||||
if (!sourceList.length) {
|
||||
this.columnData = [[]];
|
||||
return;
|
||||
}
|
||||
let selector = this.defaultSelector.length ? this.defaultSelector[0] : 0;
|
||||
if (selector >= sourceList.length) selector = 0;
|
||||
let column = sourceList[selector];
|
||||
for (let i = 0; i < this.columnNum; i++) {
|
||||
if (i == 0) {
|
||||
data[i] = sourceList;
|
||||
column = column ? column[this.childName] : [];
|
||||
} else {
|
||||
data[i] = column || [];
|
||||
const nextIndex = this.defaultSelector[i] || 0;
|
||||
column = (column && column[nextIndex]) ? column[nextIndex][this.childName] : [];
|
||||
}
|
||||
}
|
||||
} else if(this.mode == 'single-column') {
|
||||
data[0] = this.filterItems(this.list);
|
||||
} else {
|
||||
data = (this.list || []).map(col => this.filterItems(Array.isArray(col) ? col : []));
|
||||
}
|
||||
this.columnData = data.slice();
|
||||
},
|
||||
setSelectValue() {
|
||||
let tmp = null;
|
||||
for(let i = 0; i < this.columnNum; i++) {
|
||||
const col = this.columnData[i] || [];
|
||||
tmp = col[this.defaultSelector[i]];
|
||||
let data = {
|
||||
value: tmp ? tmp[this.valueName] : null,
|
||||
label: tmp ? tmp[this.labelName] : null
|
||||
};
|
||||
if(tmp && tmp.extra !== undefined) data.extra = tmp.extra;
|
||||
this.selectValue.push(data)
|
||||
}
|
||||
},
|
||||
columnChange(e) {
|
||||
let index = null;
|
||||
let columnIndex = e.detail.value;
|
||||
this.selectValue = [];
|
||||
this.defaultSelector = columnIndex;
|
||||
if(this.mode == 'mutil-column-auto') {
|
||||
this.lastSelectIndex.map((val, idx) => {
|
||||
if (val != columnIndex[idx]) index = idx;
|
||||
});
|
||||
|
||||
for (let i = index + 1; i < this.columnNum; i++) {
|
||||
this.columnData[i] = this.columnData[i - 1][i - 1 == index ? columnIndex[index] : 0][this.childName];
|
||||
this.defaultSelector[i] = 0;
|
||||
}
|
||||
columnIndex.map((item, idx) => {
|
||||
let data = this.columnData[idx][columnIndex[idx]];
|
||||
let tmp = {
|
||||
value: data ? data[this.valueName] : null,
|
||||
label: data ? data[this.labelName] : null,
|
||||
};
|
||||
if(data && data.extra !== undefined) tmp.extra = data.extra;
|
||||
this.selectValue.push(tmp);
|
||||
|
||||
})
|
||||
this.lastSelectIndex = columnIndex;
|
||||
} else if(this.mode == 'single-column') {
|
||||
let data = this.columnData[0][columnIndex[0]];
|
||||
let tmp = {
|
||||
value: data ? data[this.valueName] : null,
|
||||
label: data ? data[this.labelName] : null,
|
||||
};
|
||||
if(data && data.extra !== undefined) tmp.extra = data.extra;
|
||||
this.selectValue.push(tmp);
|
||||
} else if(this.mode == 'mutil-column') {
|
||||
columnIndex.map((item, idx) => {
|
||||
let data = this.columnData[idx][columnIndex[idx]];
|
||||
let tmp = {
|
||||
value: data ? data[this.valueName] : null,
|
||||
label: data ? data[this.labelName] : null,
|
||||
};
|
||||
if(data && data.extra !== undefined) tmp.extra = data.extra;
|
||||
this.selectValue.push(tmp);
|
||||
})
|
||||
}
|
||||
},
|
||||
close() {
|
||||
this.$emit('input', false);
|
||||
this.$set(this, 'defaultSelector', [0]);
|
||||
this.keyword = '';
|
||||
this.lastSearchEmit = '';
|
||||
this.lastSearchAt = 0;
|
||||
},
|
||||
getResult(event = null) {
|
||||
// #ifdef MP-WEIXIN
|
||||
if (this.moving) return;
|
||||
// #endif
|
||||
if (event === 'confirm' && this.isColumnEmpty) {
|
||||
this.$u.toast && this.$u.toast('无匹配选项');
|
||||
return;
|
||||
}
|
||||
if (event) this.$emit(event, this.selectValue);
|
||||
this.close();
|
||||
},
|
||||
selectHandler() {
|
||||
this.$emit('click');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "~uview-ui/libs/css/style.components.scss";
|
||||
|
||||
.u-select {
|
||||
|
||||
&__action {
|
||||
position: relative;
|
||||
line-height: $u-form-item-height;
|
||||
height: $u-form-item-height;
|
||||
|
||||
&__icon {
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
top: 50%;
|
||||
transition: transform .4s;
|
||||
transform: translateY(-50%);
|
||||
z-index: 1;
|
||||
|
||||
&--reverse {
|
||||
transform: rotate(-180deg) translateY(50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__hader {
|
||||
&__title {
|
||||
color: $u-content-color;
|
||||
}
|
||||
}
|
||||
|
||||
&--border {
|
||||
border-radius: 6rpx;
|
||||
border-radius: 4px;
|
||||
border: 1px solid $u-form-item-border-color;
|
||||
}
|
||||
|
||||
&__header {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 80rpx;
|
||||
padding: 0 40rpx;
|
||||
}
|
||||
|
||||
&__search {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
margin: 0 24rpx 16rpx;
|
||||
padding: 0 24rpx;
|
||||
height: 80rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 12rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
&__input {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
margin-left: 12rpx;
|
||||
font-size: 30rpx;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
|
||||
&__loading {
|
||||
padding: 0 24rpx 8rpx;
|
||||
font-size: 26rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
&__empty {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
font-size: 30rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
&__body {
|
||||
width: 100%;
|
||||
height: 500rpx;
|
||||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
|
||||
&__picker-view {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
&__item {
|
||||
@include vue-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 32rpx;
|
||||
color: $u-main-color;
|
||||
padding: 0 8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -534,7 +534,7 @@
|
||||
fontSize: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [20]
|
||||
return [28]
|
||||
}
|
||||
},
|
||||
defaultColWidth: {
|
||||
@@ -548,7 +548,7 @@
|
||||
minHeight: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [70]
|
||||
return [84]
|
||||
}
|
||||
},
|
||||
headerBgColor: {
|
||||
|
||||
+19
@@ -17,6 +17,25 @@ import VueI18n from '@/common/vue-i18n.min.js';
|
||||
import lang_zh_CN from '@/common/locales/zh_CN.js';
|
||||
import lang_en from '@/common/locales/en.js';
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
// Android 5.x 等老 PDA 不信任 Let's Encrypt 新证书链,全局关闭 HTTPS 证书校验
|
||||
(() => {
|
||||
const patchSslVerify = (methodName) => {
|
||||
const original = uni[methodName];
|
||||
if (typeof original !== 'function') {
|
||||
return;
|
||||
}
|
||||
uni[methodName] = (options = {}) => original({
|
||||
...options,
|
||||
sslVerify: false
|
||||
});
|
||||
};
|
||||
patchSslVerify('request');
|
||||
patchSslVerify('downloadFile');
|
||||
patchSslVerify('uploadFile');
|
||||
})();
|
||||
// #endif
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
App.mpType = 'app';
|
||||
|
||||
+14
-12
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name" : "浩拓技术",
|
||||
"name" : "浩拓ERP",
|
||||
"appid" : "__UNI__F79F300",
|
||||
"description" : "浩拓纺织平台",
|
||||
"versionName" : "1.1.1",
|
||||
"versionCode" : 101,
|
||||
"description" : "浩拓ERP",
|
||||
"versionName" : "1.1.21",
|
||||
"versionCode" : 121,
|
||||
"transformPx" : false,
|
||||
"app-plus" : {
|
||||
"optimization" : {
|
||||
"subPackages" : true
|
||||
"subPackages" : false
|
||||
},
|
||||
"safearea" : {
|
||||
"bottom" : {
|
||||
@@ -31,6 +31,7 @@
|
||||
},
|
||||
"distribute" : {
|
||||
"android" : {
|
||||
"packagename" : "uni.UNIF79F300",
|
||||
"permissions" : [
|
||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||
@@ -40,6 +41,7 @@
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||
"<uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||
@@ -75,10 +77,10 @@
|
||||
},
|
||||
"icons" : {
|
||||
"android" : {
|
||||
"hdpi" : "static/haotop/image/logo/72.png",
|
||||
"xhdpi" : "static/haotop/image/logo/96.png",
|
||||
"xxhdpi" : "static/haotop/image/logo/144.png",
|
||||
"xxxhdpi" : "static/haotop/image/logo/192.png"
|
||||
"hdpi" : "src/static/haotop/image/logo/72.png",
|
||||
"xhdpi" : "src/static/haotop/image/logo/96.png",
|
||||
"xxhdpi" : "src/static/haotop/image/logo/144.png",
|
||||
"xxxhdpi" : "src/static/haotop/image/logo/192.png"
|
||||
},
|
||||
"ios" : {
|
||||
"appstore" : "",
|
||||
@@ -108,9 +110,9 @@
|
||||
"splashscreen" : {
|
||||
"androidStyle" : "default",
|
||||
"android" : {
|
||||
"hdpi" : "static/haotop/image/logo/Start.png",
|
||||
"xhdpi" : "static/haotop/image/logo/Start.png",
|
||||
"xxhdpi" : "static/haotop/image/logo/Start.png"
|
||||
"hdpi" : "src/static/haotop/image/logo/Start.png",
|
||||
"xhdpi" : "src/static/haotop/image/logo/Start.png",
|
||||
"xxhdpi" : "src/static/haotop/image/logo/Start.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import {
|
||||
hasFilterValue,
|
||||
normalizeAuditStatus,
|
||||
isActiveFilterState,
|
||||
normalizeFilterState,
|
||||
withSelectedOption,
|
||||
} from '@/common/listFilter';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
appliedFilters: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
hasActiveFilter() {
|
||||
return isActiveFilterState(this.appliedFilters || this.filters);
|
||||
},
|
||||
filterIconColor() {
|
||||
return this.hasActiveFilter ? '#2979ff' : '#666666';
|
||||
},
|
||||
statusSelectDefault() {
|
||||
const list = this.statusSelectList || [];
|
||||
const index = list.findIndex(
|
||||
(item) => String(item.value) === String(this.filters && this.filters.audit_status)
|
||||
);
|
||||
return [index >= 0 ? index : 0];
|
||||
},
|
||||
unitSelectDefault() {
|
||||
const selectedValue = this.getUnitSelectValue();
|
||||
const index = (this.unitSelectList || []).findIndex(
|
||||
(item) => String(item.value) === String(selectedValue)
|
||||
);
|
||||
return [index >= 0 ? index : 0];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hasFilterValue,
|
||||
getDefaultFilters() {
|
||||
return typeof this.createDefaultFilters === 'function'
|
||||
? this.createDefaultFilters()
|
||||
: {};
|
||||
},
|
||||
getUnitSelectValue() {
|
||||
const type = this.unitSelectType;
|
||||
const filters = this.filters || {};
|
||||
if (type === 'warehouse') return filters.warehouse_id;
|
||||
if (type === 'delivery') return filters.delivery_unit_id;
|
||||
if (type === 'business') return filters.business_unit_id;
|
||||
if (type === 'supplier') return filters.supplier_id;
|
||||
if (type === 'process') return filters.process_unit_id;
|
||||
return filters.process_unit_id || filters.supplier_id || filters.business_unit_id || '';
|
||||
},
|
||||
getUnitSelectLabel() {
|
||||
const type = this.unitSelectType;
|
||||
const filters = this.filters || {};
|
||||
if (type === 'warehouse') return filters.warehouse_name;
|
||||
if (type === 'delivery') return filters.delivery_unit_name;
|
||||
if (type === 'business') return filters.business_unit_name;
|
||||
if (type === 'supplier') return filters.supplier_name;
|
||||
if (type === 'process') return filters.process_name;
|
||||
return filters.process_name || filters.supplier_name || filters.business_unit_name || '';
|
||||
},
|
||||
restoreFilterCache() {
|
||||
const defaults = this.getDefaultFilters();
|
||||
try {
|
||||
const cached = this.filterStorageKey ? uni.getStorageSync(this.filterStorageKey) : null;
|
||||
if (!cached || typeof cached !== 'object' || Array.isArray(cached)) {
|
||||
this.filters = { ...defaults };
|
||||
this.appliedFilters = { ...defaults };
|
||||
this.saveFilterCache();
|
||||
return;
|
||||
}
|
||||
const filters = normalizeFilterState(cached, defaults);
|
||||
this.filters = { ...filters };
|
||||
this.appliedFilters = { ...filters };
|
||||
} catch (e) {
|
||||
this.filters = { ...defaults };
|
||||
this.appliedFilters = { ...defaults };
|
||||
}
|
||||
},
|
||||
saveFilterCache() {
|
||||
if (!this.filterStorageKey) return;
|
||||
try {
|
||||
uni.setStorageSync(this.filterStorageKey, { ...this.appliedFilters });
|
||||
} catch (e) {
|
||||
// 缓存失败不影响列表筛选
|
||||
}
|
||||
},
|
||||
commitFilters() {
|
||||
const filters = normalizeFilterState(this.filters, this.getDefaultFilters());
|
||||
this.filters = { ...filters };
|
||||
this.appliedFilters = { ...filters };
|
||||
this.saveFilterCache();
|
||||
this.loadList();
|
||||
},
|
||||
applySearch() {
|
||||
this.commitFilters();
|
||||
},
|
||||
cancelFilterEdit() {
|
||||
this.filters = { ...(this.appliedFilters || this.getDefaultFilters()) };
|
||||
},
|
||||
applyFilter() {
|
||||
this.commitFilters();
|
||||
this.filterShow = false;
|
||||
},
|
||||
resetFilter() {
|
||||
this.filters = this.getDefaultFilters();
|
||||
},
|
||||
onStatusConfirm(e) {
|
||||
this.filters.audit_status = normalizeAuditStatus((e[0] || {}).value);
|
||||
},
|
||||
applyUnitSelectList(list, preserveSelected = true) {
|
||||
const selectedValue = preserveSelected ? this.getUnitSelectValue() : '';
|
||||
const selectedLabel = preserveSelected ? this.getUnitSelectLabel() : '';
|
||||
this.unitSelectList = withSelectedOption(list, selectedValue, selectedLabel);
|
||||
},
|
||||
},
|
||||
};
|
||||
+173
-14
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"easycom": {
|
||||
"^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue"
|
||||
"^u-select$": "@/components/u-select/u-select.vue",
|
||||
"^u-action-sheet$": "@/components/u-action-sheet/u-action-sheet.vue",
|
||||
"^u-dropdown-item$": "@/components/u-dropdown-item/u-dropdown-item.vue",
|
||||
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
@@ -58,7 +61,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "uview-ui/components/u-avatar-cropper/u-avatar-cropper",
|
||||
"path": "../node_modules/uview-ui/components/u-avatar-cropper/u-avatar-cropper",
|
||||
"style": {
|
||||
"navigationBarTitleText": "头像裁剪",
|
||||
"navigationBarBackgroundColor": "#000000"
|
||||
@@ -148,6 +151,13 @@
|
||||
"navigationBarTitleText": "清除缓存"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/sys/user/scan-settings",
|
||||
"style": {
|
||||
"navigationBarTitleText": "扫描设置",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/sys/workbench/index",
|
||||
"style": {
|
||||
@@ -414,6 +424,118 @@
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
|
||||
},{
|
||||
"path" : "pages/storegoods/storeGoodsProcessInList",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "加工进仓列表",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},{
|
||||
"path" : "pages/storegoods/storeGoodsProcessInAdd",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "新增加工进仓单",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},{
|
||||
"path" : "pages/storegoods/storeGoodsProcessInEdit",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "编辑加工进仓单",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},{
|
||||
"path" : "pages/storegoods/storeGoodsProcessInView",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "查看加工进仓单",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},{
|
||||
"path" : "pages/storegoods/storeGoodsPurchaseInList",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "成品采购进仓",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},{
|
||||
"path" : "pages/storegoods/storeGoodsPurchaseInAdd",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "新增采购进仓单",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},{
|
||||
"path" : "pages/storegoods/storeGoodsPurchaseInEdit",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "编辑采购进仓单",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},{
|
||||
"path" : "pages/storegoods/storeGoodsPurchaseInView",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "采购进仓单详情",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},{
|
||||
"path" : "pages/storegoods/storeGoodsProcessReturnInList",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "加工退货进仓列表",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},{
|
||||
"path" : "pages/storegoods/storeGoodsProcessReturnInAdd",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "新增加工退货进仓单",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},{
|
||||
"path" : "pages/storegoods/storeGoodsProcessReturnInEdit",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "编辑加工退货进仓单",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},{
|
||||
"path" : "pages/storegoods/storeGoodsProcessReturnInView",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "查看加工退货进仓单",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},{
|
||||
"path" : "pages/storegoods/storeGoodsProcessOutList",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "加工出仓列表",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},{
|
||||
"path" : "pages/storegoods/storeGoodsProcessOutAdd",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "新增加工出仓单",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},{
|
||||
"path" : "pages/storegoods/storeGoodsProcessOutEdit",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "编辑加工出仓单",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},{
|
||||
"path" : "pages/storegoods/storeGoodsProcessOutView",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "查看加工出仓单",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},{
|
||||
"path" : "pages/storegoods/storeGoodsBusinessIn",
|
||||
"style" :
|
||||
@@ -562,6 +684,55 @@
|
||||
"navigationBarTitleText": "",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},{
|
||||
"path" : "pages/storefabric/storeFabricBusinessOutList",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "坯布出库列表",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},{
|
||||
"path" : "pages/storefabric/storeFabricBusinessOutAdd",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "新增坯布出仓单",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},{
|
||||
"path" : "pages/storefabric/storeFabricBusinessOutEdit",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "编辑坯布出仓单",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},{
|
||||
"path" : "pages/storefabric/storeFabricBusinessOutView",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "查看坯布出仓单",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},{
|
||||
"path" : "pages/storefabric/storeFabricWarehouseGroupList",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "坯布库存",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},{
|
||||
"path" : "pages/storefabric/storeFabricWarehouseStockList",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "坯布库存汇总",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},{
|
||||
"path" : "pages/storefabric/storeFabricWarehouseDetail",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText": "细码明细",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},{
|
||||
"path" : "pages/storefabric/storefabricstoresearch",
|
||||
"style" :
|
||||
@@ -744,12 +915,6 @@
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderStyle": "white",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/sys/msg/index",
|
||||
"iconPath": "static/aidex/tabbar/msg_1.png",
|
||||
"selectedIconPath": "static/aidex/tabbar/msg_2.png",
|
||||
"text": "消息"
|
||||
},
|
||||
// {
|
||||
// "pagePath": "pages/sys/home/index",
|
||||
// "iconPath": "static/aidex/tabbar/home_1.png",
|
||||
@@ -762,12 +927,6 @@
|
||||
"selectedIconPath": "static/aidex/tabbar/apply_2.png",
|
||||
"text": "工作台"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/sys/book/index",
|
||||
"iconPath": "static/aidex/tabbar/book_1.png",
|
||||
"selectedIconPath": "static/aidex/tabbar/book_2.png",
|
||||
"text": "通讯录"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/sys/user/index",
|
||||
"iconPath": "static/aidex/tabbar/my_1.png",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<view>
|
||||
<topDropdown :isSxShow="true" :dropdown1="dropdown1" dropdown2="上架中" :options1="options1" :options2="isSjList" :optionsSx="sxList" searchUrl="./searchAdmin" @dropDownFun1="dropDownFun1" @dropDownFun2="dropDownFun2" @optionSxFun="optionSxFun">
|
||||
</topDropdown>
|
||||
<dataNull v-if="list.length == 0" src="/static/img/chahua/dataNullXz.png" title="暂无相关产品"
|
||||
<dataNull v-if="list.length == 0" src="/src/static/img/chahua/dataNullXz.png" title="暂无相关产品"
|
||||
title1="请添加或者更换查询条件"></dataNull>
|
||||
<scroll-view v-else scroll-y="true" :style="{height: scrollHeight}" @scrolltolower="getChanPinFun"
|
||||
refresher-enabled :refresher-threshold="200" :refresher-triggered="triggered" refresher-background="gray"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<view>
|
||||
<dataNull v-if="list.length == 0" src="/static/img/chahua/dataNullXz.png" title="暂无相关分类"
|
||||
<dataNull v-if="list.length == 0" src="/src/static/img/chahua/dataNullXz.png" title="暂无相关分类"
|
||||
title1="点击添加按钮添加相关分类"></dataNull>
|
||||
<view v-else class="content">
|
||||
<view class="row" v-for="(row, index) in list" :key="index">
|
||||
|
||||
@@ -44,13 +44,13 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<dataNull v-else src="/static/img/chahua/dataNullXz.png" title="暂无产品参数说明"></dataNull>
|
||||
<dataNull v-else src="/src/static/img/chahua/dataNullXz.png" title="暂无产品参数说明"></dataNull>
|
||||
<view class="xqTitle">产品详情图</view>
|
||||
<view class="imgXQ" v-if="detail.cpXqtList && detail.cpXqtList.length > 0">
|
||||
<u-image width="100%" mode="widthFix" v-for="(item, index) in detail.cpXqtList" :key="index" :src="item.url"
|
||||
:lazy-load="true" @click="xqtClickFun(item.url, detail.cpXqtList)"></u-image>
|
||||
</view>
|
||||
<dataNull v-else src="/static/img/chahua/dataNullXz.png" title="暂无产品详情图"></dataNull>
|
||||
<dataNull v-else src="/src/static/img/chahua/dataNullXz.png" title="暂无产品详情图"></dataNull>
|
||||
<!--提交按钮-->
|
||||
<view v-if="!pageType" class="submitView">
|
||||
<u-button type="primary" class="submitBtn" :ripple="true" ripple-bg-color="#909399" @click="updateCpFun">修改产品</u-button>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<view>
|
||||
<searchView placeholderStr="请输入产品名称" @searchViewClickFun="searchEventsFun($event)"></searchView>
|
||||
<dataNull v-if="list.length == 0" src="/static/img/chahua/dataNullXz.png" title="暂无相关产品"
|
||||
<dataNull v-if="list.length == 0" src="/src/static/img/chahua/dataNullXz.png" title="暂无相关产品"
|
||||
title1="请更换查询条件"></dataNull>
|
||||
<scroll-view v-else scroll-y="true" :style="{height: scrollHeight}" @scrolltolower="getChanPinFun"
|
||||
refresher-enabled :refresher-threshold="200" :refresher-triggered="triggered" refresher-background="gray"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<view>
|
||||
<searchView placeholderStr="请输入产品名称" @searchViewClickFun="searchEventsFun($event)"></searchView>
|
||||
<dataNull v-if="list.length == 0" src="/static/img/chahua/dataNullXz.png" title="暂无相关产品"
|
||||
<dataNull v-if="list.length == 0" src="/src/static/img/chahua/dataNullXz.png" title="暂无相关产品"
|
||||
title1="请添加或者更换查询条件"></dataNull>
|
||||
<scroll-view v-else scroll-y="true" :style="{height: scrollHeight}" @scrolltolower="getChanPinFun"
|
||||
refresher-enabled :refresher-threshold="200" :refresher-triggered="triggered" refresher-background="gray"
|
||||
|
||||
@@ -57,12 +57,12 @@
|
||||
<getMore :isMore="tabBars[tabCurrentIndex].isMore" style="margin-bottom: 16px;"></getMore>
|
||||
<view style="height: 36rpx;"></view>
|
||||
</view>
|
||||
<dataNull v-else src="/static/img/chahua/dataNullXz.png" title="暂无相关产品" title1="请先添加">
|
||||
<dataNull v-else src="/src/static/img/chahua/dataNullXz.png" title="暂无相关产品" title1="请先添加">
|
||||
</dataNull>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<dataNull v-else src="/static/img/chahua/dataNullXz.png" title="暂无相关产品">
|
||||
<dataNull v-else src="/src/static/img/chahua/dataNullXz.png" title="暂无相关产品">
|
||||
</dataNull>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<getMore :isMore="isMore" nullMsg="已加载全部~"></getMore>
|
||||
<view class="h200"></view>
|
||||
</view>
|
||||
<dataNull v-else src="/static/img/dataNull.png" title="暂无相关客户哦~"></dataNull>
|
||||
<dataNull v-else src="/src/static/img/dataNull.png" title="暂无相关客户哦~"></dataNull>
|
||||
</scroll-view>
|
||||
<u-action-sheet :list="sheetList" v-model="moreShow"></u-action-sheet>
|
||||
</view>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<getMore :isMore="isMore" nullMsg="已加载全部~"></getMore>
|
||||
<view class="h200"></view>
|
||||
</view>
|
||||
<dataNull v-else src="/static/img/dataNull.png" title="暂无相关客户哦~"></dataNull>
|
||||
<dataNull v-else src="/src/static/img/dataNull.png" title="暂无相关客户哦~"></dataNull>
|
||||
</scroll-view>
|
||||
<u-action-sheet :list="sheetList" v-model="moreShow"></u-action-sheet>
|
||||
</view>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<getMore :isMore="isMore" nullMsg="已加载全部~"></getMore>
|
||||
<view class="h200"></view>
|
||||
</view>
|
||||
<dataNull v-else src="/static/img/dataNull.png" title="暂无相关营销部门哦~"></dataNull>
|
||||
<dataNull v-else src="/src/static/img/dataNull.png" title="暂无相关营销部门哦~"></dataNull>
|
||||
</scroll-view>
|
||||
<u-action-sheet :list="sheetList" v-model="moreShow"></u-action-sheet>
|
||||
</view>
|
||||
|
||||
@@ -58,10 +58,11 @@
|
||||
}
|
||||
|
||||
.u-cell_title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
min-width: 655rpx;
|
||||
}
|
||||
|
||||
.u-border-bottom:last-child:after {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<dataNull v-if="list.length == 0" src="/static/img/chahua/gjNull.png" title="暂无相关排产单" title1="请添加或者更换搜索添加">
|
||||
<dataNull v-if="list.length == 0" src="/src/static/img/chahua/gjNull.png" title="暂无相关排产单" title1="请添加或者更换搜索添加">
|
||||
</dataNull>
|
||||
<scroll-view v-else scroll-y="true" :style="{height: scrollHeight}" @scrolltolower="selectDataFun"
|
||||
refresher-enabled :refresher-threshold="200" :refresher-triggered="triggered" refresher-background="gray"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<view>
|
||||
<topDropdown :isSxShow="true" :isPaiXu="true" :dropdown1="dropdown1" :options1="options1" :optionsSx="sxList" :optionsPx="optionsPx" :isDuoXuan="true" searchLabel1="报价名称" searchPh1="请输入报价单名称" @searchBoxEmitFun="searchBoxEmitFun" @dropDownFun1="dropDownFun1" @optionSxFun="optionSxFun" @optionPxFun="optionPxFun" :isKhShow="true"></topDropdown>
|
||||
<dataNull v-if="list.length == 0" src="/static/img/chahua/gjNull.png" title="暂无相关合同" title1="请添加或者更换搜索添加"></dataNull>
|
||||
<dataNull v-if="list.length == 0" src="/src/static/img/chahua/gjNull.png" title="暂无相关合同" title1="请添加或者更换搜索添加"></dataNull>
|
||||
<scroll-view v-else scroll-y="true" :style="{height: scrollHeight}" @scrolltolower="selectDataFun"
|
||||
refresher-enabled :refresher-threshold="200" :refresher-triggered="triggered" refresher-background="gray" @refresherrefresh="onRefresh" @refresherrestore="onRestore">
|
||||
<view v-for="(item, index) in list" :key="index" @click="cardClickFun(item, index)">
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
:current="activeTabIndex" @change="onTabChange"></u-tabs>
|
||||
</div>
|
||||
</u-sticky>
|
||||
<dataNull v-if="ItemDataList.length == 0" src="/static/img/chahua/gjNull.png" title="暂无相关销售付运资料" title1="请添加或者更换搜索添加">
|
||||
<dataNull v-if="ItemDataList.length == 0" src="/src/static/img/chahua/gjNull.png" title="暂无相关销售付运资料" title1="请添加或者更换搜索添加">
|
||||
</dataNull>
|
||||
<scroll-view v-else scroll-y="true" :style="{height: scrollHeight}"
|
||||
refresher-enabled :refresher-threshold="200" :refresher-triggered="triggered" refresher-background="gray">
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
:current="activeTabIndex" @change="onTabChange"></u-tabs>
|
||||
</div>
|
||||
</u-sticky>
|
||||
<dataNull v-if="list.length == 0" src="/static/img/chahua/gjNull.png" title="暂无相关销售预约单" title1="请添加或者更换搜索添加">
|
||||
<dataNull v-if="list.length == 0" src="/src/static/img/chahua/gjNull.png" title="暂无相关销售预约单" title1="请添加或者更换搜索添加">
|
||||
</dataNull>
|
||||
<scroll-view v-else scroll-y="true" :style="{height: scrollHeight}" @scrolltolower="selectDataFun"
|
||||
refresher-enabled :refresher-threshold="200" :refresher-triggered="triggered" refresher-background="gray"
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<!-- 空状态展示 -->
|
||||
<view v-if="showEmpty" class="empty-state">
|
||||
<dataNull
|
||||
src="/static/img/chahua/gjNull.png"
|
||||
src="/src/static/img/chahua/gjNull.png"
|
||||
title="暂无相关配布单"
|
||||
title1="请添加或者更换搜索添加"
|
||||
></dataNull>
|
||||
|
||||
@@ -6,30 +6,36 @@
|
||||
<text class="title" style="width: 200px">日期:{{ BillDate }}</text>
|
||||
</u-form-item>
|
||||
<u-form-item>
|
||||
<text class="title" style="width: 200px"
|
||||
>客户名称:{{ CustomerName }}</text
|
||||
>
|
||||
<text class="title" style="width: 200px"
|
||||
>销 售 员:{{ SaleUserName }}</text
|
||||
>
|
||||
<text class="title" style="width: 200px">客户名称:{{ CustomerName }}</text>
|
||||
<text class="title" style="width: 200px">销 售 员:{{ SaleUserName }}</text>
|
||||
</u-form-item>
|
||||
<u-form-item>
|
||||
<text class="title" style="width: 200px"
|
||||
>仓库名称:{{ StoreName }}{{ ToStoreName }}</text
|
||||
>
|
||||
<text class="title" style="width: 200px">仓库名称:{{ StoreName }}{{ ToStoreName }}</text>
|
||||
</u-form-item>
|
||||
<u-form-item>
|
||||
<text class="title" style="width: 200px"
|
||||
>备注内容:{{ BillRemark }}</text
|
||||
>
|
||||
<text class="title" style="width: 200px">备注内容:{{ BillRemark }}</text>
|
||||
</u-form-item>
|
||||
<PlusBarcodePanel
|
||||
v-if="usePhoneScanFallback"
|
||||
:visible.sync="phoneScanPanelOpen"
|
||||
hint-text="连续扫码配布,扫完可继续下一条"
|
||||
:scan-time-space="1200"
|
||||
@success="onPlusBarcodeSuccess"
|
||||
/>
|
||||
<u-form-item label-width="130" label="条码资料:">
|
||||
<input
|
||||
type="text"
|
||||
v-model="QRBarCode"
|
||||
maxlength="-1"
|
||||
style="width: 170px"
|
||||
@confirm="SalePickBillDetailScan"
|
||||
<input type="text" v-model="QRBarCode" maxlength="-1" style="width: 170px" @confirm="SalePickBillDetailScan" />
|
||||
<text
|
||||
v-if="usePhoneScanFallback && phoneScanPanelOpen"
|
||||
class="phone-scan-close"
|
||||
@click.stop="handlePhoneScan"
|
||||
>关闭扫码</text>
|
||||
<u-icon
|
||||
v-else-if="usePhoneScanFallback"
|
||||
name="scan"
|
||||
size="44"
|
||||
color="#2979ff"
|
||||
style="margin-left: 8px;"
|
||||
@click="handlePhoneScan"
|
||||
/>
|
||||
<checkbox-group @change="handleAllCrockNoChange">
|
||||
<checkbox :checked="AllCrockNoScanStatus">整缸</checkbox>
|
||||
@@ -41,58 +47,41 @@
|
||||
</u-form>
|
||||
<u-form ref="uForm">
|
||||
<u-form-item>
|
||||
<text class="title" style="width: 400px"
|
||||
>成品名称:{{ FabricGoodsNo }}{{ FabricGoodsName }}</text
|
||||
>
|
||||
<text class="title" style="width: 400px">成品名称:{{ FabricGoodsNo }}{{ FabricGoodsName }}</text>
|
||||
</u-form-item>
|
||||
<u-form-item>
|
||||
<text class="title" style="width: 400px"
|
||||
>色号颜色:{{ GoodsCodeNo }}{{ GoodsCodeName }}</text
|
||||
>
|
||||
<text class="title" style="width: 400px">色号颜色:{{ GoodsCodeNo }}{{ GoodsCodeName }}</text>
|
||||
</u-form-item>
|
||||
<u-form-item>
|
||||
<text class="title" style="width: 200px">成品缸号:{{ CrockNo }}</text>
|
||||
<text class="title" style="width: 200px"
|
||||
>成品卷号:{{ GoodsBillNo }}</text
|
||||
>
|
||||
<text class="title" style="width: 200px">成品卷号:{{ GoodsBillNo }}</text>
|
||||
</u-form-item>
|
||||
<u-form-item>
|
||||
<text class="title" style="width: 200px">外部卷号:{{ ExternVolumeNumber }}</text>
|
||||
</u-form-item>
|
||||
<u-form-item>
|
||||
<text class="title">{{ BillDataMessage }}</text>
|
||||
</u-form-item>
|
||||
<u-form-item>
|
||||
<text class="title" style="width: 130px"
|
||||
>配布条数:{{ BillSumRoll }}</text
|
||||
>
|
||||
<text class="title" style="width: 130px">配布条数:{{ BillSumRoll }}</text>
|
||||
<text class="title" style="width: 130px">数量:{{ BillSumQty }}</text>
|
||||
<text class="title" style="width: 120px">米数:{{ BillSumMQty }}</text>
|
||||
</u-form-item>
|
||||
<u-form-item>
|
||||
<text class="title" style="width: 130px"
|
||||
>已配条数:{{ BillScanRoll }}</text
|
||||
>
|
||||
<text class="title" style="width: 130px">已配条数:{{ BillScanRoll }}</text>
|
||||
<text class="title" style="width: 130px">数量:{{ BillScanQty }}</text>
|
||||
<text class="title" style="width: 120px">米数:{{ BillScanMQty }}</text>
|
||||
</u-form-item>
|
||||
<view class="submitView">
|
||||
<u-button
|
||||
type="primary"
|
||||
class="submitBtn"
|
||||
:ripple="true"
|
||||
:loading="submitLoading"
|
||||
ripple-bg-color="#909399"
|
||||
@click="submitBtnFun"
|
||||
>
|
||||
<u-button type="primary" class="submitBtn" :ripple="true" :loading="submitLoading" ripple-bg-color="#909399"
|
||||
@click="submitBtnFun">
|
||||
{{ pageType ? "保存" : "提交" }}
|
||||
</u-button>
|
||||
</view>
|
||||
</u-form>
|
||||
<view class="u-demo-area">
|
||||
<u-toast ref="uToast"></u-toast>
|
||||
<wyb-table
|
||||
ref="table"
|
||||
:headers="headersMaster"
|
||||
:contents="GoodsDetailList"
|
||||
/>
|
||||
<wyb-table ref="table" :headers="headersMaster" :contents="GoodsDetailList" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -105,8 +94,12 @@ import util, {
|
||||
playErrorAudio,
|
||||
} from "../../common/util";
|
||||
import wybTable from "@/components/wyb-table/wyb-table.vue";
|
||||
import scanMixin from "@/common/scanMixin.js";
|
||||
import PlusBarcodePanel from "@/components/common/PlusBarcodePanel.vue";
|
||||
let that = "";
|
||||
export default {
|
||||
components: { PlusBarcodePanel },
|
||||
mixins: [scanMixin],
|
||||
data() {
|
||||
return {
|
||||
SaleBillNo: "", // 单号
|
||||
@@ -134,6 +127,7 @@ export default {
|
||||
GoodsCodeName: "",
|
||||
CrockNo: "",
|
||||
GoodsBillNo: "",
|
||||
ExternVolumeNumber: "",
|
||||
BillSumRoll: 0,
|
||||
BillSumQty: 0,
|
||||
BillSumMQty: 0,
|
||||
@@ -192,8 +186,7 @@ export default {
|
||||
],
|
||||
scanningInput: "", // 用于累积扫码输入
|
||||
lastKeyTime: 0, // 用于判断扫码速度
|
||||
scanReceiver: null,
|
||||
isPageActive: false, // 添加页面活动状态标志
|
||||
// scanReceiver 和 isPageActive 由 scanMixin 提供
|
||||
};
|
||||
},
|
||||
|
||||
@@ -214,34 +207,39 @@ export default {
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
// #ifdef APP-PLUS
|
||||
this.isPageActive = false;
|
||||
this.unregisterScanBroadcast();
|
||||
// #endif
|
||||
},
|
||||
|
||||
onHide() {
|
||||
// 页面隐藏时
|
||||
this.isPageActive = false;
|
||||
// #ifdef APP-PLUS
|
||||
this.unregisterScanBroadcast();
|
||||
// #endif
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// 页面显示时
|
||||
this.isPageActive = true;
|
||||
// #ifdef APP-PLUS
|
||||
this.registerScanBroadcast();
|
||||
// #endif
|
||||
this.registerScanBroadcast((scanResult) => {
|
||||
console.log("配布单详情-扫码结果:", scanResult);
|
||||
this.QRBarCode = scanResult;
|
||||
this.$nextTick(() => {
|
||||
this.handleScans();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
playSuccess() {
|
||||
util.playSuccessAudio();
|
||||
handlePhoneScan() {
|
||||
this.togglePhoneScanPanel();
|
||||
},
|
||||
playError() {
|
||||
util.playErrorAudio();
|
||||
onPlusBarcodeSuccess(scanResult) {
|
||||
this.handleScanResult(scanResult, this._scanCallback);
|
||||
},
|
||||
|
||||
playSuccess(text) {
|
||||
util.playSuccessAudio(text);
|
||||
},
|
||||
playError(text) {
|
||||
util.playErrorAudio(text);
|
||||
},
|
||||
|
||||
handleAllCrockNoChange() {
|
||||
@@ -308,7 +306,7 @@ export default {
|
||||
},
|
||||
// 添加通用错误提示方法
|
||||
showError(message) {
|
||||
this.playError();
|
||||
this.playError(message);
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: message,
|
||||
@@ -317,7 +315,7 @@ export default {
|
||||
},
|
||||
// 添加成功提示方法
|
||||
showSuccess(message) {
|
||||
this.playSuccess();
|
||||
this.playSuccess(message);
|
||||
uni.showToast({
|
||||
title: message,
|
||||
icon: 'success'
|
||||
@@ -383,13 +381,13 @@ export default {
|
||||
|
||||
if (res.data.code == 0 && res.data.msg == "success") {
|
||||
var aResultData = res.data.data;
|
||||
this.playSuccess();
|
||||
this.FabricGoodsNo = aResultData.product_code;
|
||||
this.FabricGoodsName = aResultData.product_name;
|
||||
this.GoodsCodeNo = aResultData.product_color_code;
|
||||
this.GoodsCodeName = aResultData.product_color_name;
|
||||
this.CrockNo = aResultData.dyelot_number;
|
||||
this.GoodsBillNo = aResultData.volume_number;
|
||||
this.ExternVolumeNumber = aResultData.extern_volume_number || '';
|
||||
this.QRBarCode = "";
|
||||
|
||||
if (aBarCodeDelStatus == 1) {
|
||||
@@ -412,160 +410,6 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
// 注册扫码广播接收器(支持商米和新大陆扫码头)
|
||||
registerScanBroadcast() {
|
||||
try {
|
||||
// 先尝试注册商米扫码头
|
||||
this.registerSunmiBroadcast()
|
||||
.then(() => {
|
||||
console.log('商米扫码头注册成功');
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('商米扫码头注册失败,尝试新大陆扫码头:', error);
|
||||
// 如果商米扫码头注册失败,尝试新大陆扫码头
|
||||
this.registerNewlandBroadcast();
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("注册扫码广播失败:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// 注册商米扫码头广播接收器
|
||||
registerSunmiBroadcast() {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const main = plus.android.runtimeMainActivity();
|
||||
const IntentFilter = plus.android.importClass("android.content.IntentFilter");
|
||||
|
||||
const filter = new IntentFilter();
|
||||
filter.addAction("com.sunmi.scanner.ACTION_DATA_CODE_RECEIVED");
|
||||
|
||||
const receiver = plus.android.implements(
|
||||
"io.dcloud.feature.internal.reflect.BroadcastReceiver",
|
||||
{
|
||||
onReceive: (context, intent) => {
|
||||
console.log('商米扫码广播接收,isPageActive:', this.isPageActive);
|
||||
// 只有当页面活动时才处理广播
|
||||
if (!this.isPageActive) return;
|
||||
|
||||
try {
|
||||
const scanResult = intent.getStringExtra("data");
|
||||
const sourceBytes = intent.getByteArrayExtra("source_byte");
|
||||
|
||||
console.log('商米扫码结果:', scanResult, sourceBytes);
|
||||
|
||||
if (scanResult) {
|
||||
this.handleScanResult(scanResult);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('处理商米广播数据时出错:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 注册广播接收器
|
||||
main.registerReceiver(receiver, filter);
|
||||
this.scanReceiver = receiver;
|
||||
console.log('商米扫码广播接收器注册成功');
|
||||
resolve();
|
||||
} catch (error) {
|
||||
console.error('注册商米广播接收器失败:', error);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 注册新大陆扫码头广播接收器
|
||||
registerNewlandBroadcast() {
|
||||
try {
|
||||
const main = plus.android.runtimeMainActivity();
|
||||
|
||||
// 先配置扫码枪广播设置
|
||||
try {
|
||||
const Intent = plus.android.importClass("android.content.Intent");
|
||||
const intent = new Intent("com.android.scanner.service_settings");
|
||||
intent.putExtra(
|
||||
"action_barcode_broadcast",
|
||||
"com.android.server.scannerservice.broadcast"
|
||||
);
|
||||
intent.putExtra("key_barcode_broadcast", "scannerdata");
|
||||
main.sendBroadcast(intent);
|
||||
console.log('新大陆扫码枪广播配置完成');
|
||||
} catch (error) {
|
||||
console.error("配置新大陆扫码枪广播失败:", error);
|
||||
}
|
||||
|
||||
// 注册广播接收器
|
||||
const IntentFilter = plus.android.importClass(
|
||||
"android.content.IntentFilter"
|
||||
);
|
||||
const filter = new IntentFilter();
|
||||
filter.addAction("com.android.server.scannerservice.broadcast");
|
||||
console.log("添加新大陆广播action完成");
|
||||
|
||||
const receiver = plus.android.implements(
|
||||
"io.dcloud.feature.internal.reflect.BroadcastReceiver",
|
||||
{
|
||||
onReceive: (context, intent) => {
|
||||
console.log('新大陆扫码广播接收,isPageActive:', this.isPageActive);
|
||||
// 只有当页面活动时才处理广播
|
||||
if (!this.isPageActive) return;
|
||||
|
||||
try {
|
||||
const scanResult = intent.getStringExtra("scannerdata");
|
||||
console.log("新大陆扫码结果:", scanResult);
|
||||
if (scanResult) {
|
||||
this.handleScanResult(scanResult);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("处理新大陆广播数据时出错:", error);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// 注册广播接收器
|
||||
main.registerReceiver(receiver, filter);
|
||||
this.scanReceiver = receiver;
|
||||
console.log("新大陆扫码广播注册成功,等待扫码...");
|
||||
} catch (error) {
|
||||
console.error("注册新大陆扫码广播失败:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// 处理扫码结果的统一方法
|
||||
handleScanResult(scanResult) {
|
||||
try {
|
||||
// 数据清理:去除空格、换行符等
|
||||
let cleanCode = scanResult.replace(/\s+/g, '').replace(/[\r\n]/g, '');
|
||||
|
||||
console.log("配布单详情-扫码结果:", cleanCode);
|
||||
|
||||
this.QRBarCode = cleanCode;
|
||||
this.$nextTick(() => {
|
||||
this.handleScans();
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("处理扫码结果时出错:", error);
|
||||
}
|
||||
},
|
||||
|
||||
// 注销扫码广播接收器
|
||||
unregisterScanBroadcast() {
|
||||
if (this.scanReceiver) {
|
||||
try {
|
||||
const main = plus.android.runtimeMainActivity();
|
||||
main.unregisterReceiver(this.scanReceiver);
|
||||
this.scanReceiver = null;
|
||||
console.log("扫码广播注销成功");
|
||||
} catch (error) {
|
||||
console.error("注销扫码广播失败:", error);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
SalePickBillDetailScan() {
|
||||
// 聚焦输入框触发的方法
|
||||
this.handleScans();
|
||||
@@ -573,7 +417,7 @@ export default {
|
||||
// 提交按钮方法
|
||||
submitBtnFun: function () {
|
||||
if (this.BillMasterID == 0) {
|
||||
this.playError();
|
||||
this.playError("单号为空,不能提交");
|
||||
this.BillDataMessage = "单号为空,不能提交";
|
||||
return;
|
||||
}
|
||||
@@ -610,15 +454,15 @@ export default {
|
||||
console.log('outFpmArrangeOrder res', res)
|
||||
if (res) {
|
||||
this.BillDataMessage = "成功生成出仓单!";
|
||||
this.playSuccess();
|
||||
this.playSuccess("提交成功");
|
||||
} else {
|
||||
this.playError();
|
||||
this.playError("提交生成出仓单出错");
|
||||
this.BillDataMessage = "提交生成出仓单出错!" + res.msg;
|
||||
return;
|
||||
}
|
||||
}).catch(error => {
|
||||
this.submitLoading = false
|
||||
this.playError();
|
||||
this.playError("连接服务器出错");
|
||||
this.BillDataMessage = "连接服务器出错,请检查后台服务是否启动!";
|
||||
});
|
||||
},
|
||||
@@ -627,6 +471,18 @@ export default {
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.phone-scan-close {
|
||||
margin-left: 8px;
|
||||
padding: 0 12px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
border: 1px solid #2979ff;
|
||||
border-radius: 4px;
|
||||
color: #2979ff;
|
||||
background: #eaf2ff;
|
||||
font-size: 34rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
page {
|
||||
background-color: #f8f8f8;
|
||||
padding-bottom: 260rpx;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
:current="activeTabIndex" @change="onTabChange"></u-tabs>
|
||||
</div>
|
||||
</u-sticky>
|
||||
<dataNull v-if="ItemDataList.length == 0" src="/static/img/chahua/gjNull.png" title="暂无相关出仓资料" title1="请添加或者更换搜索添加">
|
||||
<dataNull v-if="ItemDataList.length == 0" src="/src/static/img/chahua/gjNull.png" title="暂无相关出仓资料" title1="请添加或者更换搜索添加">
|
||||
</dataNull>
|
||||
<scroll-view v-else scroll-y="true" :style="{height: scrollHeight}"
|
||||
refresher-enabled :refresher-threshold="200" :refresher-triggered="triggered" refresher-background="gray">
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
<template>
|
||||
|
||||
<view class="fabric-out-page">
|
||||
|
||||
<OrderHeaderFold v-model="headerCollapsed" :summary="form.order_no || '保存后自动生成'">
|
||||
|
||||
<OrderStatusBar class="fabric-out-mt32" :status="displayAuditStatus" :name="displayAuditStatusName" />
|
||||
|
||||
|
||||
|
||||
<view class="flex-white-plr26 ptb20 bdb_f5">
|
||||
|
||||
<text class="mr26">出仓类型</text>
|
||||
|
||||
<view>{{ getBillTypeName() }}</view>
|
||||
|
||||
</view>
|
||||
|
||||
<view @click="pickerSelectFun('染厂')" class="flex-white-plr26 ptb20 bdb_f5">
|
||||
|
||||
<text class="mr26">染厂<text class="redXingh">*</text></text>
|
||||
|
||||
<view :class="form.business_unit_name ? '' : 'cBlack'">
|
||||
|
||||
{{ form.business_unit_name || '请选择' }}
|
||||
|
||||
<u-icon v-if="headerEditable" class="ml26" name="arrow-right" size="40" color="#888888"></u-icon>
|
||||
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
<view class="flex-white-plr26 ptb20 bdb_f5">
|
||||
|
||||
<text class="mr26">{{ dateLabel }}<text class="redXingh">*</text></text>
|
||||
|
||||
<picker mode="date" :value="form.delivery_time" :disabled="!headerEditable" @change="bindDateChange">
|
||||
|
||||
<view>{{ form.delivery_time }}</view>
|
||||
|
||||
</picker>
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
<view class="flex-white-plr26-column ptb20 mt32">
|
||||
|
||||
<view style="margin-bottom: 8rpx;"><text>单据备注</text></view>
|
||||
|
||||
<u-input v-model="form.remark" :disabled="!headerEditable" type="textarea" :border="true" :height="100" :auto-height="true" />
|
||||
|
||||
</view>
|
||||
|
||||
</OrderHeaderFold>
|
||||
|
||||
|
||||
|
||||
<FabricOutDetailList
|
||||
|
||||
:list="form.item_data"
|
||||
|
||||
@fabric-info="openFabricInfo"
|
||||
|
||||
@fine-code="openFineCode"
|
||||
|
||||
>
|
||||
|
||||
<PlusBarcodePanel
|
||||
slot="before-list"
|
||||
:visible.sync="phoneScanPanelOpen"
|
||||
hint-text="连续扫码出仓,扫完可继续下一条"
|
||||
:scan-time-space="1200"
|
||||
@success="onPlusBarcodeSuccess"
|
||||
/>
|
||||
<FabricOutScanBar
|
||||
slot="before-list"
|
||||
:can-scan="canScan"
|
||||
:value="QRBarCode"
|
||||
:delete-mode="BarCodeDelStatus"
|
||||
:message="BillDataMessage"
|
||||
disabled-tip="请先提交保存后再扫码"
|
||||
:show-phone-scan="usePhoneScanFallback"
|
||||
:scanning="phoneScanPanelOpen"
|
||||
@input="QRBarCode = $event"
|
||||
@scan="handleScan"
|
||||
@phone-scan="handlePhoneScan"
|
||||
@delete-mode-change="BarCodeDelChange"
|
||||
/>
|
||||
|
||||
<FabricOutScanDetail
|
||||
slot="before-list"
|
||||
:detail="scanDetail"
|
||||
:item-data="form.item_data"
|
||||
:group-keys="scanGroupKeys"
|
||||
@group-change="scanGroupKeys = $event"
|
||||
/>
|
||||
|
||||
</FabricOutDetailList>
|
||||
|
||||
|
||||
|
||||
<FabricOutFooter
|
||||
|
||||
v-if="!headerSaved"
|
||||
|
||||
:buttons="[{ label: '提交保存', action: 'save' }]"
|
||||
|
||||
single
|
||||
|
||||
@action="submitSave"
|
||||
|
||||
/>
|
||||
|
||||
<FabricOutFooter
|
||||
|
||||
v-else
|
||||
|
||||
:buttons="statusActionButtons"
|
||||
|
||||
@action="handleStatusAction"
|
||||
|
||||
/>
|
||||
|
||||
|
||||
|
||||
<u-select v-model="selectShow" :list="selectList" :remote="isRemoteSelect" :loading="selectSearching" @search="onSelectSearch" @confirm="selectConfirmFun"></u-select>
|
||||
|
||||
<FabricInfoPopup :show.sync="fabricInfoShow" :data="currentFabricInfo" />
|
||||
|
||||
<FineCodePopup :show.sync="fineCodeShow" :list="currentFineCodes" :readonly="false" @delete="onDeleteFineCode" />
|
||||
|
||||
</view>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
import formMixin from './storeFabricBusinessOutMixin.js';
|
||||
|
||||
import FabricInfoPopup from '@/components/storefabric/FabricInfoPopup.vue';
|
||||
|
||||
import FineCodePopup from '@/components/storefabric/FineCodePopup.vue';
|
||||
|
||||
import OrderStatusBar from '@/components/storefabric/OrderStatusBar.vue';
|
||||
|
||||
import OrderHeaderFold from '@/components/storefabric/OrderHeaderFold.vue';
|
||||
|
||||
import FabricOutDetailList from '@/components/storefabric/FabricOutDetailList.vue';
|
||||
|
||||
import FabricOutScanBar from '@/components/storefabric/FabricOutScanBar.vue';
|
||||
|
||||
import FabricOutScanDetail from '@/components/storefabric/FabricOutScanDetail.vue';
|
||||
|
||||
import FabricOutFooter from '@/components/storefabric/FabricOutFooter.vue';
|
||||
|
||||
import PlusBarcodePanel from '@/components/common/PlusBarcodePanel.vue';
|
||||
|
||||
|
||||
|
||||
export default {
|
||||
|
||||
components: {
|
||||
|
||||
PlusBarcodePanel,
|
||||
|
||||
FabricInfoPopup,
|
||||
|
||||
FineCodePopup,
|
||||
|
||||
OrderStatusBar,
|
||||
|
||||
OrderHeaderFold,
|
||||
|
||||
FabricOutDetailList,
|
||||
|
||||
FabricOutScanBar,
|
||||
|
||||
FabricOutScanDetail,
|
||||
|
||||
FabricOutFooter,
|
||||
|
||||
},
|
||||
|
||||
mixins: [formMixin],
|
||||
|
||||
onLoad() {
|
||||
|
||||
uni.setNavigationBarTitle({ title: '新增坯布出仓单' });
|
||||
|
||||
this.pageMode = 'add';
|
||||
|
||||
this.loadBaseDropdowns();
|
||||
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@import '@/pages/storefabric/storeFabricBusinessOutPage.scss';
|
||||
|
||||
page {
|
||||
background-color: #F8F8F8;
|
||||
padding-bottom: 260rpx;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<view class="fabric-out-page">
|
||||
<OrderHeaderFold v-model="headerCollapsed" :summary="form.order_no">
|
||||
<OrderStatusBar class="fabric-out-mt32" :status="displayAuditStatus" :name="displayAuditStatusName" />
|
||||
<view class="flex-white-plr26 ptb20 bdb_f5">
|
||||
<text class="mr26">出仓类型</text>
|
||||
<text>{{ getBillTypeName() }}</text>
|
||||
</view>
|
||||
<view class="flex-white-plr26 ptb20 bdb_f5">
|
||||
<text class="mr26">染厂</text>
|
||||
<text>{{ form.business_unit_name || '-' }}</text>
|
||||
</view>
|
||||
<view class="flex-white-plr26 ptb20 bdb_f5">
|
||||
<text class="mr26">{{ dateLabel }}</text>
|
||||
<text>{{ form.delivery_time }}</text>
|
||||
</view>
|
||||
<view class="flex-white-plr26 ptb20 bdb_f5">
|
||||
<text class="mr26">单据备注</text>
|
||||
<text>{{ form.remark || '-' }}</text>
|
||||
</view>
|
||||
</OrderHeaderFold>
|
||||
|
||||
<FabricOutDetailList
|
||||
:list="form.item_data"
|
||||
@fabric-info="openFabricInfo"
|
||||
@fine-code="openFineCode"
|
||||
>
|
||||
<PlusBarcodePanel
|
||||
slot="before-list"
|
||||
:visible.sync="phoneScanPanelOpen"
|
||||
hint-text="连续扫码出仓,扫完可继续下一条"
|
||||
:scan-time-space="1200"
|
||||
@success="onPlusBarcodeSuccess"
|
||||
/>
|
||||
<FabricOutScanBar
|
||||
slot="before-list"
|
||||
:can-scan="canScan"
|
||||
:value="QRBarCode"
|
||||
:delete-mode="BarCodeDelStatus"
|
||||
:message="BillDataMessage"
|
||||
:show-phone-scan="usePhoneScanFallback"
|
||||
:scanning="phoneScanPanelOpen"
|
||||
@input="QRBarCode = $event"
|
||||
@scan="handleScan"
|
||||
@phone-scan="handlePhoneScan"
|
||||
@delete-mode-change="BarCodeDelChange"
|
||||
/>
|
||||
<FabricOutScanDetail
|
||||
slot="before-list"
|
||||
:detail="scanDetail"
|
||||
:item-data="form.item_data"
|
||||
:group-keys="scanGroupKeys"
|
||||
@group-change="scanGroupKeys = $event"
|
||||
/>
|
||||
</FabricOutDetailList>
|
||||
|
||||
<FabricOutFooter :buttons="statusActionButtons" @action="handleStatusAction" />
|
||||
|
||||
<u-select v-model="selectShow" :list="selectList" :remote="isRemoteSelect" :loading="selectSearching" @search="onSelectSearch" @confirm="selectConfirmFun"></u-select>
|
||||
<FabricInfoPopup :show.sync="fabricInfoShow" :data="currentFabricInfo" />
|
||||
<FineCodePopup :show.sync="fineCodeShow" :list="currentFineCodes" :readonly="false" @delete="onDeleteFineCode" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import formMixin from './storeFabricBusinessOutMixin.js';
|
||||
import FabricInfoPopup from '@/components/storefabric/FabricInfoPopup.vue';
|
||||
import FineCodePopup from '@/components/storefabric/FineCodePopup.vue';
|
||||
import OrderStatusBar from '@/components/storefabric/OrderStatusBar.vue';
|
||||
import OrderHeaderFold from '@/components/storefabric/OrderHeaderFold.vue';
|
||||
import FabricOutDetailList from '@/components/storefabric/FabricOutDetailList.vue';
|
||||
import FabricOutScanBar from '@/components/storefabric/FabricOutScanBar.vue';
|
||||
import FabricOutScanDetail from '@/components/storefabric/FabricOutScanDetail.vue';
|
||||
import FabricOutFooter from '@/components/storefabric/FabricOutFooter.vue';
|
||||
import PlusBarcodePanel from '@/components/common/PlusBarcodePanel.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PlusBarcodePanel,
|
||||
FabricInfoPopup,
|
||||
FineCodePopup,
|
||||
OrderStatusBar,
|
||||
OrderHeaderFold,
|
||||
FabricOutDetailList,
|
||||
FabricOutScanBar,
|
||||
FabricOutScanDetail,
|
||||
FabricOutFooter,
|
||||
},
|
||||
mixins: [formMixin],
|
||||
data() {
|
||||
return {
|
||||
headerCollapsed: true,
|
||||
};
|
||||
},
|
||||
onLoad(e) {
|
||||
uni.setNavigationBarTitle({ title: '编辑坯布出仓单' });
|
||||
this.pageMode = 'edit';
|
||||
if (e.id) {
|
||||
this.loadOrder(Number(e.id), { rejectIfLocked: true });
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@/pages/storefabric/storeFabricBusinessOutPage.scss';
|
||||
page {
|
||||
background-color: #F8F8F8;
|
||||
padding-bottom: 260rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,449 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="filterBar">
|
||||
<u-search
|
||||
v-model="filters.order_no"
|
||||
placeholder="出仓单号"
|
||||
:show-action="false"
|
||||
@search="applySearch"
|
||||
@custom="applySearch"
|
||||
@clear="applySearch"
|
||||
/>
|
||||
<view
|
||||
class="filterBtn"
|
||||
:class="{ 'filter-active': hasActiveFilter }"
|
||||
@click="filterShow = true"
|
||||
>
|
||||
<view class="filterBtn__icon">
|
||||
<u-icon
|
||||
:key="hasActiveFilter ? 'on' : 'off'"
|
||||
name="list"
|
||||
size="44"
|
||||
:color="hasActiveFilter ? '#2979ff' : '#666'"
|
||||
></u-icon>
|
||||
<view v-if="hasActiveFilter" class="filterBtn__badge"></view>
|
||||
</view>
|
||||
<text>筛选</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view scroll-y :style="{ height: scrollHeight }" refresher-enabled
|
||||
:refresher-triggered="triggered" @refresherrefresh="onRefresh">
|
||||
<dataNull v-if="list.length === 0" src="/src/static/img/chahua/gjNull.png"
|
||||
title="暂无坯布出仓单" title1="请点击右下角按钮新增">
|
||||
</dataNull>
|
||||
<view v-for="(item, index) in list" :key="item.id" @click="goView(item)">
|
||||
<FabricOutItem :item="item" :index="index" />
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<addBtn url="./storeFabricBusinessOutAdd"></addBtn>
|
||||
|
||||
<u-popup v-model="filterShow" mode="right" width="580rpx" :safe-area-inset-bottom="true" @close="cancelFilterEdit">
|
||||
<view class="filterPanel">
|
||||
<view class="filterTitle">筛选条件</view>
|
||||
<view class="filterItem">
|
||||
<text class="filterLabel">出仓单号</text>
|
||||
<view class="filterInput">
|
||||
<u-input
|
||||
v-model="filters.order_no"
|
||||
placeholder="请输入出仓单号"
|
||||
:height="100"
|
||||
:custom-style="{ fontSize: '36rpx', color: '#202328' }"
|
||||
placeholder-style="color: #aeb4bf; font-size: 34rpx;"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filterItem">
|
||||
<text class="filterLabel">出仓日期</text>
|
||||
<view class="dateRow">
|
||||
<picker class="datePicker" mode="date" :value="filters.start_date" @change="e => filters.start_date = e.detail.value">
|
||||
<view class="dateInput" :class="{ 'dateInput--placeholder': !filters.start_date }">
|
||||
{{ filters.start_date || '开始日期' }}
|
||||
</view>
|
||||
</picker>
|
||||
<text class="dateSeparator">~</text>
|
||||
<picker class="datePicker" mode="date" :value="filters.end_date" @change="e => filters.end_date = e.detail.value">
|
||||
<view class="dateInput" :class="{ 'dateInput--placeholder': !filters.end_date }">
|
||||
{{ filters.end_date || '结束日期' }}
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<!-- <view class="filterItem" @click="openDeliveryUnitSelect">-->
|
||||
<!-- <text class="filterLabel">出仓单位</text>-->
|
||||
<!-- <view class="filterValue">{{ deliveryUnitLabel || '全部' }} <u-icon name="arrow-right" size="28"></u-icon></view>-->
|
||||
<!-- </view>-->
|
||||
<view class="filterItem" @click="openBusinessUnitSelect">
|
||||
<text class="filterLabel">染厂</text>
|
||||
<view class="filterValue">{{ businessUnitLabel || '全部' }} <u-icon name="arrow-right" size="36"></u-icon></view>
|
||||
</view>
|
||||
<view class="filterItem" @click="openStatusSelect">
|
||||
<text class="filterLabel">订单状态</text>
|
||||
<view class="filterValue">{{ statusLabel || '全部' }} <u-icon name="arrow-right" size="36"></u-icon></view>
|
||||
</view>
|
||||
<view class="filterActions">
|
||||
<u-button type="warning" size="medium" @click="resetFilter">重置</u-button>
|
||||
<u-button type="primary" size="medium" @click="applyFilter">确定</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
|
||||
<u-select
|
||||
class="pdaSelect"
|
||||
v-model="statusSelectShow"
|
||||
title="选择订单状态"
|
||||
:list="statusSelectList"
|
||||
:default-value="statusSelectDefault"
|
||||
@confirm="onStatusConfirm"
|
||||
></u-select>
|
||||
<u-select
|
||||
class="pdaSelect"
|
||||
v-model="unitSelectShow"
|
||||
title="选择染厂"
|
||||
:list="unitSelectList"
|
||||
:default-value="unitSelectDefault"
|
||||
remote
|
||||
:loading="selectSearching"
|
||||
@search="onUnitSearch"
|
||||
@confirm="onUnitConfirm"
|
||||
></u-select>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import addBtn from '@/components/addBtn/addBtn.vue';
|
||||
import FabricOutItem from '@/components/card/storeFabricBusinessOutItem.vue';
|
||||
import {
|
||||
getStatusOptions,
|
||||
AUDIT_STATUS,
|
||||
AUDIT_STATUS_MAP,
|
||||
BILL_TYPES,
|
||||
BUSINESS_UNIT_TYPE_DYE_FACTORY,
|
||||
formatRoll,
|
||||
mapToSelectOptions,
|
||||
fetchAllOrderList,
|
||||
} from '@/common/storeFabricBusinessOut';
|
||||
import { withNameParam } from '@/common/remoteSelect';
|
||||
|
||||
const FILTER_STORAGE_KEY = 'storeFabricBusinessOutListFilters_v2';
|
||||
|
||||
const hasFilterValue = (value) => value !== '' && value !== null && value !== undefined;
|
||||
|
||||
const normalizeAuditStatus = (value) => {
|
||||
if (!hasFilterValue(value)) return '';
|
||||
const status = Number(value);
|
||||
return Number.isNaN(status) ? '' : status;
|
||||
};
|
||||
|
||||
const createDefaultFilters = () => ({
|
||||
order_no: '',
|
||||
start_date: '',
|
||||
end_date: '',
|
||||
delivery_unit_id: '',
|
||||
delivery_unit_name: '',
|
||||
business_unit_id: '',
|
||||
business_unit_name: '',
|
||||
audit_status: AUDIT_STATUS.PENDING,
|
||||
});
|
||||
|
||||
const normalizeFilters = (value) => {
|
||||
const filters = createDefaultFilters();
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) return filters;
|
||||
|
||||
Object.keys(filters).forEach((key) => {
|
||||
if (key === 'audit_status') return;
|
||||
if (typeof value[key] === 'string' || typeof value[key] === 'number') {
|
||||
filters[key] = value[key];
|
||||
}
|
||||
});
|
||||
if (!filters.delivery_unit_id) filters.delivery_unit_name = '';
|
||||
if (!filters.business_unit_id) filters.business_unit_name = '';
|
||||
if (Object.prototype.hasOwnProperty.call(value, 'audit_status')) {
|
||||
filters.audit_status = normalizeAuditStatus(value.audit_status);
|
||||
}
|
||||
return filters;
|
||||
};
|
||||
|
||||
export default {
|
||||
components: { addBtn, FabricOutItem },
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
scrollHeight: '667px',
|
||||
triggered: false,
|
||||
filterShow: false,
|
||||
statusSelectShow: false,
|
||||
unitSelectShow: false,
|
||||
unitSelectType: '',
|
||||
deliveryUnitOptions: [],
|
||||
businessUnitOptions: [],
|
||||
unitOptionsLoading: false,
|
||||
selectSearching: false,
|
||||
unitSearchSeq: 0,
|
||||
unitSelectList: [{ value: '', label: '全部' }],
|
||||
billTypeName: (BILL_TYPES[0] || {}).label || '坯布其他出货单',
|
||||
filters: createDefaultFilters(),
|
||||
appliedFilters: createDefaultFilters(),
|
||||
statusSelectList: [
|
||||
{ value: '', label: '全部' },
|
||||
...getStatusOptions(),
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
statusLabel() {
|
||||
if (this.filters.audit_status === '' || this.filters.audit_status === null) return '';
|
||||
return AUDIT_STATUS_MAP[Number(this.filters.audit_status)] || '';
|
||||
},
|
||||
deliveryUnitLabel() {
|
||||
return this.filters.delivery_unit_name || '';
|
||||
},
|
||||
businessUnitLabel() {
|
||||
return this.filters.business_unit_name || '';
|
||||
},
|
||||
statusSelectDefault() {
|
||||
const index = this.statusSelectList.findIndex(
|
||||
(item) => String(item.value) === String(this.filters.audit_status)
|
||||
);
|
||||
return [index >= 0 ? index : 0];
|
||||
},
|
||||
unitSelectDefault() {
|
||||
const selectedValue = this.unitSelectType === 'delivery'
|
||||
? this.filters.delivery_unit_id
|
||||
: this.filters.business_unit_id;
|
||||
const index = this.unitSelectList.findIndex(
|
||||
(item) => String(item.value) === String(selectedValue)
|
||||
);
|
||||
return [index >= 0 ? index : 0];
|
||||
},
|
||||
hasActiveFilter() {
|
||||
const filters = this.appliedFilters || {};
|
||||
return Boolean(
|
||||
String(filters.order_no || '').trim()
|
||||
|| filters.start_date
|
||||
|| filters.end_date
|
||||
|| filters.delivery_unit_id
|
||||
|| filters.business_unit_id
|
||||
|| hasFilterValue(filters.audit_status)
|
||||
);
|
||||
},
|
||||
},
|
||||
onLoad() {
|
||||
uni.setNavigationBarTitle({ title: '坯布出库列表' });
|
||||
this.restoreFilterCache();
|
||||
uni.getSystemInfo({
|
||||
success: (res) => {
|
||||
this.scrollHeight = `${res.windowHeight - 50}px`;
|
||||
},
|
||||
});
|
||||
},
|
||||
onShow() {
|
||||
this.loadList();
|
||||
},
|
||||
methods: {
|
||||
restoreFilterCache() {
|
||||
try {
|
||||
const cached = uni.getStorageSync(FILTER_STORAGE_KEY);
|
||||
if (!cached || typeof cached !== 'object' || Array.isArray(cached)) {
|
||||
const filters = createDefaultFilters();
|
||||
this.filters = { ...filters };
|
||||
this.appliedFilters = { ...filters };
|
||||
this.saveFilterCache();
|
||||
return;
|
||||
}
|
||||
const filters = normalizeFilters(cached);
|
||||
this.filters = { ...filters };
|
||||
this.appliedFilters = { ...filters };
|
||||
} catch (e) {
|
||||
const filters = createDefaultFilters();
|
||||
this.filters = { ...filters };
|
||||
this.appliedFilters = { ...filters };
|
||||
}
|
||||
},
|
||||
saveFilterCache() {
|
||||
try {
|
||||
uni.setStorageSync(FILTER_STORAGE_KEY, { ...this.appliedFilters });
|
||||
} catch (e) {
|
||||
// 缓存失败不影响列表筛选
|
||||
}
|
||||
},
|
||||
commitFilters() {
|
||||
const filters = normalizeFilters(this.filters);
|
||||
this.filters = { ...filters };
|
||||
this.appliedFilters = { ...filters };
|
||||
this.saveFilterCache();
|
||||
this.loadList();
|
||||
},
|
||||
applySearch() {
|
||||
this.commitFilters();
|
||||
},
|
||||
cancelFilterEdit() {
|
||||
this.filters = { ...this.appliedFilters };
|
||||
},
|
||||
loadDeliveryUnitOptions() {
|
||||
if (this.deliveryUnitOptions.length) return Promise.resolve(this.deliveryUnitOptions);
|
||||
return this.$u.api.businessUnit.list({})
|
||||
.then((res) => {
|
||||
this.deliveryUnitOptions = mapToSelectOptions(res);
|
||||
return this.deliveryUnitOptions;
|
||||
})
|
||||
.catch(() => {
|
||||
this.deliveryUnitOptions = [];
|
||||
return this.deliveryUnitOptions;
|
||||
});
|
||||
},
|
||||
loadBusinessUnitOptions() {
|
||||
if (this.businessUnitOptions.length) return Promise.resolve(this.businessUnitOptions);
|
||||
return this.$u.api.businessUnit.list({ unit_type_id: BUSINESS_UNIT_TYPE_DYE_FACTORY })
|
||||
.then((res) => {
|
||||
this.businessUnitOptions = mapToSelectOptions(res);
|
||||
return this.businessUnitOptions;
|
||||
})
|
||||
.catch(() => {
|
||||
this.businessUnitOptions = [];
|
||||
return this.businessUnitOptions;
|
||||
});
|
||||
},
|
||||
applyUnitSelectList(list, preserveSelected = true) {
|
||||
const rows = Array.isArray(list) ? list.slice() : [];
|
||||
if (preserveSelected) {
|
||||
const isDelivery = this.unitSelectType === 'delivery';
|
||||
const selectedValue = isDelivery
|
||||
? this.filters.delivery_unit_id
|
||||
: this.filters.business_unit_id;
|
||||
const selectedLabel = isDelivery
|
||||
? this.filters.delivery_unit_name
|
||||
: this.filters.business_unit_name;
|
||||
const selectedExists = rows.some(
|
||||
(item) => String(item.value) === String(selectedValue)
|
||||
);
|
||||
if (selectedValue !== '' && selectedValue !== null && selectedValue !== undefined && !selectedExists) {
|
||||
rows.unshift({ value: selectedValue, label: selectedLabel || String(selectedValue) });
|
||||
}
|
||||
}
|
||||
this.unitSelectList = [{ value: '', label: '全部' }].concat(rows);
|
||||
},
|
||||
openUnitSelect(type) {
|
||||
this.unitSelectType = type;
|
||||
const cache = type === 'delivery' ? this.deliveryUnitOptions : this.businessUnitOptions;
|
||||
this.applyUnitSelectList(cache);
|
||||
this.unitSelectShow = true;
|
||||
},
|
||||
onUnitSearch(keyword) {
|
||||
const seq = ++this.unitSearchSeq;
|
||||
const isDelivery = this.unitSelectType === 'delivery';
|
||||
const empty = !String(keyword || '').trim();
|
||||
this.selectSearching = true;
|
||||
const params = isDelivery ? {} : { unit_type_id: BUSINESS_UNIT_TYPE_DYE_FACTORY };
|
||||
this.$u.api.businessUnit.list(withNameParam(params, keyword))
|
||||
.then((res) => {
|
||||
if (seq !== this.unitSearchSeq) return;
|
||||
const list = mapToSelectOptions(res);
|
||||
if (empty) {
|
||||
if (isDelivery) this.deliveryUnitOptions = list;
|
||||
else this.businessUnitOptions = list;
|
||||
}
|
||||
this.applyUnitSelectList(list, empty);
|
||||
})
|
||||
.catch((e) => {
|
||||
if (seq !== this.unitSearchSeq) return;
|
||||
uni.showToast({ title: (e && e.message) || '搜索失败', icon: 'none' });
|
||||
})
|
||||
.then(() => {
|
||||
if (seq === this.unitSearchSeq) this.selectSearching = false;
|
||||
}, () => {
|
||||
if (seq === this.unitSearchSeq) this.selectSearching = false;
|
||||
});
|
||||
},
|
||||
openDeliveryUnitSelect() {
|
||||
this.openUnitSelect('delivery');
|
||||
},
|
||||
openBusinessUnitSelect() {
|
||||
this.openUnitSelect('business');
|
||||
},
|
||||
onUnitConfirm(e) {
|
||||
const item = e[0] || {};
|
||||
if (this.unitSelectType === 'delivery') {
|
||||
this.filters.delivery_unit_id = item.value;
|
||||
this.filters.delivery_unit_name = item.value ? item.label : '';
|
||||
} else {
|
||||
this.filters.business_unit_id = item.value;
|
||||
this.filters.business_unit_name = item.value ? item.label : '';
|
||||
}
|
||||
},
|
||||
loadList() {
|
||||
const filters = this.appliedFilters;
|
||||
const params = {};
|
||||
if (filters.order_no) params.order_no = filters.order_no;
|
||||
if (hasFilterValue(filters.audit_status)) {
|
||||
params.audit_status = filters.audit_status;
|
||||
}
|
||||
fetchAllOrderList((p) => this.$u.api.gfmOtherDeliveryOrder.list(p), params)
|
||||
.then((rows) => {
|
||||
this.triggered = false;
|
||||
this.list = rows.map(this.mapToCard).filter(this.matchClientFilter);
|
||||
})
|
||||
.catch((err) => {
|
||||
this.triggered = false;
|
||||
this.list = [];
|
||||
uni.showToast({ title: err.message || '加载失败', icon: 'none' });
|
||||
});
|
||||
},
|
||||
mapToCard(order) {
|
||||
return {
|
||||
id: order.id,
|
||||
order_no: order.order_no || '',
|
||||
out_date: (order.delivery_time || '').slice(0, 10),
|
||||
bill_type_name: this.billTypeName,
|
||||
delivery_unit_id: order.delivery_unit_id || '',
|
||||
delivery_unit_name: order.delivery_unit_name || '',
|
||||
business_unit_id: order.business_unit_id || '',
|
||||
business_unit_name: order.business_unit_name || '',
|
||||
create_user_name: order.creator_name || '',
|
||||
create_time: order.create_time || '',
|
||||
auditor_name: order.auditer_name || '',
|
||||
audit_date: order.audit_time || '',
|
||||
audit_status: order.audit_status,
|
||||
audit_status_name: order.audit_status_name || AUDIT_STATUS_MAP[order.audit_status] || '',
|
||||
total_roll: formatRoll(order.total_roll),
|
||||
};
|
||||
},
|
||||
// 后端列表仅支持 单号/状态 过滤,出仓日期与单位在前端做本地过滤
|
||||
matchClientFilter(item) {
|
||||
const filters = this.appliedFilters;
|
||||
if (filters.start_date && item.out_date < filters.start_date) return false;
|
||||
if (filters.end_date && item.out_date > filters.end_date) return false;
|
||||
if (filters.delivery_unit_id && String(item.delivery_unit_id) !== String(filters.delivery_unit_id)) {
|
||||
return false;
|
||||
}
|
||||
if (filters.business_unit_id && String(item.business_unit_id) !== String(filters.business_unit_id)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
onRefresh() {
|
||||
this.triggered = true;
|
||||
this.loadList();
|
||||
},
|
||||
goView(item) {
|
||||
uni.navigateTo({ url: `./storeFabricBusinessOutView?id=${item.id}` });
|
||||
},
|
||||
openStatusSelect() { this.statusSelectShow = true; },
|
||||
onStatusConfirm(e) {
|
||||
this.filters.audit_status = normalizeAuditStatus((e[0] || {}).value);
|
||||
},
|
||||
resetFilter() {
|
||||
this.filters = createDefaultFilters();
|
||||
},
|
||||
applyFilter() {
|
||||
this.commitFilters();
|
||||
this.filterShow = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@/common/list-filter.scss';
|
||||
</style>
|
||||
@@ -0,0 +1,423 @@
|
||||
/**
|
||||
* 坯布出仓单(其他出货单)表单 mixin(新增/编辑共用)
|
||||
*
|
||||
* 业务说明:
|
||||
* 1. 后端当前仅提供「坯布其他出货单」一种类型的接口,故出仓类型固定为 other,
|
||||
* 表头保留染厂(business_unit) / 出货日期 / 备注 等字段。
|
||||
* 2. 用户先填写表头并「提交保存」(addGfmOtherDeliveryOrder),拿到出仓单 id 后即可扫码;
|
||||
* 扫码新增/删除细码走 updateGfmOtherDeliveryOrder;勾选锁定维度时附带 *_lock 字段,操作后用返回的最新详情刷新。
|
||||
* 3. 后端没有「修改表头」接口,保存成功或编辑进入后表头变为只读,仅可扫码维护细码。
|
||||
*/
|
||||
import util from '@/common/util';
|
||||
import scanMixin from '@/common/scanMixin.js';
|
||||
import PlusBarcodePanel from '@/components/common/PlusBarcodePanel.vue';
|
||||
import remoteSelectMixin from '@/common/remoteSelectMixin.js';
|
||||
import { withNameParam } from '@/common/remoteSelect';
|
||||
import { initTts, speak } from '@/common/tts.js';
|
||||
import {
|
||||
AUDIT_STATUS_MAP,
|
||||
BILL_TYPES,
|
||||
BUSINESS_UNIT_TYPE_DYE_FACTORY,
|
||||
normalizeOrder,
|
||||
mapToSelectOptions,
|
||||
getFineCodeScanCode,
|
||||
canScanAtStatus,
|
||||
isLockedOrderStatus,
|
||||
getFormStatusActionButtons,
|
||||
orderToFormFields,
|
||||
confirmOrderStatusAction,
|
||||
DRAFT_AUDIT_STATUS,
|
||||
DRAFT_AUDIT_STATUS_NAME,
|
||||
getEmptyScanDetail,
|
||||
findScanContext,
|
||||
buildScanDetailFromContext,
|
||||
buildScanDetailFromOrderResponse,
|
||||
buildScanUpdateParams,
|
||||
getOrderDisplayWeight,
|
||||
getFineCodeDisplayWeight,
|
||||
resolveScanMismatchVoice,
|
||||
} from '@/common/storeFabricBusinessOut';
|
||||
|
||||
export default {
|
||||
components: { PlusBarcodePanel },
|
||||
mixins: [scanMixin, remoteSelectMixin],
|
||||
data() {
|
||||
return {
|
||||
pageMode: 'add',
|
||||
orderId: 0,
|
||||
form: this.getEmptyForm(),
|
||||
headerSaved: false,
|
||||
headerCollapsed: false,
|
||||
selectShow: false,
|
||||
selectList: [],
|
||||
selectType: '',
|
||||
canScan: false,
|
||||
QRBarCode: '',
|
||||
BarCodeDelStatus: false,
|
||||
BillDataMessage: '',
|
||||
currentScanItem: null,
|
||||
fabricInfoShow: false,
|
||||
fineCodeShow: false,
|
||||
currentFabricInfo: {},
|
||||
currentFineCodes: [],
|
||||
currentGroupKey: '',
|
||||
scanDetail: getEmptyScanDetail(),
|
||||
scanGroupKeys: [],
|
||||
lastScanCode: '',
|
||||
billTypeName: (BILL_TYPES[0] || {}).label || '坯布其他出货单',
|
||||
auditStatus: 0,
|
||||
businessUnitOptions: [],
|
||||
dropdownLoading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isReadonly() {
|
||||
return this.pageMode === 'view';
|
||||
},
|
||||
isEditable() {
|
||||
return this.pageMode === 'add' || this.pageMode === 'edit';
|
||||
},
|
||||
// 表头是否可编辑:仅新增且未保存时可编辑(后端无修改表头接口)
|
||||
headerEditable() {
|
||||
return this.pageMode === 'add' && !this.headerSaved;
|
||||
},
|
||||
dateLabel() {
|
||||
return '出货日期';
|
||||
},
|
||||
/** 表头保存后,待审核/已驳回状态下显示审核、作废 */
|
||||
statusActionButtons() {
|
||||
return getFormStatusActionButtons(this.headerSaved, this.orderId, this.auditStatus);
|
||||
},
|
||||
displayAuditStatus() {
|
||||
if (this.pageMode === 'add' && !this.headerSaved) return DRAFT_AUDIT_STATUS;
|
||||
return this.auditStatus;
|
||||
},
|
||||
displayAuditStatusName() {
|
||||
if (this.pageMode === 'add' && !this.headerSaved) return DRAFT_AUDIT_STATUS_NAME;
|
||||
return AUDIT_STATUS_MAP[this.auditStatus] || '';
|
||||
},
|
||||
},
|
||||
onShow() {
|
||||
this.isPageActive = true;
|
||||
initTts();
|
||||
if (this.canScan && this.isEditable) {
|
||||
this.registerScanBroadcast((scanResult) => {
|
||||
this.QRBarCode = scanResult.trim().replace(/[\r\n]/g, '');
|
||||
this.$nextTick(() => this.handleScan());
|
||||
});
|
||||
}
|
||||
},
|
||||
onHide() {
|
||||
this.isPageActive = false;
|
||||
this.unregisterScanBroadcast();
|
||||
},
|
||||
onUnload() {
|
||||
this.isPageActive = false;
|
||||
this.unregisterScanBroadcast();
|
||||
},
|
||||
methods: {
|
||||
handlePhoneScan() {
|
||||
if (!this.canScan) {
|
||||
this.showError('请先提交保存后再扫码');
|
||||
return;
|
||||
}
|
||||
this.togglePhoneScanPanel();
|
||||
},
|
||||
onPlusBarcodeSuccess(scanResult) {
|
||||
this.QRBarCode = String(scanResult || '').trim().replace(/[\r\n]/g, '');
|
||||
return this.handleScan();
|
||||
},
|
||||
registerOrderScanBroadcast() {
|
||||
this.registerScanBroadcast((scanResult) => {
|
||||
this.QRBarCode = scanResult.trim().replace(/[\r\n]/g, '');
|
||||
this.$nextTick(() => this.handleScan());
|
||||
});
|
||||
},
|
||||
syncCanScanFromStatus(status) {
|
||||
const nextCanScan = this.isEditable && canScanAtStatus(status);
|
||||
if (this.canScan && !nextCanScan) {
|
||||
this.unregisterScanBroadcast();
|
||||
this.closePhoneScanPanel();
|
||||
}
|
||||
this.canScan = nextCanScan;
|
||||
},
|
||||
loadBaseDropdowns() {
|
||||
if (this.dropdownLoading) return Promise.resolve();
|
||||
if (this.businessUnitOptions.length) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
this.dropdownLoading = true;
|
||||
return this.ensureBusinessUnitOptions().finally(() => {
|
||||
this.dropdownLoading = false;
|
||||
});
|
||||
},
|
||||
ensureBusinessUnitOptions() {
|
||||
if (this.businessUnitOptions.length) return Promise.resolve(this.businessUnitOptions);
|
||||
return this.$u.api.businessUnit.list({ unit_type_id: BUSINESS_UNIT_TYPE_DYE_FACTORY })
|
||||
.then((res) => {
|
||||
this.businessUnitOptions = mapToSelectOptions(res);
|
||||
return this.businessUnitOptions;
|
||||
});
|
||||
},
|
||||
fetchRemoteSelectOptions(type, keyword) {
|
||||
if (type !== '染厂') return Promise.resolve([]);
|
||||
return this.$u.api.businessUnit.list(withNameParam({ unit_type_id: BUSINESS_UNIT_TYPE_DYE_FACTORY }, keyword))
|
||||
.then((res) => {
|
||||
const list = mapToSelectOptions(res);
|
||||
if (!String(keyword || '').trim()) this.businessUnitOptions = list;
|
||||
return list;
|
||||
});
|
||||
},
|
||||
getEmptyForm() {
|
||||
return {
|
||||
order_no: '',
|
||||
business_unit_id: '',
|
||||
business_unit_name: '',
|
||||
delivery_time: this.$u.timeFormat(new Date(), 'yyyy-mm-dd'),
|
||||
remark: '',
|
||||
item_data: [],
|
||||
};
|
||||
},
|
||||
loadOrder(id, options = {}) {
|
||||
const { rejectIfLocked = false } = options;
|
||||
return this.$u.api.gfmOtherDeliveryOrder.detail({ id })
|
||||
.then((res) => {
|
||||
const order = normalizeOrder(res);
|
||||
if (!order) {
|
||||
this.showError('单据不存在');
|
||||
return null;
|
||||
}
|
||||
if (rejectIfLocked && isLockedOrderStatus(order.audit_status)) {
|
||||
this.showError('当前状态不可编辑');
|
||||
setTimeout(() => uni.navigateBack(), 1500);
|
||||
return null;
|
||||
}
|
||||
this.orderId = order.id;
|
||||
this.auditStatus = order.audit_status;
|
||||
this.headerSaved = true;
|
||||
this.headerCollapsed = true;
|
||||
this.form = orderToFormFields(order);
|
||||
this.refreshScanDetailTotals(order);
|
||||
this.syncCanScanFromStatus(order.audit_status);
|
||||
if (this.canScan) this.registerOrderScanBroadcast();
|
||||
return order;
|
||||
})
|
||||
.catch((e) => {
|
||||
this.showError(e.message || '加载单据失败');
|
||||
return null;
|
||||
});
|
||||
},
|
||||
pickerSelectFun(type) {
|
||||
if (!this.headerEditable) return;
|
||||
if (type === '染厂') {
|
||||
this.openSelectPicker(type, this.businessUnitOptions);
|
||||
return;
|
||||
}
|
||||
},
|
||||
selectConfirmFun(e) {
|
||||
const item = e[0];
|
||||
if (this.selectType === '染厂') {
|
||||
this.form.business_unit_id = item.value;
|
||||
this.form.business_unit_name = item.label;
|
||||
}
|
||||
},
|
||||
bindDateChange(e) {
|
||||
this.form.delivery_time = e.detail.value;
|
||||
},
|
||||
validateForm() {
|
||||
if (!this.form.business_unit_id) return '请选择染厂';
|
||||
if (!this.form.delivery_time) return '请选择出货日期';
|
||||
return '';
|
||||
},
|
||||
buildAddParam() {
|
||||
return {
|
||||
business_unit_id: Number(this.form.business_unit_id),
|
||||
delivery_time: this.form.delivery_time,
|
||||
remark: this.form.remark || '',
|
||||
};
|
||||
},
|
||||
submitSave() {
|
||||
if (this.headerSaved) {
|
||||
this.showError('表头已保存,请直接扫码维护细码');
|
||||
return;
|
||||
}
|
||||
const err = this.validateForm();
|
||||
if (err) {
|
||||
this.showError(err);
|
||||
return;
|
||||
}
|
||||
uni.showLoading({ title: '保存中...' });
|
||||
this.$u.api.gfmOtherDeliveryOrder.add(this.buildAddParam())
|
||||
.then((res) => {
|
||||
this.orderId = (res && res.id) || 0;
|
||||
if (!this.orderId) {
|
||||
throw new Error('保存失败,未返回单据ID');
|
||||
}
|
||||
return this.$u.api.gfmOtherDeliveryOrder.detail({ id: this.orderId });
|
||||
})
|
||||
.then((detail) => {
|
||||
uni.hideLoading();
|
||||
const order = normalizeOrder(detail);
|
||||
this.applyOrder(order);
|
||||
this.headerSaved = true;
|
||||
this.headerCollapsed = true;
|
||||
this.syncCanScanFromStatus(order.audit_status);
|
||||
this.showSuccess('保存成功,可以扫码', '保存成功', { voiceOnly: true });
|
||||
this.registerOrderScanBroadcast();
|
||||
})
|
||||
.catch((e) => {
|
||||
uni.hideLoading();
|
||||
this.showError(e.message || '保存失败');
|
||||
});
|
||||
},
|
||||
applyOrder(order) {
|
||||
if (!order) return;
|
||||
this.auditStatus = order.audit_status;
|
||||
Object.assign(this.form, orderToFormFields(order));
|
||||
this.refreshScanDetailTotals(order);
|
||||
},
|
||||
refreshScanDetailTotals(order) {
|
||||
this.scanDetail = {
|
||||
...this.scanDetail,
|
||||
total_roll: order.total_roll ?? 0,
|
||||
total_weight: getOrderDisplayWeight(order),
|
||||
};
|
||||
},
|
||||
applyScanDetail(scanCode, order, scanType) {
|
||||
const fromResponse = buildScanDetailFromOrderResponse(order);
|
||||
if (fromResponse) {
|
||||
const context = findScanContext(order.item_data, scanCode);
|
||||
if (context) {
|
||||
fromResponse.weight = getFineCodeDisplayWeight(context.fineCode);
|
||||
}
|
||||
this.scanDetail = fromResponse;
|
||||
if (scanType !== 2) this.lastScanCode = scanCode;
|
||||
else if (this.lastScanCode === scanCode) this.lastScanCode = '';
|
||||
return;
|
||||
}
|
||||
const context = findScanContext(order.item_data, scanCode);
|
||||
if (context) {
|
||||
this.scanDetail = buildScanDetailFromContext(context, order);
|
||||
this.lastScanCode = scanCode;
|
||||
return;
|
||||
}
|
||||
if (scanType === 2 && this.lastScanCode === scanCode) {
|
||||
this.scanDetail = {
|
||||
...getEmptyScanDetail(),
|
||||
total_roll: order.total_roll ?? 0,
|
||||
total_weight: getOrderDisplayWeight(order),
|
||||
};
|
||||
this.lastScanCode = '';
|
||||
return;
|
||||
}
|
||||
this.refreshScanDetailTotals(order);
|
||||
},
|
||||
handleScan() {
|
||||
const code = (this.QRBarCode || '').trim();
|
||||
if (!this.canScan || !this.orderId) {
|
||||
this.showError('请先提交保存后再扫码');
|
||||
this.QRBarCode = '';
|
||||
return;
|
||||
}
|
||||
if (!code) {
|
||||
this.QRBarCode = '';
|
||||
return;
|
||||
}
|
||||
const scanType = this.BarCodeDelStatus ? 2 : 1;
|
||||
this.doScanUpdate(code, scanType);
|
||||
},
|
||||
BarCodeDelChange(e) {
|
||||
this.BarCodeDelStatus = (e.detail.value || []).length > 0;
|
||||
},
|
||||
/** 扫码新增/删除细码,统一走 scanUpdate 接口 */
|
||||
doScanUpdate(scanCode, scanType, options = {}) {
|
||||
const { refreshFineCodes = false } = options;
|
||||
uni.showLoading({ title: scanType === 2 ? '删除中...' : '处理中...' });
|
||||
return this.$u.api.gfmOtherDeliveryOrder.scanUpdate(buildScanUpdateParams({
|
||||
id: this.orderId,
|
||||
scanCode,
|
||||
scanType,
|
||||
groupKeys: this.scanGroupKeys,
|
||||
detail: this.scanDetail,
|
||||
}))
|
||||
.then((detail) => {
|
||||
uni.hideLoading();
|
||||
const order = normalizeOrder(detail);
|
||||
this.form.item_data = order.item_data;
|
||||
this.auditStatus = order.audit_status;
|
||||
this.applyScanDetail(scanCode, order, scanType);
|
||||
if (refreshFineCodes) {
|
||||
const row = order.item_data.find((i) => (i.group_key || String(i.id)) === this.currentGroupKey);
|
||||
this.currentFineCodes = row ? [...row.fine_codes] : [];
|
||||
}
|
||||
this.BillDataMessage = scanType === 2 ? '删除成功' : '扫描成功';
|
||||
this.QRBarCode = '';
|
||||
if (scanType === 2) {
|
||||
speak('已删除出仓');
|
||||
uni.showToast({ title: '删除成功', icon: 'success' });
|
||||
} else {
|
||||
speak('成功');
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
uni.hideLoading();
|
||||
const message = e.message || (scanType === 2 ? '删除失败' : '扫码失败');
|
||||
const mismatchVoice = resolveScanMismatchVoice(message);
|
||||
speak(mismatchVoice || (scanType === 2 ? '删除失败' : '扫码失败'));
|
||||
uni.showToast({ title: message, icon: 'none', duration: 2500 });
|
||||
this.QRBarCode = '';
|
||||
});
|
||||
},
|
||||
openFabricInfo(row) {
|
||||
this.currentFabricInfo = { ...row };
|
||||
this.fabricInfoShow = true;
|
||||
},
|
||||
openFineCode(row) {
|
||||
this.currentGroupKey = row.group_key || String(row.id);
|
||||
this.currentFineCodes = [...(row.fine_codes || [])];
|
||||
this.fineCodeShow = true;
|
||||
},
|
||||
onDeleteFineCode(fineCode) {
|
||||
if (this.isReadonly || !this.canScan) return;
|
||||
const scanCode = getFineCodeScanCode(fineCode);
|
||||
if (!scanCode) {
|
||||
this.showError('该细码缺少条码,无法删除');
|
||||
return;
|
||||
}
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: `确认删除细码 ${scanCode}?`,
|
||||
success: (res) => {
|
||||
if (!res.confirm) return;
|
||||
this.doScanUpdate(scanCode, 2, { refreshFineCodes: true });
|
||||
},
|
||||
});
|
||||
},
|
||||
handleStatusAction(action) {
|
||||
confirmOrderStatusAction(this, {
|
||||
orderId: this.orderId,
|
||||
action,
|
||||
onSuccess: () => this.$u.api.gfmOtherDeliveryOrder.detail({ id: this.orderId })
|
||||
.then((detail) => {
|
||||
uni.hideLoading();
|
||||
const order = normalizeOrder(detail);
|
||||
if (!order) return;
|
||||
this.applyOrder(order);
|
||||
this.syncCanScanFromStatus(order.audit_status);
|
||||
this.showSuccess(util.statusSuccessVoice(action));
|
||||
}),
|
||||
});
|
||||
},
|
||||
showError(message, options = {}) {
|
||||
if (!options.skipErrorAudio) util.playErrorAudio(message);
|
||||
uni.showModal({ title: '提示', content: message, showCancel: false });
|
||||
},
|
||||
showSuccess(message, voiceText, options) {
|
||||
util.playSuccessAudio(voiceText || message, options);
|
||||
uni.showToast({ title: message, icon: 'success' });
|
||||
},
|
||||
getBillTypeName() {
|
||||
return this.billTypeName;
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,160 @@
|
||||
/* 坯布出仓单页面共用样式(新增/编辑/查看) */
|
||||
.fabric-out-page {
|
||||
background-color: #F8F8F8;
|
||||
padding-bottom: 260rpx;
|
||||
}
|
||||
.fabric-out-mt32 {
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
.fabric-out-sectionTitle {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
padding: 24rpx 26rpx 12rpx;
|
||||
border-left: 6rpx solid #007AFF;
|
||||
margin: 20rpx 26rpx 0;
|
||||
}
|
||||
.fabric-out-scanTip {
|
||||
padding: 20rpx 26rpx;
|
||||
color: #fa8c16;
|
||||
font-size: 28rpx;
|
||||
background: #fff7e6;
|
||||
margin: 16rpx 26rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
.fabric-out-msgTip {
|
||||
padding: 12rpx 26rpx;
|
||||
color: #52c41a;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.fabric-out-detailList {
|
||||
padding: 0 26rpx;
|
||||
}
|
||||
.fabric-out-detailCard {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
.fabric-out-detailRow {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
padding-bottom: 12rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
.fabric-out-detailMeta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
padding-top: 12rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
.fabric-out-scanRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
}
|
||||
.fabric-out-scanInput {
|
||||
width: 200px;
|
||||
flex: 1;
|
||||
min-width: 160px;
|
||||
}
|
||||
.fabric-out-delCheck {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.fabric-out-scanDetail {
|
||||
background: #fff;
|
||||
margin: 0 26rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
.fabric-out-scanDetailRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
padding: 16rpx 20rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
.fabric-out-scanDetailRow--2col {
|
||||
justify-content: space-between;
|
||||
gap: 12rpx;
|
||||
}
|
||||
.fabric-out-scanDetailCell {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.fabric-out-scanDetailCheck {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
gap: 4rpx;
|
||||
}
|
||||
.fabric-out-scanDetailLabel {
|
||||
color: #333;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.fabric-out-scanDetailValue {
|
||||
color: #333;
|
||||
word-break: break-all;
|
||||
}
|
||||
.fabric-out-scanDetailTableScroll {
|
||||
width: 100%;
|
||||
}
|
||||
.fabric-out-scanDetailTable {
|
||||
min-width: 100%;
|
||||
}
|
||||
.fabric-out-scanDetailTableRow {
|
||||
display: flex;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
.fabric-out-scanDetailTableHead {
|
||||
background: #f5f5f5;
|
||||
font-weight: bold;
|
||||
}
|
||||
.fabric-out-scanDetailTableCell {
|
||||
padding: 16rpx 10rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
word-break: break-all;
|
||||
}
|
||||
.fabric-out-scanDetailEmpty {
|
||||
padding: 24rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
.fabric-out-submitView {
|
||||
width: 100%;
|
||||
padding: 16rpx 26rpx 26rpx;
|
||||
background: #fff;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
border-top: 1rpx solid #f1f1f1;
|
||||
display: flex;
|
||||
z-index: 100;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.fabric-out-actionBtnWrap {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.fabric-out-actionBtnWrap + .fabric-out-actionBtnWrap {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
.fabric-out-actionBtn {
|
||||
flex: 1;
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<view class="fabric-out-page">
|
||||
<OrderHeaderFold v-model="headerCollapsed" :summary="order.order_no">
|
||||
<OrderStatusBar class="fabric-out-mt32" :status="order.audit_status" :name="order.audit_status_name" />
|
||||
<view class="flex-white-plr26 ptb20 bdb_f5">
|
||||
<text class="mr26">出仓类型</text>
|
||||
<text>{{ billTypeName }}</text>
|
||||
</view>
|
||||
<view class="flex-white-plr26 ptb20 bdb_f5">
|
||||
<text class="mr26">染厂</text><text>{{ order.business_unit_name }}</text>
|
||||
</view>
|
||||
<view class="flex-white-plr26 ptb20 bdb_f5">
|
||||
<text class="mr26">出货日期</text><text>{{ formatTime(order.delivery_time) }}</text>
|
||||
</view>
|
||||
<view class="flex-white-plr26 ptb20 bdb_f5">
|
||||
<text class="mr26">单据备注</text><text>{{ order.remark || '-' }}</text>
|
||||
</view>
|
||||
<view class="flex-white-plr26 ptb20 bdb_f5">
|
||||
<text class="mr26">创建人</text><text>{{ order.creator_name }} {{ formatTime(order.create_time, 'YYYY-MM-DD') }}</text>
|
||||
</view>
|
||||
<view v-if="order.auditer_name" class="flex-white-plr26 ptb20 bdb_f5">
|
||||
<text class="mr26">审核人</text><text>{{ order.auditer_name }} {{ formatTime(order.audit_time, 'YYYY-MM-DD') }}</text>
|
||||
</view>
|
||||
</OrderHeaderFold>
|
||||
|
||||
<FabricOutDetailList
|
||||
:title="detailTitle"
|
||||
:list="order.item_data"
|
||||
@fabric-info="openFabricInfo"
|
||||
@fine-code="openFineCode"
|
||||
/>
|
||||
|
||||
<FabricOutFooter :buttons="actionButtons" @action="handleAction" />
|
||||
|
||||
<FabricInfoPopup :show.sync="fabricInfoShow" :data="currentFabricInfo" />
|
||||
<FineCodePopup :show.sync="fineCodeShow" :list="currentFineCodes" :readonly="true" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import util from '@/common/util';
|
||||
import FabricInfoPopup from '@/components/storefabric/FabricInfoPopup.vue';
|
||||
import FineCodePopup from '@/components/storefabric/FineCodePopup.vue';
|
||||
import OrderStatusBar from '@/components/storefabric/OrderStatusBar.vue';
|
||||
import OrderHeaderFold from '@/components/storefabric/OrderHeaderFold.vue';
|
||||
import FabricOutDetailList from '@/components/storefabric/FabricOutDetailList.vue';
|
||||
import FabricOutFooter from '@/components/storefabric/FabricOutFooter.vue';
|
||||
import {
|
||||
BILL_TYPES,
|
||||
normalizeOrder,
|
||||
getViewStatusActionButtons,
|
||||
confirmOrderStatusAction,
|
||||
formatDisplayTime,
|
||||
} from '@/common/storeFabricBusinessOut';
|
||||
|
||||
export default {
|
||||
components: { FabricInfoPopup, FineCodePopup, OrderStatusBar, OrderHeaderFold, FabricOutDetailList, FabricOutFooter },
|
||||
data() {
|
||||
return {
|
||||
headerCollapsed: true,
|
||||
orderId: 0,
|
||||
order: {},
|
||||
billTypeName: (BILL_TYPES[0] || {}).label || '坯布其他出货单',
|
||||
fabricInfoShow: false,
|
||||
fineCodeShow: false,
|
||||
currentFabricInfo: {},
|
||||
currentFineCodes: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
actionButtons() {
|
||||
return getViewStatusActionButtons(this.order.audit_status);
|
||||
},
|
||||
detailTitle() {
|
||||
return `出仓细码(共 ${this.order.total_roll || 0} 匹 / ${this.order.total_weight || 0} KG)`;
|
||||
},
|
||||
},
|
||||
onLoad(e) {
|
||||
uni.setNavigationBarTitle({ title: '查看坯布出仓单' });
|
||||
if (e.id) {
|
||||
this.orderId = Number(e.id);
|
||||
this.refreshOrder();
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
if (this.orderId) this.refreshOrder();
|
||||
},
|
||||
methods: {
|
||||
formatTime: formatDisplayTime,
|
||||
refreshOrder() {
|
||||
this.$u.api.gfmOtherDeliveryOrder.detail({ id: this.orderId })
|
||||
.then((res) => {
|
||||
const order = normalizeOrder(res);
|
||||
if (!order) {
|
||||
this.showError('单据不存在');
|
||||
return;
|
||||
}
|
||||
this.order = order;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.showError(err.message || '加载单据失败');
|
||||
});
|
||||
},
|
||||
openFabricInfo(row) {
|
||||
this.currentFabricInfo = { ...row };
|
||||
this.fabricInfoShow = true;
|
||||
},
|
||||
openFineCode(row) {
|
||||
this.currentFineCodes = [...(row.fine_codes || [])];
|
||||
this.fineCodeShow = true;
|
||||
},
|
||||
handleAction(action) {
|
||||
if (action === 'edit') {
|
||||
uni.navigateTo({ url: `./storeFabricBusinessOutEdit?id=${this.orderId}` });
|
||||
return;
|
||||
}
|
||||
confirmOrderStatusAction(this, {
|
||||
orderId: this.orderId,
|
||||
action,
|
||||
onSuccess: () => {
|
||||
uni.hideLoading();
|
||||
const voice = util.statusSuccessVoice(action);
|
||||
util.playSuccessAudio(voice);
|
||||
uni.showToast({ title: voice, icon: 'success' });
|
||||
if (action === 'unapprove') {
|
||||
uni.redirectTo({ url: `./storeFabricBusinessOutEdit?id=${this.orderId}` });
|
||||
return;
|
||||
}
|
||||
return this.$u.api.gfmOtherDeliveryOrder.detail({ id: this.orderId })
|
||||
.then((res) => {
|
||||
const order = normalizeOrder(res);
|
||||
if (order) this.order = order;
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
showError(message) {
|
||||
util.playErrorAudio(message);
|
||||
uni.showModal({ title: '提示', content: message, showCancel: false });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@/pages/storefabric/storeFabricBusinessOutPage.scss';
|
||||
page {
|
||||
background-color: #F8F8F8;
|
||||
padding-bottom: 260rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,260 @@
|
||||
<template>
|
||||
<view class="warehouse-detail-page">
|
||||
<view class="infoCard">
|
||||
<view class="infoTitle">坯布资料</view>
|
||||
<view class="infoRow">
|
||||
<text class="infoLabel">坯布编号</text>
|
||||
<text class="infoValue">{{ header.grey_fabric_code || '-' }}</text>
|
||||
</view>
|
||||
<view class="infoRow">
|
||||
<text class="infoLabel">坯布名称</text>
|
||||
<text class="infoValue">{{ header.grey_fabric_name || '-' }}</text>
|
||||
</view>
|
||||
<view class="infoRow">
|
||||
<text class="infoLabel">颜 色</text>
|
||||
<text class="infoValue">{{ header.gray_fabric_color_name || '-' }}</text>
|
||||
</view>
|
||||
<view class="infoRow">
|
||||
<text class="infoLabel">织 厂</text>
|
||||
<text class="infoValue">{{ header.supplier_name || '-' }}</text>
|
||||
</view>
|
||||
<view class="infoRow">
|
||||
<text class="infoLabel">机 台 号</text>
|
||||
<text class="infoValue">{{ header.machine_number || '-' }}</text>
|
||||
</view>
|
||||
<view class="infoRow">
|
||||
<text class="infoLabel">纱 批</text>
|
||||
<text class="infoValue">{{ header.yarn_batch || '-' }}</text>
|
||||
</view>
|
||||
<view class="infoRow">
|
||||
<text class="infoLabel">仓 库</text>
|
||||
<text class="infoValue">{{ header.warehouse_name || '-' }}</text>
|
||||
</view>
|
||||
<view class="infoRow">
|
||||
<text class="infoLabel">汇总库存</text>
|
||||
<text class="infoValue">{{ header.stock_roll || '-' }} 匹 / {{ header.stock_weight || '-' }} KG</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="summaryBar">
|
||||
<text>细码:{{ totals.count }} 条 / {{ totals.roll }} 匹 / {{ totals.weight }} KG</text>
|
||||
</view>
|
||||
|
||||
<scroll-view
|
||||
scroll-y
|
||||
:style="{ height: scrollHeight }"
|
||||
refresher-enabled
|
||||
:refresher-triggered="triggered"
|
||||
@refresherrefresh="onRefresh"
|
||||
>
|
||||
<dataNull
|
||||
v-if="tableRows.length === 0"
|
||||
src="/src/static/img/chahua/gjNull.png"
|
||||
title="暂无细码明细"
|
||||
title1="请返回上一级调整筛选条件"
|
||||
/>
|
||||
<scroll-view v-else scroll-x class="tableScroll">
|
||||
<view class="fineTable" :style="{ width: tableWidth }">
|
||||
<view class="tableRow tableHead">
|
||||
<view
|
||||
v-for="col in columns"
|
||||
:key="col.key"
|
||||
class="tableCell headCell"
|
||||
:style="cellStyle(col)"
|
||||
>{{ col.label }}</view>
|
||||
</view>
|
||||
<view v-for="(row, rowIndex) in tableRows" :key="rowIndex" class="tableRow">
|
||||
<view
|
||||
v-for="col in columns"
|
||||
:key="col.key"
|
||||
class="tableCell"
|
||||
:style="cellStyle(col)"
|
||||
>{{ row[col.key] }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
WAREHOUSE_DETAIL_COLUMNS,
|
||||
buildDetailQuery,
|
||||
fetchAllWarehouseList,
|
||||
getWarehouseDetailTableWidth,
|
||||
mapWarehouseDetailRows,
|
||||
parsePageQuery,
|
||||
sumDetailTotals,
|
||||
} from '@/common/storeFabricWarehouse';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
rawList: [],
|
||||
scrollHeight: '667px',
|
||||
triggered: false,
|
||||
columns: WAREHOUSE_DETAIL_COLUMNS,
|
||||
header: {
|
||||
grey_fabric_id: '',
|
||||
grey_fabric_code: '',
|
||||
grey_fabric_name: '',
|
||||
gray_fabric_color_name: '',
|
||||
supplier_name: '',
|
||||
machine_number: '',
|
||||
yarn_batch: '',
|
||||
warehouse_name: '',
|
||||
stock_roll: '',
|
||||
stock_weight: '',
|
||||
},
|
||||
queryParams: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
tableRows() {
|
||||
return mapWarehouseDetailRows(this.rawList);
|
||||
},
|
||||
tableWidth() {
|
||||
return getWarehouseDetailTableWidth(this.columns);
|
||||
},
|
||||
totals() {
|
||||
return sumDetailTotals(this.rawList);
|
||||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
const parsed = parsePageQuery(options || {});
|
||||
this.header = {
|
||||
grey_fabric_id: parsed.grey_fabric_id || '',
|
||||
grey_fabric_code: parsed.grey_fabric_code || '',
|
||||
grey_fabric_name: parsed.grey_fabric_name || '',
|
||||
gray_fabric_color_name: parsed.gray_fabric_color_name || '',
|
||||
supplier_name: parsed.supplier_name || '',
|
||||
machine_number: parsed.machine_number || '',
|
||||
yarn_batch: parsed.yarn_batch || '',
|
||||
warehouse_name: parsed.warehouse_name || '',
|
||||
stock_roll: parsed.stock_roll || '',
|
||||
stock_weight: parsed.stock_weight || '',
|
||||
};
|
||||
this.queryParams = buildDetailQuery(parsed, parsed);
|
||||
uni.setNavigationBarTitle({ title: '细码明细' });
|
||||
uni.getSystemInfo({
|
||||
success: (res) => {
|
||||
this.scrollHeight = `${res.windowHeight - 360}px`;
|
||||
},
|
||||
});
|
||||
if (!this.queryParams.grey_fabric_id) {
|
||||
uni.showToast({ title: '缺少坯布参数', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
this.loadList();
|
||||
},
|
||||
methods: {
|
||||
cellStyle(col) {
|
||||
return {
|
||||
width: `${col.width}rpx`,
|
||||
minWidth: `${col.width}rpx`,
|
||||
maxWidth: `${col.width}rpx`,
|
||||
};
|
||||
},
|
||||
loadList() {
|
||||
fetchAllWarehouseList((p) => this.$u.api.gfmWarehouse.detailList(p), this.queryParams)
|
||||
.then((rows) => {
|
||||
this.triggered = false;
|
||||
this.rawList = rows || [];
|
||||
})
|
||||
.catch((err) => {
|
||||
this.triggered = false;
|
||||
this.rawList = [];
|
||||
uni.showToast({ title: err.message || '加载失败', icon: 'none' });
|
||||
});
|
||||
},
|
||||
onRefresh() {
|
||||
this.triggered = true;
|
||||
this.loadList();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
page { background-color: #F8F8F8; }
|
||||
.warehouse-detail-page { min-height: 100%; padding-bottom: 24rpx; }
|
||||
.infoCard {
|
||||
margin: 20rpx 24rpx 0;
|
||||
padding: 20rpx 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
.infoTitle {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 16rpx;
|
||||
color: #333;
|
||||
}
|
||||
.infoRow {
|
||||
display: flex;
|
||||
font-size: 28rpx;
|
||||
padding: 10rpx 0;
|
||||
}
|
||||
.infoLabel {
|
||||
width: 160rpx;
|
||||
color: #999;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.infoValue {
|
||||
flex: 1;
|
||||
color: #333;
|
||||
word-break: break-all;
|
||||
}
|
||||
.summaryBar {
|
||||
margin: 16rpx 24rpx 0;
|
||||
padding: 14rpx 16rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
background: #fff;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
.tableScroll {
|
||||
width: 100%;
|
||||
margin: 16rpx 24rpx 0;
|
||||
background: #fff;
|
||||
border-radius: 8rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.fineTable {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
border: 1rpx solid #e1e1e1;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.tableRow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
border-bottom: 1rpx solid #e1e1e1;
|
||||
}
|
||||
.tableRow:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.tableHead {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
.tableCell {
|
||||
flex-shrink: 0;
|
||||
padding: 16rpx 10rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
border-right: 1rpx solid #e1e1e1;
|
||||
box-sizing: border-box;
|
||||
word-break: break-all;
|
||||
white-space: normal;
|
||||
}
|
||||
.tableCell:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
.headCell {
|
||||
font-weight: bold;
|
||||
color: #3e3e3e;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<view class="warehouse-page">
|
||||
<view class="filterBar">
|
||||
<u-search
|
||||
v-model="filters.grey_fabric_code"
|
||||
placeholder="坯布编号"
|
||||
shape="square"
|
||||
:show-action="false"
|
||||
height="64"
|
||||
@search="loadList"
|
||||
@custom="loadList"
|
||||
/>
|
||||
<u-search
|
||||
v-model="filters.grey_fabric_name"
|
||||
placeholder="坯布名称"
|
||||
shape="square"
|
||||
:show-action="false"
|
||||
height="64"
|
||||
@search="loadList"
|
||||
@custom="loadList"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="summaryBar">
|
||||
<text>合计:{{ totals.roll }} 匹 / {{ totals.weight }} KG({{ totals.count }} 条)</text>
|
||||
</view>
|
||||
|
||||
<scroll-view
|
||||
scroll-y
|
||||
:style="{ height: scrollHeight }"
|
||||
refresher-enabled
|
||||
:refresher-triggered="triggered"
|
||||
@refresherrefresh="onRefresh"
|
||||
>
|
||||
<dataNull
|
||||
v-if="!loading && tableContents.length === 0"
|
||||
src="/src/static/img/chahua/gjNull.png"
|
||||
title="暂无库存汇总"
|
||||
title1="请输入坯布编号或名称查询"
|
||||
/>
|
||||
<view v-else-if="tableContents.length" class="tableBox">
|
||||
<WarehouseDataTable
|
||||
:headers="tableHeaders"
|
||||
:contents="tableContents"
|
||||
:height="tableHeight"
|
||||
@row-click="goStockList"
|
||||
/>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WarehouseDataTable from '@/components/storefabric/WarehouseDataTable.vue';
|
||||
import {
|
||||
GROUP_TABLE_COLUMNS,
|
||||
buildGroupListQuery,
|
||||
buildStockListNavigateQuery,
|
||||
calcTableHeight,
|
||||
fetchAllWarehouseList,
|
||||
mapGroupTableRow,
|
||||
sumStockTotals,
|
||||
toQueryString,
|
||||
} from '@/common/storeFabricWarehouse';
|
||||
|
||||
export default {
|
||||
components: { WarehouseDataTable },
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
scrollHeight: '667px',
|
||||
triggered: false,
|
||||
loading: false,
|
||||
tableHeaders: GROUP_TABLE_COLUMNS,
|
||||
filters: {
|
||||
grey_fabric_code: '',
|
||||
grey_fabric_name: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
tableContents() {
|
||||
return this.list.map(mapGroupTableRow);
|
||||
},
|
||||
totals() {
|
||||
return sumStockTotals(this.list);
|
||||
},
|
||||
tableHeight() {
|
||||
return calcTableHeight(this.tableContents.length);
|
||||
},
|
||||
},
|
||||
onLoad() {
|
||||
uni.setNavigationBarTitle({ title: '坯布库存' });
|
||||
uni.getSystemInfo({
|
||||
success: (res) => {
|
||||
this.scrollHeight = `${res.windowHeight - 108}px`;
|
||||
},
|
||||
});
|
||||
this.loadList();
|
||||
},
|
||||
methods: {
|
||||
loadList() {
|
||||
if (this.loading) return;
|
||||
this.loading = true;
|
||||
const params = buildGroupListQuery(this.filters);
|
||||
fetchAllWarehouseList((p) => this.$u.api.gfmWarehouse.groupSummaryList(p), params)
|
||||
.then((rows) => {
|
||||
this.triggered = false;
|
||||
this.list = rows || [];
|
||||
})
|
||||
.catch((err) => {
|
||||
this.triggered = false;
|
||||
this.list = [];
|
||||
uni.showToast({ title: err.message || '加载失败', icon: 'none' });
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
onRefresh() {
|
||||
this.triggered = true;
|
||||
this.loadList();
|
||||
},
|
||||
goStockList(row) {
|
||||
const query = buildStockListNavigateQuery(row);
|
||||
uni.navigateTo({
|
||||
url: `./storeFabricWarehouseStockList?${toQueryString(query)}`,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
page { background-color: #F8F8F8; }
|
||||
.warehouse-page { min-height: 100%; }
|
||||
.filterBar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
padding: 12rpx 20rpx;
|
||||
background: #fff;
|
||||
}
|
||||
.summaryBar {
|
||||
padding: 10rpx 24rpx;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
background: #fff;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
.tableBox {
|
||||
padding: 12rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<view class="warehouse-page">
|
||||
<view class="fabricHeader">
|
||||
<view class="fabricCode">{{ context.grey_fabric_code || '-' }}</view>
|
||||
<view class="fabricName">{{ context.grey_fabric_name || '-' }}</view>
|
||||
</view>
|
||||
|
||||
<view class="filterBar">
|
||||
<u-search
|
||||
v-model="filters.machine_number"
|
||||
placeholder="机台号"
|
||||
:show-action="false"
|
||||
@search="loadList"
|
||||
@custom="loadList"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="summaryBar">
|
||||
<text>合计:{{ totals.roll }} 匹 / {{ totals.weight }} KG({{ totals.count }} 条)</text>
|
||||
</view>
|
||||
|
||||
<scroll-view
|
||||
scroll-y
|
||||
:style="{ height: scrollHeight }"
|
||||
refresher-enabled
|
||||
:refresher-triggered="triggered"
|
||||
@refresherrefresh="onRefresh"
|
||||
>
|
||||
<dataNull
|
||||
v-if="tableContents.length === 0"
|
||||
src="/src/static/img/chahua/gjNull.png"
|
||||
title="暂无维度汇总"
|
||||
title1="请调整机台号筛选后重试"
|
||||
/>
|
||||
<view v-else class="tableBox">
|
||||
<WarehouseDataTable
|
||||
:headers="tableHeaders"
|
||||
:contents="tableContents"
|
||||
:height="tableHeight"
|
||||
@row-click="goDetail"
|
||||
/>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WarehouseDataTable from '@/components/storefabric/WarehouseDataTable.vue';
|
||||
import {
|
||||
STOCK_TABLE_COLUMNS,
|
||||
buildDetailNavigateQuery,
|
||||
buildStockListQuery,
|
||||
calcTableHeight,
|
||||
fetchAllWarehouseList,
|
||||
mapStockTableRow,
|
||||
parsePageQuery,
|
||||
sumStockTotals,
|
||||
toQueryString,
|
||||
} from '@/common/storeFabricWarehouse';
|
||||
|
||||
export default {
|
||||
components: { WarehouseDataTable },
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
scrollHeight: '667px',
|
||||
triggered: false,
|
||||
tableHeaders: STOCK_TABLE_COLUMNS,
|
||||
context: {
|
||||
grey_fabric_id: '',
|
||||
grey_fabric_code: '',
|
||||
grey_fabric_name: '',
|
||||
warehouse_id: '',
|
||||
},
|
||||
filters: {
|
||||
machine_number: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
tableContents() {
|
||||
return this.list.map(mapStockTableRow);
|
||||
},
|
||||
totals() {
|
||||
return sumStockTotals(this.list);
|
||||
},
|
||||
tableHeight() {
|
||||
return calcTableHeight(this.tableContents.length, { maxPx: 9999 });
|
||||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
const parsed = parsePageQuery(options || {});
|
||||
this.context = {
|
||||
grey_fabric_id: parsed.grey_fabric_id || '',
|
||||
grey_fabric_code: parsed.grey_fabric_code || '',
|
||||
grey_fabric_name: parsed.grey_fabric_name || '',
|
||||
warehouse_id: parsed.warehouse_id || '',
|
||||
};
|
||||
const title = this.context.grey_fabric_name
|
||||
? `${this.context.grey_fabric_name} 库存汇总`
|
||||
: '坯布库存汇总';
|
||||
uni.setNavigationBarTitle({ title });
|
||||
uni.getSystemInfo({
|
||||
success: (res) => {
|
||||
this.scrollHeight = `${res.windowHeight - 120}px`;
|
||||
},
|
||||
});
|
||||
if (!this.context.grey_fabric_id) {
|
||||
uni.showToast({ title: '缺少坯布参数', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
this.loadList();
|
||||
},
|
||||
methods: {
|
||||
loadList() {
|
||||
const params = buildStockListQuery(this.filters, this.context);
|
||||
fetchAllWarehouseList((p) => this.$u.api.gfmWarehouse.stockSummaryList(p), params)
|
||||
.then((rows) => {
|
||||
this.triggered = false;
|
||||
this.list = rows || [];
|
||||
})
|
||||
.catch((err) => {
|
||||
this.triggered = false;
|
||||
this.list = [];
|
||||
uni.showToast({ title: err.message || '加载失败', icon: 'none' });
|
||||
});
|
||||
},
|
||||
onRefresh() {
|
||||
this.triggered = true;
|
||||
this.loadList();
|
||||
},
|
||||
goDetail(row) {
|
||||
const query = buildDetailNavigateQuery(row, this.context);
|
||||
uni.navigateTo({
|
||||
url: `./storeFabricWarehouseDetail?${toQueryString(query)}`,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
page { background-color: #F8F8F8; }
|
||||
.warehouse-page { min-height: 100%; }
|
||||
.fabricHeader { padding: 20rpx 24rpx; background: #fff; border-bottom: 1rpx solid #f0f0f0; }
|
||||
.fabricCode { font-size: 30rpx; font-weight: bold; color: #333; }
|
||||
.fabricName { margin-top: 8rpx; font-size: 26rpx; color: #666; }
|
||||
.filterBar { padding: 16rpx 20rpx; background: #fff; }
|
||||
.summaryBar {
|
||||
padding: 12rpx 24rpx;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
background: #fff;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
.tableBox { padding: 16rpx 12rpx; }
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user