Compare commits

67 Commits
Author SHA1 Message Date
xuan b1648bcb30 feat: 优化手动进仓流程并修复N5S扫码识别升至1.1.26 2026-11-10 18:43:55 +08:00
xuan c0067e3b89 feat: 半屏连续扫与修改密码报错并升至1.1.25 2026-11-10 08:27:17 +08:00
xuan 706fcda5b9 feat: 手机连续扫与出仓汇总表改用t-table并升至1.1.24 2026-11-09 21:53:32 +08:00
xuan fdbd06f5f5 fix: 详情页消审状态判断并升至1.1.23 2026-11-09 12:42:58 +08:00
xuan 0e630d0fb0 fix: 版本更新下载地址改用周尚CDN前缀 2026-11-09 10:07:57 +08:00
xuan 4d3f767e16 feat: 优化手动进仓扫码流程并升至1.1.22 2026-11-08 22:38:49 +08:00
xuan 5b6690b4d8 fix: 修复坯布库存表格在东集上列错位 2026-11-08 18:54:20 +08:00
xuan db12e1a249 fix: 织厂安装包改传到周尚CDN并升至1.1.21 2026-11-06 21:19:34 +08:00
xuan 870f857be8 merge: 合并 origin/zhoushang,保留远程 1.1.20 及相关改动 2026-11-06 15:31:10 +08:00
xuan c390b9f72b feat: 无扫码配置时自动匹配并升至1.1.20 2026-11-04 16:15:52 +08:00
xuan 8315222788 feat: 进仓等级可多选并提示改密,升至1.1.19 2026-11-04 14:50:44 +08:00
xuan 59e699b4f6 feat: 登录默认密码提示改密并可取消,版本升至1.1.19 2026-11-03 16:01:56 +08:00
xuan fdacdfbda0 feat: 无扫码配置时自动匹配,并修复我的页列表样式 2026-11-03 15:39:03 +08:00
xuan 569b671b7b feat: 上传版本包后同步发布latest.apk 2026-11-02 14:27:56 +08:00
xuan 804e1a8f2b feat: 更新出仓扫码页并升至1.1.18 2026-11-02 14:24:06 +08:00
xuan 61bb43f759 fix: 取消更新后登录页不再自动弹框,等级改为非必填并升至1.1.17 2026-11-01 17:54:32 +08:00
xuan 7adec1b960 fix: 修复登录环境选择器选中框错位并升至1.1.16 2026-10-31 22:20:44 +08:00
xuan 640dcf3493 chore: 修正正式环境接口路径并将版本升至1.1.15 2026-10-31 22:12:16 +08:00
xuan b1404fdd89 feat: 更新正式环境API地址并默认选用 2026-10-30 11:59:10 +08:00
xuan a4dbcbe504 chore(manifest): 更新应用版本号至1.1.13 2026-10-26 15:34:58 +08:00
xuan 39e8393fde fix: 修复手动进仓语音乱码 2026-10-26 15:25:52 +08:00
xuan 35fb587e22 chore: 去掉上传脚本的本地配置文件 2026-10-14 09:35:27 +08:00
xuan 83645ccbf6 chore(manifest): 更新应用版本号至1.1.12 2026-10-14 09:35:26 +08:00
xuan 232a3e179a feat: 新增命令行上传PDA安装包 2026-10-13 18:23:13 +08:00
xuan 1c434a608a chore(manifest): 更新应用版本号至1.1.11 2026-10-13 17:35:15 +08:00
xuan f8ec49c79d feat: 我的页显示应用名称和版本 2026-10-13 17:35:15 +08:00
xuan 7b10d36361 feat: 登录页显示应用名称 2026-10-13 17:35:14 +08:00
xuan e8ae05b87b feat: 放大坯布出仓扫码按钮 2026-10-13 17:32:51 +08:00
xuan c4d5db056b feat: 手动进仓增加扫码和进仓成功语音 2026-10-13 17:32:50 +08:00
xuan 7b199ba559 feat: 坯布出仓增加操作语音并精简扫码明细 2026-10-13 17:13:01 +08:00
xuan 322ada8428 feat: 优化出库筛选字号与缓存 2026-10-08 17:14:10 +08:00
xuan 3e9b0fd1a8 chore(manifest): 更新应用版本号至1.1.10 2026-10-06 15:05:10 +08:00
xuan b734f50f0a fix: 手动进仓重量为空时不再默认显示0 2026-10-06 14:56:29 +08:00
xuan b04f890664 chore(manifest): 更新应用版本号至1.1.9 2026-09-30 12:30:16 +08:00
xuan 304d7ccde0 feat: 新增手动进仓并统一PDA字号标准 2026-09-30 12:29:45 +08:00
xuan 45a8928564 chore(manifest): 更新应用版本号至1.1.8 2026-09-29 11:29:31 +08:00
xuan f6f85ed8ce feat: 工作台改版并区分东大与新大陆扫码 2026-09-29 11:28:47 +08:00
xuan 3e936b064d fix: 关闭App端SSL校验以兼容老PDA 2026-09-28 12:13:17 +08:00
xuan 7c6c7a1b32 feat: 将版本更新入口移到我的页面 2026-09-24 22:09:12 +08:00
xuan c8ce45ae38 chore(manifest): 更新应用版本号至1.1.7 2026-09-24 21:56:17 +08:00
xuan bff563b444 feat: 放大小屏界面并让待审核单直达编辑 2026-09-24 21:54:17 +08:00
xuan 63755eab5c fix(scan): 修复新大陆扫码广播读取条码失败 2026-09-24 18:00:30 +08:00
xuan 26c7085ab2 feat: 默认测试环境并优化出仓与网络提示 2026-09-24 17:56:06 +08:00
xuan 53a059f16b chore(env): 更新周商织厂升级包名称 2026-09-24 16:48:24 +08:00
xuan e0ee8d8c8d chore(env): 切换周商正式环境并更新版本号 2026-09-24 16:35:12 +08:00
xuan fa0ff1614b feat: 开放服务器切换并修复扫码方式列表 2026-09-24 16:33:01 +08:00
xuanandCursor 3f2481d1eb fix(storefabric): 修复坯布库存表格上滑误触发下拉刷新
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-05 16:12:52 +08:00
xuanandCursor 162e10a6fc chore(env): 更新周商环境配置与应用名称
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 10:41:50 +08:00
xuanandCursor 26ff443107 feat(scan): 添加优博讯扫码广播配置
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 10:41:21 +08:00
litianyouandCursor 02f192cac6 fix(storefabric): 限制接收单位下拉仅显示织厂类型
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-04 13:17:04 +08:00
xuan 530ca0c3ad chore(version): 更新应用版本号
- 将版本名称从 1.1.4 更新为 1.1.5
- 将版本代码从 104 更新为 105
2026-06-24 20:00:58 +08:00
xuan b3ecd77fb2 chore(build): 添加周商环境构建配置
- 在package.json中添加build:app-plus:zhoushang构建命令
- 删除默认模块.openapi.json文件
- 创建.env.zhoushang环境配置文件,包含生产环境API地址等配置项
2026-06-24 19:59:39 +08:00
xuan 36c0f9a3a0 feat(scan): 添加手机扫码功能支持
- 在 FabricOutScanBar 组件中添加手机扫码图标和事件处理
- 更新工作台页面的扫码逻辑以支持手机扫码回退
- 在销售拣货详情页添加手机扫码按钮和处理方法
- 重构扫码设置页面以支持扫码方式选择
- 添加手机扫码配置和判断逻辑到扫码配置模块
- 扩展扫码混入模块以支持手机扫码模式和设备检测
- 在多个业务页面中集成手机扫码功能和界面元素
- 更新扫码配置存储和读取逻辑以兼容新配置结构
2026-06-23 17:08:17 +08:00
xuan 084bb235f8 feat(storefabric): 新增坯布库存管理功能
- 在http.api.js中添加坯布库存接口前缀和相关API定义
- 更新工作台页面,将坯布库存菜单链接到新的库存查询页面
- 在pages.json中注册坯布库存相关的三个新页面路由
- 创建storeFabricWarehouse.js文件,包含库存查询的常量、格式化函数和参数处理逻辑
- 新增storeFabricWarehouseDetail.vue页面,实现细码明细查看功能
- 添加storeFabricWarehouseGroupList.vue页面,实现坯布库存汇总查询
- 创建storeFabricWarehouseStockList.vue页面,实现库存维度汇总功能
- 新增storeFabricWarehouseGroupItem.vue和storeFabricWarehouseStockItem.vue组件,用于显示库存卡片
- 添加WarehouseDataTable.vue组件,提供可滚动的表格展示功能
2026-06-22 14:06:06 +08:00
xuan 567da03aea feat(storefabric): 新增坯布出库相关页面及功能
- 在 manifest.json 中添加 Android 包名配置
- 在 package.json 中添加 pnpm 依赖覆盖配置
- 在 pages.json 中注册坯布出库相关页面路由
- 重命名 storefabricBusinessOutAdd.vue 为 storeFabricBusinessOutAdd.vue
- 重构坯布出库新增页面,优化界面布局和交互逻辑
- 添加坯布出库列表、编辑、查看等功能页面
- 集成订单状态栏、面料信息弹窗、细码弹窗等组件
- 实现坯布出库详情列表和扫码功能模块
- 优化表单验证和数据提交流程
2026-06-17 16:59:31 +08:00
xuan d5b2e19658 refactor(workbench): 调整工作台界面布局和功能模块
- 将销售管理和成品管理模块注释掉,暂时隐藏相关功能
- 启用坯布管理模块,显示坯布相关业务功能
- 修改坯布出仓功能链接,使用新的业务流程页面
- 更新坯布出仓功能名称,从"坯布出仓"改为"坯布出库"
- 保留坯布库存查询功能,其他坯布管理功能暂时注释
2026-06-17 16:32:39 +08:00
xuan cff4debea4 feat(pda): 添加坯布出仓单功能和扫码设置界面
- 新增坯布出仓单相关API接口,包括列表、详情、新增、审核等功能
- 添加营销体系、往来单位、字典明细等基础数据接口
- 在用户中心添加扫描设置入口,支持PDA扫码枪配置
- 创建扫描设置页面,支持选择PDA品牌并配置广播动作和数据标签
- 重构扫码混入逻辑,支持远程配置和本地配置合并
- 添加扫码配置存储和读取功能,支持配置持久化
- 优化扫码广播注册流程,优先使用用户配置的扫码枪设置
2026-06-17 16:32:24 +08:00
xuan ebfa5bf2a0 docs(env): 更新环境配置文件和API端点地址
- 修改 .env 文件中的环境名称和API端点地址
- 新增 .env.cjt 配置文件用于本地开发环境
- 更新 .env.mes 和 .env.zhisheng 中的API端点地址
- 在 config.js 中更新生产环境API URL和服务名段
- 添加本地环境API URL选项到开发环境选项列表
- 更新登录页面中的自定义API URL地址
- 更新 README.md 中的打包部署说明文档
2026-06-17 16:30:35 +08:00
xuan dc6e2ece54 chore(manifest): 更新应用版本号至1.1.4
- 将 versionName 从1.1.3更新为1.1.4
- 将 versionCode 从103更新为104
2026-01-04 14:16:57 +08:00
xuan e0b00cc62b feat(app): 添加自动检查版本更新功能
- 引入 app_upgrade 模块用于版本检测
- 实现 autoCheckUpgrade 方法自动检查版本更新
- 在 APP-PLUS 平台的 onLoad 生命周期调用自动更新检查
- 调用后端接口获取升级信息并判断是否需要更新
- 支持强制检查新版本,跳过时间戳限制
- 优化日志打印,便于排查版本升级过程中的问题
2026-01-04 14:16:00 +08:00
xuan 9272ed0e23 feat(scanner): 多厂商PDA扫码器兼容方案及扫码功能重构
- 采用策略模式及配置表驱动方式支持多品牌扫码器广播注册
- 实现扫码广播的统一注册和注销管理,支持并发多个扫码广播接收器
- 重构扫码处理逻辑,统一处理扫码结果,支持条码和二维码识别
- 移除原有单一品牌扫码注册,整合商米、新大陆等扫码头支持
- 调整扫码相关页面代码,集成scanMixin,实现扫码广播注册和注销自动化
- 优化扫码结果处理,支持删除模式及整缸扫描状态区分
- 修正多处界面文本格式与代码风格,提高代码一致性和可维护性
- 升级manifest版本号至1.1.3,标记此功能更新
2026-01-04 13:57:28 +08:00
xuan 9e4be6db92 refactor(http): 优化升级检查请求及新增完整URL PUT请求方法
- 将升级检查接口调用改为使用统一的 putJson 方法,简化请求逻辑
- 新增 vm.$u.putJsonFullUrl 方法,支持传入完整URL且带默认JSON请求头
- putJsonFullUrl 方法封装了请求的统一header和响应处理逻辑
- manifest版本号升级至1.1.2 (112)
- sys user setting页面增加升级检查返回日志打印,方便调试
2025-12-30 18:16:08 +08:00
xuan 03024c395a fix(http): 更新接口请求方式及环境配置
- 修改.env文件,切换为zhisheng预发布环境变量
- 将outFpmArrangeOrder接口请求改为使用putJson,支持json格式请求体
- 在http.interceptor.js中新增putJson方法,封装带json请求头的PUT请求接口
- 统一接口请求方式,确保请求头为application/json,提高接口兼容性和稳定性
2025-12-30 17:30:47 +08:00
xuan 7043a49206 feat(login): 优化登录逻辑及环境配置展示
- 修改环境变量配置,新增 VUE_APP_ENV_NAME 用于区分环境名称显示
- 调整登录接口请求,pdaLogin与pdaLogout改为相对路径调用
- 登录页面新增环境名称显示,区分不同环境使用状态
- 登录流程支持开发环境动态选择服务器地址,支持自定义地址输入及存储
- 增加请求和响应拦截器日志输出,方便调试接口请求问题
- 优化登录表单输入清除按钮显示与密码显示切换交互体验
- 修复和改善登录相关本地存储,自动保存账号密码和服务器地址
- 调整manifest包配置,禁用app-plus子包
- 格式化和规范登录页面样式调整,提升页面结构与样式一致性
2025-12-26 15:11:37 +08:00
xuan 8a9bb09c18 fix(http): 修正 PDA 登录登出接口的请求地址
- 将 PDA 登录接口的请求地址修改为使用生产环境 API URL 前缀
- 将 PDA 登出接口的请求地址修改为使用生产环境 API URL 前缀
- 保持接口调用方式不变,确保请求路径正确指向后端服务
2025-12-25 11:39:00 +08:00
xuan f63d202327 build(deps): update dependencies and manifest version
更新了项目依赖,包括:
- `@dcloudio` 相关依赖
- `@icon-park/vue`
- `@vue/shared`
- `core-js`
- `flyio`
- `vue-i18n`
- `vuex`
- `@dcloudio/types`
- `babel-plugin-import`

同时,更新了 `manifest.json` 中的 `versionName` 和 `versionCode`。

这些更新旨在提升项目性能、修复已知问题并引入新功能。
2025-08-18 11:43:03 +08:00
xuan aa186a3f82 feat(config): add environment-specific build and dev scripts with dotenv support
- 添加了针对不同环境(mes、zhisheng)的构建和开发脚本
- 集成 dotenv 支持,通过 .env 文件管理环境变量
- 更新 manifest.json 中的图标和启动页路径,修正版本号
- 重构配置文件以支持动态环境变量注入
- 修改 HTTP API 调用地址为可配置化,便于多环境部署切换
2025-08-16 18:09:06 +08:00
109 changed files with 10030 additions and 3622 deletions
+37
View File
@@ -0,0 +1,37 @@
---
description: PDA 小屏界面字号与排版标准(以手动进仓页为准)
alwaysApply: true
---
# PDA 字号标准
本项目面向工业 PDA 小屏。新增/改版业务页时,**以后台手动进仓页**(`storeFabricManualIn.vue`)的字号为准,不要用偏小的默认字号。
共用变量:`@import '@/common/pda-typography.scss';`(或等价路径)。
## 字号(rpx)
| 用途 | 字号 | 变量 |
|------|------|------|
| 表单标签 / 正文 / 输入值 | **36** | `$pda-font-body` |
| 扫码输入框 | **36** | `$pda-font-body` |
| 占位提示 | **34** | `$pda-font-placeholder` |
| 行内操作(取消/修改) | **34** | `$pda-font-action` |
| 单位角标(如 KG) | **32** | `$pda-font-unit` |
| 顶栏次要文字(设置等) | **32** | `$pda-font-nav-sub` |
| 主按钮文字 | **36** | `$pda-font-button` |
| 弹层标题 / 选项 | **36** | `$pda-font-popup-title` / `$pda-font-popup-item` |
| 弹层取消 / 空态 | **34** | `$pda-font-popup-sub` |
## 行高与触控
- 列表行最小高度约 **100rpx**,可略加上下 padding
- 扫码输入框高度约 **100rpx**
- 主按钮高度约 **92rpx**
- 标签列宽约 **200rpx**(中文四字标签)
## 注意
- 登录环境选择、出仓明细等已放大过的页面,新改动保持不低于上表
- 不要为了“好看”再缩小字号;宁可少露内容、可滚动,也要可读
- 选择类弹层(如 `SearchSelectPopup`)与表单页使用同一套字号
+6
View File
@@ -0,0 +1,6 @@
NODE_ENV = production
VUE_APP_ENV_NAME = zhoushang环境
VUE_APP_UPGRADE_NAME = erp_pda_zhoushang_zhichang
VUE_APP_UPGRADE_CDN_PREFIX = https://zsfzcdn.yushangfangzhi.com/version/erp_pda_zhoushang_zhichang
VUE_APP_PRODUCTION_API_URL = https://zszc.yushangfangzhi.com/hcscm_weave/pda/v1
VUE_APP_DEV_API_URL = https://zszc.yushangfangzhi.com/hcscm_weave/pda/v1
+5
View File
@@ -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
+5
View File
@@ -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
+5
View File
@@ -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_weave/pda/v1
VUE_APP_DEV_API_URL = https://hcscmpre.zzfzyc.com/hcscm_weave/pda/v1
+6
View File
@@ -0,0 +1,6 @@
NODE_ENV = production
VUE_APP_ENV_NAME = zhoushang环境
VUE_APP_UPGRADE_NAME = erp_pda_zhoushang_zhichang
VUE_APP_UPGRADE_CDN_PREFIX = https://zsfzcdn.yushangfangzhi.com/version/erp_pda_zhoushang_zhichang
VUE_APP_PRODUCTION_API_URL = https://zszc.yushangfangzhi.com/hcscm_weave/pda/v1
VUE_APP_DEV_API_URL = https://zszc.yushangfangzhi.com/hcscm_weave/pda/v1
+17
View File
@@ -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
+57 -45
View File
@@ -7,6 +7,9 @@
"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: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 +28,8 @@
"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: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 +48,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,63 +61,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",
"@icon-park/vue": "^1.3.5",
"@vue/shared": "^3.0.0",
"core-js": "^3.8.3",
"@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",
"flyio": "^0.6.2",
"dotenv": "^17.2.1",
"flyio": "^0.6.14",
"uview-ui": "1.8.8",
"vue": ">= 2.6.14 < 2.7",
"vue-i18n": "^8.20.0",
"vuex": "^3.2.0"
"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",
"sass": "1.26.2",
"sass-loader": "8.0.2",
"vue-template-compiler": ">= 2.6.14 < 2.7"
"vue-template-compiler": "^2.6.14"
},
"browserslist": [
"Android >= 4.4",
"ios >= 9"
],
"pnpm": {
"overrides": {
"postcss-urlrewrite": "0.3.0"
}
},
"uni-app": {
"scripts": {}
}
+333
View File
@@ -0,0 +1,333 @@
/**
* 上传已打包的 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_weave';
const DEFAULT_CDN_HOST = {
test: 'https://testhcscm.cdn.zzfzyc.com',
prod: 'https://hcscm.cdn.zzfzyc.com',
};
/** 织厂环境签到 zhoushang 空间,下载域名与 ERP 相同 */
const MODE_UPLOAD = {
zhoushang: {
apiBase: 'https://zszc.yushangfangzhi.com/hcscm_weave',
cdnHost: 'https://zsfzcdn.yushangfangzhi.com',
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);
});
+9
View File
@@ -6,6 +6,7 @@ import { audioManager } from '@/utils/audioManager'
import util from '@/common/util.js';
import md5 from '@/common/md5.js';
import { ensureScanConfigAfterLogin } from '@/common/scanAutoConfig.js';
export default {
globalData: {
AppName: '',
@@ -30,6 +31,14 @@ import { audioManager } from '@/utils/audioManager'
audioManager.init();
// 初始化通知系统
this.$store.dispatch('initNotification');
// 已登录且本地无扫码配置时,自动匹配一次
const loginData = uni.getStorageSync('RemoteTokenData');
if (loginData && loginData.token && this.$u && this.$u.api) {
ensureScanConfigAfterLogin(this.$u.api).catch((error) => {
console.log('启动时自动匹配扫码设置失败', error);
});
}
}
}
</script>
+1 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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"
+2 -2
View File
@@ -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>
+26 -14
View File
@@ -6,7 +6,8 @@
export const isDevelopment = process.env.NODE_ENV === 'development'
const config = {
upgradeName: 'erp_pda_pre',
upgradeName: process.env.VUE_APP_UPGRADE_NAME || 'erp_pda',
upgradeCdnPrefix: process.env.VUE_APP_UPGRADE_CDN_PREFIX || '',
// 产品名称
productName: '浩拓技术',
@@ -17,28 +18,39 @@ 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_weave)
productionApiUrl: process.env.VUE_APP_PRODUCTION_API_URL,
// 开发环境API URL选项
// 开发环境API URL选项(仅替换 //域名/ 后第一个路径段 hcscm → hcscm_weave,不改域名)
devApiUrlOptions: [
{ label: '测试环境', value: 'https://hcscmtest.zzfzyc.com/hcscm/pda/v1' },
{ label: '正式环境', value: 'https://hcscmpre.zzfzyc.com/hcscm/pda/v1' },
{ label: '测试环境', value: 'https://hcscmtest.zzfzyc.com/hcscm_weave/pda/v1' },
{ label: '正式环境', value: process.env.VUE_APP_PRODUCTION_API_URL },
{ label: '本地环境', value: 'http://192.168.1.97:7010/hcscm_weave/pda/v1' },
{ label: '自定义', value: 'custom' }
]
}
// 设置后台接口服务的基础地址 - 使用动态API URL
// config.baseUrl = 'https://aidex.vip/api'; // 注释掉固定的baseUrl
export function resolveUpgradeDownloadUrl(downloadUrl) {
const raw = String(downloadUrl || '').trim();
const prefix = String(config.upgradeCdnPrefix || '').trim().replace(/\/+$/, '');
if (!raw || !prefix) {
return raw;
}
const path = raw.split('?')[0].split('#')[0];
const fileName = path.split('/').filter(Boolean).pop() || '';
if (!fileName || !/\.apk$/i.test(fileName)) {
return raw;
}
return `${prefix}/${fileName}`;
}
export function getDefaultApiUrl() {
const prod = config.devApiUrlOptions.find(item => item.label === '正式环境');
return (prod && prod.value) || config.productionApiUrl || 'https://zszc.yushangfangzhi.com/server/hcscm_weave/pda/v1';
}
export default config;
+87 -27
View File
@@ -7,14 +7,76 @@ const install = (Vue, vm) => {
// 参数配置对象
const config = vm.vuex_config;
console.log('install u',config)
// 坯布出仓单接口前缀(apiurl 已在 config.js 统一配置为 hcscm_weave 服务)
const GFM_OTHER_DELIVERY_PREFIX = '/grey_fabric_manage/gfmOtherDeliveryOrder/';
const gfmOtherDeliveryUrl = (name) => (vm.$store.state.apiurl || '') + GFM_OTHER_DELIVERY_PREFIX + name;
// 坯布库存接口前缀
const GFM_WAREHOUSE_PREFIX = '/grey_fabric_manage/gfmWarehouse/';
const gfmWarehouseUrl = (name) => (vm.$store.state.apiurl || '') + GFM_WAREHOUSE_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),
},
// PDA 生产进仓(排产单细码扫码进仓)
productionScheduleOrder: {
getScanFineCodeDetail: (params = {}) => vm.$u.get('/produce/productionScheduleOrder/getScanFineCodeDetail', params),
scanInWarehouseFineCode: (params = {}) => vm.$u.putJson('/produce/productionScheduleOrder/scanInWarehouseFineCode', params),
},
// 营销体系下拉(路径相对 apiurl:.../pda/v1/saleSystem/getSaleSystemDropdownList)
saleSystem: {
getDropdownList: (params = {}) => vm.$u.get('/saleSystem/getSaleSystemDropdownList', params),
},
// 往来单位列表(路径相对 apiurl:.../pda/v1/business_unit/list)
businessUnit: {
list: (params = {}) => vm.$u.get('/business_unit/list', params),
},
// 员工枚举(路径相对 apiurl:.../pda/v1/employee/list_enum)
employee: {
listEnum: (params = {}) => vm.$u.get('/employee/list_enum', 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),
@@ -23,19 +85,19 @@ const install = (Vue, vm) => {
// 获取成品配布单详情
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 +108,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('/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),
},
};
+80 -9
View File
@@ -22,6 +22,13 @@ const install = (Vue, vm) => {
// 请求拦截,配置Token等参数
Vue.prototype.$u.http.interceptor.request = (req) => {
console.log('request', req);
console.log('baseUrl', vm.$store.state.apiurl);
console.log('完整URL', vm.$store.state.apiurl + req.url);
// #ifdef APP-PLUS
// Android 5.x 等老 PDA 不信任 Let's Encrypt 新证书链,关闭校验证书
req.sslVerify = false;
// #endif
if (!req.header){
req.header = [];
}
@@ -42,13 +49,15 @@ 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) {
@@ -63,14 +72,34 @@ const install = (Vue, vm) => {
});
return false;
}
if (!(res.data)){
vm.$u.toast('未连接到服务器')
return Promise.reject(new Error('未连接到服务器'));
console.log('res',res)
const rejectWithMessage = (message) => {
const text = String(message || '请求失败').trim() || '请求失败';
vm.$u.toast(text);
return Promise.reject(new Error(text));
};
// HTTP 非 200(如 404)时直接报错,避免把字符串/HTML 当业务 data 解析
if (res.statusCode && res.statusCode !== 200) {
let message = `请求失败(${res.statusCode})`;
if (typeof res.data === 'string' && res.data.trim()) {
message = res.data.trim().slice(0, 120);
} else if (res.data && typeof res.data === 'object') {
message = res.data.msg || res.data.message || message;
}
return rejectWithMessage(message);
}
if(res.data.code !== 0) {
vm.$u.toast(res.data.msg)
return Promise.reject(new Error(res.data.msg))
if (res.data === undefined || res.data === null || res.data === '') {
const errMsg = String((res && res.errMsg) || '');
const isNetworkError = res.statusCode === -1
|| /Unable to resolve host|Failed to connect|request:fail|timeout|网络/i.test(errMsg);
const message = isNetworkError ? '当前没有网络,请检查网络后重试' : '未连接到服务器';
return rejectWithMessage(message);
}
if (typeof res.data !== 'object') {
return rejectWithMessage(String(res.data).slice(0, 120));
}
if (res.data.code !== 0) {
return rejectWithMessage(res.data.msg || res.data.message || '请求失败');
}
let data = res.data.data;
console.log('data',data)
@@ -125,7 +154,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 +163,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 {
+19
View File
@@ -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;
+47
View File
@@ -0,0 +1,47 @@
/**
* 登录后 / 冷启动:拉取扫码字典并在本地无配置时自动匹配写入
*/
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) {
if (result.reason === 'rematch_n5s_from_seuic') {
console.log('N5S 误识别为东集,已纠正为', result.config && result.config.name);
} else {
console.log('已自动写入扫码设置', result.config && result.config.name);
}
} else {
console.log('自动匹配扫码设置未写入', result.reason);
}
return result;
}
+507
View File
@@ -0,0 +1,507 @@
export const STORAGE_KEY = 'pda_scan_config';
export const DICTIONARY_TYPE_SCANNER = 10046;
export const PHONE_SCAN_BRAND = 'phone';
// 历史误用 brand=newland 表示东集;真正的新大陆是 nlscan
const LEGACY_SEUIC_BRANDS = ['newland', '东集', 'idata'];
export const PHONE_SCAN_CONFIG = {
brand: PHONE_SCAN_BRAND,
name: '手机扫码',
isPhoneScan: true,
};
/** 内置扫码配置:补 needSetup,并在字典拉不到时兜底 */
export const BUILTIN_SCANNER_CONFIGS = [
{
brand: 'seuic',
name: '东大集成(AUTOID)',
action: 'com.android.server.scannerservice.broadcast',
dataKey: 'scannerdata',
needSetup: true,
setupAction: 'com.android.scanner.service_settings',
setupParams: {
barcode_send_mode: 'BROADCAST',
action_barcode_broadcast: 'com.android.server.scannerservice.broadcast',
key_barcode_broadcast: 'scannerdata',
endchar: 'NONE',
},
},
{
brand: 'sunmi',
name: '商米',
action: 'com.sunmi.scanner.ACTION_DATA_CODE_RECEIVED',
dataKey: 'data',
needSetup: false,
},
{
brand: 'urovo',
name: '优博讯',
action: 'android.intent.ACTION_DECODE_DATA',
dataKey: 'barcode_string',
needSetup: false,
},
{
brand: 'nlscan',
name: '新大陆',
action: 'nlscan.action.SCANNER_RESULT',
dataKey: 'SCAN_BARCODE1',
needSetup: false,
},
{
// N5S 部分固件走智联天地兼容广播,而不是 nlscan.action
brand: 'nlscan_compat',
name: '新大陆(兼容广播)',
action: 'android.intent.action.RECEIVE_SCANDATA_BROADCAST',
dataKey: 'android.intent.extra.SCAN_BROADCAST_DATA',
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',
'android.intent.action.RECEIVE_SCANDATA_BROADCAST',
],
// N5S 机型常标成 alps/zltd,不能只靠 newland 关键字
keywords: ['newland', 'nls', 'nlscan', 'n5s', 'zltd'],
models: ['n5s', 'n5'],
packages: [
'com.nlscan.barcodescanner',
'com.newland',
'com.nlscan.android.scan.service',
],
},
{
matchNames: ['优博讯'],
matchActions: ['android.intent.ACTION_DECODE_DATA'],
keywords: ['urovo', 'ubx'],
packages: ['com.urovo', 'com.ubx'],
},
{
matchNames: ['盈达'],
matchActions: ['android.intent.action.SCANRESULT'],
keywords: ['idata', 'yingda'],
packages: ['com.idata'],
},
{
matchNames: ['海雅达'],
matchActions: ['com.android.scanner.broadcast'],
keywords: ['haiyada'],
packages: [],
},
{
matchNames: ['东大集成(AUTOID)', '东大集成', '东集'],
matchActions: ['com.android.server.scannerservice.broadcast'],
keywords: ['seuic', 'autoid', 'eastcom', '东集', '东大'],
packages: ['com.seuic.scanner'],
},
];
const PDA_DEVICE_KEYWORDS = [
'newland', 'nls', 'nlscan', 'n5s', 'zltd', 'sunmi', 'urovo', 'ubx', 'zebra', 'honeywell',
'idata', 'seuic', 'autoid', '东集', '东大',
];
/** 明确机型 → 优先品牌,避免通用东集广播抢分 */
const MODEL_PREFERRED_BRANDS = [
{
models: ['n5s'],
brands: ['nlscan', 'nlscan_compat'],
score: 220,
},
];
const NO_SETUP_HINT_KEY = 'pda_scan_config_hint_shown';
export function isPhoneScanConfig(config) {
return !!(config && (config.isPhoneScan || config.brand === PHONE_SCAN_BRAND));
}
function migrateLegacySeuicBrand(config) {
if (!config || isPhoneScanConfig(config)) {
return config;
}
const brand = String(config.brand || '');
const name = String(config.name || '');
const action = String(config.action || '');
// 历史内置配置把东集误标成 brand=newland;真正的新大陆是 nlscan
const isLegacySeuicLabel = LEGACY_SEUIC_BRANDS.includes(brand) || name === '东集';
if (!isLegacySeuicLabel) {
return config;
}
if (action.includes('nlscan')) {
return config;
}
return {
...config,
brand: 'seuic',
name: (!name || name === '东集') ? '东大集成(AUTOID)' : name,
};
}
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
|| (item.brand === 'seuic' && (
String(config.name || '').includes('东大')
|| String(config.name || '').includes('东集')
|| String(config.name || '').includes('AUTOID')
|| String(config.action || '').includes('scannerservice.broadcast')
));
});
if (!builtin) {
return config;
}
return {
...config,
needSetup: builtin.needSetup,
setupAction: builtin.setupAction || '',
setupParams: builtin.setupParams || null,
};
}
export function normalizeConfig(raw, builtinConfigs = []) {
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 };
}
let config = {
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,
};
config = migrateLegacySeuicBrand(config);
config = enrichFromBuiltinConfig(config, builtinConfigs);
if (!config.brand || !config.action || !config.dataKey) {
return null;
}
return config;
}
export function mapDictionaryToScannerConfig(raw, builtinConfigs = []) {
return normalizeConfig(raw, builtinConfigs);
}
export function getScanConfig(builtinConfigs = []) {
try {
const data = uni.getStorageSync(STORAGE_KEY);
const normalized = normalizeConfig(data, builtinConfigs);
// 旧 brand=newland(东集) 迁移后写回,避免下次仍读到错误 brand
if (normalized && data && String(data.brand || '') !== String(normalized.brand || '')) {
try {
uni.setStorageSync(STORAGE_KEY, normalized);
} catch (e) {
console.log('迁移扫码枪配置写回失败', e);
}
}
return normalized;
} catch (e) {
console.log('读取扫码枪配置失败', e);
return null;
}
}
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, BUILTIN_SCANNER_CONFIGS);
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(String(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 isModelMatch(identity, models = []) {
const model = String((identity && identity.model) || '').toLowerCase();
if (!model) return false;
return (models || []).some((item) => model === item || model.includes(item));
}
function getModelPreferredBoost(config, identity) {
if (!config || isPhoneScanConfig(config)) {
return 0;
}
const brand = String(config.brand || '');
for (let i = 0; i < MODEL_PREFERRED_BRANDS.length; i += 1) {
const rule = MODEL_PREFERRED_BRANDS[i];
if (!isModelMatch(identity, rule.models)) continue;
if ((rule.brands || []).includes(brand)) {
return Number(rule.score) || 0;
}
}
return 0;
}
/** 新大陆 N5S / 智联天地 N5S:机型名常为 n5s,厂商可能是 newland/zltd/alps */
export function isNewlandN5sDevice(identity = getDeviceIdentity()) {
const model = String((identity && identity.model) || '').toLowerCase();
const text = `${identity.manufacturer || ''} ${identity.brand || ''} ${model}`.toLowerCase();
if (model === 'n5s' || model.includes('n5s')) {
return true;
}
return text.includes('n5s') && (
text.includes('newland')
|| text.includes('zltd')
|| text.includes('nlscan')
|| text.includes('alps')
);
}
function isWrongSeuicOnNewlandDevice(config, identity = getDeviceIdentity()) {
if (!config || isPhoneScanConfig(config)) {
return false;
}
if (!isNewlandN5sDevice(identity) && !isModelMatch(identity, ['n5s'])) {
return false;
}
const brand = String(config.brand || '');
const action = String(config.action || '');
return brand === 'seuic'
|| action.includes('scannerservice.broadcast')
|| String(config.name || '').includes('东集')
|| String(config.name || '').includes('东大');
}
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;
}
if (isModelMatch(identity, rule.models || [])) {
score += 80;
}
}
score += getModelPreferredBoost(config, identity);
// 明确是 N5S 时,压低东集通用广播抢分
if (isNewlandN5sDevice(identity) && (
config.brand === 'seuic'
|| String(config.action || '').includes('scannerservice.broadcast')
)) {
score = Math.min(score, 30);
}
return score;
}
/**
* 在候选配置中选出最匹配当前设备的一项;非 PDA 且无命中则回落手机扫码
*/
export function matchBestScanConfig(candidateConfigs = []) {
const configs = (candidateConfigs || [])
.map((item) => normalizeConfig(item, BUILTIN_SCANNER_CONFIGS))
.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 };
}
/**
* 仅本地没有有效配置时,自动匹配并写入扫描设置。
* 若本地误把 N5S 识别成东集,则强制重配。
* @returns {{ saved: boolean, config: object|null, skipped: boolean, reason?: string }}
*/
export function autoConfigureScanSettings({ remoteConfigs = [] } = {}) {
const existing = getScanConfig(BUILTIN_SCANNER_CONFIGS);
const identity = getDeviceIdentity();
const forceRematch = !!(existing && isWrongSeuicOnNewlandDevice(existing, identity));
if (existing && !forceRematch) {
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: existing || null,
reason: 'no_match',
};
}
const saved = saveScanConfig(matched);
return {
saved,
skipped: false,
config: matched,
reason: saved
? (forceRematch ? 'rematch_n5s_from_seuic' : 'saved')
: 'save_failed',
};
}
+338 -137
View File
@@ -1,176 +1,377 @@
// 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,
_scanCallback: null,
}
},
methods: {
// 注册扫码广播(兼容多种设备)
registerScanBroadcast(scanCallback) {
getScannerConfigs() {
return BUILTIN_SCANNER_CONFIGS;
},
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;
},
/** 停止手机连续扫码(含 Ba-Scanner) */
stopPhoneScanContinuous() {
this._phoneScanContinuousActive = false;
// #ifdef APP-PLUS
try {
// 先尝试注册商米扫码头
this.registerSunmiBroadcast(scanCallback)
.then(() => {
console.log('商米扫码头注册成功');
})
.catch((error) => {
console.log('商米扫码头注册失败,尝试新大陆扫码头:', error);
// 如果商米扫码头注册失败,尝试新大陆扫码头
this.registerNewlandBroadcast(scanCallback);
const scanner = uni.requireNativePlugin('Ba-Scanner');
if (scanner && typeof scanner.onClose === 'function') {
scanner.onClose();
}
} catch (e) {
// 未购买/未打包原生插件时忽略
}
// #endif
},
/**
* 打开手机扫码
* @param {{ continuous?: boolean, callback?: Function, fail?: Function, scanType?: string[], hintText?: string, scanTimeSpace?: number }} options
* continuous=true 时优先用 Ba-Scanner 连续扫;无插件则扫完自动再开相机
*/
openPhoneScan(options = {}) {
const callback = options.callback || this._scanCallback;
const continuous = !!options.continuous;
this._phoneScanContinuousActive = continuous;
// #ifdef APP-PLUS
if (continuous && this.openBaScannerContinuous(options, callback)) {
return;
}
// #endif
// #ifdef APP-PLUS || MP-WEIXIN
uni.scanCode({
scanType: options.scanType || ['qrCode', 'barCode'],
success: (res) => {
const result = this.handleScanResult(res.result, callback);
if (!continuous || !this._phoneScanContinuousActive) return;
Promise.resolve(result)
.catch(() => null)
.then(() => {
if (!this._phoneScanContinuousActive || !this.isPageActive) return;
setTimeout(() => {
if (!this._phoneScanContinuousActive || !this.isPageActive) return;
this.openPhoneScan(options);
}, Number(options.scanTimeSpace) > 0 ? Number(options.scanTimeSpace) : 400);
});
},
fail: (err) => {
console.error('手机扫码失败:', err);
this._phoneScanContinuousActive = false;
if (options.fail) {
options.fail(err);
return;
}
// 用户取消不打扰
const msg = (err && (err.errMsg || err.message)) || '';
if (/cancel|取消/i.test(msg)) return;
uni.showToast({
title: '扫码失败',
icon: 'none',
});
} catch (error) {
console.error("注册扫码广播失败:", error);
},
});
return;
// #endif
// #ifdef H5
uni.showToast({
title: 'H5环境不支持扫码功能',
icon: 'none',
});
// #endif
},
/**
* Ba-Scanner 连续扫码(插件市场 id=9669)
* @returns {boolean} 是否已成功唤起原生连续扫
*/
openBaScannerContinuous(options = {}, callback) {
// #ifdef APP-PLUS
try {
const scanner = uni.requireNativePlugin('Ba-Scanner');
if (!scanner || typeof scanner.onScan !== 'function') return false;
scanner.onScan(
{
isContinuous: true,
scanTimeSpace: Number(options.scanTimeSpace) > 0 ? Number(options.scanTimeSpace) : 1500,
isShowVibrate: options.isShowVibrate !== false,
isShowBeep: !!options.isShowBeep,
isShowToast: false,
isShowPhotoAlbum: options.isShowPhotoAlbum !== false,
hintText: options.hintText || '扫二维码/条形码,可连续扫',
barcodeFormats: options.barcodeFormats || ['QR Code', 'Code 128', 'Code 39', 'EAN-13', 'EAN-8'],
},
(ret) => {
if (!ret || !ret.result) return;
if (!this._phoneScanContinuousActive) return;
this.handleScanResult(ret.result, callback);
},
);
return true;
} catch (e) {
console.warn('Ba-Scanner 不可用,回退 uni.scanCode', e);
return false;
}
// #endif
return false;
},
triggerScan() {
if (this.usePhoneScanFallback) {
this.openPhoneScan();
}
},
// 注册商米扫码头广播接收器
registerSunmiBroadcast(scanCallback) {
return new Promise((resolve, reject) => {
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
},
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 main = plus.android.runtimeMainActivity();
const IntentFilter = plus.android.importClass("android.content.IntentFilter");
const Intent = plus.android.importClass('android.content.Intent');
const setupIntent = new Intent(config.setupAction);
const filter = new IntentFilter();
filter.addAction("com.sunmi.scanner.ACTION_DATA_CODE_RECEIVED");
Object.keys(config.setupParams || {}).forEach(key => {
setupIntent.putExtra(key, config.setupParams[key]);
});
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("data");
const sourceBytes = intent.getByteArrayExtra("source_byte");
console.log('商米扫码结果:', scanResult, sourceBytes);
if (scanResult) {
self.handleScanResult(scanResult, scanCallback);
}
} catch (error) {
console.error('处理商米广播数据时出错:', error);
}
}
}
);
// 注册广播接收器
main.registerReceiver(receiver, filter);
this.scanReceiver = receiver;
console.log('商米扫码广播接收器注册成功');
resolve();
main.sendBroadcast(setupIntent);
console.log(`${config.name}扫码配置完成`);
} catch (error) {
console.error('注册商米广播接收器失败:', error);
reject(error);
console.error(`${config.name}扫码配置失败:`, error);
}
}
const IntentFilter = plus.android.importClass('android.content.IntentFilter');
const filter = new IntentFilter();
filter.addAction(config.action);
const self = this;
const receiver = plus.android.implements(
'io.dcloud.feature.internal.reflect.BroadcastReceiver',
{
onReceive: (context, intent) => {
console.log(`${config.name}扫码广播接收,isPageActive:`, self.isPageActive);
if (!self.isPageActive) return;
try {
// 广播回调里的 intent 需要按实例导入,否则 getStringExtra 不可用
plus.android.importClass(intent);
const scanResult = intent.getStringExtra(config.dataKey);
console.log(`${config.name}扫码结果:`, scanResult);
if (scanResult) {
self.handleScanResult(scanResult, scanCallback);
}
} catch (error) {
console.error(`处理${config.name}广播数据出错:`, error);
}
}
}
);
main.registerReceiver(receiver, filter);
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);
if (scanCallback) {
scanCallback(cleanCode);
return scanCallback(cleanCode);
}
} catch (error) {
console.error("处理扫码结果时出错:", error);
}
return undefined;
},
// 取消扫码广播监听
unregisterScanBroadcast() {
if (this.scanReceiver) {
try {
const main = plus.android.runtimeMainActivity();
main.unregisterReceiver(this.scanReceiver);
this.scanReceiver = null;
console.log("扫码广播注销成功");
} catch (err) {
console.error("注销扫码广播失败:", err);
}
// #ifdef APP-PLUS
if (this.scanReceivers.length === 0) return;
try {
const main = plus.android.runtimeMainActivity();
this.scanReceivers.forEach(item => {
try {
main.unregisterReceiver(item.receiver);
console.log(`${item.brand}扫码广播注销成功`);
} catch (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 +381,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 +396,13 @@ export default {
onHide() {
this.isPageActive = false;
// 不在 onHide 里关 Ba-Scanner:打开原生扫码页也会触发 hide,误关会打断连续扫
// 无插件回退模式靠 isPageActive 控制是否自动再开相机
},
onUnload() {
this.isPageActive = false;
this.stopPhoneScanContinuous();
this.unregisterScanBroadcast();
}
}
+562
View File
@@ -0,0 +1,562 @@
/**
* 坯布出仓单(其他出货单)常量与数据工具
*
* 仅存放与接口无关的常量 / 状态映射 / 字段规范化 / 单位换算等纯工具,
* 真实接口请求统一注册在 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 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: 90 },
{ label: '卷号', key: 'roll_no', width: 110 },
{ label: '匹数', key: 'roll', width: 100 },
{ label: '数量(重量)', key: 'weight', width: 160 },
{ label: '条码', key: 'bar_code', width: 280 },
{ label: '仓位', key: 'position', width: 140 },
];
/** 审核状态变更操作配置(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 STATUS_ACTION_VOICE = {
approve: '审核成功',
unapprove: '消审成功',
void: '作废成功',
};
/** 将接口状态统一为数字,避免字符串导致按钮判断失效 */
export function toAuditStatus(status) {
const n = Number(status);
return Number.isFinite(n) ? n : DRAFT_AUDIT_STATUS;
}
// 出仓类型:当前后端仅提供「坯布其他出货单」一种类型的接口
export const BILL_TYPES = [
{ value: 'other', label: '坯布其他出货单' },
];
/** 列表分页拉取每页条数 */
export const LIST_PAGE_SIZE = 100;
/** 往来单位类型:染厂(接收单位下拉筛选) */
export const BUSINESS_UNIT_TYPE_DYEING_FACTORY = 12;
export function getStatusOptions() {
return Object.keys(AUDIT_STATUS_MAP).map((value) => ({
value,
label: AUDIT_STATUS_MAP[value],
}));
}
/** 将下拉/列表接口响应转为 u-select 所需的 { value, label } */
export function mapToSelectOptions(data) {
const list = Array.isArray(data) ? data : (data && (data.list || data.items || data.records)) || [];
return list.map((item) => ({
value: item.id,
label: item.name || item.label || item.code || String(item.id),
}));
}
// 匹数:后端以 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 normalizedItems = (order.item_data || []).map((item) => normalizeItem(item));
const itemData = groupItemData(normalizedItems);
const auditStatus = toAuditStatus(order.audit_status);
return {
id: order.id,
order_no: order.order_no || '',
audit_status: auditStatus,
audit_status_name: order.audit_status_name || AUDIT_STATUS_MAP[auditStatus] || '',
// 表头
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,
summary_rows: buildSummaryRows(normalizedItems),
_raw: order,
};
}
/** 扫码区下方汇总表列(与出仓明细表一致,可横向滑动) */
export const SUMMARY_TABLE_COLUMNS = [
{ label: '坯布编号', key: 'grey_fabric_code', width: 200 },
{ label: '坯布名称', key: 'grey_fabric_name', width: 240 },
{ label: '纱批', key: 'yarn_batch', width: 180 },
{ label: '机台号', key: 'machine_number', width: 160 },
{ label: '条数', key: 'roll', width: 140 },
{ label: '重量', key: 'weight', width: 160 },
];
/** 汇总行归类:坯布编号 + 名称 + 纱批 + 机台号 */
export function getSummaryGroupKey(item) {
return [
item.grey_fabric_code || '',
item.grey_fabric_name || '',
item.yarn_batch || '',
item.machine_number || '',
].join('|');
}
/** 按坯布、纱批、机台号汇总条数和重量,供扫码区表格展示 */
export function buildSummaryRows(items) {
const grouped = new Map();
(items || []).forEach((item) => {
const groupKey = getSummaryGroupKey(item);
const existing = grouped.get(groupKey);
if (!existing) {
grouped.set(groupKey, {
...item,
group_key: groupKey,
fine_codes: [...(item.fine_codes || [])],
});
return;
}
existing.fine_codes.push(...(item.fine_codes || []));
existing.roll = Number((Number(existing.roll || 0) + Number(item.roll || 0)).toFixed(2));
existing.weight = Number((Number(existing.weight || 0) + Number(item.weight || 0)).toFixed(2));
});
return Array.from(grouped.values());
}
/** 扫码详情区汇总维度(勾选后随扫码请求把对应 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: 'volume_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',
};
/** 构建扫码新增/删除请求参数(勾选锁定维度时附带当前详情区字段值) */
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: '',
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 || '',
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 || '',
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),
};
}
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 || '',
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,
summary_rows: order.summary_rows || [],
};
}
export function getFineCodeTableColumns(readonly) {
const cols = [...FINE_CODE_TABLE_COLUMNS];
// 操作列放前面,避免 PDA 小屏横向滚动后才看到删除
if (!readonly) cols.splice(1, 0, { label: '操作', key: 'action', width: 160 });
return cols;
}
export function mapFineCodeTableRows(list, readonly) {
return (list || []).map((item, index) => ({
seq: index + 1,
roll_no: item.roll_no || '-',
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, onCancel }) {
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) {
if (typeof onCancel === 'function') onCancel();
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 || '操作失败');
});
},
});
}
+274
View File
@@ -0,0 +1,274 @@
/**
* 坯布库存查询:常量、格式化、查询参数组装
*/
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: 70 },
{ label: '卷号', key: 'volume_number', width: 90 },
{ label: '匹数', key: 'roll', width: 90 },
{ label: '数量(KG)', key: 'weight', width: 110 },
{ label: '条码', key: 'bar_code', width: 240 },
{ label: '仓位', key: 'position', width: 100 },
{ label: '颜色', key: 'gray_fabric_color_name', width: 100 },
{ label: '织厂', key: 'supplier_name', width: 120 },
{ label: '机台', key: 'machine_number', width: 90 },
{ label: '库龄(天)', key: 'days_of_age', width: 90 },
];
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 || '-',
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`;
}
+56
View File
@@ -23,6 +23,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,
@@ -363,6 +366,58 @@ function playErrorAudio() {
errorAudio.play();
}
// 固定中文提示语音
const SPEAK_AUDIO = {
'扫码出仓成功': '/static/audio/scan_out_ok.mp3',
'删除成功': '/static/audio/delete_ok.mp3',
'提交成功': '/static/audio/submit_ok.mp3',
'审核成功': '/static/audio/audit_ok.mp3',
'作废成功': '/static/audio/void_ok.mp3',
'扫码成功': '/static/audio/scan_ok.wav',
'进仓成功': '/static/audio/in_ok.wav',
};
const speakAudioMap = {};
function stopOtherSpeak(exceptText) {
Object.keys(speakAudioMap).forEach((key) => {
if (key === exceptText || !speakAudioMap[key]) return;
try {
speakAudioMap[key].stop();
} catch (e) {}
});
if (successAudio) {
try {
successAudio.stop();
} catch (e) {}
}
}
function playSpeak(text) {
const src = SPEAK_AUDIO[text];
if (!src) return;
stopOtherSpeak(text);
const prev = speakAudioMap[text];
if (prev) {
try {
prev.stop();
prev.destroy();
} catch (e) {}
speakAudioMap[text] = undefined;
}
const audio = uni.createInnerAudioContext();
audio.autoplay = true;
audio.src = src;
audio.onError((res) => {
console.log('语音播放失败', text, res && res.errMsg);
try {
audio.destroy();
} catch (e) {}
if (speakAudioMap[text] === audio) speakAudioMap[text] = undefined;
});
speakAudioMap[text] = audio;
}
export default {
get apiurl() {
@@ -375,4 +430,5 @@ export default {
parGetData,
playSuccessAudio,
playErrorAudio,
playSpeak,
};
@@ -0,0 +1,66 @@
<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>
.cardTopName {
display: flex;
align-items: center;
justify-content: space-between;
}
.statusTag {
font-size: 24rpx;
padding: 4rpx 12rpx;
border-radius: 6rpx;
font-weight: normal;
}
.status-0 { background: #f0f0f0; color: #666; }
.status-1 { background: #fff7e6; color: #fa8c16; }
.status-2 { background: #f6ffed; color: #52c41a; }
.status-3 { background: #fff1f0; color: #f5222d; }
.status-4 { background: #f5f5f5; color: #999; }
</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>
+259
View File
@@ -0,0 +1,259 @@
<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="holderId" 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 {
holderId: `plusBarcodeHolder_${barcodeSeq}`,
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];
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;
// 等占位区域完成布局后再量位置,避免 Android 上 rect 为 0
setTimeout(() => {
if (!this.visible || !this._active) {
this._creating = false;
return;
}
uni.createSelectorQuery()
.in(this)
.select('.plus-barcode-panel__holder')
.boundingClientRect((rect) => {
this._creating = false;
if (!this.visible || !this._active) return;
if (!rect || !rect.width || !rect.height) {
uni.showToast({ title: '扫码区域未就绪', icon: 'none' });
return;
}
this.createBarcode(rect);
})
.exec();
}, 80);
// #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);
try {
const barcode = plus.barcode.create(
this.barcodeViewId,
this.filters && this.filters.length ? this.filters : DEFAULT_FILTERS,
{
top: `${Math.round(rect.top)}px`,
left: `${Math.round(rect.left)}px`,
width: `${Math.round(rect.width)}px`,
height: `${Math.round(rect.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>
+1 -1
View File
@@ -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: 32rpx;
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: 28rpx;
}
.label {
width: 180rpx;
color: #888;
flex-shrink: 0;
}
.value {
flex: 1;
color: #333;
word-break: break-all;
}
</style>
@@ -0,0 +1,36 @@
<template>
<view>
<view v-if="title" class="fabric-out-sectionTitle">{{ title }}</view>
<slot name="before-list" />
<view class="fabric-out-detailList">
<view
v-for="row in list"
:key="row.group_key || row.id"
class="fabric-out-detailCard"
>
<view class="fabric-out-detailRow" @click="$emit('fabric-info', row)">
<text>{{ row.grey_fabric_code }}#{{ row.grey_fabric_name }}</text>
<u-icon name="arrow-right" size="32" color="#888" />
</view>
<view class="fabric-out-detailMeta">
<text>匹数:{{ row.roll }}</text>
<text>重量:{{ row.weight }}KG</text>
<u-button type="primary" size="mini" @click="$emit('fine-code', row)">细码</u-button>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
list: { type: Array, default: () => [] },
title: { type: String, default: '' },
},
};
</script>
<style scoped lang="scss">
@import '@/pages/storefabric/storeFabricBusinessOutPage.scss';
</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,82 @@
<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="scanning"
class="fabric-out-scanClose"
@click.stop="$emit('phone-scan')"
>
关闭扫码
</view>
<view
v-else
class="fabric-out-toolbarIcon"
@click.stop="$emit('phone-scan')"
>
<u-icon name="scan" size="44" color="#595959" />
</view>
<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: '当前状态不可扫码' },
scanning: { type: Boolean, default: false },
},
};
</script>
<style scoped lang="scss">
@import '@/pages/storefabric/storeFabricBusinessOutPage.scss';
@import '@/common/pda-typography.scss';
.fabric-out-toolbarIcon {
display: flex;
align-items: center;
justify-content: center;
width: 72rpx;
height: 72rpx;
margin-left: 8rpx;
border-radius: 8rpx;
border: 1rpx solid #d9d9d9;
background: #f7f7f7;
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,100 @@
<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">
<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>
<view class="fabric-out-scanDetailRow">
<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-scanDetailCheck" @click="toggleGroup('volume_number')">
<checkbox :checked="isGroupChecked('volume_number')" />
<text class="fabric-out-scanDetailLabel">布号:</text>
<text class="fabric-out-scanDetailValue">{{ detail.volume_number || '' }}</text>
</view>
</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>
</view>
</template>
<script>
export default {
props: {
detail: { type: Object, default: () => ({}) },
groupKeys: { type: Array, default: () => [] },
},
methods: {
isGroupChecked(key) {
return (this.groupKeys || []).includes(key);
},
toggleGroup(key) {
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));
},
},
};
</script>
<style scoped lang="scss">
@import '@/pages/storefabric/storeFabricBusinessOutPage.scss';
</style>
@@ -0,0 +1,140 @@
<template>
<view class="fabric-out-summary">
<scroll-view scroll-x class="fabric-out-summaryScroll">
<view class="fabric-out-summaryInner" :style="{ width: tableWidth }">
<t-table border="1" border-color="#e1e1e1">
<t-tr font-size="18" color="#3e3e3e" align="center">
<t-th
v-for="col in headers"
:key="col.key"
align="center"
>
<view class="fabric-out-th" :style="cellStyle(col)">{{ col.label }}</view>
</t-th>
</t-tr>
<t-tr
v-for="(row, rowIndex) in contents"
:key="row.group_key || rowIndex"
font-size="18"
color="#333"
align="center"
>
<t-td
v-for="col in headers"
:key="col.key"
align="center"
>
<view
class="fabric-out-td"
:style="cellStyle(col)"
@click="onCellClick(col.key, row)"
>{{ formatCell(row, col.key) }}</view>
</t-td>
</t-tr>
</t-table>
</view>
</scroll-view>
<view v-if="!contents.length" class="fabric-out-summaryEmpty">暂无数据~~</view>
</view>
</template>
<script>
import tTable from '@/components/t-table/t-table.vue';
import tTr from '@/components/t-table/t-tr.vue';
import tTh from '@/components/t-table/t-th.vue';
import tTd from '@/components/t-table/t-td.vue';
import { SUMMARY_TABLE_COLUMNS } from '@/common/storeFabricBusinessOut';
export default {
components: { tTable, tTr, tTh, tTd },
props: {
rows: { type: Array, default: () => [] },
},
computed: {
headers() {
return SUMMARY_TABLE_COLUMNS;
},
tableWidth() {
const total = this.headers.reduce((sum, col) => sum + (col.width || 160), 0);
return `${total}rpx`;
},
contents() {
return (this.rows || []).map((row) => ({
...row,
grey_fabric_code: row.grey_fabric_code || '',
grey_fabric_name: row.grey_fabric_name || '',
yarn_batch: row.yarn_batch || '',
machine_number: row.machine_number || '',
roll: row.roll != null && row.roll !== '' ? String(row.roll) : '0',
weight: row.weight != null && row.weight !== '' ? String(row.weight) : '0',
}));
},
},
methods: {
cellStyle(col) {
const width = `${col.width || 160}rpx`;
return {
width,
minWidth: width,
maxWidth: width,
};
},
formatCell(row, key) {
const val = row[key];
if (val === 0) return '0';
return val != null && val !== '' ? val : '-';
},
onCellClick(key, row) {
this.$emit('cell-click', { key, lineData: row });
},
},
};
</script>
<style scoped lang="scss">
@import '@/common/pda-typography.scss';
.fabric-out-summary {
background: #fff;
margin-top: 16rpx;
}
.fabric-out-summaryScroll {
width: 100%;
}
.fabric-out-summaryInner {
min-width: 100%;
}
.fabric-out-summary ::v-deep .t-table {
width: 100%;
}
.fabric-out-summary ::v-deep .t-th,
.fabric-out-summary ::v-deep .t-td {
flex: 0 0 auto;
width: auto;
padding: 0;
min-height: $pda-row-min-height;
box-sizing: border-box;
}
.fabric-out-th,
.fabric-out-td {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
min-height: $pda-row-min-height;
padding: 16rpx 12rpx;
font-size: $pda-font-body;
word-break: break-all;
text-align: center;
}
.fabric-out-th {
font-weight: bold;
}
.fabric-out-summaryEmpty {
text-align: center;
color: #999;
font-size: $pda-font-body;
line-height: $pda-row-min-height;
background: #fff;
}
</style>
@@ -0,0 +1,188 @@
<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)"
>
<text
v-if="col.key === 'action' && !readonly"
class="deleteBtn"
>{{ row[col.key] }}</text>
<template v-else>{{ row[col.key] }}</template>
</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 = 56;
const headerHeight = 56;
const total = headerHeight + this.list.length * rowHeight;
return `${Math.min(Math.max(total, 112), 480)}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 lang="scss">
@import '@/common/pda-typography.scss';
.popupWrap {
padding: 28rpx 16rpx 32rpx;
max-height: 80vh;
box-sizing: border-box;
display: flex;
flex-direction: column;
overflow: hidden;
}
.popupTitle {
font-size: $pda-font-popup-title;
font-weight: bold;
text-align: center;
margin-bottom: 24rpx;
color: #333;
flex-shrink: 0;
}
.emptyTip {
text-align: center;
color: #999;
padding: 48rpx 0;
font-size: $pda-font-popup-sub;
}
.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;
align-items: stretch;
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;
display: flex;
align-items: center;
justify-content: center;
min-height: $pda-row-min-height;
padding: 16rpx 10rpx;
font-size: $pda-font-popup-item;
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;
min-height: 80rpx;
}
.actionCell {
padding: 12rpx 8rpx;
}
.deleteBtn {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 120rpx;
min-height: 72rpx;
padding: 0 20rpx;
border-radius: 8rpx;
background: #fff1f0;
border: 1rpx solid #ffa39e;
color: #f5222d;
font-size: $pda-font-action;
line-height: 1.2;
}
.tableTip {
margin-top: 20rpx;
font-size: $pda-font-popup-sub;
color: #999;
text-align: center;
flex-shrink: 0;
}
</style>
@@ -0,0 +1,49 @@
<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>
.statusRow {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 26rpx;
background: #fff;
border-bottom: 1rpx solid #f5f5f5;
}
.statusLabel {
font-size: 40rpx;
color: #333;
}
.statusTag {
font-size: 34rpx;
padding: 4rpx 12rpx;
border-radius: 6rpx;
font-weight: normal;
}
.status-0 { background: #f0f0f0; color: #666; }
.status-1 { background: #fff7e6; color: #fa8c16; }
.status-2 { background: #f6ffed; color: #52c41a; }
.status-3 { background: #fff1f0; color: #f5222d; }
.status-4 { background: #f5f5f5; color: #999; }
</style>
@@ -0,0 +1,199 @@
<template>
<u-popup v-model="visible" mode="bottom" :safe-area-inset-bottom="true" border-radius="16">
<view class="search-select" :style="{ maxHeight: panelMaxHeight + 'px' }">
<view class="search-select__bar">
<text class="search-select__title">{{ title }}</text>
<text class="search-select__cancel" @click="visible = false">取消</text>
</view>
<view class="search-select__search">
<u-search v-model="keyword" :placeholder="placeholder" :show-action="false" />
</view>
<scroll-view
scroll-y
class="search-select__list"
:style="{ height: listHeight + 'px' }"
:enable-back-to-top="false"
>
<view v-if="!filteredList.length" class="search-select__empty">暂无匹配数据</view>
<view
v-for="item in filteredList"
:key="String(item.value)"
class="search-select__item"
:class="{ 'is-selected': multiple && isPicked(item) }"
@click="onItemClick(item)"
>
<text class="search-select__item-label">{{ item.label }}</text>
<text v-if="multiple && isPicked(item)" class="search-select__check">已选</text>
</view>
</scroll-view>
<view v-if="multiple" class="search-select__footer">
<view class="search-select__ok" @click="confirmMultiple">确定</view>
</view>
</view>
</u-popup>
</template>
<script>
export default {
props: {
show: { type: Boolean, default: false },
title: { type: String, default: '请选择' },
placeholder: { type: String, default: '请输入名称搜索' },
list: { type: Array, default: () => [] },
multiple: { type: Boolean, default: false },
selected: { type: Array, default: () => [] },
},
data() {
return {
keyword: '',
panelMaxHeight: 520,
listHeight: 360,
picked: [],
};
},
computed: {
visible: {
get() { return this.show; },
set(val) { this.$emit('update:show', val); },
},
filteredList() {
const keyword = (this.keyword || '').trim().toLowerCase();
if (!keyword) return this.list;
return this.list.filter((item) => String(item.label || '').toLowerCase().includes(keyword));
},
},
watch: {
show(val) {
if (val) {
this.keyword = '';
this.picked = (this.selected || []).map((item) => String(item));
this.calcHeight();
}
},
},
mounted() {
this.calcHeight();
},
methods: {
calcHeight() {
try {
const sys = uni.getSystemInfoSync();
const winH = sys.windowHeight || 600;
// 弹层约占屏高 65%,列表为弹层去掉标题+搜索后的剩余高度
this.panelMaxHeight = Math.floor(winH * 0.65);
const chrome = this.multiple ? 230 : 140;
this.listHeight = Math.max(220, this.panelMaxHeight - chrome);
} catch (e) {
this.panelMaxHeight = 520;
this.listHeight = this.multiple ? 280 : 360;
}
},
isPicked(item) {
return this.picked.includes(String(item.value));
},
onItemClick(item) {
if (!this.multiple) {
this.confirm(item);
return;
}
const key = String(item.value);
if (this.picked.includes(key)) {
this.picked = this.picked.filter((value) => value !== key);
return;
}
this.picked = this.picked.concat(key);
},
confirm(item) {
this.visible = false;
this.$emit('confirm', item);
},
confirmMultiple() {
const selected = (this.list || []).filter((item) => this.isPicked(item));
this.visible = false;
this.$emit('confirm', selected);
},
},
};
</script>
<style scoped lang="scss">
@import '@/common/pda-typography.scss';
.search-select {
background: #fff;
padding-bottom: 20rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
overflow: hidden;
}
.search-select__bar {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 30rpx 12rpx;
}
.search-select__title {
font-size: $pda-font-popup-title;
font-weight: bold;
color: #202328;
}
.search-select__cancel {
font-size: $pda-font-popup-sub;
color: #888;
}
.search-select__search {
flex-shrink: 0;
padding: 0 24rpx 12rpx;
}
.search-select__list {
width: 100%;
box-sizing: border-box;
}
.search-select__empty {
padding: 80rpx 0;
text-align: center;
color: #999;
font-size: $pda-font-popup-sub;
}
.search-select__item {
display: flex;
align-items: center;
justify-content: space-between;
min-height: $pda-row-min-height;
padding: 20rpx 30rpx;
box-sizing: border-box;
font-size: $pda-font-popup-item;
color: #202328;
border-bottom: 1px solid #f2f2f2;
}
.search-select__item-label {
flex: 1;
min-width: 0;
font-size: $pda-font-popup-item;
color: #202328;
}
.search-select__item.is-selected .search-select__item-label {
color: #2f78e6;
}
.search-select__check {
flex-shrink: 0;
margin-left: 16rpx;
font-size: $pda-font-action;
color: #2f78e6;
}
.search-select__footer {
flex-shrink: 0;
padding: 16rpx 30rpx 8rpx;
}
.search-select__ok {
height: $pda-button-height;
line-height: $pda-button-height;
text-align: center;
font-size: $pda-font-button;
color: #fff;
background: #2979ff;
border-radius: 46rpx;
}
</style>
@@ -0,0 +1,57 @@
<template>
<view class="warehouse-table-wrap" :class="{ 'is-auto-height': isAutoHeight }">
<wyb-table
ref="table"
:headers="headers"
:contents="contents"
:width="tableWidth"
:height="height"
:first-line-fixed="firstLineFixed"
@onCellClick="onCellClick"
/>
</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: () => [] },
// auto:高度随内容撑开,纵向滚动交给外层 scroll-view,避免与下拉刷新抢手势
height: { type: String, default: 'auto' },
firstLineFixed: { type: Boolean, default: false },
},
computed: {
isAutoHeight() {
return !this.height || this.height === 'auto';
},
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;
}
/* 取消表格内部纵向滚动,只保留横向;纵向由页面 scroll-view 处理 */
.is-auto-height ::v-deep .wyb-table-scroll-view {
height: auto !important;
overflow-x: auto;
overflow-y: visible;
}
</style>
+86
View File
@@ -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>
+71
View File
@@ -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}">
<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>
+71
View File
@@ -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>
+81
View File
@@ -0,0 +1,81 @@
<template>
<view class="t-tr">
<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>
+589
View File
@@ -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>
+1 -1
View File
@@ -73,7 +73,7 @@
borderRight: index === headers.length - 1 || (!showVertBorder && index !== 0) ? 'none' : '1px solid' + borderColor,
zIndex: index === 0 ? 20 : 0,
left: index === 0 && firstLineFixed ? (enableCheck ? checkColWidth + 'rpx' : 0) : 'auto',
position: index === 0 ? 'sticky' : 'static'}">
position: index === 0 && firstLineFixed ? 'sticky' : 'static'}">
<text :style="{marginLeft: autoSortShow(index) && textAlign !== 'left' ? fontSize[0] * 0.65 + 'rpx' : 0}">
{{item.label || emptyString}}
</text>
+19
View File
@@ -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 -13
View File
@@ -1,13 +1,13 @@
{
"name" : "浩拓技术",
"name" : "浩拓织厂",
"appid" : "__UNI__F79F300",
"description" : "浩拓纺织平台",
"versionName" : "1.1.1",
"versionCode" : 101,
"description" : "周尚-浩拓织厂",
"versionName" : "1.1.26",
"versionCode" : 126,
"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,7 +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.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\"/>",
@@ -76,10 +77,10 @@
},
"icons" : {
"android" : {
"hdpi" : "dist/dev/app-plus/static/haotop/image/logo/72.png",
"xhdpi" : "dist/dev/app-plus/static/haotop/image/logo/96.png",
"xxhdpi" : "dist/dev/app-plus/static/haotop/image/logo/144.png",
"xxxhdpi" : "dist/dev/app-plus/static/haotop/image/logo/1024.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" : "",
@@ -109,9 +110,9 @@
"splashscreen" : {
"androidStyle" : "default",
"android" : {
"hdpi" : "dist/dev/app-plus/static/haotop/image/logo/Start.png",
"xhdpi" : "dist/dev/app-plus/static/haotop/image/logo/Start.png",
"xxhdpi" : "dist/dev/app-plus/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"
}
}
}
+64 -18
View File
@@ -1,5 +1,6 @@
{
"easycom": {
"^u-select$": "@/components/u-select/u-select.vue",
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
},
"pages": [
@@ -148,6 +149,13 @@
"navigationBarTitleText": "清除缓存"
}
},
{
"path": "pages/sys/user/scan-settings",
"style": {
"navigationBarTitleText": "扫描设置",
"navigationStyle": "custom"
}
},
{
"path": "pages/sys/workbench/index",
"style": {
@@ -562,6 +570,62 @@
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
},{
"path" : "pages/storefabric/storeFabricBusinessOutList",
"style" :
{
"navigationBarTitleText": "坯布出库列表",
"enablePullDownRefresh": false
}
},{
"path": "pages/storefabric/storeFabricManualIn",
"style": {
"navigationBarTitleText": "手动进仓",
"navigationStyle": "custom",
"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,30 +808,12 @@
"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",
// "selectedIconPath": "static/aidex/tabbar/home_2.png",
// "text": "首页"
// },
{
"pagePath": "pages/sys/workbench/index",
"iconPath": "static/aidex/tabbar/apply_1.png",
"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",
+1 -1
View File
@@ -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 -1
View File
@@ -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">
+2 -2
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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"
+2 -2
View File
@@ -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>
+1 -1
View File
@@ -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>
+1 -1
View File
@@ -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>
+1 -1
View File
@@ -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 -1
View File
@@ -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)">
+1 -1
View File
@@ -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">
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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>
+119 -296
View File
@@ -6,30 +6,24 @@
<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>
<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" />
<u-icon
v-if="usePhoneScanFallback"
name="scan"
size="44"
color="#2979ff"
style="margin-left: 8px;"
@click="handlePhoneScan"
/>
<checkbox-group @change="handleAllCrockNoChange">
<checkbox :checked="AllCrockNoScanStatus">整缸</checkbox>
@@ -41,58 +35,38 @@
</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">{{ 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 +79,10 @@ import util, {
playErrorAudio,
} from "../../common/util";
import wybTable from "@/components/wyb-table/wyb-table.vue";
import scanMixin from "@/common/scanMixin.js";
let that = "";
export default {
mixins: [scanMixin],
data() {
return {
SaleBillNo: "", // 单号
@@ -115,7 +91,7 @@ export default {
align: "center",
index: 0,
pageType: "",
submitLoading: false,
submitLoading: false,
actionSheetShow: false,
QRBarCode: "",
BillNo: "",
@@ -192,8 +168,7 @@ export default {
],
scanningInput: "", // 用于累积扫码输入
lastKeyTime: 0, // 用于判断扫码速度
scanReceiver: null,
isPageActive: false, // 添加页面活动状态标志
// scanReceiver 和 isPageActive 由 scanMixin 提供
};
},
@@ -214,29 +189,31 @@ 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: {
handlePhoneScan() {
this.openPhoneScan();
},
playSuccess() {
util.playSuccessAudio();
},
@@ -323,247 +300,93 @@ export default {
icon: 'success'
});
},
handleScans() {
// 删除模式判断
let aBarCodeDelStatus = 1; // 1=正常扫码,3=删除模式
if (this.BarCodeDelStatus) {
aBarCodeDelStatus = 3;
}
// 整缸扫描判断
let aAllCrockNoScanStatus = 2; // 0=普通扫描,1=整缸扫描
if (this.AllCrockNoScanStatus) {
aAllCrockNoScanStatus = "1";
}
// 数据清理:去除空格、换行符等
let cleanCode = this.QRBarCode.replace(/\s+/g, '').replace(/[\r\n]/g, '');
console.log("this.QRBarCode ---->>" + cleanCode);
// 初始化条码和二维码变量
let aQRBarCode = "";
let aBarCode = "";
if (cleanCode.startsWith("66^") || cleanCode.startsWith("99^") || /[\u4E00-\u9FA5]/.test(cleanCode)) {
aQRBarCode = cleanCode;
} else {
aBarCode = cleanCode;
}
if (aQRBarCode == "" && aBarCode == "") {
this.QRBarCode = "";
this.showError('请扫描二维码或者条码');
return;
}
console.log('请求参数', aBarCodeDelStatus);
console.log('请求参数', aBarCode);
console.log('请求参数', aQRBarCode);
console.log('请求参数', this.BillMasterID);
console.log('请求参数 SaleBillNo', this.SaleBillNo);
console.log('请求参数 token', uni.getStorageSync("userToken").Token);
// 发送请求
util.request({
url: "/product/fpmArrangeOrder/updateFpmArrangeOrder",
method: "PUT",
header: {
Platform: 2,
Authorization: uni.getStorageSync("userToken").Token,
},
data: {
arrange_type: aBarCodeDelStatus, // 1=正常扫码,3=删除模式
bar_code: aBarCode, // 条码(如果是条形码就使用这个)
qr_code: aQRBarCode, // 二维码(如果是二维码就使用这个)
id: parseInt(this.BillMasterID), // 订单ID
// order_no: this.SaleBillNo, // 单号
},
success: (res) => {
console.log('API响应:', res);
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.QRBarCode = "";
if (aBarCodeDelStatus == 1) {
this.showSuccess("扫描成功!");
} else {
this.showSuccess("删除成功!");
}
this.SalePickDetail();
} else {
this.showError("扫描出错," + res.data.msg);
this.QRBarCode = "";
}
},
fail: (error) => {
console.error('API请求失败:', error);
this.QRBarCode = "";
this.showError("连接服务器出错,请检查后台服务是否启动!");
},
});
},
// 注册扫码广播接收器(支持商米和新大陆扫码头)
registerScanBroadcast() {
try {
// 先尝试注册商米扫码头
this.registerSunmiBroadcast()
.then(() => {
console.log('商米扫码头注册成功');
})
.catch((error) => {
console.log('商米扫码头注册失败,尝试新大陆扫码头:', error);
// 如果商米扫码头注册失败,尝试新大陆扫码头
this.registerNewlandBroadcast();
});
} catch (error) {
console.error("注册扫码广播失败:", error);
handleScans() {
// 删除模式判断
let aBarCodeDelStatus = 1; // 1=正常扫码,3=删除模式
if (this.BarCodeDelStatus) {
aBarCodeDelStatus = 3;
}
},
// 注册商米扫码头广播接收器
registerSunmiBroadcast() {
return new Promise((resolve, reject) => {
try {
const main = plus.android.runtimeMainActivity();
const IntentFilter = plus.android.importClass("android.content.IntentFilter");
// 整缸扫描判断
let aAllCrockNoScanStatus = 2; // 0=普通扫描,1=整缸扫描
if (this.AllCrockNoScanStatus) {
aAllCrockNoScanStatus = "1";
}
const filter = new IntentFilter();
filter.addAction("com.sunmi.scanner.ACTION_DATA_CODE_RECEIVED");
// 数据清理:去除空格、换行符等
let cleanCode = this.QRBarCode.replace(/\s+/g, '').replace(/[\r\n]/g, '');
const receiver = plus.android.implements(
"io.dcloud.feature.internal.reflect.BroadcastReceiver",
{
onReceive: (context, intent) => {
console.log('商米扫码广播接收,isPageActive:', this.isPageActive);
// 只有当页面活动时才处理广播
if (!this.isPageActive) return;
console.log("this.QRBarCode ---->>" + cleanCode);
try {
const scanResult = intent.getStringExtra("data");
const sourceBytes = intent.getByteArrayExtra("source_byte");
// 初始化条码和二维码变量
let aQRBarCode = "";
let aBarCode = "";
console.log('商米扫码结果:', scanResult, sourceBytes);
if (cleanCode.startsWith("66^") || cleanCode.startsWith("99^") || /[\u4E00-\u9FA5]/.test(cleanCode)) {
aQRBarCode = cleanCode;
} else {
aBarCode = cleanCode;
}
if (scanResult) {
this.handleScanResult(scanResult);
}
} catch (error) {
console.error('处理商米广播数据时出错:', error);
}
}
if (aQRBarCode == "" && aBarCode == "") {
this.QRBarCode = "";
this.showError('请扫描二维码或者条码');
return;
}
console.log('请求参数', aBarCodeDelStatus);
console.log('请求参数', aBarCode);
console.log('请求参数', aQRBarCode);
console.log('请求参数', this.BillMasterID);
console.log('请求参数 SaleBillNo', this.SaleBillNo);
console.log('请求参数 token', uni.getStorageSync("userToken").Token);
// 发送请求
util.request({
url: "/product/fpmArrangeOrder/updateFpmArrangeOrder",
method: "PUT",
header: {
Platform: 2,
Authorization: uni.getStorageSync("userToken").Token,
},
data: {
arrange_type: aBarCodeDelStatus, // 1=正常扫码,3=删除模式
bar_code: aBarCode, // 条码(如果是条形码就使用这个)
qr_code: aQRBarCode, // 二维码(如果是二维码就使用这个)
id: parseInt(this.BillMasterID), // 订单ID
// order_no: this.SaleBillNo, // 单号
},
success: (res) => {
console.log('API响应:', res);
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.QRBarCode = "";
if (aBarCodeDelStatus == 1) {
this.showSuccess("扫描成功!");
} else {
this.showSuccess("删除成功!");
}
);
// 注册广播接收器
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);
}
},
this.SalePickDetail();
} else {
this.showError("扫描出错," + res.data.msg);
this.QRBarCode = "";
}
);
// 注册广播接收器
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);
}
}
},
fail: (error) => {
console.error('API请求失败:', error);
this.QRBarCode = "";
this.showError("连接服务器出错,请检查后台服务是否启动!");
},
});
},
SalePickBillDetailScan() {
@@ -601,13 +424,13 @@ export default {
}
},
// 新增提交订单的方法
submitOrder: function() {
this.submitLoading = true
submitOrder: function () {
this.submitLoading = true
this.$u.api.outFpmArrangeOrder({
id: parseInt(this.BillMasterID)
}).then(res => {
this.submitLoading = false
console.log('outFpmArrangeOrder res',res)
this.submitLoading = false
console.log('outFpmArrangeOrder res', res)
if (res) {
this.BillDataMessage = "成功生成出仓单!";
this.playSuccess();
@@ -617,7 +440,7 @@ export default {
return;
}
}).catch(error => {
this.submitLoading = false
this.submitLoading = false
this.playError();
this.BillDataMessage = "连接服务器出错,请检查后台服务是否启动!";
});
@@ -676,7 +499,7 @@ page {
margin-right: 12rpx;
}
.cpInput > input {
.cpInput>input {
box-sizing: border-box;
border: 1rpx solid #dddddd;
width: 100%;
@@ -690,7 +513,7 @@ page {
margin-right: 12rpx;
}
.cpInput1 > input {
.cpInput1>input {
box-sizing: border-box;
border: 1rpx solid #dddddd;
width: 100%;
@@ -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,138 @@
<template>
<view class="fabric-out-page">
<view v-if="showHeaderToggle" class="fabric-out-headerBar" @click="toggleHeader">
<view class="fabric-out-headerBarMain">
<text class="fabric-out-headerBarTitle">单据信息</text>
<text v-if="form.order_no" class="fabric-out-headerBarSub">{{ form.order_no }}</text>
</view>
<view class="fabric-out-headerBarAction">
<text>{{ headerExpanded ? '收起' : '展开' }}</text>
<u-icon class="fabric-out-headerBarIcon" :name="headerExpanded ? 'arrow-up' : 'arrow-down'" size="32" color="#2979ff"></u-icon>
</view>
</view>
<view v-show="headerExpanded">
<OrderStatusBar class="fabric-out-mt32" :status="displayAuditStatus" :name="displayAuditStatusName" />
<view class="flex-white-plr26 ptb10 bdb_f5">
<text class="mr26">出仓单号</text>
<u-input disabled placeholder="保存后自动生成" v-model="form.order_no" />
</view>
<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>
</view>
<view v-if="showScanSection">
<PlusBarcodePanel
ref="plusBarcodePanel"
:visible.sync="phoneScanPanelOpen"
hint-text="连续扫码出仓,扫完可继续下一条"
:scan-time-space="1200"
@success="onPlusBarcodeSuccess"
/>
<FabricOutScanBar
:can-scan="canScan"
:value="QRBarCode"
:delete-mode="BarCodeDelStatus"
:message="BillDataMessage"
disabled-tip="请先提交保存后再扫码"
:scanning="phoneScanPanelOpen"
@input="QRBarCode = $event"
@scan="handleScan"
@phone-scan="handlePhoneScan"
@delete-mode-change="BarCodeDelChange"
/>
<FabricOutScanDetail
:detail="scanDetail"
:group-keys="scanGroupKeys"
@group-change="scanGroupKeys = $event"
/>
<FabricOutSummaryTable
:rows="form.summary_rows"
@cell-click="onSummaryCellClick"
/>
</view>
<FabricOutFooter
v-if="!headerSaved"
:buttons="[{ label: '提交保存', action: 'save' }]"
single
@action="submitSave"
/>
<FabricOutFooter
v-else
:buttons="statusActionButtons"
@action="handleStatusAction"
/>
<search-select-popup
:show.sync="selectShow"
title="选择接收单位"
placeholder="请输入染厂名称"
:list="selectList"
@confirm="selectConfirmFun"
/>
<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 FabricOutScanBar from '@/components/storefabric/FabricOutScanBar.vue';
import FabricOutScanDetail from '@/components/storefabric/FabricOutScanDetail.vue';
import FabricOutSummaryTable from '@/components/storefabric/FabricOutSummaryTable.vue';
import FabricOutFooter from '@/components/storefabric/FabricOutFooter.vue';
import SearchSelectPopup from '@/components/storefabric/SearchSelectPopup.vue';
import PlusBarcodePanel from '@/components/common/PlusBarcodePanel.vue';
export default {
components: {
FabricInfoPopup,
FineCodePopup,
OrderStatusBar,
FabricOutScanBar,
FabricOutScanDetail,
FabricOutSummaryTable,
FabricOutFooter,
SearchSelectPopup,
PlusBarcodePanel,
},
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,130 @@
<template>
<view class="fabric-out-page">
<view v-if="showHeaderToggle" class="fabric-out-headerBar" @click="toggleHeader">
<view class="fabric-out-headerBarMain">
<text class="fabric-out-headerBarTitle">单据信息</text>
<text v-if="form.order_no" class="fabric-out-headerBarSub">{{ form.order_no }}</text>
</view>
<view class="fabric-out-headerBarAction">
<text>{{ headerExpanded ? '收起' : '展开' }}</text>
<u-icon class="fabric-out-headerBarIcon" :name="headerExpanded ? 'arrow-up' : 'arrow-down'" size="32" color="#2979ff"></u-icon>
</view>
</view>
<view v-show="headerExpanded">
<OrderStatusBar class="fabric-out-mt32" :status="displayAuditStatus" :name="displayAuditStatusName" />
<view class="flex-white-plr26 ptb10 bdb_f5">
<text class="mr26">出仓单号</text>
<text>{{ form.order_no }}</text>
</view>
<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>
</view>
<view v-if="showScanSection">
<PlusBarcodePanel
ref="plusBarcodePanel"
:visible.sync="phoneScanPanelOpen"
hint-text="连续扫码出仓,扫完可继续下一条"
:scan-time-space="1200"
@success="onPlusBarcodeSuccess"
/>
<FabricOutScanBar
:can-scan="canScan"
:value="QRBarCode"
:delete-mode="BarCodeDelStatus"
:message="BillDataMessage"
:scanning="phoneScanPanelOpen"
@input="QRBarCode = $event"
@scan="handleScan"
@phone-scan="handlePhoneScan"
@delete-mode-change="BarCodeDelChange"
/>
<FabricOutScanDetail
:detail="scanDetail"
:group-keys="scanGroupKeys"
@group-change="scanGroupKeys = $event"
/>
<FabricOutSummaryTable
:rows="form.summary_rows"
@cell-click="onSummaryCellClick"
/>
</view>
<FabricOutFooter :buttons="statusActionButtons" @action="handleStatusAction" />
<search-select-popup
:show.sync="selectShow"
title="选择接收单位"
placeholder="请输入染厂名称"
:list="selectList"
@confirm="selectConfirmFun"
/>
<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 FabricOutScanBar from '@/components/storefabric/FabricOutScanBar.vue';
import FabricOutScanDetail from '@/components/storefabric/FabricOutScanDetail.vue';
import FabricOutSummaryTable from '@/components/storefabric/FabricOutSummaryTable.vue';
import FabricOutFooter from '@/components/storefabric/FabricOutFooter.vue';
import SearchSelectPopup from '@/components/storefabric/SearchSelectPopup.vue';
import PlusBarcodePanel from '@/components/common/PlusBarcodePanel.vue';
export default {
components: {
FabricInfoPopup,
FineCodePopup,
OrderStatusBar,
FabricOutScanBar,
FabricOutScanDetail,
FabricOutSummaryTable,
FabricOutFooter,
SearchSelectPopup,
PlusBarcodePanel,
},
mixins: [formMixin],
data() {
return {
pageMode: 'edit',
headerExpanded: false,
};
},
onLoad(e) {
uni.setNavigationBarTitle({ title: '编辑坯布出仓单' });
this.pageMode = 'edit';
this.headerExpanded = false;
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,548 @@
<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="{ 'filterBtn--active': hasActiveFilter }" @click="filterShow = true">
<view class="filterBtn__icon">
<u-icon 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="36"></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"
@confirm="onUnitConfirm"
></u-select>
<search-select-popup
:show.sync="businessUnitSelectShow"
title="选择接收单位"
placeholder="请输入染厂名称"
:list="businessUnitSelectList"
@confirm="onBusinessUnitConfirm"
/>
</view>
</template>
<script>
import addBtn from '@/components/addBtn/addBtn.vue';
import FabricOutItem from '@/components/card/storeFabricBusinessOutItem.vue';
import SearchSelectPopup from '@/components/storefabric/SearchSelectPopup.vue';
import {
getStatusOptions,
AUDIT_STATUS,
AUDIT_STATUS_MAP,
BILL_TYPES,
BUSINESS_UNIT_TYPE_DYEING_FACTORY,
formatRoll,
mapToSelectOptions,
fetchAllOrderList,
} from '@/common/storeFabricBusinessOut';
const FILTER_STORAGE_KEY = 'storeFabricBusinessOutListFilters';
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 (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 = '';
return filters;
};
export default {
components: { addBtn, FabricOutItem, SearchSelectPopup },
data() {
return {
list: [],
scrollHeight: '667px',
triggered: false,
filterShow: false,
statusSelectShow: false,
unitSelectShow: false,
businessUnitSelectShow: false,
unitSelectType: '',
deliveryUnitOptions: [],
businessUnitOptions: [],
unitOptionsLoading: false,
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 || '';
},
unitSelectList() {
return [{ value: '', label: '全部' }, ...this.deliveryUnitOptions];
},
businessUnitSelectList() {
return [{ value: '', label: '全部' }, ...this.businessUnitOptions];
},
statusSelectDefault() {
const index = this.statusSelectList.findIndex(
(item) => String(item.value) === String(this.filters.audit_status)
);
return [index >= 0 ? index : 0];
},
unitSelectDefault() {
const index = this.unitSelectList.findIndex(
(item) => String(item.value) === String(this.filters.delivery_unit_id)
);
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
|| (filters.audit_status !== '' && filters.audit_status !== null)
);
},
},
onLoad() {
uni.setNavigationBarTitle({ title: '坯布出库列表' });
this.restoreFilterCache();
uni.getSystemInfo({
success: (res) => {
this.scrollHeight = `${res.windowHeight - 50}px`;
},
});
},
onShow() {
this.loadList();
},
methods: {
restoreFilterCache() {
try {
const filters = normalizeFilters(uni.getStorageSync(FILTER_STORAGE_KEY));
this.filters = { ...filters };
this.appliedFilters = { ...filters };
} catch (e) {
this.filters = createDefaultFilters();
this.appliedFilters = createDefaultFilters();
}
},
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_DYEING_FACTORY })
.then((res) => {
this.businessUnitOptions = mapToSelectOptions(res);
return this.businessUnitOptions;
})
.catch(() => {
this.businessUnitOptions = [];
return this.businessUnitOptions;
});
},
openUnitSelect(type) {
if (this.unitOptionsLoading) return;
this.unitOptionsLoading = true;
const loadFn = type === 'delivery' ? this.loadDeliveryUnitOptions : this.loadBusinessUnitOptions;
loadFn.call(this)
.then((list) => {
if (!list.length) {
uni.showToast({ title: '暂无可选单位', icon: 'none' });
return;
}
this.unitSelectType = type;
this.unitSelectShow = true;
})
.finally(() => {
this.unitOptionsLoading = false;
});
},
openDeliveryUnitSelect() {
this.openUnitSelect('delivery');
},
openBusinessUnitSelect() {
if (this.unitOptionsLoading) return;
this.unitOptionsLoading = true;
this.loadBusinessUnitOptions()
.then((list) => {
if (!list.length) {
uni.showToast({ title: '暂无可选单位', icon: 'none' });
return;
}
this.businessUnitSelectShow = true;
})
.finally(() => {
this.unitOptionsLoading = false;
});
},
onBusinessUnitConfirm(item) {
this.filters.business_unit_id = item.value;
this.filters.business_unit_name = item.value ? item.label : '';
},
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 (filters.audit_status !== '' && filters.audit_status !== null) {
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) {
const page = Number(item.audit_status) === AUDIT_STATUS.PENDING
? 'storeFabricBusinessOutEdit'
: 'storeFabricBusinessOutView';
uni.navigateTo({ url: `./${page}?id=${item.id}` });
},
openStatusSelect() { this.statusSelectShow = true; },
onStatusConfirm(e) { this.filters.audit_status = e[0].value; },
resetFilter() {
this.filters = createDefaultFilters();
},
applyFilter() {
this.commitFilters();
this.filterShow = false;
},
},
};
</script>
<style lang="scss">
@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: 6rpx 8rpx;
border: 2rpx solid transparent;
border-radius: 10rpx;
box-sizing: border-box;
font-size: $pda-font-nav-sub;
line-height: 1.2;
color: #666;
transition: color 0.2s, background-color 0.2s, border-color 0.2s;
}
.filterBtn--active {
color: #2979ff;
background: #eef5ff;
border-color: #9fc5ff;
}
.filterBtn__icon {
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.filterBtn__badge {
position: absolute;
top: -3rpx;
right: -7rpx;
width: 13rpx;
height: 13rpx;
border: 3rpx 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;
}
</style>
@@ -0,0 +1,441 @@
/**
* 坯布出仓单(其他出货单)表单 mixin(新增/编辑共用)
*
* 业务说明:
* 1. 后端当前仅提供「坯布其他出货单」一种类型的接口,故出仓类型固定为 other,
* 表头保留接收单位(business_unit) / 出货日期 / 备注 等字段。
* 2. 用户先填写表头并「提交保存」(addGfmOtherDeliveryOrder),拿到出仓单 id 后才显示扫码区和汇总表;
* 扫码新增/删除细码走 updateGfmOtherDeliveryOrder;勾选锁定维度时附带 *_lock 字段,操作后用返回的最新详情刷新。
* 3. 后端没有「修改表头」接口,保存成功或编辑进入后表头变为只读,并默认收起,仅可扫码维护细码。
*/
import util from '@/common/util';
import scanMixin from '@/common/scanMixin.js';
import {
AUDIT_STATUS_MAP,
BILL_TYPES,
BUSINESS_UNIT_TYPE_DYEING_FACTORY,
normalizeOrder,
mapToSelectOptions,
getFineCodeScanCode,
canScanAtStatus,
isLockedOrderStatus,
getFormStatusActionButtons,
orderToFormFields,
confirmOrderStatusAction,
STATUS_ACTION_VOICE,
DRAFT_AUDIT_STATUS,
DRAFT_AUDIT_STATUS_NAME,
getEmptyScanDetail,
findScanContext,
buildScanDetailFromContext,
buildScanDetailFromOrderResponse,
buildScanUpdateParams,
getOrderDisplayWeight,
getFineCodeDisplayWeight,
} from '@/common/storeFabricBusinessOut';
export default {
mixins: [scanMixin],
data() {
return {
pageMode: 'add',
orderId: 0,
form: this.getEmptyForm(),
headerSaved: false,
// 新增未保存时展开表头;保存后及编辑进入时收起
headerExpanded: true,
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,
phoneScanPanelOpen: false,
};
},
computed: {
isReadonly() {
return this.pageMode === 'view';
},
isEditable() {
return this.pageMode === 'add' || this.pageMode === 'edit';
},
// 表头是否可编辑:仅新增且未保存时可编辑(后端无修改表头接口)
headerEditable() {
return this.pageMode === 'add' && !this.headerSaved;
},
/** 已保存或编辑页才提供收起/展开 */
showHeaderToggle() {
return this.pageMode === 'edit' || this.headerSaved;
},
/** 未保存只填表头;保存后才出现扫码区与汇总表 */
showScanSection() {
return 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;
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.closePhoneScanPanel();
this.unregisterScanBroadcast();
},
onUnload() {
this.isPageActive = false;
this.closePhoneScanPanel();
this.unregisterScanBroadcast();
},
methods: {
handlePhoneScan() {
if (!this.canScan) {
this.showError('请先提交保存后再扫码');
return;
}
this.phoneScanPanelOpen = !this.phoneScanPanelOpen;
},
closePhoneScanPanel() {
this.phoneScanPanelOpen = false;
},
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_DYEING_FACTORY })
.then((res) => {
this.businessUnitOptions = mapToSelectOptions(res);
return this.businessUnitOptions;
});
},
getEmptyForm() {
return {
order_no: '',
business_unit_id: '',
business_unit_name: '',
delivery_time: this.$u.timeFormat(new Date(), 'yyyy-mm-dd'),
remark: '',
item_data: [],
summary_rows: [],
};
},
toggleHeader() {
if (!this.showHeaderToggle) return;
this.headerExpanded = !this.headerExpanded;
},
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.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;
this.selectType = type;
const openPicker = (list) => {
if (!list.length) {
this.showError('暂无可选数据');
return;
}
this.selectList = list;
this.selectShow = true;
};
if (type === '接收单位') {
this.ensureBusinessUnitOptions()
.then(openPicker)
.catch((e) => this.showError(e.message || '加载单位列表失败'));
}
},
selectConfirmFun(item) {
if (!item || this.selectType !== '接收单位') return;
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.headerExpanded = false;
this.syncCanScanFromStatus(order.audit_status);
this.showSuccess('保存成功,可以扫码', '提交成功');
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 Promise.resolve();
}
if (!code) {
this.QRBarCode = '';
return Promise.resolve();
}
const scanType = this.BarCodeDelStatus ? 2 : 1;
return 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.form.summary_rows = order.summary_rows || [];
this.auditStatus = order.audit_status;
this.applyScanDetail(scanCode, order, scanType);
if (refreshFineCodes) {
const rows = order.summary_rows || [];
const row = rows.find((i) => i.group_key === this.currentGroupKey);
this.currentFineCodes = row ? [...(row.fine_codes || [])] : [];
}
this.BillDataMessage = scanType === 2 ? '删除成功' : '扫码出仓成功';
this.QRBarCode = '';
if (scanType === 2) this.showSuccess('删除成功', '删除成功');
else util.playSpeak('扫码出仓成功');
})
.catch((e) => {
uni.hideLoading();
this.showError(e.message || (scanType === 2 ? '删除失败' : '扫码失败'));
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;
},
onSummaryCellClick(event) {
const row = event && event.lineData;
if (!row) return;
if (event.key === 'grey_fabric_code' || event.key === 'grey_fabric_name') {
this.openFabricInfo(row);
return;
}
this.openFineCode(row);
},
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);
const voice = STATUS_ACTION_VOICE[action] || '';
this.showSuccess(voice || '操作成功', voice);
}),
});
},
showError(message) {
util.playErrorAudio();
uni.showModal({ title: '提示', content: message, showCancel: false });
},
showSuccess(message, speakText) {
if (speakText) util.playSpeak(speakText);
else util.playSuccessAudio();
uni.showToast({ title: message, icon: 'success' });
},
getBillTypeName() {
return this.billTypeName;
},
},
};
@@ -0,0 +1,190 @@
/* 坯布出仓单页面共用样式(新增/编辑/查看) */
.fabric-out-page {
background-color: #F8F8F8;
padding-bottom: 260rpx;
}
.fabric-out-mt32 {
margin-top: 32rpx;
}
.fabric-out-headerBar {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 100rpx;
padding: 16rpx 26rpx;
background: #fff;
border-bottom: 1rpx solid #f5f5f5;
box-sizing: border-box;
}
.fabric-out-headerBarMain {
display: flex;
align-items: center;
min-width: 0;
flex: 1;
}
.fabric-out-headerBarTitle {
font-size: 40rpx;
font-weight: bold;
color: #333;
flex-shrink: 0;
}
.fabric-out-headerBarSub {
margin-left: 16rpx;
font-size: 36rpx;
color: #666;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.fabric-out-headerBarAction {
display: flex;
align-items: center;
flex-shrink: 0;
margin-left: 16rpx;
font-size: 36rpx;
color: #2979ff;
}
.fabric-out-headerBarIcon {
margin-left: 8rpx;
}
.fabric-out-page .flex-white-plr26,
.fabric-out-page .flex-white-plr26-column,
.fabric-out-page .flex-white-plr26 text,
.fabric-out-page .flex-white-plr26 view,
.fabric-out-page .u-input__input,
.fabric-out-page .uni-input-input,
.fabric-out-page .uni-textarea-textarea {
font-size: 40rpx;
}
.fabric-out-page .redXingh {
font-size: 40rpx;
}
.fabric-out-page .u-btn {
font-size: 36rpx !important;
height: 88rpx;
line-height: 88rpx;
}
.fabric-out-sectionTitle {
font-size: 42rpx;
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: 36rpx;
background: #fff7e6;
margin: 16rpx 26rpx;
border-radius: 8rpx;
}
.fabric-out-msgTip {
padding: 12rpx 26rpx;
color: #52c41a;
font-size: 36rpx;
}
.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: 38rpx;
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: 36rpx;
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: 36rpx;
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: 36rpx;
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-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,148 @@
<template>
<view class="fabric-out-page">
<OrderStatusBar class="fabric-out-mt32" :status="order.audit_status" :name="order.audit_status_name" />
<view class="flex-white-plr26 ptb10 bdb_f5">
<text class="mr26">出仓单号</text>
<text>{{ order.order_no }}</text>
</view>
<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>
<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 FabricOutDetailList from '@/components/storefabric/FabricOutDetailList.vue';
import FabricOutFooter from '@/components/storefabric/FabricOutFooter.vue';
import {
BILL_TYPES,
normalizeOrder,
getViewStatusActionButtons,
confirmOrderStatusAction,
formatDisplayTime,
STATUS_ACTION_VOICE,
} from '@/common/storeFabricBusinessOut';
export default {
components: { FabricInfoPopup, FineCodePopup, OrderStatusBar, FabricOutDetailList, FabricOutFooter },
data() {
return {
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: () => this.$u.api.gfmOtherDeliveryOrder.detail({ id: this.orderId })
.then((res) => {
uni.hideLoading();
const order = normalizeOrder(res);
if (order) this.order = order;
const voice = STATUS_ACTION_VOICE[action] || '';
if (voice) util.playSpeak(voice);
else util.playSuccessAudio();
uni.showToast({ title: voice || '操作成功', icon: 'success' });
}),
});
},
showError(message) {
util.playErrorAudio();
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,691 @@
<template>
<view class="manual-in">
<common-navbar title="手动进仓">
<template #right>
<view class="manual-in__setting" @click="navToSetting">
<u-icon name="setting" color="#333333" size="34"></u-icon>
<text>设置</text>
</view>
</template>
</common-navbar>
<PlusBarcodePanel
ref="plusBarcodePanel"
:visible.sync="phoneScanPanelOpen"
hint-text="先扫描布票,再填写信息后确认进仓"
:scan-time-space="1200"
@success="onPlusBarcodeSuccess"
/>
<view class="manual-in__scan">
<input
class="manual-in__scan-input"
type="text"
confirm-type="done"
:confirm-hold="true"
:focus="scanFocus"
v-model="scanCode"
placeholder="请先扫描布票编码"
placeholder-class="manual-in__placeholder"
@confirm="onScanConfirm"
/>
<view
v-if="scanCode"
class="manual-in__scan-icon"
@click.stop="clearScanCode"
>
<u-icon name="close-circle-fill" size="44" color="#bfbfbf" />
</view>
<view
v-if="phoneScanPanelOpen"
class="manual-in__scan-close"
@click.stop="handlePhoneScan"
>
关闭扫码
</view>
<view
v-else
class="manual-in__scan-icon"
@click.stop="handlePhoneScan"
>
<u-icon name="scan" size="44" color="#595959" />
</view>
</view>
<view class="manual-in__panel">
<view class="manual-in__row">
<text class="manual-in__label">布票编码</text>
<text class="manual-in__value">{{ detail.fabric_piece_code || '-' }}</text>
</view>
<view class="manual-in__row">
<text class="manual-in__label">生产单号</text>
<text class="manual-in__value">{{ detail.production_order_no || '-' }}</text>
</view>
<view class="manual-in__row">
<text class="manual-in__label">机台</text>
<text class="manual-in__value">{{ detail.machine_number || '-' }}</text>
</view>
<view class="manual-in__row">
<text class="manual-in__label">布种名称</text>
<text class="manual-in__value">{{ detail.grey_fabric_name || '-' }}</text>
</view>
<view class="manual-in__row">
<text class="manual-in__label">坯布编号</text>
<text class="manual-in__value">{{ detail.grey_fabric_code || '-' }}</text>
</view>
<view class="manual-in__row">
<text class="manual-in__label">卷号</text>
<text class="manual-in__value">{{ detail.volume_number != null && detail.volume_number !== '' ? detail.volume_number : '-' }}</text>
</view>
</view>
<view class="manual-in__panel manual-in__panel--form">
<view class="manual-in__row manual-in__row--input" @click="openInspectorPicker">
<text class="manual-in__label">查布工</text>
<view class="manual-in__right">
<text :class="detail.inspector_name ? 'manual-in__field-text' : 'manual-in__placeholder'">
{{ detail.inspector_name || '请选择查布工' }}
</text>
<text class="manual-in__action" @click.stop="clearInspector">取消</text>
</view>
</view>
<view class="manual-in__row manual-in__row--input">
<text class="manual-in__label">重量</text>
<view class="manual-in__right">
<input
class="manual-in__field"
type="digit"
v-model="weightKg"
placeholder="请输入"
placeholder-class="manual-in__placeholder"
/>
<text class="manual-in__unit">KG</text>
<text class="manual-in__action" @click="clearWeight">取消</text>
</view>
</view>
<view class="manual-in__row manual-in__row--input" @click="openLevelPicker">
<text class="manual-in__label">等级</text>
<view class="manual-in__right">
<text :class="levelDisplayName ? 'manual-in__field-text' : 'manual-in__placeholder'">
{{ levelDisplayName || '请选择等级' }}
</text>
<text class="manual-in__action" @click.stop="clearLevel">取消</text>
</view>
</view>
<view class="manual-in__row manual-in__row--input">
<text class="manual-in__label">存放仓位</text>
<view class="manual-in__right">
<input
class="manual-in__field"
type="text"
v-model="warehouseBinName"
placeholder="/"
placeholder-class="manual-in__placeholder"
/>
<text class="manual-in__action" @click="editWarehouseBin">修改</text>
</view>
</view>
</view>
<view class="manual-in__footer">
<u-button
type="primary"
:loading="submitting"
:disabled="submitting || loadingDetail"
@click="submitInWarehouse"
>
确认进仓
</u-button>
</view>
<SearchSelectPopup
:show.sync="levelPickerShow"
title="选择等级"
placeholder="请输入等级名称"
:list="levelOptions"
multiple
:selected="detail.grey_fabric_level_id"
@confirm="onLevelConfirm"
/>
<SearchSelectPopup
:show.sync="inspectorPickerShow"
title="选择查布工"
placeholder="请输入查布工姓名"
:list="inspectorOptions"
@confirm="onInspectorConfirm"
/>
</view>
</template>
<script>
import CommonNavbar from '@/components/common-navbar/index';
import SearchSelectPopup from '@/components/storefabric/SearchSelectPopup.vue';
import PlusBarcodePanel from '@/components/common/PlusBarcodePanel.vue';
import scanMixin from '@/common/scanMixin.js';
import util from '@/common/util.js';
import { formatWeightKg } from '@/common/storeFabricBusinessOut.js';
// 坯布等级字典 id(与后台字典配置一致)
const DICTIONARY_TYPE_GREY_FABRIC_LEVEL = 10025;
// 查布工岗位 duty(验布/查布相关)
const INSPECTOR_DUTY = '20004,20011';
const WEIGHT_SCALE = 10000;
function emptyDetail() {
return {
fabric_piece_code: '',
production_order_no: '',
machine_number: '',
grey_fabric_name: '',
grey_fabric_code: '',
inspection_weight: null,
grey_fabric_level_id: [],
grey_fabric_level_name: '',
inspector_id: null,
inspector_name: '',
volume_number: '',
grey_fabric_id: '',
id: '',
};
}
function toWeightInt(kg) {
const raw = String(kg == null ? '' : kg).trim();
if (!raw) return null;
const n = Number(raw);
if (!Number.isFinite(n) || n <= 0) return null;
return Math.round(n * WEIGHT_SCALE);
}
function formatDisplayWeightKg(value) {
if (value == null || value === '') return '';
const n = Number(value);
if (!Number.isFinite(n) || n <= 0) return '';
return String(formatWeightKg(n));
}
function normalizeLevelIds(value) {
if (Array.isArray(value)) {
return value.map((item) => Number(item)).filter((item) => Number.isFinite(item));
}
if (value == null || value === '') return [];
const n = Number(value);
return Number.isFinite(n) ? [n] : [];
}
export default {
components: { CommonNavbar, SearchSelectPopup, PlusBarcodePanel },
mixins: [scanMixin],
data() {
return {
scanCode: '',
scanFocus: true,
submitting: false,
loadingDetail: false,
warehouseBinId: null,
warehouseBinName: '',
weightKg: '',
detail: emptyDetail(),
levelPickerShow: false,
levelOptions: [],
levelOptionsLoaded: false,
inspectorPickerShow: false,
inspectorOptions: [],
inspectorOptionsLoaded: false,
phoneScanPanelOpen: false,
};
},
computed: {
levelDisplayName() {
if (this.detail.grey_fabric_level_name) return this.detail.grey_fabric_level_name;
const ids = normalizeLevelIds(this.detail.grey_fabric_level_id);
if (!ids.length) return '';
const nameMap = {};
this.levelOptions.forEach((item) => {
nameMap[String(item.value)] = item.label;
});
return ids.map((id) => nameMap[String(id)]).filter(Boolean).join('、');
},
},
onLoad() {
this.registerScanBroadcast(this.handleScanCode);
this.loadLevelOptions();
this.loadInspectorOptions();
},
onShow() {
this.isPageActive = true;
this.refocusScanInput(50);
},
onHide() {
this.isPageActive = false;
this.closePhoneScanPanel();
},
onUnload() {
this.isPageActive = false;
this.closePhoneScanPanel();
clearTimeout(this._scanFocusTimer);
},
methods: {
refocusScanInput(delay = 120) {
clearTimeout(this._scanFocusTimer);
this.scanFocus = false;
this._scanFocusTimer = setTimeout(() => {
this.scanFocus = true;
}, delay);
},
closePhoneScanPanel() {
this.phoneScanPanelOpen = false;
},
onPlusBarcodeSuccess(scanResult) {
return this.handleScanCode(scanResult);
},
navToSetting() {
uni.navigateTo({ url: '/pages/sys/user/scan-settings' });
},
clearWeight() {
this.weightKg = '';
this.detail.inspection_weight = null;
},
clearLevel() {
this.detail.grey_fabric_level_id = [];
this.detail.grey_fabric_level_name = '';
},
clearInspector() {
this.detail.inspector_id = null;
this.detail.inspector_name = '';
},
editWarehouseBin() {
uni.showModal({
title: '存放仓位',
editable: true,
placeholderText: '请输入仓位名称',
content: this.warehouseBinName || '',
success: (res) => {
if (!res.confirm) return;
this.warehouseBinName = (res.content || '').trim();
this.warehouseBinId = null;
},
});
},
async loadLevelOptions() {
try {
const res = await this.$u.api.dictionary.getDetailEnumList({
dictionary_id: DICTIONARY_TYPE_GREY_FABRIC_LEVEL,
status: 1,
size: 200,
});
const list = Array.isArray(res) ? res : (Array.isArray(res && res.list) ? res.list : []);
this.levelOptions = list
.map((item) => ({
value: item.id,
label: item.name || item.code || String(item.id),
}))
.filter((item) => item.value != null);
this.levelOptionsLoaded = true;
} catch (error) {
console.log('加载坯布等级枚举失败', error);
this.levelOptions = [];
}
},
async openLevelPicker() {
if (!this.levelOptionsLoaded || !this.levelOptions.length) {
await this.loadLevelOptions();
}
if (!this.levelOptions.length) {
this.$u.toast('暂无等级可选,请检查等级字典配置');
return;
}
this.levelPickerShow = true;
},
onLevelConfirm(items) {
const list = Array.isArray(items) ? items : (items ? [items] : []);
this.detail.grey_fabric_level_id = list
.map((item) => Number(item.value))
.filter((item) => Number.isFinite(item));
this.detail.grey_fabric_level_name = list
.map((item) => item.label)
.filter(Boolean)
.join('、');
},
async loadInspectorOptions() {
try {
const res = await this.$u.api.employee.listEnum({
size: 50,
duty: INSPECTOR_DUTY,
});
const list = Array.isArray(res) ? res : (Array.isArray(res && res.list) ? res.list : []);
this.inspectorOptions = list
.map((item) => ({
value: item.id,
label: item.name || item.employee_name || item.real_name || item.code || String(item.id),
}))
.filter((item) => item.value != null);
this.inspectorOptionsLoaded = true;
} catch (error) {
console.log('加载查布工枚举失败', error);
this.inspectorOptions = [];
}
},
async openInspectorPicker() {
if (!this.inspectorOptionsLoaded || !this.inspectorOptions.length) {
await this.loadInspectorOptions();
}
if (!this.inspectorOptions.length) {
this.$u.toast('暂无查布工可选');
return;
}
this.inspectorPickerShow = true;
},
onInspectorConfirm(item) {
if (!item) return;
this.detail.inspector_id = Number(item.value);
this.detail.inspector_name = item.label || '';
},
handleScanCode(scanResult) {
const code = String(scanResult || '').trim().replace(/[\r\n]/g, '');
if (!code) return;
this.fetchScanDetail(code);
},
clearScanCode() {
this.scanCode = '';
this.refocusScanInput();
},
handlePhoneScan() {
this.phoneScanPanelOpen = !this.phoneScanPanelOpen;
},
onScanConfirm() {
const code = String(this.scanCode || '').trim();
if (!code) {
this.$u.toast('请扫描或输入布票编码');
return;
}
this.fetchScanDetail(code);
},
validateBeforeSubmit() {
const fabricPieceCode = String(this.detail.fabric_piece_code || this.scanCode || '').trim();
if (!fabricPieceCode) {
return '请先扫描或输入布票编码';
}
const inspectorName = String(this.detail.inspector_name || '').trim();
if (!inspectorName && (this.detail.inspector_id == null || this.detail.inspector_id === '')) {
return '请选择查布工';
}
if (toWeightInt(this.weightKg) == null) {
return '请输入有效重量';
}
return '';
},
async fetchScanDetail(code) {
if (this.loadingDetail || this.submitting) return;
this.loadingDetail = true;
this.scanCode = code;
try {
const res = await this.$u.api.productionScheduleOrder.getScanFineCodeDetail({
fabric_piece_code: code,
});
this.applyScanDetail(res, code);
} catch (error) {
console.log('扫码获取细码失败', error);
const kept = this.keepFormFields();
this.detail = {
...emptyDetail(),
fabric_piece_code: code,
...kept,
};
this.scanCode = '';
this.refocusScanInput(200);
} finally {
this.loadingDetail = false;
}
},
keepFormFields() {
return {
grey_fabric_level_id: normalizeLevelIds(this.detail.grey_fabric_level_id),
grey_fabric_level_name: this.detail.grey_fabric_level_name || '',
inspector_id: this.detail.inspector_id,
inspector_name: this.detail.inspector_name || '',
};
},
applyScanDetail(res, fallbackCode) {
const data = res || {};
const kept = this.keepFormFields();
this.detail = {
...emptyDetail(),
fabric_piece_code: data.fabric_piece_code || fallbackCode || '',
production_order_no: data.production_order_no || '',
machine_number: data.machine_number || '',
grey_fabric_name: data.grey_fabric_name || '',
grey_fabric_code: data.grey_fabric_code || '',
inspection_weight: toWeightInt(this.weightKg),
volume_number: data.volume_number != null && data.volume_number !== '' ? data.volume_number : null,
...kept,
};
this.scanCode = this.detail.fabric_piece_code;
},
async submitInWarehouse() {
const err = this.validateBeforeSubmit();
if (err) {
this.$u.toast(err);
return;
}
const fabricPieceCode = String(this.detail.fabric_piece_code || this.scanCode || '').trim();
const actualWeight = toWeightInt(this.weightKg);
const levelIds = normalizeLevelIds(this.detail.grey_fabric_level_id);
if (this.submitting) return;
this.submitting = true;
try {
const payload = {
fabric_piece_code: fabricPieceCode,
actual_weight: actualWeight,
electronic_scale_weight: actualWeight,
};
if (levelIds.length) {
payload.grey_fabric_level_id = levelIds;
}
const inspectorName = String(this.detail.inspector_name || '').trim();
if (inspectorName) {
payload.inspector_name = inspectorName;
}
if (this.detail.inspector_id != null && this.detail.inspector_id !== '') {
payload.inspector_id = Number(this.detail.inspector_id);
}
const binName = String(this.warehouseBinName || '').trim();
if (binName && binName !== '/') {
payload.warehouse_bin_name = binName;
}
if (this.warehouseBinId != null && this.warehouseBinId !== '') {
payload.warehouse_bin_id = Number(this.warehouseBinId);
}
const res = await this.$u.api.productionScheduleOrder.scanInWarehouseFineCode(payload);
if (res) {
this.detail.volume_number = res.volume_number != null ? res.volume_number : this.detail.volume_number;
this.detail.grey_fabric_id = res.grey_fabric_id != null ? res.grey_fabric_id : this.detail.grey_fabric_id;
this.detail.id = res.id != null ? res.id : this.detail.id;
if (res.fabric_piece_code) {
this.detail.fabric_piece_code = res.fabric_piece_code;
}
}
const volumeText = res && res.volume_number != null ? `,卷号 ${res.volume_number}` : '';
util.playSpeak('进仓成功');
this.$u.toast(`进仓成功${volumeText}`);
this.resetForNextScan();
} catch (error) {
console.log('手动进仓失败', error);
this.scanCode = '';
this.refocusScanInput(200);
} finally {
this.submitting = false;
}
},
resetForNextScan() {
const kept = this.keepFormFields();
this.scanCode = '';
this.detail = {
...emptyDetail(),
...kept,
};
// toast 会抢焦点,稍后再聚焦,方便连续扫码
this.refocusScanInput(600);
},
},
};
</script>
<style lang="scss" scoped>
@import '@/common/pda-typography.scss';
.manual-in {
min-height: 100vh;
background: #f5f6f8;
padding-bottom: calc(160rpx + env(safe-area-inset-bottom));
box-sizing: border-box;
}
.manual-in__setting {
display: flex;
align-items: center;
gap: 8rpx;
font-size: $pda-font-nav-sub;
color: #333;
}
.manual-in__scan {
margin: 16rpx 0;
padding: 0 28rpx;
background: #fff;
display: flex;
align-items: center;
gap: 12rpx;
}
.manual-in__scan-input {
flex: 1;
min-width: 0;
height: $pda-scan-input-height;
font-size: $pda-font-body;
color: #202328;
}
.manual-in__scan-icon {
display: flex;
align-items: center;
justify-content: center;
width: 72rpx;
height: 72rpx;
flex-shrink: 0;
border-radius: 8rpx;
border: 1rpx solid #d9d9d9;
background: #f7f7f7;
}
.manual-in__scan-close {
display: flex;
align-items: center;
justify-content: center;
height: 72rpx;
padding: 0 16rpx;
flex-shrink: 0;
border-radius: 8rpx;
border: 1rpx solid #2979ff;
background: #eaf2ff;
color: #2979ff;
font-size: $pda-font-action;
white-space: nowrap;
}
.manual-in__placeholder {
color: #b0b4bb;
font-size: $pda-font-placeholder;
}
.manual-in__panel {
background: #fff;
}
.manual-in__panel--form {
margin-top: 16rpx;
}
.manual-in__footer {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 20rpx 28rpx calc(20rpx + env(safe-area-inset-bottom));
background: #fff;
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.06);
box-sizing: border-box;
z-index: 20;
::v-deep .u-btn {
height: $pda-button-height !important;
font-size: $pda-font-button !important;
}
}
.manual-in__row {
display: flex;
align-items: center;
min-height: $pda-row-min-height;
padding: 12rpx 28rpx;
border-bottom: 1rpx solid #eceff3;
box-sizing: border-box;
}
.manual-in__row--input {
justify-content: space-between;
}
.manual-in__label {
width: $pda-label-width;
flex-shrink: 0;
font-size: $pda-font-body;
color: #666;
}
.manual-in__value {
flex: 1;
font-size: $pda-font-body;
color: #202328;
text-align: left;
}
.manual-in__right {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 18rpx;
}
.manual-in__field {
flex: 1;
min-width: 0;
text-align: right;
font-size: $pda-font-body;
color: #202328;
}
.manual-in__field-text {
flex: 1;
min-width: 0;
text-align: right;
font-size: $pda-font-body;
color: #202328;
word-break: break-all;
}
.manual-in__unit {
font-size: $pda-font-unit;
color: #666;
padding: 6rpx 14rpx;
border: 1rpx solid #d7dbe3;
border-radius: 8rpx;
}
.manual-in__action {
font-size: $pda-font-action;
color: #2f78e6;
flex-shrink: 0;
}
</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: 30rpx;
font-weight: bold;
margin-bottom: 16rpx;
color: #333;
}
.infoRow {
display: flex;
font-size: 26rpx;
padding: 8rpx 0;
}
.infoLabel {
width: 160rpx;
color: #999;
flex-shrink: 0;
}
.infoValue {
flex: 1;
color: #333;
word-break: break-all;
}
.summaryBar {
margin: 16rpx 24rpx 0;
padding: 12rpx 16rpx;
font-size: 26rpx;
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: 12rpx 8rpx;
font-size: 22rpx;
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,150 @@
<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="auto"
@row-click="goStockList"
/>
</view>
</scroll-view>
</view>
</template>
<script>
import WarehouseDataTable from '@/components/storefabric/WarehouseDataTable.vue';
import {
GROUP_TABLE_COLUMNS,
buildGroupListQuery,
buildStockListNavigateQuery,
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);
},
},
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,153 @@
<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="auto"
@row-click="goDetail"
/>
</view>
</scroll-view>
</view>
</template>
<script>
import WarehouseDataTable from '@/components/storefabric/WarehouseDataTable.vue';
import {
STOCK_TABLE_COLUMNS,
buildDetailNavigateQuery,
buildStockListQuery,
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);
},
},
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
+1 -1
View File
@@ -6,7 +6,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}"
refresher-enabled :refresher-threshold="200" :refresher-triggered="triggered"
+1 -1
View File
@@ -15,7 +15,7 @@
@change="onTabChange"></u-tabs>
</div>
</u-sticky>
<dataNull v-if="ItemDataList.length == 0" src="/static/img/chahua/gjNull.png" title="暂无相关出仓资料"
<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"
@@ -15,7 +15,7 @@
@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}"
refresher-enabled :refresher-threshold="200" :refresher-triggered="triggered" refresher-background="gray">
@@ -31,6 +31,14 @@
</view>
<u-form-item label-width="150" label="条码资料:">
<input type="text" maxlength="-1" v-model="QRBarCode" style="width:200px;" @confirm="GoodsCheckBillDetailScan" />
<u-icon
v-if="usePhoneScanFallback"
name="scan"
size="44"
color="#2979ff"
style="margin-left: 8px;"
@click="handlePhoneScan"
/>
<checkbox-group @change="BarCodeDelChange">
<checkbox ref="checkBoxRef" :checked="BarCodeDelStatus">删除</checkbox>
</checkbox-group>
@@ -193,28 +201,20 @@
},
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((scanResult) => {
console.log("扫码结果:", scanResult);
// 处理扫描结果,按照原有逻辑
if(!this.StoreName){
this.showError('请先选择仓库');
return;
@@ -240,9 +240,11 @@
return;
}
});
// #endif
},
methods: {
handlePhoneScan() {
this.openPhoneScan();
},
// 添加通用错误提示方法
showError(message) {
@@ -2,47 +2,55 @@
<view class="wrap">
<u-form ref="uForm">
<u-form-item>
<text class="title" style="width:200px;">单号:{{GoodsCheckBillNo}}</text>
<text class="title" style="width:200px;">日期:{{GoodsCheckBillDate}}</text>
<text class="title" style="width:200px;">单号:{{ GoodsCheckBillNo }}</text>
<text class="title" style="width:200px;">日期:{{ GoodsCheckBillDate }}</text>
</u-form-item>
<u-form-item>
<text class="title" style="width:200px;">仓库名称:{{StoreName}}</text>
<text class="title" style="width:200px;">仓位名称:{{StoreStationName}}</text>
<text class="title" style="width:200px;">仓库名称:{{ StoreName }}</text>
<text class="title" style="width:200px;">仓位名称:{{ StoreStationName }}</text>
</u-form-item>
<u-form-item label-width="150" label="条码资料:">
<input type="text" v-model="QRBarCode" maxlength="-1" style="width:200px;"
@confirm="GoodsCheckBillDetailScan" />
<u-icon
v-if="usePhoneScanFallback"
name="scan"
size="44"
color="#2979ff"
style="margin-left: 8px;"
@click="handlePhoneScan"
/>
<checkbox-group @change="BarCodeDelChange">
<checkbox ref="checkBoxRef" :checked="BarCodeDelStatus">删除</checkbox>
</checkbox-group>
</u-form-item>
<u-form-item>
<text class="title">{{BillDataMessage}}</text>
<text class="title">{{ BillDataMessage }}</text>
</u-form-item>
<u-form-item>
<text class="title" style="width:200px;">成品编号:{{FabricGoodsNo}}</text>
<text class="title" style="width:200px;">成品重量:{{GoodsQty}}KG</text>
<text class="title" style="width:200px;">成品编号:{{ FabricGoodsNo }}</text>
<text class="title" style="width:200px;">成品重量:{{ GoodsQty }}KG</text>
</u-form-item>
<u-form-item>
<text class="title" style="width:200px;">成品色号:{{GoodsCodeNo}}</text>
<text class="title" style="width:200px;">成品颜色:{{GoodsCodeName}}</text>
<text class="title" style="width:200px;">成品色号:{{ GoodsCodeNo }}</text>
<text class="title" style="width:200px;">成品颜色:{{ 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;">成品缸号:{{ CrockNo }}</text>
<text class="title" style="width:200px;">缸号卷号:{{ GoodsBillNo }}</text>
</u-form-item>
<u-form-item>
<text class="title" style="width:200px;">本架盘前:{{BillSumOldRoll}}条</text>
<text class="title" style="width:200px;">本架实盘:{{BillSumNewRoll}}条</text>
<text class="title" style="width:200px;">本架盘前:{{ BillSumOldRoll }}条</text>
<text class="title" style="width:200px;">本架实盘:{{ BillSumNewRoll }}条</text>
</u-form-item>
<u-form-item>
<text class="title" style="width:200px;">本色盘前:{{GoodsCodeNoSumOldRoll}}条</text>
<text class="title" style="width:200px;">本色实盘:{{GoodsCodeNoSumNewRoll}}条</text>
<text class="title" style="width:200px;">本色盘前:{{ GoodsCodeNoSumOldRoll }}条</text>
<text class="title" style="width:200px;">本色实盘:{{ GoodsCodeNoSumNewRoll }}条</text>
</u-form-item>
<u-form-item>
<text class="title" style="width:200px;">本缸盘前:{{CrockNoSumOldRoll}}条</text>
<text class="title" style="width:200px;">本缸实盘:{{CrockNoSumNewRoll}}条</text>
<text class="title" style="width:200px;">本缸盘前:{{ CrockNoSumOldRoll }}条</text>
<text class="title" style="width:200px;">本缸实盘:{{ CrockNoSumNewRoll }}条</text>
</u-form-item>
</u-form>
<view class="u-demo-area">
@@ -55,417 +63,419 @@
<view class="submitView">
<u-button type="primary" style="width:100px;" class="commitBtn" :ripple="true" ripple-bg-color="#909399"
@click="commitBtnFun">
{{CommitType ? '消审' : '审核'}}
{{ CommitType ? '消审' : '审核' }}
</u-button>
</view>
</view>
</template>
<script>
import util, {
parFabricGoodsBarCode2D,
parYarnGoodsBarCode2D,
playSuccessAudio,
playErrorAudio
} from '../../common/util';
import scanMixin from '@/common/scanMixin.js';
import wybTable from '@/components/wyb-table/wyb-table.vue';
//let that = '';
export default {
mixins: [scanMixin],
data() {
return {
borderColor: '#e4e7ed',
align: 'center',
index: 0,
CommitType: '',
CommitProcName: '',
actionSheetShow: false,
QRBarCode: '',
GoodsCheckBillID: 0,
GoodsCheckBillNo: '',
GoodsCheckBillDate: '',
GoodsCheckBillMasterNo: '',
StoreNameID: '',
StoreName: '',
StoreStationID: '',
StoreStationNo: '',
StoreStationName: '',
CustomerName: '',
FabricGoodsNo: '',
FabricGoodsName: '',
GoodsCodeNo: '',
GoodsCodeName: '',
CrockNo: '',
GoodsBillNo: '',
GoodsQty: 0,
StoreStationNameFocus: false,
BillSumOldRoll: 0,
BillSumNewRoll: 0,
GoodsCodeNoSumOldRoll: 0,
GoodsCodeNoSumNewRoll: 0,
CrockNoSumOldRoll: 0,
CrockNoSumNewRoll: 0,
BarCodeDelStatus: false,
GoodsDetailList: [],
BillDataMessage: '',
headersMaster: [{
label: '成品编号',
key: 'product_code'
}, {
label: '成品名称',
key: 'product_name'
}, {
label: '成品色号',
key: 'product_color_code'
}, {
label: '成品颜色',
key: 'product_color_name'
}, {
label: '成品缸号',
key: 'dyelot_number'
}, {
label: '盘前条数',
key: 'roll'
}, {
label: '实盘条数',
key: 'check_roll'
}, {
label: '盈亏条数',
key: 'different_roll'
}],
// 移除了 scanReceiver 和 isPageActive,因为它们现在由 scanMixin 提供
}
},
import util, {
parFabricGoodsBarCode2D,
parYarnGoodsBarCode2D,
playSuccessAudio,
playErrorAudio
} from '../../common/util';
import scanMixin from '@/common/scanMixin.js';
import wybTable from '@/components/wyb-table/wyb-table.vue';
//let that = '';
export default {
mixins: [scanMixin],
data() {
return {
borderColor: '#e4e7ed',
align: 'center',
index: 0,
CommitType: '',
CommitProcName: '',
actionSheetShow: false,
QRBarCode: '',
GoodsCheckBillID: 0,
GoodsCheckBillNo: '',
GoodsCheckBillDate: '',
GoodsCheckBillMasterNo: '',
StoreNameID: '',
StoreName: '',
StoreStationID: '',
StoreStationNo: '',
StoreStationName: '',
CustomerName: '',
FabricGoodsNo: '',
FabricGoodsName: '',
GoodsCodeNo: '',
GoodsCodeName: '',
CrockNo: '',
GoodsBillNo: '',
GoodsQty: 0,
StoreStationNameFocus: false,
BillSumOldRoll: 0,
BillSumNewRoll: 0,
GoodsCodeNoSumOldRoll: 0,
GoodsCodeNoSumNewRoll: 0,
CrockNoSumOldRoll: 0,
CrockNoSumNewRoll: 0,
BarCodeDelStatus: false,
GoodsDetailList: [],
BillDataMessage: '',
headersMaster: [{
label: '成品编号',
key: 'product_code'
}, {
label: '成品名称',
key: 'product_name'
}, {
label: '成品色号',
key: 'product_color_code'
}, {
label: '成品颜色',
key: 'product_color_name'
}, {
label: '成品缸号',
key: 'dyelot_number'
}, {
label: '盘前条数',
key: 'roll'
}, {
label: '实盘条数',
key: 'check_roll'
}, {
label: '盈亏条数',
key: 'different_roll'
}],
// 移除了 scanReceiver 和 isPageActive,因为它们现在由 scanMixin 提供
}
},
onLoad(e) {
//that = this;
if (e.billid) {
this.GoodsCheckBillID = e.billid;
this.GoodsCheckBillDetailData();
}
},
onLoad(e) {
//that = this;
if (e.billid) {
this.GoodsCheckBillID = e.billid;
this.GoodsCheckBillDetailData();
}
},
onShow() {
// 调用 mixin 中的 onShow 方法
if (this.$options.mixins && this.$options.mixins[0] && this.$options.mixins[0].onShow) {
this.$options.mixins[0].onShow.call(this);
}
// #ifdef APP-PLUS
this.registerScanBroadcast((scanResult) => {
console.log("扫码结果:", scanResult);
// 处理扫描结果,按照原有逻辑
if(!this.QRBarCode){
console.log("请先扫描资料!", scanResult);
this.QRBarCode = scanResult;
this.$nextTick(() => {
this.GoodsCheckBillDetailScan();
});
return;
}
onShow() {
this.isPageActive = true;
this.registerScanBroadcast((scanResult) => {
console.log("扫码结果:", scanResult);
this.QRBarCode = scanResult;
this.$nextTick(() => {
this.GoodsCheckBillDetailScan();
});
// #endif
});
},
onHide() {
this.isPageActive = false;
this.unregisterScanBroadcast();
},
onUnload() {
this.isPageActive = false;
this.unregisterScanBroadcast();
},
methods: {
handlePhoneScan() {
this.openPhoneScan();
},
methods: {
// 添加通用错误提示方法
showError(message) {
this.playError();
uni.showModal({
title: '提示',
content: message,
showCancel: false
});
},
playSuccess() {
util.playSuccessAudio();
},
playError() {
util.playErrorAudio();
},
// 添加通用错误提示方法
showError(message) {
this.playError();
uni.showModal({
title: '提示',
content: message,
showCancel: false
});
},
BarCodeDelChange: function() {
this.BarCodeDelStatus = !this.BarCodeDelStatus;
},
playSuccess() {
util.playSuccessAudio();
},
playError() {
util.playErrorAudio();
},
GoodsCheckBillDetailScan() {
if (this.StoreNameID == 0 && this.GoodsCheckBillNo == '') {
this.QRBarCode = '';
this.showError('请先新增单据');
return;
}
BarCodeDelChange: function () {
this.BarCodeDelStatus = !this.BarCodeDelStatus;
},
// 数据清理:去除空格、换行符等
let cleanCode = this.QRBarCode.replace(/\s+/g, '').replace(/[\r\n]/g, '');
GoodsCheckBillDetailScan() {
if (this.StoreNameID == 0 && this.GoodsCheckBillNo == '') {
this.QRBarCode = '';
this.showError('请先新增单据');
return;
}
console.log("this.QRBarCode ---->>" + cleanCode);
// 数据清理:去除空格、换行符等
let cleanCode = this.QRBarCode.replace(/\s+/g, '').replace(/[\r\n]/g, '');
var aQRBarCode = '';
var aBarCode = '';
if (cleanCode.startsWith('66^')>0 || cleanCode.startsWith('99^')>0 || /[\u4E00-\u9FA5]/.test(cleanCode)){
aQRBarCode = cleanCode
}else {
aBarCode = cleanCode;
}
console.log("this.QRBarCode ---->>" + cleanCode);
if (aQRBarCode == '' && aBarCode == ''){
this.QRBarCode = '';
this.aBarCode = ''
this.showError('请扫描二维码或者条码');
return;
}
var aQRBarCode = '';
var aBarCode = '';
if (cleanCode.startsWith('66^') > 0 || cleanCode.startsWith('99^') > 0 || /[\u4E00-\u9FA5]/.test(cleanCode)) {
aQRBarCode = cleanCode
} else {
aBarCode = cleanCode;
}
let aBarCodeDelStatus = 1
if (this.BarCodeDelStatus) {
aBarCodeDelStatus = 3;
};
if (aQRBarCode == '' && aBarCode == '') {
this.QRBarCode = '';
this.aBarCode = ''
this.showError('请扫描二维码或者条码');
return;
}
util.request({
url: '/product/productCheckOrder/updateProductCheckOrder',
method: 'put',
data: {
'arrange_type': aBarCodeDelStatus,
'id': parseInt(this.GoodsCheckBillID),
'bar_code': aBarCode,
'qr_code': aQRBarCode,
},
success: (res) => {
console.log("-->>--" + JSON.stringify(res.data));
if (res.data.code == '0'){
let aResultData = res.data.data;
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.GoodsQty = aResultData.weight / 10000;
this.BillSumOldRoll = aResultData.warehouse_bin_check_before_roll/100;
this.BillSumNewRoll = aResultData.warehouse_bin_check_roll/100;
this.GoodsCodeNoSumOldRoll = aResultData.color_check_before_roll/100;
this.GoodsCodeNoSumNewRoll = aResultData.color_check_roll/100;
this.CrockNoSumOldRoll = aResultData.dye_check_before_roll/100;
this.CrockNoSumNewRoll = aResultData.dye_check_roll/100;
let aBarCodeDelStatus = 1
if (this.BarCodeDelStatus) {
aBarCodeDelStatus = 3;
};
util.request({
url: '/product/productCheckOrder/updateProductCheckOrder',
method: 'put',
data: {
'arrange_type': aBarCodeDelStatus,
'id': parseInt(this.GoodsCheckBillID),
'bar_code': aBarCode,
'qr_code': aQRBarCode,
},
success: (res) => {
console.log("-->>--" + JSON.stringify(res.data));
if (res.data.code == '0') {
let aResultData = res.data.data;
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.GoodsQty = aResultData.weight / 10000;
this.BillSumOldRoll = aResultData.warehouse_bin_check_before_roll / 100;
this.BillSumNewRoll = aResultData.warehouse_bin_check_roll / 100;
this.GoodsCodeNoSumOldRoll = aResultData.color_check_before_roll / 100;
this.GoodsCodeNoSumNewRoll = aResultData.color_check_roll / 100;
this.CrockNoSumOldRoll = aResultData.dye_check_before_roll / 100;
this.CrockNoSumNewRoll = aResultData.dye_check_roll / 100;
this.playSuccess();
this.GoodsCheckBillDetailData();
this.BillDataMessage = res.data.msg;
this.QRBarCode = '';
}
else {
this.showError(res.data.msg);
this.QRBarCode = '';
}
},
fail: (error) => {
this.showError('连接服务器出错,请检查后台服务是否启动!');
console.log('error', error)
},
})
},
GoodsCheckBillDetailData: function () {
console.log("--aaa->>" + this.GoodsCheckBillID);
console.log("--aaa->>" + this.GoodsCheckBillID);
util.request({
url: '/product/productCheckOrder/getProductCheckOrder',
method: 'get',
data: {
'id': parseInt(this.GoodsCheckBillID),
},
success: (res) => {
console.log("-->>--" + JSON.stringify(res.data.data));
this.GoodsDetailList = [];
this.GoodsCheckBillNo = res.data.data.order_no;
this.StoreName = res.data.data.warehouse_name
this.StoreStationName = res.data.data.warehouse_bin_name;
this.GoodsCheckBillDate = this.$u.timeFormat(res.data.data.check_time, 'yyyy-mm-dd')
/* this.BillSumOldRoll = res.data.data.item_data.roll;
this.BillSumNewRoll = res.data.data.item_data.check_roll; */
var aResultData = res.data.data.item_data;
this.BillSumOldRoll = 0;
this.BillSumNewRoll = 0;
for (var i = 0; i < aResultData.length; i++) {
aResultData[i].roll = aResultData[i].roll / 100;
aResultData[i].check_roll = aResultData[i].check_roll / 100;
aResultData[i].different_roll = aResultData[i].different_roll / 100;
this.BillSumOldRoll = this.BillSumOldRoll + aResultData[i].roll;
this.BillSumNewRoll = this.BillSumNewRoll + aResultData[i].check_roll;
};
this.GoodsDetailList = aResultData;
},
})
},
// 审核按钮方法
commitBtnFun: function () {
if (this.BillMasterID <= 0) {
this.showError('当前单据未提交,不能审核或消审!');
return;
}
var aCommitRecallName = '审核';
if (this.CommitType == '') {
aCommitRecallName = '审核';
} else {
aCommitRecallName = '消审';
}
// if (this.CommitProcName == '') {
// this.playError();
// this.BillDataMessage = '当前' + aCommitRecallName + '操作配置有误,不能审核!';
// return;
// }
console.log('GoodsCheckBillID', this.GoodsCheckBillID)
util.request({
url: '/product/productCheckOrder/updateProductCheckOrderAuditStatusPass',
method: 'put',
data: {
'id': parseInt(this.GoodsCheckBillID),
},
success: (res) => {
console.log('审核', res)
if (res.data.code == '0') {
var aResultData = JSON.parse(res.data.data);
console.log('aResultData', aResultData)
if (aResultData.code == '0' && aResultData.msg ==
'success') {
this.playSuccess();
this.GoodsCheckBillDetailData();
this.BillDataMessage = res.data.msg;
this.QRBarCode = '';
}
else {
this.showError(res.data.msg);
this.QRBarCode = '';
}
},
fail: (error) => {
this.showError('连接服务器出错,请检查后台服务是否启动!');
console.log('error',error)
},
})
},
this.BillDataMessage = aCommitRecallName + "成功!";
GoodsCheckBillDetailData: function() {
console.log("--aaa->>" + this.GoodsCheckBillID);
console.log("--aaa->>" + this.GoodsCheckBillID);
util.request({
url: '/product/productCheckOrder/getProductCheckOrder',
method: 'get',
data: {
'id': parseInt(this.GoodsCheckBillID),
},
success: (res) => {
console.log("-->>--" + JSON.stringify(res.data.data));
this.GoodsDetailList = [];
this.GoodsCheckBillNo = res.data.data.order_no;
this.StoreName = res.data.data.warehouse_name
this.StoreStationName = res.data.data.warehouse_bin_name;
this.GoodsCheckBillDate = this.$u.timeFormat(res.data.data.check_time, 'yyyy-mm-dd')
/* this.BillSumOldRoll = res.data.data.item_data.roll;
this.BillSumNewRoll = res.data.data.item_data.check_roll; */
var aResultData = res.data.data.item_data;
this.BillSumOldRoll = 0;
this.BillSumNewRoll = 0;
for (var i = 0; i < aResultData.length; i++) {
aResultData[i].roll = aResultData[i].roll / 100;
aResultData[i].check_roll = aResultData[i].check_roll / 100;
aResultData[i].different_roll = aResultData[i].different_roll / 100;
this.BillSumOldRoll = this.BillSumOldRoll + aResultData[i].roll;
this.BillSumNewRoll = this.BillSumNewRoll + aResultData[i].check_roll;
};
this.GoodsDetailList = aResultData;
},
})
},
// 审核按钮方法
commitBtnFun: function() {
if (this.BillMasterID <= 0) {
this.showError('当前单据未提交,不能审核或消审!');
return;
}
var aCommitRecallName = '审核';
if (this.CommitType == '') {
aCommitRecallName = '审核';
} else {
aCommitRecallName = '消审';
}
// if (this.CommitProcName == '') {
// this.playError();
// this.BillDataMessage = '当前' + aCommitRecallName + '操作配置有误,不能审核!';
// return;
// }
console.log('GoodsCheckBillID',this.GoodsCheckBillID)
util.request({
url: '/product/productCheckOrder/updateProductCheckOrderAuditStatusPass',
method: 'put',
data: {
'id': parseInt(this.GoodsCheckBillID),
},
success: (res) => {
console.log('审核',res)
if (res.data.code == '0') {
var aResultData = JSON.parse(res.data.data);
console.log('aResultData',aResultData)
if (aResultData.code == '0' && aResultData.msg ==
'success') {
this.playSuccess();
this.BillDataMessage = aCommitRecallName + "成功!";
if (aCommitRecallName == '审核') {
this.CommitType = '已审核'
} else {
this.CommitType = ''
}
if (aCommitRecallName == '审核') {
this.CommitType = '已审核'
} else {
this.showError(aCommitRecallName + '出错!' + aResultData.BillDataMessage);
return;
this.CommitType = ''
}
} else {
this.showError(aCommitRecallName + '出错,' + res.data.msg);
this.showError(aCommitRecallName + '出错!' + aResultData.BillDataMessage);
return;
}
},
fail: (error) => {
this.showError('连接服务器出错,请检查后台服务是否启动!');
},
})
},
} else {
this.showError(aCommitRecallName + '出错,' + res.data.msg);
return;
}
},
fail: (error) => {
this.showError('连接服务器出错,请检查后台服务是否启动!');
},
})
},
}
}
}
</script>
<style>
page {
background-color: #F8F8F8;
padding-bottom: 260rpx;
}
page {
background-color: #F8F8F8;
padding-bottom: 260rpx;
}
.u-radio {
width: 200rpx !important;
}
.u-radio {
width: 200rpx !important;
}
.submitView {
width: 100%;
padding: 16rpx 0 26rpx;
background-color: #FFFFFF;
position: fixed;
bottom: 0;
left: 0;
border-top: 1rpx solid #f1f1f1;
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
}
.submitView {
width: 100%;
padding: 16rpx 0 26rpx;
background-color: #FFFFFF;
position: fixed;
bottom: 0;
left: 0;
border-top: 1rpx solid #f1f1f1;
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
}
.submitBtn {
width: 666rpx;
}
.submitBtn {
width: 666rpx;
}
.productBox {
background-color: #FFFFFF;
margin-top: 32rpx;
padding: 26rpx 0;
}
.productBox {
background-color: #FFFFFF;
margin-top: 32rpx;
padding: 26rpx 0;
}
.tjcpName {
width: 686rpx;
height: 40rpx;
font-size: 16px;
font-weight: bold;
border-left: 6rpx solid #007AFF;
padding-left: 12rpx;
margin-left: 26rpx;
margin-top: 26rpx;
}
.tjcpName {
width: 686rpx;
height: 40rpx;
font-size: 16px;
font-weight: bold;
border-left: 6rpx solid #007AFF;
padding-left: 12rpx;
margin-left: 26rpx;
margin-top: 26rpx;
}
.cpInput {
width: 150rpx !important;
margin-right: 12rpx;
}
.cpInput {
width: 150rpx !important;
margin-right: 12rpx;
}
.cpInput>input {
box-sizing: border-box;
border: 1rpx solid #DDDDDD;
width: 100%;
height: 60rpx;
border-radius: 10rpx;
padding: 0 10rpx;
}
.cpInput>input {
box-sizing: border-box;
border: 1rpx solid #DDDDDD;
width: 100%;
height: 60rpx;
border-radius: 10rpx;
padding: 0 10rpx;
}
.cpInput1 {
width: 200rpx !important;
margin-right: 12rpx;
}
.cpInput1 {
width: 200rpx !important;
margin-right: 12rpx;
}
.cpInput1>input {
box-sizing: border-box;
border: 1rpx solid #DDDDDD;
width: 100%;
height: 60rpx;
border-radius: 10rpx;
padding: 0 10rpx;
}
.cpInput1>input {
box-sizing: border-box;
border: 1rpx solid #DDDDDD;
width: 100%;
height: 60rpx;
border-radius: 10rpx;
padding: 0 10rpx;
}
.clearIcon {
position: absolute;
right: 6rpx;
top: 6rpx;
}
.clearIcon {
position: absolute;
right: 6rpx;
top: 6rpx;
}
.greenPrice {
font-size: 16px;
color: #19BE6B !important;
font-weight: bold;
}
.greenPrice {
font-size: 16px;
color: #19BE6B !important;
font-weight: bold;
}
.disFlex {
display: flex;
align-items: center;
margin-bottom: 8rpx;
}
.disFlex {
display: flex;
align-items: center;
margin-bottom: 8rpx;
}
.inputName {
color: #ADADAD;
font-size: 16px;
}
.inputName {
color: #ADADAD;
font-size: 16px;
}
.addHKQS {
display: flex;
align-items: center;
padding: 16rpx 26rpx;
font-size: 15px;
font-weight: bold;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.addHKQS {
display: flex;
align-items: center;
padding: 16rpx 26rpx;
font-size: 15px;
font-weight: bold;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
</style>
@@ -18,7 +18,7 @@
</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}"
@@ -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">
@@ -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">
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -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"
@@ -6,7 +6,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">
+1 -1
View File
@@ -6,7 +6,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">
+1 -1
View File
@@ -6,7 +6,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">
File diff suppressed because it is too large Load Diff
+54 -16
View File
@@ -41,19 +41,28 @@
<u-gap height="20" bg-color="#f5f5f5"></u-gap>
<u-cell-item icon="question-circle" :iconStyle="{ color: '#ff8d06' }" title="常见问题" @click="navTo('problem')"></u-cell-item>
<u-cell-item icon="kefu-ermai" :iconStyle="{ color: '#5f8dff' }" title="意见反馈" @click="navTo('comment')"></u-cell-item>
<u-cell-item icon="map" :iconStyle="{ color: '#316ede' }" title="账号安全" @click="navTo('pwd')"></u-cell-item>
<u-cell-item icon="map" :iconStyle="{ color: '#316ede' }" title="修改密码" @click="navTo('pwd')"></u-cell-item>
<u-cell-item icon="scan" :iconStyle="{ color: '#36cfc9' }" title="扫描设置" @click="navTo('scan-settings')"></u-cell-item>
<u-cell-item icon="order" :iconStyle="{ color: '#59bdf9' }" title="清除缓存" @click="navTo('clear-cache')"></u-cell-item>
<!--<u-cell-item icon="account" :iconStyle="{ color: '#27c0dc' }" title="关于我们" @click="navTo('about')"></u-cell-item>
<u-cell-item icon="kefu-ermai" :iconStyle="{ color: '#ff8a00' }" title="联系客服" @click="navTo('service')"></u-cell-item>!-->
<u-gap height="20" bg-color="#f5f5f5"></u-gap>
<u-cell-item icon="reload" :iconStyle="{ color: '#19be6b' }" title="版本更新" @click="upgrade"></u-cell-item>
<u-cell-item icon="setting" :iconStyle="{ color: '#2767dc' }" title="系统设置" @click="navTo('setting')"></u-cell-item>
</u-cell-group>
</view>
</view>
<view class="app-meta">
<text class="app-name">{{ appName }}</text>
<text class="app-version">版本 {{ appVersion }}</text>
</view>
</view>
</template>
<script>
import CommonNavbar from "@/components/common-navbar/index";
import app_upgrade from '@/uni_modules/app-upgrade/js_sdk/index.js';
import config, { resolveUpgradeDownloadUrl } from '@/common/config.js';
import manifest from '@/manifest.json';
/**
* Copyright (c) 2013-Now http://aidex.vip All rights reserved.
*/
@@ -72,6 +81,12 @@ export default {
},
computed: {
appName() {
return manifest.name || '';
},
appVersion() {
return manifest.versionName || '';
},
avatarUrl() {
// let url = this.vuex_user.avatarUrl || '/ctxPath/static/images/user1.jpg';
// url = url.replace('/ctxPath/', this.vuex_config.baseUrl + '/');
@@ -101,25 +116,30 @@ export default {
},
upgrade(){
// #ifdef APP-PLUS
this.$u.api.upgradeCheck().then(res => {
if (res.result == 'true'){
uni.showModal({
title: '提示',
content: res.message + '是否下载更新?',
showCancel: true,
success: function (res2) {
if (res2.confirm) {
plus.runtime.openURL(res.data.apkUrl);
}
}
app_upgrade(async () => {
const appInfo = await new Promise((resolve) => {
plus.runtime.getProperty(plus.runtime.appid, (inf) => {
resolve(inf);
});
}else{
this.$u.toast(res.message);
});
const res = await this.$u.api.upgradeCheck({
app_name: config.upgradeName,
current_version: appInfo.version
});
if (!res.download_url) {
this.$u.toast('当前版本已是最新版');
return;
}
});
return {
changelog: res.message + ',最新版本:' + res.latest_version,
status: res.need_update ? 1 : 0,
path: resolveUpgradeDownloadUrl(res.download_url),
latest_version: res.latest_version
};
}, 1);
// #endif
// #ifndef APP-PLUS
this.$u.toast('小程序端或H5端无需检查更新')
this.$u.toast('小程序端或H5端已是最新版,无需检查更新!');
// #endif
}
}
@@ -144,4 +164,22 @@ page {
height:50rpx;
}
}
.app-meta {
padding: 48rpx 30rpx 40rpx;
text-align: center;
}
.app-name {
display: block;
color: #242820;
font-size: 36rpx;
font-weight: 700;
line-height: 1.4;
}
.app-version {
display: block;
margin-top: 8rpx;
color: #8a8f86;
font-size: 32rpx;
line-height: 1.3;
}
</style>
+22 -23
View File
@@ -68,31 +68,30 @@ export default {
methods: {
submit() {
this.$refs.uForm.validate(valid => {
if (valid) {
this.$u.api.user.infoSavePwd({
oldPassword: this.model.oldPassword,
newPassword: this.model.newPassword
}).then(res => {
if(res.code == '200'){
uni.showModal({
title: '提示',
content: res.msg,
showCancel: false,
success: function () {
uni.navigateBack();
}
});
}else{
uni.showModal({
title: '提示',
content: res.msg,
showCancel: false
});
if (!valid) {
this.$u.toast('您填写的信息有误,请根据提示修正。');
return;
}
this.$u.api.user.infoSavePwd({
oldPassword: this.model.oldPassword,
newPassword: this.model.newPassword
}).then(() => {
uni.showModal({
title: '提示',
content: '密码修改成功',
showCancel: false,
success: () => {
uni.navigateBack();
}
});
} else {
this.$u.toast('您填写的信息有误,请根据提示修正。');
}
}).catch((err) => {
const message = (err && (err.message || err.msg)) || '修改密码失败';
uni.showModal({
title: '提示',
content: message,
showCancel: false
});
});
});
},
cancel() {
+23 -24
View File
@@ -33,7 +33,7 @@ export default {
model: {
oldPassword: '',
newPassword: '',
confirmNewPassword: ''
confirmPassword: ''
},
rules: {
oldPassword: [
@@ -78,31 +78,30 @@ export default {
methods: {
submit() {
this.$refs.uForm.validate(valid => {
if (valid) {
this.$u.api.user.infoSavePwd({
oldPassword: this.model.oldPassword,
newPassword: this.model.newPassword
}).then(res => {
if(res.code == '200'){
uni.showModal({
title: '提示',
content: res.msg,
showCancel: false,
success: function () {
uni.navigateBack();
}
});
}else{
uni.showModal({
title: '提示',
content: res.msg,
showCancel: false
});
if (!valid) {
this.$u.toast('您填写的信息有误,请根据提示修正。');
return;
}
this.$u.api.user.infoSavePwd({
oldPassword: this.model.oldPassword,
newPassword: this.model.newPassword
}).then(() => {
uni.showModal({
title: '提示',
content: '密码修改成功',
showCancel: false,
success: () => {
uni.navigateBack();
}
});
} else {
this.$u.toast('您填写的信息有误,请根据提示修正。');
}
}).catch((err) => {
const message = (err && (err.message || err.msg)) || '修改密码失败';
uni.showModal({
title: '提示',
content: message,
showCancel: false
});
});
});
},
cancel() {
+253
View File
@@ -0,0 +1,253 @@
<template>
<view class="wrap">
<common-navbar title="扫描设置"></common-navbar>
<u-gap height="20" bg-color="#f5f5f5"></u-gap>
<u-cell-group :border="false">
<u-cell-item title="扫码方式" :value="form.name || '请选择'" @click="brandSelectShow = true"></u-cell-item>
<template v-if="!form.isPhoneScan">
<u-cell-item title="广播动作" :arrow="false">
<u-input
slot="right-icon"
v-model="form.action"
placeholder="请输入广播动作"
input-align="right"
:clearable="false"
></u-input>
</u-cell-item>
<u-cell-item title="数据标签" :arrow="false">
<u-input
slot="right-icon"
v-model="form.dataKey"
placeholder="请输入数据标签"
input-align="right"
:clearable="false"
></u-input>
</u-cell-item>
</template>
</u-cell-group>
<view class="form-footer">
<u-button type="primary" :loading="submitting" @click="submit">确定</u-button>
</view>
<u-select v-model="brandSelectShow" :list="brandSelectList" @confirm="onBrandConfirm"></u-select>
</view>
</template>
<script>
import CommonNavbar from '@/components/common-navbar/index';
import scanMixin from '@/common/scanMixin.js';
import {
getScanConfig,
DICTIONARY_TYPE_SCANNER,
isPhoneScanConfig,
mapDictionaryToScannerConfig,
PHONE_SCAN_BRAND,
saveScanConfig,
autoConfigureScanSettings,
} from '@/common/scanConfig.js';
export default {
components: {
CommonNavbar,
},
mixins: [scanMixin],
data() {
return {
brandSelectShow: false,
submitting: false,
configList: [],
form: {
brand: '',
name: '',
action: '',
dataKey: '',
isPhoneScan: false,
needSetup: false,
setupAction: '',
setupParams: null,
},
};
},
computed: {
brandSelectList() {
return this.configList.map((item) => ({
value: item.brand,
label: item.name,
}));
},
},
onLoad() {
this.loadConfigList();
},
methods: {
async loadConfigList() {
try {
const res = await this.$u.api.dictionary.getDetailEnumList({
dictionary_id: DICTIONARY_TYPE_SCANNER,
});
const list = Array.isArray(res) ? res : (Array.isArray(res && res.list) ? res.list : []);
const builtinConfigs = this.getScannerConfigs();
if (list.length) {
this.configList = list
.map((item) => mapDictionaryToScannerConfig(item, builtinConfigs))
.filter(Boolean);
this.remoteScannerConfigs = this.configList.filter((item) => !isPhoneScanConfig(item));
} else {
this.useFallbackConfigList();
}
} catch (error) {
console.log('获取扫码枪字典配置失败,使用内置配置', error);
this.useFallbackConfigList();
}
this.ensurePhoneScanOption();
this.ensureSeuicOption();
this.ensureNlscanOption();
this.ensureNlscanCompatOption();
this.initFormFromSaved();
},
useFallbackConfigList() {
this.configList = this.getScannerConfigs();
this.remoteScannerConfigs = [];
},
ensurePhoneScanOption() {
const phoneConfig = this.getPhoneScanConfig();
if (!this.configList.find((item) => item.brand === PHONE_SCAN_BRAND)) {
this.configList.push(phoneConfig);
}
},
ensureBuiltinScannerOption(brand) {
const builtinConfig = this.getScannerConfigs().find((item) => item.brand === brand);
if (!builtinConfig) {
return;
}
const exists = this.configList.some((item) => {
return item.brand === builtinConfig.brand || item.action === builtinConfig.action;
});
if (exists) {
return;
}
const phoneIndex = this.configList.findIndex((item) => item.brand === PHONE_SCAN_BRAND);
if (phoneIndex >= 0) {
this.configList.splice(phoneIndex, 0, builtinConfig);
return;
}
this.configList.push(builtinConfig);
},
ensureSeuicOption() {
this.ensureBuiltinScannerOption('seuic');
},
ensureNlscanOption() {
this.ensureBuiltinScannerOption('nlscan');
},
ensureNlscanCompatOption() {
this.ensureBuiltinScannerOption('nlscan_compat');
},
initFormFromSaved() {
// N5S 曾被误识别为东集时,进入设置页会纠正并写回
const rematch = autoConfigureScanSettings({
remoteConfigs: this.remoteScannerConfigs || [],
});
const savedConfig = (rematch && rematch.config) || getScanConfig(this.getScannerConfigs());
if (savedConfig) {
this.applyConfig(savedConfig);
return;
}
if (!this.isPdaDevice()) {
this.applyConfig(this.getPhoneScanConfig());
return;
}
const pdaConfigs = this.configList.filter((item) => !isPhoneScanConfig(item));
if (pdaConfigs.length) {
this.applyConfig(pdaConfigs[0]);
}
},
applyConfig(config) {
const isPhoneScan = isPhoneScanConfig(config);
this.form = {
brand: config.brand || '',
name: config.name || '',
action: config.action || '',
dataKey: config.dataKey || '',
isPhoneScan,
needSetup: !!config.needSetup,
setupAction: config.setupAction || '',
setupParams: config.setupParams || null,
};
},
onBrandConfirm(selected) {
const brand = selected[0]?.value;
const config = this.configList.find((item) => item.brand === brand);
if (config) {
this.applyConfig(config);
}
},
submit() {
if (!this.form.brand) {
this.$u.toast('请选择扫码方式');
return;
}
let payload;
if (this.form.isPhoneScan) {
payload = this.getPhoneScanConfig();
} else {
const action = (this.form.action || '').trim();
const dataKey = (this.form.dataKey || '').trim();
if (!action) {
this.$u.toast('请输入广播动作');
return;
}
if (!dataKey) {
this.$u.toast('请输入数据标签');
return;
}
payload = {
...this.form,
action,
dataKey,
};
}
this.submitting = true;
if (!saveScanConfig(payload)) {
this.submitting = false;
this.$u.toast('保存失败');
return;
}
// #ifdef APP-PLUS
this.unregisterScanBroadcast();
const registered = this.registerScanBroadcastByConfig(payload, null);
if (!registered) {
this.submitting = false;
this.$u.toast('保存成功,但广播注册失败');
return;
}
// #endif
this.submitting = false;
this.$u.toast('保存成功');
setTimeout(() => {
uni.navigateBack();
}, 500);
},
},
};
</script>
<style lang="scss">
page {
background-color: #f5f5f5;
}
.form-footer {
padding: 40rpx 30rpx;
}
::v-deep .u-cell_title {
color: #202328;
}
::v-deep .u-input__input {
min-width: 360rpx;
}
</style>
-34
View File
@@ -4,7 +4,6 @@
<u-cell-group :border="false">
<u-cell-item title="通用" @click="navTo('/pages/sys/user/currency')" :arrow="true"></u-cell-item>
<u-gap height="20" bg-color="#f5f5f5"></u-gap>
<u-cell-item title="版本更新" @click="upgrade()" :arrow="true"></u-cell-item>
<u-cell-item title="隐私政策" @click="navTo('')" :arrow="true"></u-cell-item>
<u-cell-item title="用户协议" @click="navTo('')" :arrow="true"></u-cell-item>
</u-cell-group>
@@ -18,10 +17,6 @@
/**
* Copyright (c) 2013-Now http://aidex.vip All rights reserved.
*/
import app_upgrade from '@/uni_modules/app-upgrade/js_sdk/index.js'
import util from '@/common/util.js';
import config from '../../../common/config';
export default {
data() {
return {
@@ -34,35 +29,6 @@ export default {
url: url
});
},
upgrade(){
// #ifdef APP-PLUS
app_upgrade(async ()=>{
// 获取应用信息
const appInfo = await new Promise((resolve) => {
plus.runtime.getProperty(plus.runtime.appid, (inf) => {
resolve(inf);
});
});
const res = await this.$u.api.upgradeCheck({
app_name: config.upgradeName,
current_version: appInfo.version
})
if(!res.download_url){
this.$u.toast('当前版本已是最新版');
return
}
return {
changelog: res.message + ',最新版本:' + res.latest_version,
status: res.need_update ? 1 : 0, // 0 无新版本 | 1 有新版本
path: res.download_url // 新apk地址
}
}, 1)
// #endif
// #ifndef APP-PLUS
this.$u.toast('小程序端或H5端已是最新版,无需检查更新!');
// #endif
},
openSettings() {
// #ifdef APP-PLUS
uni.getSystemInfo({
+283 -368
View File
@@ -1,406 +1,321 @@
<template>
<view class="wrap">
<common-navbar title="工作台" :showBack="false">
<template #right>
<view class="scan_box">
<u-icon
name="scan"
color="#333333"
size="46"
@click="handleScan"
></u-icon>
<view class="app-center">
<view class="app-center__status" :style="{ height: statusBarHeight + 'px' }"></view>
<view class="app-center__header">
<text class="app-center__version">v{{ appVersion }}</text>
<text class="app-center__title">工作台</text>
<view class="app-center__setting" @click="navTo('/pages/sys/user/setting')">
<u-icon name="setting" color="#333333" size="34"></u-icon>
<text>设置</text>
</view>
</view>
<view class="app-center__grid">
<view
v-for="item in menuList"
:key="item.path"
class="menu-card"
:class="item.tone"
hover-class="menu-card--pressed"
@click="navTo(item.path)"
>
<image class="menu-card__bg" :src="item.bg" mode="aspectFill"></image>
<view class="menu-card__mask"></view>
<text class="menu-card__quote">“</text>
<view v-if="item.tag" class="menu-card__tag">
<text>{{ item.tag }}</text>
</view>
</template>
</common-navbar>
<view class="workbench-title">销售管理</view>
<view class="toolbar">
<u-grid class="grid" :col="4" :border="false">
<u-grid-item :index="0" @click="navTo('/pages/saleship/salepickscan')">
<view class="home-icon icon-color05">
<i class="iconfont icon-hetongguanli"></i>
</view>
<view class="grid-text">成品配布</view>
</u-grid-item>
<u-grid-item
:index="1"
@click="navTo('/pages/storegoods/QRBarCodeReview')"
>
<view class="home-icon icon-color02">
<i class="iconfont icon-mall-bag"></i>
</view>
<view class="grid-text">标签检测</view>
</u-grid-item>
</u-grid>
<view class="menu-card__body">
<text class="menu-card__name">{{ item.name }}</text>
<text class="menu-card__en">{{ item.en }}</text>
</view>
<view class="menu-card__arrow">
<text>›</text>
</view>
</view>
</view>
<view class="workbench-title">成品管理</view>
<view class="toolbar">
<u-grid class="grid" :col="4" :border="false">
<u-grid-item
:index="0"
@click="navTo('/pages/storegoods/dyeworksDyeback')"
>
<view class="home-icon icon-color04">
<i class="iconfont icon-finance"></i>
</view>
<view class="grid-text">染整进仓</view>
</u-grid-item>
<u-grid-item
:index="1"
@click="navTo('/pages/storegoods/storeGoodsBusinessIn')"
>
<view class="home-icon icon-color03">
<i class="iconfont icon-mall-bag"></i>
</view>
<view class="grid-text">成品进仓</view>
</u-grid-item>
<u-grid-item
:index="2"
@click="navTo('/pages/storegoods/storeGoodsBusinessOut')"
>
<view class="home-icon icon-color12">
<i class="iconfont icon-baoxiaodan"></i>
</view>
<view class="grid-text">成品出仓</view>
</u-grid-item>
<u-grid-item
@click="navTo('/pages/storegoods/storeGoodsBusinessCheck')"
>
<view class="home-icon icon-color04">
<i class="iconfont icon-shenpi"></i>
</view>
<view class="grid-text">成品盘点</view>
</u-grid-item>
<u-grid-item
@click="navTo('/pages/storegoods/storeGoodsBusinessStationMoveOnly')"
>
<view class="home-icon icon-color04">
<i class="iconfont icon-shenpi"></i>
</view>
<view class="grid-text">成品移架</view>
</u-grid-item>
<u-grid-item @click="navTo('/pages/storegoods/storegoodsstoresearch')">
<view class="home-icon icon-color04">
<i class="iconfont icon-shenpi"></i>
</view>
<view class="grid-text">成品库存</view>
</u-grid-item>
<u-grid-item @click="navTo('/pages/storegoods/storegoodsstoreallin')">
<view class="home-icon icon-color04">
<i class="iconfont icon-shenpi"></i>
</view>
<view class="grid-text">进仓查询</view>
</u-grid-item>
<u-grid-item @click="navTo('/pages/storegoods/storegoodsstoreallout')">
<view class="home-icon icon-color04">
<i class="iconfont icon-shenpi"></i>
</view>
<view class="grid-text">出仓查询</view>
</u-grid-item>
</u-grid>
</view>
<!--
<view class="workbench-title">坯布管理</view>
<view class="toolbar">
<u-grid class="grid" :col="4" :border="false">
<u-grid-item :index="0" @click="navTo('/pages/storefabric/storefabricInStore')">
<view class="home-icon icon-color04">
<i class="iconfont icon-hetongguanli"></i>
</view>
<view class="grid-text">坯布进仓</view>
</u-grid-item>
<u-grid-item :index="1" @click="navTo('/pages/storefabric/storefabricOutStore')">
<view class="home-icon icon-color03">
<i class="iconfont icon-chucha"></i>
</view>
<view class="grid-text">坯布出仓</view>
</u-grid-item>
<u-grid-item :index="2" @click="navTo('')">
<view class="home-icon icon-color12">
<i class="iconfont icon-ribao"></i>
</view>
<view class="grid-text">坯布盘点</view>
</u-grid-item>
<u-grid-item @click="navTo('')">
<view class="home-icon icon-color04">
<i class="iconfont icon-tongzhi"></i>
</view>
<view class="grid-text">坯布移架</view>
</u-grid-item>
<u-grid-item @click="navTo('/pages/storefabric/storefabricstoresearch')">
<view class="home-icon icon-color05">
<i class="iconfont icon-huiyishi"></i>
</view>
<view class="grid-text">坯布库存</view>
</u-grid-item>
<u-grid-item @click="navTo('/pages/storefabric/storefabricstoreallin')">
<view class="home-icon icon-color05">
<i class="iconfont icon-huiyishi"></i>
</view>
<view class="grid-text">进仓查询</view>
</u-grid-item>
<u-grid-item @click="navTo('/pages/storefabric/storefabricstoreallout')">
<view class="home-icon icon-color05">
<i class="iconfont icon-huiyishi"></i>
</view>
<view class="grid-text">出仓查询</view>
</u-grid-item>
</u-grid>
</view>
-->
</view>
</template>
<script>
import CommonNavbar from "@/components/common-navbar/index";
import scanMixin from '@/common/scanMixin.js';
/**
* Copyright (c) 2013-Now http://aidex.vip All rights reserved.
*/
export default {
components: {
CommonNavbar,
},
mixins: [scanMixin],
data() {
return {
show: false,
head: "/static/aidex/images/head.png",
imgList: [
{ image: "/static/aidex/banner/banner01.png" },
//{image: '/static/aidex/banner/banner02.png'},
//{image: '/static/aidex/banner/banner03.png'}
],
todoCount: 3,
scanReceiver: null,
isPageActive: false,
statusBarHeight: 20,
appVersion: '1.1.10',
menuList: [
{
name: '坯布出库',
en: 'FABRIC OUT',
tag: '出库',
path: '/pages/storefabric/storeFabricBusinessOutList',
tone: 'tone-out',
bg: '/static/img/workbench/fabric-out.jpg'
},
{
name: '坯布库存',
en: 'FABRIC STOCK',
tag: '库存',
path: '/pages/storefabric/storeFabricWarehouseGroupList',
tone: 'tone-stock',
bg: '/static/img/workbench/fabric-stock.jpg'
},
{
name: '手动进仓',
en: 'MANUAL ENTRY',
tag: '进仓',
path: '/pages/storefabric/storeFabricManualIn',
tone: 'tone-in',
bg: '/static/img/workbench/fabric-stock.jpg'
}
]
};
},
onLoad() {
onLoad(options) {
const sys = uni.getSystemInfoSync();
this.statusBarHeight = sys.statusBarHeight || 20;
if (options && String(options.defaultPassword) === '1') {
this.promptChangePassword();
}
// #ifdef APP-PLUS
this.isPageActive = true;
this.registerScanBroadcast();
plus.runtime.getProperty(plus.runtime.appid, (inf) => {
if (inf && inf.version) {
this.appVersion = inf.version;
}
});
// #endif
this.registerScanBroadcast(this.handleScanCode);
},
onUnload() {
// #ifdef APP-PLUS
this.isPageActive = false;
this.unregisterBroadcast();
// #endif
},
onHide() {
// 页面隐藏时
this.isPageActive = false;
},
onShow() {
// 页面显示时
this.isPageActive = true;
},
computed: {
contentStyle() {
const navHeight = getStatusBarHeight() + 44;
return {
height: `${navHeight}px`,
};
},
},
methods: {
navTo(url) {
promptChangePassword() {
setTimeout(() => {
uni.showModal({
title: '提示',
content: '当前使用的是默认密码,请修改密码',
confirmText: '去修改',
cancelText: '稍后',
success: (res) => {
if (!res.confirm) return;
uni.navigateTo({ url: '/pages/sys/user/pwd' });
},
});
}, 400);
},
handleScanCode(scanResult) {
const cleanResult = scanResult.trim().replace(/[\r\n]/g, '');
uni.navigateTo({
url: url,
url: '/pages/saleship/salepickscandetail?order_no=' + cleanResult
});
},
imgListClick(index) {
console.log(`点击了第${index + 1}页图片`);
},
itemClick(index) {
console.log(index);
},
// 处理扫码
handleScan() {
// uni.navigateTo({
// url: '/pages/saleship/salepickscandetail?order_no=FPD-PB-202412270196'
// })
// return
// #ifdef APP-PLUS || MP-WEIXIN
uni.scanCode({
scanType: ['qrCode', 'barCode'], // 同时支持二维码和条形码
success: (res) => {
console.log('扫码成功:', res);
const cleanResult = res.result.trim().replace(/[\r\n]/g, '');
uni.navigateTo({
url: '/pages/saleship/salepickscandetail?order_no=' + cleanResult
})
},
fail: (err) => {
console.error('扫码失败:', err);
uni.showToast({
title: '扫码失败',
icon: 'none'
});
}
});
// #endif
// #ifdef H5
uni.showToast({
title: "H5环境不支持扫码功能",
icon: "error",
});
// #endif
},
// 处理扫码成功
handleScanSuccess(result) {
try {
// 这里可以根据实际需求处理扫码结果
// 例如:验证扫码结果的格式或内容
if (this.validateScanResult(result)) {
// 扫码验证通过,执行其他逻辑
uni.showToast({
title: "执行成功",
icon: "success",
});
} else {
uni.showToast({
title: "无效的二维码",
icon: "none",
});
}
} catch (error) {
console.error("处理扫码结果失败:", error);
uni.showToast({
title: "处理失败",
icon: "none",
});
}
},
// 注册扫码广播
registerScanBroadcast() {
try {
console.log('开始注册扫码广播');
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');
const receiver = plus.android.implements('io.dcloud.feature.internal.reflect.BroadcastReceiver', {
onReceive: (context, intent) => {
// 只有当页面活动时才处理广播
if (!this.isPageActive) return;
try {
console.log("Workbench intent:", intent);
const scanResult = intent.getStringExtra('scannerdata');
console.log('工作台-扫码结果:', scanResult);
if (scanResult) {
this.$nextTick(() => {
const cleanResult = scanResult.trim().replace(/[\r\n]/g, '');
uni.navigateTo({
url: '/pages/saleship/salepickscandetail?order_no=' + cleanResult
});
});
}
} catch (error) {
console.error('处理广播数据时出错:', error, error.stack);
}
}
});
// 注册广播接收器
main.registerReceiver(receiver, filter);
this.scanReceiver = receiver;
console.log('扫码广播注册成功,等待扫码...');
} catch (error) {
console.error('注册扫码广播失败:', error);
console.error('错误详情:', error.message);
console.error('错误堆栈:', error.stack);
}
},
// 注销广播接收器
unregisterBroadcast() {
if (this.scanReceiver) {
try {
const main = plus.android.runtimeMainActivity();
main.unregisterReceiver(this.scanReceiver);
this.scanReceiver = null;
console.log('扫码广播注销成功');
} catch (error) {
console.error('注销扫码广播失败:', error);
}
}
},
},
navTo(url) {
uni.navigateTo({ url });
}
}
};
</script>
<style lang="scss">
@import "index.scss";
.scan_box {
page {
background: #eef1f4;
}
.app-center {
min-height: 100vh;
background: #eef1f4;
box-sizing: border-box;
}
.app-center__header {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.scan_text {
font-size: 12px;
color: #2979ff;
}
}
.banner-box {
padding: 0 2%;
width: 96%;
height: 170rpx;
margin: 30rpx 0 30rpx;
}
.u-swiper-wrap {
padding: 0 10px;
height: 88rpx;
padding: 0 24rpx;
background: #ffffff;
border-bottom: 1rpx solid #e5e8ec;
}
.banner-pic {
width: 47%;
float: left;
display: inline-block;
margin: 0 1.5%;
}
.banner-pic image {
width: 100%;
height: 170rpx;
border-radius: 12rpx;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
.app-center__version {
position: absolute;
left: 24rpx;
font-size: 24rpx;
color: #9aa3ad;
line-height: 1;
}
.u-mode-light-info {
background-color: #ffffff;
color: #666666;
border: 1px solid #e9ecf6;
font-size: 12px;
padding: 2px 8px;
position: relative;
top: -3px;
.app-center__title {
font-size: 36rpx;
font-weight: 700;
color: #1f2430;
letter-spacing: 2rpx;
}
.workbench-title {
font-size: 32rpx;
font-weight: bold;
.app-center__setting {
position: absolute;
right: 24rpx;
display: flex;
align-items: center;
gap: 6rpx;
font-size: 26rpx;
color: #333333;
padding: 15px 30rpx;
}
.home-icon i.icon-tongzhi {
font-size: 22px;
.app-center__grid {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
padding: 28rpx 48rpx 40rpx;
box-sizing: border-box;
}
.menu-card {
position: relative;
width: 310rpx;
height: 200rpx;
margin-bottom: 22rpx;
border-radius: 16rpx;
overflow: hidden;
box-sizing: border-box;
box-shadow: 0 6rpx 14rpx rgba(20, 40, 70, 0.12);
}
.menu-card--pressed {
opacity: 0.88;
transform: scale(0.985);
}
.menu-card__bg {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 0;
opacity: 0.38;
}
.menu-card__mask {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 1;
}
.menu-card__quote {
position: absolute;
left: 14rpx;
top: 2rpx;
font-size: 48rpx;
line-height: 1;
color: rgba(255, 255, 255, 0.55);
font-weight: 700;
z-index: 2;
}
.menu-card__tag {
position: absolute;
top: 14rpx;
right: 48rpx;
padding: 2rpx 12rpx;
border-radius: 6rpx;
background: #ffffff;
z-index: 3;
}
.menu-card__tag text {
font-size: 20rpx;
font-weight: 700;
line-height: 1.2;
}
.menu-card__body {
position: absolute;
left: 22rpx;
right: 22rpx;
top: 50%;
transform: translateY(-42%);
z-index: 2;
}
.menu-card__name {
display: block;
font-size: 34rpx;
font-weight: 700;
color: #ffffff;
line-height: 1.2;
}
.menu-card__en {
display: block;
margin-top: 8rpx;
font-size: 18rpx;
letter-spacing: 1rpx;
color: rgba(255, 255, 255, 0.88);
line-height: 1.2;
}
.menu-card__arrow {
position: absolute;
right: 0;
bottom: 0;
width: 48rpx;
height: 42rpx;
background: #eef1f4;
border-top-left-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
z-index: 3;
}
.menu-card__arrow text {
font-size: 30rpx;
color: #8a93a0;
line-height: 1;
margin-top: -4rpx;
}
.tone-out {
background: #e14b3b;
}
.tone-out .menu-card__mask {
background: rgba(225, 75, 59, 0.72);
}
.tone-out .menu-card__tag text {
color: #e14b3b;
}
.tone-stock {
background: #2f78e6;
}
.tone-stock .menu-card__mask {
background: rgba(47, 120, 230, 0.72);
}
.tone-stock .menu-card__tag text {
color: #2f78e6;
}
.tone-in {
background: #3f51b5;
}
.tone-in .menu-card__mask {
background: rgba(63, 81, 181, 0.72);
}
.tone-in .menu-card__tag text {
color: #3f51b5;
}
</style>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More