初版提交

This commit is contained in:
李宇奇 2025-10-31 22:49:51 +08:00
parent 6ebdfd33bc
commit 12ded6e413
31 changed files with 5169 additions and 2 deletions

48
.gitignore vendored Normal file
View File

@ -0,0 +1,48 @@
# ---> Vue
# gitignore template for Vue.js projects
#
# Recommended template: Node.gitignore
# TODO: where does this rule come from?
docs/_book
# TODO: where does this rule come from?
test/
# ---> Android
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Log/OS Files
*.log
# Android Studio generated files and folders
captures/
.externalNativeBuild/
.cxx/
*.apk
output.json
# IntelliJ
*.iml
.idea/
misc.xml
deploymentTargetDropDown.xml
render.experimental.xml
# Keystore files
*.jks
*.keystore
# Google Services (e.g. APIs or Firebase)
google-services.json
# Android Profiling
*.hprof
unpackage/

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<!-- 针对特定域名的配置 -->
<domain-config cleartextTrafficPermitted="false">
<!-- 配置您的服务器域名 -->
<domain includeSubdomains="true">wxperkinsasrs.ap.cat.com</domain>
<!-- 信任策略 -->
<trust-anchors>
<!-- 信任系统预装的 CA 证书 -->
<certificates src="system" />
<!-- 信任用户手动安装的 CA 证书(最重要!) -->
<certificates src="user" />
</trust-anchors>
</domain-config>
<!-- 其他所有域名使用默认安全策略 -->
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">.</domain>
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</domain-config>
</network-security-config>

36
App.vue Normal file
View File

@ -0,0 +1,36 @@
<script>
export default {
setup() {
// uni-app console.clear
try {
if (typeof console !== 'undefined' && !console.clear) {
Object.defineProperty(console, 'clear', {
value: function() { /* no-op */ },
writable: false,
configurable: true
});
}
} catch (error) {
// console
}
}
}
</script>
<template>
<view class="app">
<!-- 应用主内容 -->
<router-view />
</view>
</template>
<style lang="scss">
@import './common/uni.css';
@import '/static/mui-icons.css';
page { background-color: #F5F5F5; min-height: 100%; }
.app {
width: 100%;
min-height: 100vh;
}
</style>

View File

@ -1,3 +1,13 @@
# pda-uni-app-perkins
## pda-uni-app-perkins
铂金斯pda
> uni-app版手持pda
### HBuilderX配置
* MuMu模拟器端口号16384
* adb路径选择本地adb.exe文件
### 运行方式
> 运行 -> 运行到手机或模拟器 -> 运行到Android App基座

230
common/env.js Normal file
View File

@ -0,0 +1,230 @@
// 环境配置
export const ENV = {
// API 地址配置
// 说明:
// - HTTP 模式(端口 80用于 PDA 应用,避免证书验证问题(企业内网使用)
// - HTTPS 模式(端口 443用于 Web 端或需要安全连接的客户端
//
// 现已配置 Android 网络安全策略信任自签名证书,可改回 HTTPS
API_URL: 'https://wxperkinsasrs.ap.cat.com', // 改回 HTTPS 以使用安全连接
APP_NAME: 'PdaUniAppPerkins',
VERSION: '1.0.0',
// 测试模式开关true使用 Mock 数据false使用真实 API
TEST_MODE: true,
// URL 调试模式(显示请求信息)
DEBUG_URL: true,
// SSL 证书验证开关
// 说明:
// - Android 平台:由 src/main/res/xml/network_security_config.xml 控制
// - iOS 平台:此参数可能无效,需通过 Info.plist 配置
// - H5 平台:此参数无效,由浏览器控制
// - 开发环境已配置信任自签名证书wxperkinsasrs.ap.cat.com
SSL_VERIFY: false,
// 协议选择(调试用)
// 'http' - 使用 HTTP 协议(端口 80
// 'https' - 使用 HTTPS 协议(端口 443推荐
// 注意:修改此值需要同时修改 API_URL
PROTOCOL: 'https'
};
// Mock 数据定义
// 格式:{ [url]: { code: 200, data: {...}, message: '...' } }
export const MOCK_DATA = {
// 1. 发动机入库 - POST /wmsServer/wms/api/orderIn/loginInBad
'/wmsServer/wms/api/orderIn/loginInBad': {
code: 200,
data: {
code: 0,
message: '发动机入库成功',
orderId: 'ENG-IN-20251030001',
standId: 'EF01',
vehicleNo: 'V12345',
goodsId: 'SN-ENG-ABC123456',
model: 'C13-ACERT',
goodsNum: 1,
createTime: '2025-10-30T10:15:30.000Z',
operatorName: 'PDA-USER-001'
},
message: 'success'
},
// 2. 空载具入库 - POST /wmsServer/wms/api/orderIn/loginToByVehicle
'/wmsServer/wms/api/orderIn/loginToByVehicle': {
code: 200,
data: {
code: 0,
message: '空载具入库成功',
orderId: 'EMPTY-IN-20251030001',
standId: 'EF05',
vehicleNo: 'D99999999',
createTime: '2025-10-30T10:20:15.000Z',
operatorName: 'PDA-USER-001'
},
message: 'success'
},
// 3. 绑定物料 - POST /wmsServer/wms/api/orderIn/bindingVehicl
'/wmsServer/wms/api/orderIn/bindingVehicl': {
code: 200,
data: {
code: 0,
message: '物料绑定成功',
bindId: 'BIND-20251030001',
vehicleNo: 'V23456',
goodsId: 'RAW-MAT-001',
goodsNum: 10,
userName: 'PDA',
bindTime: '2025-10-30T10:25:45.000Z'
},
message: 'success'
},
// 3-失败场景:物料已绑定(可选,用于测试错误处理)
// 如果需要测试失败场景,可以临时替换上面的成功场景
'/wmsServer/wms/api/orderIn/bindingVehicl_ERROR': {
code: 200,
data: {
code: 1001,
message: '该物料已经绑定到母托号 V23456请勿重复绑定',
vehicleNo: 'V23456',
goodsId: 'RAW-MAT-001'
},
message: 'success'
},
// 4. 查询物料列表 - GET /wmsServer/wms/api/orderIn/getOrderInWithVehicleNo
'/wmsServer/wms/api/orderIn/getOrderInWithVehicleNo': {
code: 200,
data: {
code: 0,
message: '查询成功',
vehicleNo: 'V23456',
returnData: [{
rowId: '10001',
goodsId: 'RAW-MAT-001',
goodsNum: 10,
batchNo: 'BATCH-2025-10-001',
productionDate: '2025-10-15T00:00:00.000Z',
model: 'C13-RAW',
itemId: 'ITM-001',
segment1: 'SEG-001',
weight: 25.5,
productData: 'PD-2025-10-001',
area: 'RF01'
},
{
rowId: '10002',
goodsId: 'RAW-MAT-002',
goodsNum: 5,
batchNo: 'BATCH-2025-10-002',
productionDate: '2025-10-20T00:00:00.000Z',
model: 'C15-RAW',
itemId: 'ITM-002',
segment1: 'SEG-002',
weight: 15.3,
productData: 'PD-2025-10-002',
area: 'RF02'
},
{
rowId: '10003',
goodsId: 'RAW-MAT-003',
goodsNum: 8,
batchNo: 'BATCH-2025-10-003',
productionDate: '2025-10-25T00:00:00.000Z',
model: 'C18-RAW',
itemId: 'ITM-003',
segment1: 'SEG-003',
weight: 32.1,
productData: 'PD-2025-10-003',
area: 'RF03'
}
],
totalCount: 3
},
message: 'success'
},
// 4-空列表场景(可选,用于测试空数据处理)
'/wmsServer/wms/api/orderIn/getOrderInWithVehicleNo_EMPTY': {
code: 200,
data: {
code: 0,
message: '该母托号暂无绑定物料',
vehicleNo: 'V99999',
returnData: [],
totalCount: 0
},
message: 'success'
},
// 5. 原材料入库 - POST /wmsServer/wms/api/orderIn/addInByTask
'/wmsServer/wms/api/orderIn/addInByTask': {
code: 200,
data: {
code: 0,
message: '原材料入库成功',
orderId: 'RAW-IN-20251030001',
vehicleNo: 'V23456',
standId: 'RF01',
totalItems: 3,
totalWeight: 72.9,
createTime: '2025-10-30T10:35:20.000Z',
operatorName: 'PDA-USER-001',
taskId: 'TASK-20251030001'
},
message: 'success'
},
// 5-失败场景:未绑定物料(可选)
'/wmsServer/wms/api/orderIn/addInByTask_ERROR': {
code: 200,
data: {
code: 1002,
message: '母托号 V23456 未绑定任何物料,请先绑定物料后再入库',
vehicleNo: 'V23456'
},
message: 'success'
},
// 6. 删除入库记录 - DELETE /wmsServer/wms/api/orderIn/deleteOrderIn/{rowId}
// 注意:由于 http.js 的 httpDelete 函数会移除路径参数key 使用基础路径
'/wmsServer/wms/api/orderIn/deleteOrderIn': {
code: 200,
data: {
code: 0,
message: '物料删除成功',
rowId: '10001',
deleteTime: '2025-10-30T10:40:15.000Z'
},
message: 'success'
},
// 6-失败场景:记录不存在(可选)
'/wmsServer/wms/api/orderIn/deleteOrderIn_ERROR': {
code: 200,
data: {
code: 1003,
message: '物料记录不存在或已被删除',
rowId: '99999'
},
message: 'success'
},
// 7. 测试连通性 - GET /wmsServer/wms/api/orderIn/test
'/wmsServer/wms/api/orderIn/test': {
code: 200,
data: {
code: 0,
message: 'API 连接正常',
serverTime: '2025-10-30T10:45:00.000Z',
version: '1.0.0',
status: 'healthy'
},
message: 'success'
}
};

239
common/http.js Normal file
View File

@ -0,0 +1,239 @@
import {
ENV,
MOCK_DATA
} from './env.js';
import {
DialogUtils
} from '../utils/dialog.js';
/**
* HTTP 拦截器 - 统一处理测试模式和生产模式的请求
*
* 核心功能
* 1. 测试模式返回 MOCK_DATA 中定义的模拟数据
* 2. 生产模式发起真实的 HTTP 请求到 API 服务器
* 3. 调试模式显示请求信息和详细日志
* 4. 统一的错误处理和响应格式
*/
function createHttpInterceptor() {
return {
/**
* 请求前拦截器 - 统一处理 URL 拼接和调试信息
* @param {string} method - HTTP 方法GET/POST/DELETE
* @param {string} url - API 路径不包含域名
* @param {object} params - 请求参数
* @param {object} extra - 额外配置timeoutmockDelay
* @returns {object} 处理后的请求信息
*/
beforeRequest(method, url, params, extra) {
const fullUrl = ENV.API_URL + url;
// URL 调试功能 - 在测试和生产模式下都可用
if (ENV.DEBUG_URL) {
// 显示详细的 URL 调试信息
console.log('HTTP Request Debug:', {
method,
fullUrl,
path: url,
params,
isTestMode: ENV.TEST_MODE
});
}
return {
fullUrl,
params,
extra
};
},
/**
* 响应处理器 - 根据模式返回 Mock 数据或真实请求
* @param {string} method - HTTP 方法
* @param {string} url - API 路径
* @param {object} params - 请求参数
* @param {object} extra - 额外配置
* @returns {Promise} 统一格式的响应 Promise
*/
processResponse(method, url, params, extra) {
return new Promise((resolve, reject) => {
// 统一调用前置拦截器(测试模式和生产模式都需要)
const {
fullUrl
} = this.beforeRequest(method, url, params, extra);
// 测试模式 - 返回模拟数据
if (ENV.TEST_MODE && MOCK_DATA[url]) {
const mockResponse = JSON.parse(JSON.stringify(MOCK_DATA[url])); // 深拷贝避免数据污染
// 只在开发环境输出 Mock 日志
if (process.env.NODE_ENV !== 'production') {
console.log('测试模式 - 返回模拟数据:', {
method,
url,
response: mockResponse
});
}
// 模拟网络延迟(让 UI 更贴近真实场景)
setTimeout(() => {
resolve(mockResponse);
}, extra.mockDelay || 500);
return;
}
// 生产模式 - 执行真实请求
// 从本地存储获取认证 token
const token = uni.getStorageSync('user_token') || '';
// 对于 GET 请求,将参数拼接到 URL 查询字符串中
let requestUrl = fullUrl;
let requestData;
if (method === 'GET') {
// GET 请求:参数拼接到 URLbody 为空
if (params && Object.keys(params).length > 0) {
const queryString = Object.entries(params)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&');
requestUrl = `${fullUrl}?${queryString}`;
}
requestData = undefined;
} else if (method === 'DELETE') {
// DELETE 请求:通常不需要 body资源 ID 在 URL 中)
requestData = undefined;
} else {
// POST/PUT 请求:参数在 body 中
requestData = params;
}
// 构建请求头
const headers = {
'Content-Type': 'application/json',
'X-App-Version': ENV.VERSION || '1.0.0'
};
// 添加认证头(如果存在 token
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
uni.request({
url: requestUrl,
method: method.toUpperCase(),
data: requestData,
header: headers,
timeout: extra.timeout || 5000,
sslVerify: ENV.SSL_VERIFY, // SSL 证书验证
// Android: 由 res/xml/network_security_config.xml 控制
// iOS: 由 Info.plist 控制
success: (res) => {
const response = {
code: res.statusCode,
data: res.data,
message: res.errMsg
};
// 只在开发环境和调试模式下输出响应日志
if (ENV.DEBUG_URL && process.env.NODE_ENV !== 'production') {
console.log('生产模式 - 真实请求响应:', {
method,
url,
fullUrl,
response
});
}
resolve(response);
},
fail: (err) => {
const errorResponse = {
code: 500,
data: null,
message: err.errMsg || 'network error'
};
// 错误日志在所有环境下都输出(帮助排查问题)
console.error('请求失败:', {
method,
url,
fullUrl,
error: errorResponse
});
reject(errorResponse);
},
});
});
}
};
}
const httpInterceptor = createHttpInterceptor();
/**
* GET 请求 - 用于查询数据
* @param {string} url - API 路径例如'/wmsServer/wms/api/orderIn/test'
* @param {object} params - 查询参数会自动拼接到 URL
* @param {object} extra - 额外配置 { timeout, mockDelay }
* @returns {Promise<{code: number, data: any, message: string}>}
*
* @example
* httpGet('/wmsServer/wms/api/orderIn/getOrderInWithVehicleNo', { vehicleNo: 'V12345' })
*/
export async function httpGet(url, params = {}, extra = {}) {
return httpInterceptor.processResponse('GET', url, params, extra);
}
/**
* POST 请求 - 用于创建或提交数据
* @param {string} url - API 路径
* @param {object} data - 请求体数据
* @param {object} extra - 额外配置 { timeout, mockDelay }
* @returns {Promise<{code: number, data: any, message: string}>}
*
* @example
* httpPost('/wmsServer/wms/api/orderIn/loginInBad', { standId: 'EF01', vehicleNo: 'V12345' })
*/
export async function httpPost(url, data = {}, extra = {}) {
return httpInterceptor.processResponse('POST', url, data, extra);
}
/**
* DELETE 请求 - 用于删除资源
* 注意DELETE 请求应该保持完整的 URL包括资源 ID
* Mock 数据匹配时会自动处理路径参数env.js 中使用基础路径作为 key
*
* @param {string} url - 完整的 API 路径例如'/wmsServer/wms/api/orderIn/deleteOrderIn/12345'
* @param {object} extra - 额外配置 { timeout, mockDelay }
* @returns {Promise<{code: number, data: any, message: string}>}
*
* @example
* httpDelete('/wmsServer/wms/api/orderIn/deleteOrderIn/12345')
*
* 对于测试模式
* - 会先尝试匹配完整 URL
* - 如果找不到会尝试匹配基础路径去除末尾数字 ID
*/
export async function httpDelete(url, extra = {}) {
// 测试模式下,支持基础路径匹配(兼容 env.js 的 MOCK_DATA 定义)
if (ENV.TEST_MODE) {
// 先尝试完整 URL 匹配
if (MOCK_DATA[url]) {
return httpInterceptor.processResponse('DELETE', url, {}, extra);
}
// 如果找不到,尝试基础路径匹配(移除末尾的数字 ID
const cleanUrl = url.split('?')[0]; // 移除查询参数
const baseUrl = cleanUrl.replace(/\/\d+$/, ''); // 移除末尾数字 ID
if (MOCK_DATA[baseUrl]) {
// 使用基础路径进行 Mock 数据匹配
return httpInterceptor.processResponse('DELETE', baseUrl, {}, extra);
}
}
// 生产模式:保持完整的 URL包括资源 ID
return httpInterceptor.processResponse('DELETE', url, {}, extra);
}

69
common/theme.js Normal file
View File

@ -0,0 +1,69 @@
export const theme = {
colors: {
aqua: '#05DCEF',
background: '#ffffff',
backgroundGray: '#F5F5F5',
text: '#333333',
textLight: '#666666',
border: '#dddddd',
error: '#ff3b30',
cyan: '#00FFFF',
lightGreen: '#7FFFAA',
gradients: {
primary: ['#05DCEF', '#7DE2F5', '#B8F2FF'],
contrast: ['#00F5A0', '#00D9F5'],
header: ['#05DCEF', '#7DE2F5'],
button: ['#05DCEF', '#7DE2F5']
},
chart: {
cyan: '#00FFFF',
lightGreen: '#7FFFAA'
}
},
fontSize: {
small: 12,
regular: 14,
medium: 16,
large: 18,
xlarge: 20,
xxlarge: 24,
title: 32
},
spacing: {
xs: 4,
small: 8,
medium: 16,
large: 24,
xl: 32
},
borderRadius: {
small: 4,
medium: 8,
large: 12,
xl: 16,
circle: 999
},
shadow: {
small: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 2
},
medium: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 4.65,
elevation: 4
},
large: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 6 },
shadowOpacity: 0.37,
shadowRadius: 7.49,
elevation: 6
}
}
};

1458
common/uni.css Normal file

File diff suppressed because it is too large Load Diff

87
common/wmsApi.js Normal file
View File

@ -0,0 +1,87 @@
import { httpGet, httpPost, httpDelete } from './http.js';
import { ENV } from './env.js';
const BASE_PATH = '/wmsServer/wms/api/orderIn';
function buildUrl(endpoint) {
return `${BASE_PATH}${endpoint}`;
}
/**
* 统一的响应数据解包器
* @param {object} response - HTTP 响应对象 { code, data, message }
* @param {string} fallbackMessage - 失败时的默认错误消息
* @returns {object} 解包后的业务数据附带 httpStatus
* @throws {Error} HTTP 状态码不是 200 或响应格式不正确时抛出错误
*/
function unwrapResponse(response, fallbackMessage) {
const { code: statusCode, data, message } = response || {};
if (statusCode !== 200) {
throw new Error(message || fallbackMessage || '请求失败');
}
if (!data || typeof data !== 'object') {
throw new Error(fallbackMessage || '响应格式不正确');
}
return Object.assign({ httpStatus: statusCode }, data);
}
export const WmsApiClient = {
async engineStockIn({ standId, vehicleNo, goodsId, model, goodsNum = 0, timeout = 10000 }) {
const payload = {
efSelect: standId,
vehicleNo,
goodsId,
goodsNum,
model,
};
const res = await httpPost(buildUrl('/loginInBad'), payload, { timeout });
return unwrapResponse(res, '发动机入库失败');
},
async emptyVehicleIn({ standId, vehicleNo = 'D99999999', timeout = 8000 }) {
const payload = {
efSelect: standId,
vehicleNo,
goodsId: '',
goodsNum: 0,
model: '',
};
const res = await httpPost(buildUrl('/loginToByVehicle'), payload, { timeout });
return unwrapResponse(res, '空载具入库失败');
},
async bindVehicle({ vehicleNo, goodsId, goodsNum = 0, userName = 'PDA', timeout = 8000 }) {
const payload = {
goodsId,
goodsNum,
vehicleNo,
userName,
};
const res = await httpPost(buildUrl('/bindingVehicl'), payload, { timeout });
return unwrapResponse(res, '绑定物料失败');
},
async getOrderInWithVehicleNo({ vehicleNo, timeout = 8000 }) {
const res = await httpGet(buildUrl('/getOrderInWithVehicleNo'), { vehicleNo }, { timeout });
return unwrapResponse(res, '获取物料列表失败');
},
async rawStockIn({ vehicleNo, standId, timeout = 10000 }) {
const payload = {
vehicleNo,
selectByIn: standId,
};
const res = await httpPost(buildUrl('/addInByTask'), payload, { timeout });
return unwrapResponse(res, '原材料入库失败');
},
async deleteOrderIn({ rowId, timeout = 8000 }) {
const res = await httpDelete(`${buildUrl('/deleteOrderIn')}/${rowId}`, { timeout });
return unwrapResponse(res, '删除物料失败');
},
async testLink({ vehicleNo = 'LSP00168', timeout = 8000 } = {}) {
const res = await httpGet(buildUrl('/getOrderInWithVehicleNo'), { vehicleNo }, { timeout });
return unwrapResponse(res, '接口不通');
},
};

42
index.html Normal file

File diff suppressed because one or more lines are too long

11
jest.config.js Normal file
View File

@ -0,0 +1,11 @@
module.exports = {
testTimeout: 20000,
reporters: [
'default'
],
watchPathIgnorePatterns: ['/node_modules/', '/dist/', '/.git/'],
moduleFileExtensions: ['js', 'json'],
rootDir: __dirname,
testMatch: ["<rootDir>/pages/**/*test.[jt]s?(x)"],
testPathIgnorePatterns: ['/node_modules/']
}

7
main.js Normal file
View File

@ -0,0 +1,7 @@
import App from './App'
import { createSSRApp } from 'vue'
export function createApp() {
const app = createSSRApp(App)
return { app }
}

192
manifest.json Normal file
View File

@ -0,0 +1,192 @@
{
"name" : "pda-perkins",
"appid" : "__UNI__D3D7919",
"description" : "应用描述",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
"app-plus" : {
"usingComponents" : true,
"nvueCompiler" : "uni-app",
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"nvueLaunchMode" : "fast",
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
"modules" : {
"OAuth" : {},
"Payment" : {},
"Push" : {},
"Share" : {},
"Speech" : {},
"VideoPlayer" : {}
},
"distribute" : {
"android" : {
"networkSecurityConfig": "AndroidManifest/network_security_config.xml",
"permissions" : [
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_MOCK_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<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.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.GET_TASKS\"/>",
"<uses-permission android:name=\"android.permission.INTERNET\"/>",
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.READ_SMS\"/>",
"<uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\"/>",
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
"<uses-permission android:name=\"android.permission.SEND_SMS\"/>",
"<uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SMS\"/>",
"<uses-permission android:name=\"android.permission.RECEIVE_USER_PRESENT\"/>"
]
},
"ios" : {
"UIBackgroundModes" : [ "audio" ],
"urlschemewhitelist" : [ "baidumap", "iosamap" ]
},
"sdkConfigs" : {
"speech" : {
"ifly" : {}
}
},
"orientation" : [ "portrait-primary" ]
},
"uniStatistics" : {
"enable" : true
}
},
"quickapp" : {},
"quickapp-native" : {
"icon" : "/static/logo.png",
"package" : "com.example.demo",
"features" : [
{
"name" : "system.clipboard"
}
]
},
"quickapp-webview" : {
"icon" : "/static/logo.png",
"package" : "com.example.demo",
"minPlatformVersion" : 1070,
"versionName" : "1.0.0",
"versionCode" : 100
},
"mp-weixin" : {
"appid" : "",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true,
"permission" : {
"scope.userLocation" : {
"desc" : "演示定位能力"
}
},
"uniStatistics" : {
"enable" : true
}
},
"mp-alipay" : {
"usingComponents" : true,
"uniStatistics" : {
"enable" : true
}
},
"mp-baidu" : {
"usingComponents" : true,
"uniStatistics" : {
"enable" : true
},
"dynamicLib" : {
"editorLib" : {
"provider" : "swan-editor"
}
}
},
"mp-toutiao" : {
"usingComponents" : true,
"uniStatistics" : {
"enable" : true
}
},
"mp-jd" : {
"usingComponents" : true,
"uniStatistics" : {
"enable" : true
}
},
"h5" : {
"template" : "template.h5.html",
"router" : {
"mode" : "history",
"base" : ""
},
"sdkConfigs" : {
"maps" : {
"qqmap" : {
"key" : "TKUBZ-D24AF-GJ4JY-JDVM2-IBYKK-KEBCU"
}
}
},
"async" : {
"timeout" : 20000
},
"uniStatistics" : {
"enable" : true
}
},
"vueVersion" : "3",
"mp-kuaishou" : {
"uniStatistics" : {
"enable" : true
}
},
"mp-lark" : {
"uniStatistics" : {
"enable" : true
}
},
"mp-qq" : {
"uniStatistics" : {
"enable" : true
}
},
"quickapp-webview-huawei" : {
"uniStatistics" : {
"enable" : true
}
},
"quickapp-webview-union" : {
"uniStatistics" : {
"enable" : true
}
},
"uniStatistics" : {
"version" : "2",
"enable" : true
}
}

133
package.json Normal file
View File

@ -0,0 +1,133 @@
{
"id": "hello-uniapp",
"name": "hello-uniapp",
"displayName": "hello-uniapp 示例工程",
"version": "3.4.9",
"description": "uni-app 框架示例一套代码同时发行到iOS、Android、H5、小程序等多个平台请使用手机扫码快速体验 uni-app 的强大功能",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": "https://github.com/dcloudio/hello-uniapp.git",
"keywords": [
"hello-uniapp",
"uni-app",
"uni-ui",
"示例工程"
],
"author": "",
"license": "MIT",
"bugs": {
"url": "https://github.com/dcloudio/hello-uniapp/issues"
},
"homepage": "https://github.com/dcloudio/hello-uniapp#readme",
"dependencies": {},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"type": "uniapp-template-project",
"darkmode": "x",
"i18n": "x",
"widescreen": "x"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "√",
"aliyun": "√",
"alipay": "x"
},
"client": {
"uni-app": {
"vue": {
"vue2": "√",
"vue3": "√"
},
"web": {
"safari": "√",
"chrome": "√"
},
"app": {
"vue": "√",
"nvue": "√",
"android": "√",
"ios": "√",
"harmony": "√"
},
"mp": {
"weixin": "√",
"alipay": "√",
"toutiao": "√",
"baidu": "√",
"kuaishou": "√",
"jd": "√",
"harmony": "√",
"qq": "√",
"lark": "√"
},
"quickapp": {
"huawei": "-",
"union": "-"
}
},
"uni-app-x": {
"web": {
"safari": "-",
"chrome": "-"
},
"app": {
"android": "-",
"ios": "-",
"harmony": "-"
},
"mp": {
"weixin": "-"
}
}
}
}
},
"uni-app": {
"scripts": {
"mp-dingtalk": {
"title": "钉钉小程序",
"env": {
"UNI_PLATFORM": "mp-alipay"
},
"define": {
"MP-DINGTALK": true
}
},
"hello-uniapp-demo": {
"title": "hello-uniapp 演示网站",
"env": {
"UNI_PLATFORM": "h5"
},
"define": {
"H5-DEMO": true
}
}
}
},
"engines": {
"HBuilderX": "^3.1.0",
"uni-app": "^4.03",
"uni-app-x": ""
}
}

107
pages.json Normal file
View File

@ -0,0 +1,107 @@
{
"pages": [
{
"path": "pages/login/index",
"style": {
"navigationBarTitleText": "登录",
"app-plus": {
"titleNView": false
},
"h5": {
"titleNView": false
}
}
},
{
"path": "pages/home/index",
"style": {
"navigationBarTitleText": "首页",
"app-plus": {
"titleNView": false
},
"h5": {
"titleNView": false
}
}
},
{
"path": "pages/stock-in/empty",
"style": {
"navigationBarTitleText": "空载具入库",
"app-plus": {
"titleNView": false
},
"h5": {
"titleNView": false
}
}
},
{
"path": "pages/stock-in/engine",
"style": {
"navigationBarTitleText": "发动机入库",
"app-plus": {
"titleNView": false
},
"h5": {
"titleNView": false
}
}
},
{
"path": "pages/stock-in/raw",
"style": {
"navigationBarTitleText": "原材料入库",
"app-plus": {
"titleNView": false
},
"h5": {
"titleNView": false
}
}
},
{
"path": "pages/stock-in/raw-return",
"style": {
"navigationBarTitleText": "通道三原材料回库",
"app-plus": {
"titleNView": false
},
"h5": {
"titleNView": false
}
}
},
{
"path": "pages/stock-in/manual",
"style": {
"navigationBarTitleText": "手动码盘入库",
"app-plus": {
"titleNView": false
},
"h5": {
"titleNView": false
}
}
},
{
"path": "pages/test/index",
"style": {
"navigationBarTitleText": "测试页面",
"app-plus": {
"titleNView": false
},
"h5": {
"titleNView": false
}
}
}
],
"globalStyle": {
"pageOrientation": "portrait",
"navigationBarTitleText": "PDA",
"navigationBarTextStyle": "white",
"navigationBarBackgroundColor": "#05DCEF",
"backgroundColor": "#F5F5F5"
}
}

340
pages/home/index.vue Normal file
View File

@ -0,0 +1,340 @@
<template>
<view class="container">
<view class="header">
<view class="header-bg">
<view class="wave w1" />
<view class="wave w2" />
</view>
<button class="icon-btn" @click="toggleDrawer">
<i class="icon icon-menu" style="font-size:40rpx;color:#fff"></i>
</button>
<text class="header-title">WMS移动终端模板</text>
<button class="icon-btn" @click="noop">
<i class="icon icon-notifications" style="font-size:40rpx;color:#fff"></i>
</button>
</view>
<!-- Drawer Overlay -->
<view class="drawer-mask" :class="{ show: showDrawer }" @click="closeDrawer" />
<view class="drawer" :class="{ open: showDrawer }">
<view class="drawer-top">
<text class="drawer-title">导航</text>
</view>
<view class="drawer-list">
<button
v-for="item in drawerItems"
:key="item.path"
class="drawer-link"
@click="nav(item.path)"
>
<i class="icon" :class="item.icon"></i>
<text class="drawer-text">{{ item.label }}</text>
</button>
</view>
</view>
<view class="content">
<view class="grid">
<view
v-for="(item, index) in quickLinks"
:key="item.path"
class="card"
:class="{ single: quickLinks.length % 2 === 1 && index === quickLinks.length - 1 }"
@click="go(item.path)"
>
<view class="action-icon">
<i class="icon" :class="item.icon" style="font-size:48rpx;color:#fff"></i>
</view>
<text class="action-title">{{ item.label }}</text>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
chartSize: 260,
showDrawer: false,
quickLinks: [
{ label: '测试页面', path: '/pages/test/index', icon: 'icon-menu' },
{ label: '空载具入库', path: '/pages/stock-in/empty', icon: 'icon-local_shipping' },
{ label: '发动机入库', path: '/pages/stock-in/engine', icon: 'icon-save' },
{ label: '原材料入库', path: '/pages/stock-in/raw', icon: 'icon-inventory' },
{ label: '通道三原材料回库', path: '/pages/stock-in/raw-return', icon: 'icon-task_alt' }
]
}
},
computed: {
drawerItems() {
return this.quickLinks
}
},
methods: {
noop() { },
toggleDrawer() { this.showDrawer = !this.showDrawer },
closeDrawer() { this.showDrawer = false },
nav(url) { this.showDrawer = false; this.go(url) },
go(url) {
uni.navigateTo({
url
})
},
arcPath(startRatio, endRatio) {
const start = this.polar(50, 50, 40, startRatio * 2 * Math.PI)
const end = this.polar(50, 50, 40, endRatio * 2 * Math.PI)
const large = endRatio - startRatio > 0.5 ? 1 : 0
return `M 50 50 L ${start.x} ${start.y} A 40 40 0 ${large} 1 ${end.x} ${end.y} Z`
},
polar(cx, cy, r, ang) {
return {
x: cx + r * Math.cos(ang),
y: cy + r * Math.sin(ang)
}
}
}
}
</script>
<style scoped>
.container {
min-height: 100vh;
background: var(--bg-gray);
}
.header {
height: 200rpx;
position: relative;
padding: 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.header-bg {
position: absolute;
inset: 0;
background: linear-gradient(180deg, var(--grad-primary-start), var(--grad-primary-mid));
}
.wave {
position: absolute;
left: 0;
right: 0;
height: 120rpx;
background: radial-gradient(ellipse at 50% 30%, rgba(255, 255, 255, .25), rgba(255, 255, 255, 0) 60%);
opacity: .6;
}
.w1 {
top: 10rpx
}
.w2 {
top: 80rpx
}
.icon-btn {
z-index: 1;
width: 64rpx;
height: 64rpx;
border-radius: 32rpx;
background: transparent;
border: none;
display: flex;
align-items: center;
justify-content: center;
}
.header-title {
z-index: 1;
color: #fff;
font-size: 34rpx;
font-weight: 600;
}
.content {
padding: 24rpx;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 24rpx;
}
.card {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 180rpx;
border-radius: 20rpx;
background: linear-gradient(90deg, var(--grad-contrast-start), var(--grad-contrast-end));
box-shadow: 0 8rpx 22rpx rgba(5, 220, 239, .25);
}
.card.single {
grid-column: span 2;
height: 180rpx;
}
.action-icon {
width: 96rpx;
height: 96rpx;
border-radius: 48rpx;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, .25);
margin-bottom: 12rpx;
}
.action-icon .icon {
color: #fff;
font-size: 48rpx
}
.action-title {
color: #fff;
font-size: 28rpx;
}
.section {
margin-top: 24rpx;
border-radius: 16rpx;
padding: 24rpx;
background: #fff;
}
.section-title {
color: var(--text-color);
font-size: 32rpx;
font-weight: 600;
margin-bottom: 16rpx;
}
.chart-row {
display: flex;
align-items: center;
}
.legend {
margin-left: 24rpx;
}
.legend-item {
display: flex;
align-items: center;
margin-bottom: 24rpx;
}
.dot {
width: 20rpx;
height: 20rpx;
border-radius: 10rpx;
margin-right: 12rpx;
}
.legend-text {
color: var(--text-light);
font-size: 28rpx;
}
.pie {
width: 280rpx;
height: 280rpx;
border-radius: 50%;
background: conic-gradient(#7FFFAA 0 60%, #00FFFF 60% 100%);
box-shadow: inset 0 0 0 0 #fff;
}
.drawer-mask {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, .28);
opacity: 0;
pointer-events: none;
transition: opacity .22s ease;
z-index: 998;
}
.drawer-mask.show {
opacity: 1;
pointer-events: auto;
}
.drawer {
position: fixed;
left: 0;
top: 0;
bottom: 0;
width: 460rpx;
background: linear-gradient(180deg, #05DCEF 0%, #7DE2F5 60%, #FFFFFF 100%);
box-shadow: 16rpx 0 32rpx rgba(0, 0, 0, .15);
transform: translateX(-104%);
transition: transform .26s cubic-bezier(.2, .8, .2, 1);
z-index: 999;
border-top-right-radius: 24rpx;
border-bottom-right-radius: 24rpx;
overflow: hidden;
display: flex;
flex-direction: column;
}
.drawer.open {
transform: translateX(0);
}
.drawer-top {
padding: calc(env(safe-area-inset-top, 0) + 24rpx) 16rpx 8rpx 24rpx;
}
.drawer-title {
color: #fff;
font-size: 44rpx;
font-weight: 800;
letter-spacing: 1rpx;
}
.drawer-list {
flex: 1;
overflow: auto;
padding: 8rpx 0 24rpx 0;
display: flex;
flex-direction: column;
}
.drawer-link {
width: 100%;
height: 110rpx;
border: none;
border-radius: 0;
background: transparent;
display: flex;
align-items: center;
justify-content: flex-start;
padding: 0 24rpx;
}
.drawer-link:active {
background: rgba(255, 255, 255, .12);
}
.drawer-link .icon {
color: #ffffff;
font-size: 38rpx;
margin-right: 16rpx;
}
.drawer-text {
color: #fff;
font-size: 34rpx;
font-weight: 600;
}
</style>

159
pages/login/index.vue Normal file
View File

@ -0,0 +1,159 @@
<template>
<view class="container">
<view class="hero">
<view class="hero-bg">
<view class="wave w1" />
<view class="wave w2" />
</view>
<text class="title">仓库模板</text>
<text class="subtitle">智能仓储管理系统</text>
<view class="logo-wrap">
<view class="logo"><text>BK</text></view>
</view>
<button class="login-btn" @click="goHome">进入系统</button>
</view>
<view class="footer"><text>© 2025 菲达宝开</text></view>
</view>
</template>
<script>
export default {
methods: {
goHome() {
uni.reLaunch({
url: '/pages/home/index'
})
}
}
}
</script>
<style scoped>
.container {
min-height: 100vh;
background: linear-gradient(180deg, #9BEAF8 0%, #E6FBFF 60%, #FFFFFF 100%);
display: flex;
flex-direction: column;
}
.hero {
flex: 1;
align-items: center;
display: flex;
flex-direction: column;
position: relative;
}
.hero-bg {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 68vh;
background: linear-gradient(180deg, #05DCEF 0%, #7DE2F5 60%, #FFFFFF 100%);
z-index: 0;
}
.hero-bg::after {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 0;
background: linear-gradient(180deg, rgba(255, 255, 255, 0) 65%, rgba(255, 255, 255, .18) 80%, rgba(255, 255, 255, .32) 92%, rgba(255, 255, 255, .45) 100%);
pointer-events: none;
}
.wave {
position: absolute;
left: 0;
right: 0;
height: 140rpx;
background: radial-gradient(ellipse at 50% 0%, rgba(255, 255, 255, .35), rgba(255, 255, 255, 0) 60%);
opacity: .5;
}
.w1 {
bottom: 200rpx;
}
.w2 {
bottom: 120rpx;
}
.title {
margin-top: 160rpx;
color: #fff;
font-size: 64rpx;
font-weight: 700;
position: relative;
z-index: 1;
}
.subtitle {
color: #fff;
font-size: 30rpx;
opacity: .95;
margin-top: 12rpx;
position: relative;
z-index: 1;
}
.logo {
width: 180rpx;
height: 180rpx;
background: #fff;
border-radius: 90rpx;
align-items: center;
justify-content: center;
display: flex;
box-shadow: 0 12rpx 28rpx rgba(0, 0, 0, .12);
}
.logo text {
color: var(--grad-primary-start);
font-size: 56rpx;
font-weight: 700;
}
.login-btn {
position: absolute;
top: 70vh;
transform: translateY(-10%);
width: 600rpx;
height: 96rpx;
line-height: 96rpx;
text-align: center;
color: #fff;
border: none;
border-radius: 48rpx;
background: linear-gradient(90deg, var(--grad-primary-start), var(--grad-primary-mid));
box-shadow: 0 10rpx 24rpx rgba(5, 220, 239, .35);
font-size: 32rpx;
z-index: 1;
}
.footer {
padding: 40rpx 0 60rpx;
align-items: center;
display: flex;
justify-content: center;
}
.footer text {
color: #8a8a8a;
font-size: 24rpx;
}
.logo-wrap {
position: absolute;
top: 56vh;
transform: translateY(-50%);
z-index: 2;
}
</style>

240
pages/stock-in/empty.vue Normal file
View File

@ -0,0 +1,240 @@
<template>
<view class="container">
<view class="header">
<button class="back" @click="back"><i class="icon icon-arrow_back" style="color:#05DCEF;font-size:36rpx"></i></button>
<text class="title">空载具入库</text>
</view>
<view class="content">
<view class="tip">
<view class="tip-icon"><i class="icon icon-info" style="color:#05DCEF"></i></view>
<text class="tip-text">请扫描或输入入库口完成空载具入库操作</text>
</view>
<view class="field">
<text class="label">入库口 <text class="required">*</text></text>
<view class="input-wrap">
<i class="left-icon icon icon-qr_code_2" style="font-size:36rpx"></i>
<input class="input" placeholder="请扫描或输入入库口" v-model="standId" />
</view>
</view>
<view class="static-field">
<text class="static-label">母托号 <text class="required">*</text></text>
<text class="static-value">{{ defaultVehicle }}</text>
</view>
<button class="submit" :disabled="loading" @click="submit">
<i class="icon icon-save" style="margin-right:12rpx"></i> 空载具入库
</button>
</view>
</view>
</template>
<script>
import { WmsApiClient } from '@/common/wmsApi.js';
import { DialogUtils } from '@/utils/dialog.js';
export default {
data() {
return {
standId: '',
defaultVehicle: 'D99999999',
loading: false,
};
},
methods: {
back() {
uni.navigateBack();
},
resolveError(error, fallback = '请求失败') {
if (!error) return fallback;
if (typeof error === 'string') return error;
if (typeof error.message === 'string') return error.message;
if (typeof error.errMsg === 'string') return error.errMsg;
if (typeof error.code === 'number') return `${fallback} (${error.code})`;
return fallback;
},
async submit() {
const stand = (this.standId || '').trim();
if (!stand) {
DialogUtils.showWarningMessage('提示', '请先输入入库口');
return;
}
if (this.loading) return;
this.loading = true;
uni.showLoading({ title: '正在请求', mask: true });
try {
const response = await WmsApiClient.emptyVehicleIn({ standId: stand });
const code = Number(response.code);
if (code === 0) {
DialogUtils.showSuccessMessage('成功', '空载具入库成功');
this.standId = '';
} else {
DialogUtils.showWarningMessage('操作未成功', `${response.message || '提交失败'} (${response.code})`);
}
} catch (error) {
DialogUtils.showErrorMessage('错误', this.resolveError(error));
} finally {
this.loading = false;
uni.hideLoading();
}
},
},
};
</script>
<style scoped>
.header {
position: relative;
height: 120rpx;
padding: 0;
background: linear-gradient(90deg, var(--grad-primary-start), var(--grad-primary-mid));
display: flex;
align-items: center;
justify-content: center;
}
.container {
min-height: 100vh;
background: #F5F5F5;
}
.title {
color: #fff;
font-size: 32rpx;
font-weight: 600;
}
.content {
padding: 24rpx;
}
.tip {
flex-direction: row;
display: flex;
align-items: center;
background: #f2fdff;
border-radius: 12rpx;
padding: 24rpx;
color: #333;
margin-bottom: 28rpx;
}
.tip-icon {
width: 40rpx;
height: 40rpx;
border-radius: 20rpx;
background: #e6f7ff;
text-align: center;
line-height: 40rpx;
margin-right: 12rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
}
.tip-text {
color: #333;
font-size: 26rpx;
}
.field {
margin-bottom: 20rpx;
}
.label {
color: #333;
font-size: 30rpx;
margin-bottom: 12rpx;
display: block;
}
.required {
color: #ff4d4f;
}
.input-wrap {
height: 96rpx;
border-radius: 12rpx;
border: 1px solid #e6e6e6;
background: #fff;
padding: 0 20rpx;
display: flex;
align-items: center;
}
.left-icon {
color: #05DCEF;
font-size: 36rpx;
margin-right: 12rpx;
}
.input {
flex: 1;
height: 100%;
font-size: 30rpx;
}
.static-field {
background: #fff;
border-radius: 12rpx;
border: 1px solid #e6e6e6;
padding: 24rpx 20rpx;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 32rpx;
}
.static-label {
color: #333;
font-size: 30rpx;
display: flex;
align-items: center;
gap: 6rpx;
}
.static-value {
font-size: 32rpx;
color: #111;
font-weight: 600;
}
.submit {
width: 100%;
height: 92rpx;
border: none;
color: #fff;
border-radius: 46rpx;
background: linear-gradient(90deg, var(--grad-primary-start), var(--grad-primary-mid));
font-size: 32rpx;
box-shadow: 0 8rpx 22rpx rgba(5, 220, 239, .25);
text-align: center;
margin-top: 28rpx;
display: flex;
align-items: center;
justify-content: center;
}
.submit[disabled] {
opacity: .6;
}
.back {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 80rpx;
height: 64rpx;
line-height: 64rpx;
border: none;
color: #05DCEF;
background: #fff;
border-radius: 12rpx;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
</style>

265
pages/stock-in/engine.vue Normal file
View File

@ -0,0 +1,265 @@
<template>
<view class="container">
<view class="header">
<button class="back" @click="back"><i class="icon icon-arrow_back" style="color:#05DCEF;font-size:36rpx"></i></button>
<text class="title">发动机入库</text>
</view>
<view class="content">
<view class="tip">
<view class="tip-icon"><i class="icon icon-info" style="color:#05DCEF"></i></view>
<text class="tip-text">请依次扫描入库口母托号与发动机信息后提交</text>
</view>
<view class="field">
<text class="label">入库口 <text class="required">*</text></text>
<view class="input-wrap">
<i class="left-icon icon icon-qr_code_2" style="font-size:36rpx"></i>
<input class="input" placeholder="请扫描或输入入库口" v-model="standId" />
</view>
</view>
<view class="field">
<text class="label">母托号 <text class="required">*</text></text>
<view class="input-wrap">
<i class="left-icon icon icon-qr_code_2" style="font-size:36rpx"></i>
<input class="input" placeholder="请扫描或输入母托号" v-model="vehicleNo" />
</view>
</view>
<view class="field">
<text class="label">发动机序列号 <text class="required">*</text></text>
<view class="input-wrap">
<i class="left-icon icon icon-qr_code_2" style="font-size:36rpx"></i>
<input class="input" placeholder="请扫描或输入发动机序列号" v-model="goodsId" />
</view>
</view>
<view class="field">
<text class="label">发动机机型 <text class="required">*</text></text>
<view class="input-wrap">
<i class="left-icon icon icon-qr_code_2" style="font-size:36rpx"></i>
<input class="input" placeholder="请扫描或输入发动机机型" v-model="model" />
</view>
</view>
<button class="submit" :disabled="loading" @click="submit">
<i class="icon icon-save" style="margin-right:12rpx"></i> 确认入库
</button>
</view>
</view>
</template>
<script>
import { WmsApiClient } from '@/common/wmsApi.js';
import { DialogUtils } from '@/utils/dialog.js';
export default {
data() {
return {
standId: '',
vehicleNo: '',
goodsId: '',
model: '',
loading: false,
};
},
methods: {
back() {
uni.navigateBack();
},
resolveError(error, fallback = '请求失败') {
if (!error) return fallback;
if (typeof error === 'string') return error;
if (typeof error.message === 'string') return error.message;
if (typeof error.errMsg === 'string') return error.errMsg;
if (typeof error.code === 'number') return `${fallback} (${error.code})`;
return fallback;
},
resetForm() {
this.standId = '';
this.vehicleNo = '';
this.goodsId = '';
this.model = '';
},
async submit() {
const stand = (this.standId || '').trim();
const vehicle = (this.vehicleNo || '').trim();
const goods = (this.goodsId || '').trim();
const model = (this.model || '').trim();
if (!stand) {
DialogUtils.showWarningMessage('提示', '请先输入入库口');
return;
}
if (!vehicle) {
DialogUtils.showWarningMessage('提示', '请输入母托号');
return;
}
if (!goods) {
DialogUtils.showWarningMessage('提示', '请输入发动机序列号');
return;
}
if (!model) {
DialogUtils.showWarningMessage('提示', '请输入发动机机型');
return;
}
if (this.loading) return;
this.loading = true;
uni.showLoading({ title: '正在请求', mask: true });
try {
const response = await WmsApiClient.engineStockIn({
standId: stand,
vehicleNo: vehicle,
goodsId: goods,
model,
});
const code = Number(response.code);
if (code === 0) {
DialogUtils.showSuccessMessage('成功', '入库成功');
this.resetForm();
} else {
DialogUtils.showWarningMessage('操作未成功', `${response.message || '提交失败'} (${response.code})`);
}
} catch (error) {
DialogUtils.showErrorMessage('错误', this.resolveError(error));
} finally {
this.loading = false;
uni.hideLoading();
}
},
},
};
</script>
<style scoped>
.header {
position: relative;
height: 120rpx;
padding: 0;
background: linear-gradient(90deg, var(--grad-primary-start), var(--grad-primary-mid));
display: flex;
align-items: center;
justify-content: center;
}
.container {
min-height: 100vh;
background: #F5F5F5;
}
.title {
color: #fff;
font-size: 32rpx;
font-weight: 600;
}
.content {
padding: 24rpx;
}
.tip {
flex-direction: row;
display: flex;
align-items: center;
background: #f2fdff;
border-radius: 12rpx;
padding: 24rpx;
color: #333;
margin-bottom: 28rpx;
}
.tip-icon {
width: 40rpx;
height: 40rpx;
border-radius: 20rpx;
background: #e6f7ff;
text-align: center;
line-height: 40rpx;
margin-right: 12rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
}
.tip-text {
color: #333;
font-size: 26rpx;
}
.field {
margin-bottom: 20rpx;
}
.label {
color: #333;
font-size: 30rpx;
margin-bottom: 12rpx;
display: block;
}
.required {
color: #ff4d4f;
}
.input-wrap {
height: 96rpx;
border-radius: 12rpx;
border: 1px solid #e6e6e6;
background: #fff;
padding: 0 20rpx;
display: flex;
align-items: center;
}
.left-icon {
color: #05DCEF;
font-size: 36rpx;
margin-right: 12rpx;
}
.input {
flex: 1;
height: 100%;
font-size: 30rpx;
}
.submit {
width: 100%;
height: 92rpx;
border: none;
color: #fff;
border-radius: 46rpx;
background: linear-gradient(90deg, var(--grad-primary-start), var(--grad-primary-mid));
font-size: 32rpx;
box-shadow: 0 8rpx 22rpx rgba(5, 220, 239, .25);
text-align: center;
margin-top: 28rpx;
display: flex;
align-items: center;
justify-content: center;
}
.submit[disabled] {
opacity: .6;
}
.back {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 80rpx;
height: 64rpx;
line-height: 64rpx;
border: none;
color: #05DCEF;
background: #fff;
border-radius: 12rpx;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
</style>

392
pages/stock-in/manual.vue Normal file
View File

@ -0,0 +1,392 @@
<template>
<view class="container">
<view class="header">
<button class="back" @click="back"><i class="icon icon-arrow_back"
style="color:#05DCEF;font-size:36rpx"></i></button>
<text class="title">手动码盘入库</text>
</view>
<view class="content">
<!-- 顶部提示 -->
<view class="tip">
<view class="tip-icon"><i class="icon icon-info" style="color:#05DCEF"></i></view>
<text class="tip-text">请先扫描载具号然后添加物料信息进行码盘入库</text>
</view>
<!-- 载具条码 -->
<view class="field">
<text class="label">载具号</text>
<view class="input-wrap">
<i class="left-icon icon icon-qr_code_2" style="font-size:36rpx"></i>
<input class="input" placeholder="请扫描或输入载具号" v-model="vehicleCode" @blur="resolveVehicle" />
</view>
</view>
<!-- 物料条码 -->
<view class="field">
<text class="label">物料条码</text>
<view class="input-wrap">
<i class="left-icon icon icon-qr_code_2" style="font-size:36rpx"></i>
<input class="input" placeholder="请扫描物料二维码" v-model="goodsCode" @blur="resolveCode" />
</view>
</view>
<!-- 操作按钮组 -->
<view class="button-row">
<button class="btn" @click="addGoods"><i class="icon icon-add"
style="margin-right:8rpx"></i>添加物料</button>
<button class="btn" @click="finish"><i class="icon icon-task_alt"
style="margin-right:8rpx"></i>码盘完成</button>
</view>
<!-- 列表统计 -->
<view class="list-header"><text>已添加物料 ({{ packageData.length }})</text></view>
<!-- 物料卡片列表尽量还原 RN 的布局与字段 -->
<view class="list">
<view v-for="item in packageData" :key="item.id" class="card">
<view class="card-header">
<view class="card-header-left">
<text class="card-header-text">批次{{ item.batch }}</text>
<text class="card-header-text">物料{{ item.segment1 }}</text>
</view>
<view class="card-actions">
<button class="card-action-button" @click="editItem(item)"><i
class="icon icon-edit"></i></button>
<button class="card-action-button" @click="deleteItem(item.id)"><i
class="icon icon-delete"></i></button>
</view>
</view>
<view class="card-content">
<view class="card-row">
<view class="card-field">
<text class="card-label">物料编码</text>
<text class="card-value">{{ item.segment1 }}</text>
</view>
<view class="card-field">
<text class="card-label">物料ID</text>
<text class="card-value">{{ item.itemId }}</text>
</view>
</view>
<view class="card-row">
<view class="card-field">
<text class="card-label">数量</text>
<view class="editable-value">
<input class="num-input" type="number" v-model="item.quantity" />
</view>
</view>
<view class="card-field">
<text class="card-label">重量(kg)</text>
<view class="editable-value">
<input class="num-input" type="number" v-model="item.weight" />
</view>
</view>
</view>
<view class="card-row">
<view class="card-field">
<text class="card-label">产品数据</text>
<text class="card-value">{{ item.productData }}</text>
</view>
<view class="card-field">
<text class="card-label">区域/库位</text>
<text class="card-value">{{ item.area || '-' }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import { DialogUtils } from '@/utils/dialog.js';
let nextId = 1
export default {
data() {
return {
vehicleCode: '',
goodsCode: '',
packageData: []
}
},
methods: {
back() {
uni.navigateBack()
},
resolveVehicle() {
const code = (this.vehicleCode || '').trim()
if (!code) return
if (code.length === 15) {
DialogUtils.showSuccessMessage('扫描成功', `车辆码: ${code}`)
} else {
DialogUtils.showErrorMessage('校验失败', '无效的车辆码长度')
}
},
resolveCode() {
const code = (this.goodsCode || '').trim()
if (!code) return
const parts = code.split(',')
if (![6, 7, 8].includes(parts.length)) {
DialogUtils.showWarningMessage('提示', '物料条码格式不正确')
return
}
const p = parts.slice(0, 6)
const item = {
id: String(nextId++),
segment1: p[0],
itemId: p[1],
batch: p[2],
quantity: p[3],
weight: p[4],
productData: p[5],
area: ''
}
if (this.packageData.some(x => x.batch === item.batch)) {
DialogUtils.showWarningMessage('重复', '该物料已在列表中')
} else {
this.packageData.push(item)
}
this.goodsCode = ''
},
addGoods() {
this.resolveCode()
},
deleteItem(id) {
this.packageData = this.packageData.filter(x => x.id !== id)
},
editItem(item) {
DialogUtils.toast('可编辑数量与重量')
},
finish() {
DialogUtils.toast('码盘完成')
}
}
}
</script>
<style scoped>
.header {
position: relative;
height: 120rpx;
padding: 0;
background: linear-gradient(90deg, var(--grad-primary-start), var(--grad-primary-mid));
display: flex;
align-items: center;
justify-content: center;
}
.container {
min-height: 100vh;
background: #F5F5F5;
}
.title {
color: #fff;
font-size: 32rpx;
font-weight: 600;
}
.content {
padding: 24rpx;
}
.tip {
flex-direction: row;
display: flex;
align-items: center;
background: #f2fdff;
border-radius: 12rpx;
padding: 24rpx;
color: #333;
margin-bottom: 28rpx;
}
.tip-icon {
width: 40rpx;
height: 40rpx;
border-radius: 20rpx;
background: #e6f7ff;
text-align: center;
line-height: 40rpx;
margin-right: 12rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
}
.tip-text {
color: #333;
font-size: 26rpx;
}
.field {
margin-bottom: 20rpx;
}
.label {
color: #333;
font-size: 30rpx;
margin-bottom: 12rpx;
display: block;
}
.input-wrap {
height: 96rpx;
border-radius: 12rpx;
border: 1px solid #e6e6e6;
background: #fff;
padding: 0 20rpx;
display: flex;
align-items: center;
}
.left-icon {
color: #05DCEF;
font-size: 36rpx;
margin-right: 12rpx;
}
.input {
flex: 1;
height: 100%;
font-size: 30rpx;
}
.button-row {
display: flex;
gap: 24rpx;
margin: 24rpx 0;
}
.btn {
flex: 1;
height: 92rpx;
border: none;
color: #fff;
border-radius: 46rpx;
background: linear-gradient(90deg, var(--grad-primary-start), var(--grad-primary-mid));
font-size: 30rpx;
box-shadow: 0 8rpx 22rpx rgba(5, 220, 239, .25);
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
.list-header {
margin-top: 8rpx;
color: #333;
font-size: 28rpx;
}
.card {
background: #fff;
border-radius: 12rpx;
padding: 0;
margin-top: 16rpx;
overflow: hidden;
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, .06)
}
.card-header {
flex-direction: row;
display: flex;
align-items: center;
justify-content: space-between;
padding: 16rpx;
border-bottom: 1px solid rgba(0, 0, 0, .08);
background: linear-gradient(90deg, var(--grad-primary-start), var(--grad-primary-mid));
}
.card-header-left {
flex: 1
}
.card-header-text {
color: #fff;
font-size: 24rpx;
margin-right: 16rpx
}
.card-actions {
display: flex;
gap: 8rpx
}
.card-action-button {
width: 64rpx;
height: 64rpx;
border: none;
border-radius: 8rpx;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
}
.card-content {
padding: 16rpx;
background: #fff
}
.card-row {
display: flex;
gap: 16rpx;
margin-bottom: 12rpx
}
.card-field {
flex: 1
}
.card-label {
color: #888;
font-size: 24rpx;
margin-bottom: 4rpx;
display: block
}
.card-value {
color: #333;
font-size: 28rpx
}
.editable-value {
display: flex;
align-items: center;
gap: 8rpx
}
.num-input {
width: 220rpx;
height: 64rpx;
border: 1px solid #e6e6e6;
border-radius: 8rpx;
padding: 0 12rpx;
font-size: 28rpx
}
.back {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 80rpx;
height: 64rpx;
line-height: 64rpx;
border: none;
color: #05DCEF;
background: #fff;
border-radius: 12rpx;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
</style>

View File

@ -0,0 +1,241 @@
<template>
<view class="container">
<view class="header">
<button class="back" @click="back"><i class="icon icon-arrow_back" style="color:#05DCEF;font-size:36rpx"></i></button>
<text class="title">通道三原材料回库</text>
</view>
<view class="content">
<view class="tip">
<view class="tip-icon"><i class="icon icon-info" style="color:#05DCEF"></i></view>
<text class="tip-text">请输入母托号系统会自动回库至固定入库口</text>
</view>
<view class="field">
<text class="label">母托号 <text class="required">*</text></text>
<view class="input-wrap">
<i class="left-icon icon icon-qr_code_2" style="font-size:36rpx"></i>
<input class="input" placeholder="请扫描或输入母托号" v-model="vehicleNo" />
</view>
</view>
<view class="static-field">
<text class="static-label">回库口</text>
<text class="static-value">{{ fixedStand }}</text>
</view>
<button class="submit" :disabled="loading" @click="submit">
<i class="icon icon-task_alt" style="margin-right:12rpx"></i> 确认回库
</button>
</view>
</view>
</template>
<script>
import { WmsApiClient } from '@/common/wmsApi.js';
import { DialogUtils } from '@/utils/dialog.js';
export default {
data() {
return {
vehicleNo: '',
fixedStand: '104',
loading: false,
};
},
methods: {
back() {
uni.navigateBack();
},
resolveError(error, fallback = '请求失败') {
if (!error) return fallback;
if (typeof error === 'string') return error;
if (typeof error.message === 'string') return error.message;
if (typeof error.errMsg === 'string') return error.errMsg;
if (typeof error.code === 'number') return `${fallback} (${error.code})`;
return fallback;
},
async submit() {
const vehicle = (this.vehicleNo || '').trim();
if (!vehicle) {
DialogUtils.showWarningMessage('提示', '请输入母托号');
return;
}
if (this.loading) return;
this.loading = true;
uni.showLoading({ title: '正在请求', mask: true });
try {
const response = await WmsApiClient.rawStockIn({
vehicleNo: vehicle,
standId: this.fixedStand,
});
const code = Number(response.code);
if (code === 0) {
DialogUtils.showSuccessMessage('成功', '回库成功');
this.vehicleNo = '';
} else {
DialogUtils.showWarningMessage('操作未成功', `${response.message || '提交失败'} (${response.code})`);
}
} catch (error) {
DialogUtils.showErrorMessage('错误', this.resolveError(error));
} finally {
uni.hideLoading();
this.loading = false;
}
},
},
};
</script>
<style scoped>
.header {
position: relative;
height: 120rpx;
padding: 0;
background: linear-gradient(90deg, var(--grad-primary-start), var(--grad-primary-mid));
display: flex;
align-items: center;
justify-content: center;
}
.container {
min-height: 100vh;
background: #F5F5F5;
}
.title {
color: #fff;
font-size: 32rpx;
font-weight: 600;
}
.content {
padding: 24rpx;
}
.tip {
flex-direction: row;
display: flex;
align-items: center;
background: #f2fdff;
border-radius: 12rpx;
padding: 24rpx;
color: #333;
margin-bottom: 28rpx;
}
.tip-icon {
width: 40rpx;
height: 40rpx;
border-radius: 20rpx;
background: #e6f7ff;
text-align: center;
line-height: 40rpx;
margin-right: 12rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
}
.tip-text {
color: #333;
font-size: 26rpx;
}
.field {
margin-bottom: 24rpx;
}
.label {
color: #333;
font-size: 30rpx;
margin-bottom: 12rpx;
display: block;
}
.required {
color: #ff4d4f;
}
.input-wrap {
height: 96rpx;
border-radius: 12rpx;
border: 1px solid #e6e6e6;
background: #fff;
padding: 0 20rpx;
display: flex;
align-items: center;
}
.left-icon {
color: #05DCEF;
font-size: 36rpx;
margin-right: 12rpx;
}
.input {
flex: 1;
height: 100%;
font-size: 30rpx;
}
.static-field {
background: #fff;
border-radius: 12rpx;
border: 1px solid #e6e6e6;
padding: 24rpx 20rpx;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 32rpx;
}
.static-label {
color: #666;
font-size: 28rpx;
}
.static-value {
font-size: 32rpx;
color: #111;
font-weight: 600;
}
.submit {
width: 100%;
height: 92rpx;
border: none;
color: #fff;
border-radius: 46rpx;
background: linear-gradient(90deg, var(--grad-primary-start), var(--grad-primary-mid));
font-size: 32rpx;
box-shadow: 0 8rpx 22rpx rgba(52, 199, 89, .25);
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
.submit[disabled] {
opacity: .6;
}
.back {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 80rpx;
height: 64rpx;
line-height: 64rpx;
border: none;
color: #05DCEF;
background: #fff;
border-radius: 12rpx;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
</style>

511
pages/stock-in/raw.vue Normal file
View File

@ -0,0 +1,511 @@
<template>
<view class="container">
<view class="header">
<button class="back" @click="back"><i class="icon icon-arrow_back" style="color:#05DCEF;font-size:36rpx"></i></button>
<text class="title">原材料入库</text>
</view>
<view class="content">
<view class="tip">
<view class="tip-icon"><i class="icon icon-info" style="color:#05DCEF"></i></view>
<text class="tip-text">请输入母托号并逐个绑定原材料最后确认入库</text>
</view>
<view class="field">
<text class="label">母托号 <text class="required">*</text></text>
<view class="input-wrap">
<i class="left-icon icon icon-qr_code_2" style="font-size:36rpx"></i>
<input class="input" placeholder="请扫描或输入母托号" v-model="vehicleNo" />
</view>
</view>
<view class="field">
<text class="label">原材料号</text>
<view class="input-wrap">
<i class="left-icon icon icon-qr_code_2" style="font-size:36rpx"></i>
<input class="input" placeholder="请扫描或输入原材料号" v-model="goodsId" />
</view>
</view>
<button class="action-button" :disabled="isBusy" @click="handleNextMaterial">
<i class="icon icon-arrow_forward" style="margin-right:10rpx"></i> 下一颗物料
</button>
<view class="field">
<text class="label">入库口 <text class="required">*</text></text>
<view class="input-wrap">
<i class="left-icon icon icon-qr_code_2" style="font-size:36rpx"></i>
<input class="input" placeholder="请扫描或输入入库口" v-model="standId" />
</view>
</view>
<view class="list-section">
<view class="list-header">
<text class="list-title">已绑定的原材料</text>
<text class="list-count">({{ orderInList.length }})</text>
</view>
<view v-if="!orderInList.length" class="empty-placeholder">
<i class="icon icon-info" style="margin-right:8rpx;color:#05DCEF"></i>
<text>暂无数据</text>
</view>
<view v-else class="card-group">
<view v-for="item in orderInList" :key="item.rowId" class="card">
<view class="card-header">
<view class="card-header-text">
<text class="card-title">物料号{{ item.goodsId }}</text>
<text class="card-subtitle">数量{{ formatNumber(item.goodsNum) }}</text>
</view>
<button class="card-action" :disabled="isBusy" @click="deleteItem(item)">
<i class="icon icon-delete" style="color:#ff4d4f;font-size:32rpx"></i>
</button>
</view>
<view class="card-row">
<text class="card-label">批次</text>
<text class="card-value">{{ item.batchNo || '-' }}</text>
</view>
<view class="card-row">
<text class="card-label">生产日期</text>
<text class="card-value">{{ formatDate(item.productionDate) }}</text>
</view>
<view class="card-row">
<text class="card-label">机型</text>
<text class="card-value">{{ item.model || '-' }}</text>
</view>
</view>
</view>
</view>
<button class="submit" :disabled="isBusy" @click="submitRawIn">
<i class="icon icon-task_alt" style="margin-right:10rpx"></i> 确认入库
</button>
</view>
</view>
</template>
<script>
import { WmsApiClient } from '@/common/wmsApi.js';
import { DialogUtils } from '@/utils/dialog.js';
export default {
data() {
return {
vehicleNo: '',
goodsId: '',
standId: '',
orderInList: [],
loadingAction: '',
};
},
computed: {
isBusy() {
return this.loadingAction !== '';
},
},
methods: {
back() {
uni.navigateBack();
},
resolveError(error, fallback = '请求失败') {
if (!error) return fallback;
if (typeof error === 'string') return error;
if (typeof error.message === 'string') return error.message;
if (typeof error.errMsg === 'string') return error.errMsg;
if (typeof error.code === 'number') return `${fallback} (${error.code})`;
return fallback;
},
formatNumber(value) {
const num = Number(value);
if (Number.isNaN(num)) return '0';
return num % 1 === 0 ? String(num) : num.toFixed(2);
},
formatDate(value) {
if (!value) return '-';
const parsed = new Date(value);
if (!Number.isNaN(parsed.getTime())) {
const y = parsed.getFullYear();
const m = String(parsed.getMonth() + 1).padStart(2, '0');
const d = String(parsed.getDate()).padStart(2, '0');
return `${y}-${m}-${d}`;
}
const normalized = value.replace('T', ' ').split(' ')[0];
return normalized || '-';
},
async handleNextMaterial() {
const vehicle = (this.vehicleNo || '').trim();
const goods = (this.goodsId || '').trim();
if (!vehicle) {
DialogUtils.showWarningMessage('提示', '请输入母托号');
return;
}
if (this.isBusy) return;
if (goods) {
this.loadingAction = 'binding';
uni.showLoading({ title: '正在绑定物料', mask: true });
try {
const bindRes = await WmsApiClient.bindVehicle({
vehicleNo: vehicle,
goodsId: goods,
});
const code = Number(bindRes.code);
if (code === 0) {
DialogUtils.showSuccessMessage('成功', '物料绑定成功');
this.goodsId = '';
} else {
DialogUtils.showWarningMessage('绑定未成功', `${bindRes.message || '操作失败'} (${bindRes.code})`);
}
} catch (error) {
DialogUtils.showErrorMessage('错误', this.resolveError(error));
} finally {
uni.hideLoading();
this.loadingAction = '';
}
}
this.loadingAction = 'query';
uni.showLoading({ title: '获取物料列表', mask: true });
try {
await this.loadOrderList(vehicle);
} catch (error) {
DialogUtils.showErrorMessage('错误', this.resolveError(error));
} finally {
uni.hideLoading();
this.loadingAction = '';
}
},
async loadOrderList(vehicle) {
const listRes = await WmsApiClient.getOrderInWithVehicleNo({ vehicleNo: vehicle });
const code = Number(listRes.code);
if (code === 0) {
const data = Array.isArray(listRes.returnData) ? listRes.returnData : [];
this.orderInList = data.map((item) => ({
...item,
goodsNum: item.goodsNum,
}));
} else {
DialogUtils.showWarningMessage('提示', `${listRes.message || '获取物料列表失败'} (${listRes.code})`);
}
},
async deleteItem(item) {
const vehicle = (this.vehicleNo || '').trim();
if (!vehicle) {
DialogUtils.showWarningMessage('提示', '母托号不能为空');
return;
}
if (!item || !item.rowId) return;
if (this.isBusy) return;
this.loadingAction = 'deleting';
uni.showLoading({ title: '正在删除', mask: true });
try {
const res = await WmsApiClient.deleteOrderIn({ rowId: item.rowId });
const code = Number(res.code);
if (code === 0) {
DialogUtils.showSuccessMessage('成功', '物料删除成功');
await this.loadOrderList(vehicle);
} else {
DialogUtils.showWarningMessage('删除未成功', `${res.message || '删除失败'} (${res.code})`);
}
} catch (error) {
DialogUtils.showErrorMessage('错误', this.resolveError(error));
} finally {
uni.hideLoading();
this.loadingAction = '';
}
},
async submitRawIn() {
const vehicle = (this.vehicleNo || '').trim();
const stand = (this.standId || '').trim();
if (!vehicle) {
DialogUtils.showWarningMessage('提示', '请输入母托号');
return;
}
if (!stand) {
DialogUtils.showWarningMessage('提示', '请输入入库口');
return;
}
if (!this.orderInList.length) {
DialogUtils.showWarningMessage('提示', '请先绑定原材料');
return;
}
if (this.isBusy) return;
this.loadingAction = 'submitting';
uni.showLoading({ title: '正在提交', mask: true });
try {
const res = await WmsApiClient.rawStockIn({ vehicleNo: vehicle, standId: stand });
const code = Number(res.code);
if (code === 0) {
DialogUtils.showSuccessMessage('成功', '原材料入库成功');
this.vehicleNo = '';
this.goodsId = '';
this.standId = '';
this.orderInList = [];
} else {
DialogUtils.showWarningMessage('入库未成功', `${res.message || '提交失败'} (${res.code})`);
}
} catch (error) {
DialogUtils.showErrorMessage('错误', this.resolveError(error));
} finally {
uni.hideLoading();
this.loadingAction = '';
}
},
},
};
</script>
<style scoped>
.header {
position: relative;
height: 120rpx;
padding: 0;
background: linear-gradient(90deg, var(--grad-primary-start), var(--grad-primary-mid));
display: flex;
align-items: center;
justify-content: center;
}
.container {
min-height: 100vh;
background: #F5F5F5;
}
.title {
color: #fff;
font-size: 32rpx;
font-weight: 600;
}
.content {
padding: 24rpx;
padding-bottom: 56rpx;
}
.tip {
flex-direction: row;
display: flex;
align-items: center;
background: #f2fdff;
border-radius: 12rpx;
padding: 24rpx;
color: #333;
margin-bottom: 28rpx;
}
.tip-icon {
width: 40rpx;
height: 40rpx;
border-radius: 20rpx;
background: #e6f7ff;
text-align: center;
line-height: 40rpx;
margin-right: 12rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
}
.tip-text {
color: #333;
font-size: 26rpx;
}
.field {
margin-bottom: 24rpx;
}
.label {
color: #333;
font-size: 30rpx;
margin-bottom: 12rpx;
display: block;
}
.required {
color: #ff4d4f;
}
.input-wrap {
height: 96rpx;
border-radius: 12rpx;
border: 1px solid #e6e6e6;
background: #fff;
padding: 0 20rpx;
display: flex;
align-items: center;
}
.left-icon {
color: #05DCEF;
font-size: 36rpx;
margin-right: 12rpx;
}
.input {
flex: 1;
height: 100%;
font-size: 30rpx;
}
.action-button {
width: 100%;
height: 92rpx;
border: none;
color: #fff;
border-radius: 46rpx;
background: linear-gradient(90deg, var(--grad-primary-start), var(--grad-primary-mid));
font-size: 32rpx;
box-shadow: 0 8rpx 22rpx rgba(5, 220, 239, .25);
text-align: center;
margin-bottom: 28rpx;
display: flex;
align-items: center;
justify-content: center;
}
.action-button[disabled] {
opacity: .6;
}
.list-section {
margin-top: 12rpx;
margin-bottom: 32rpx;
}
.list-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16rpx;
}
.list-title {
font-size: 30rpx;
font-weight: 600;
color: #333;
}
.list-count {
font-size: 28rpx;
color: #888;
}
.empty-placeholder {
height: 140rpx;
border-radius: 12rpx;
border: 1px dashed #d9d9d9;
color: #999;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
}
.card-group {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.card {
background: #fff;
border-radius: 16rpx;
padding: 24rpx;
box-shadow: 0 8rpx 22rpx rgba(0, 0, 0, .05);
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12rpx;
}
.card-header-text {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.card-title {
font-size: 30rpx;
color: #111;
font-weight: 600;
}
.card-subtitle {
font-size: 26rpx;
color: #555;
}
.card-action {
width: 64rpx;
height: 64rpx;
border-radius: 32rpx;
border: none;
background: rgba(255, 77, 79, .12);
display: flex;
align-items: center;
justify-content: center;
}
.card-action[disabled] {
opacity: .4;
}
.card-row {
display: flex;
align-items: center;
margin-top: 6rpx;
}
.card-label {
font-size: 26rpx;
color: #666;
margin-right: 8rpx;
}
.card-value {
font-size: 26rpx;
color: #333;
}
.submit {
width: 100%;
height: 92rpx;
border: none;
color: #fff;
border-radius: 46rpx;
background: linear-gradient(90deg, var(--grad-primary-start), var(--grad-primary-mid));
font-size: 32rpx;
box-shadow: 0 8rpx 22rpx rgba(52, 199, 89, .25);
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
.submit[disabled] {
opacity: .6;
}
.back {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 80rpx;
height: 64rpx;
line-height: 64rpx;
border: none;
color: #05DCEF;
background: #fff;
border-radius: 12rpx;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
</style>

Binary file not shown.

BIN
static/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

28
static/mui-icons.css Normal file
View File

@ -0,0 +1,28 @@
@font-face {
font-family: 'muiicon';
src: url('/static/fonts/MaterialIcons-Regular.ttf') format('truetype');
font-weight: normal;
font-style: normal;
font-display: swap;
}
.icon { font-family: 'muiicon' !important; font-style: normal; font-weight: normal; display: inline-block; line-height: 1; text-transform: none; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
.icon-menu:before { content: "\e5d2"; }
.icon-notifications:before { content: "\e7f4"; }
.icon-local_shipping:before { content: "\e558"; }
.icon-inventory:before { content: "\e179"; }
.icon-arrow_back:before { content: "\e5c4"; }
.icon-save:before { content: "\e161"; }
.icon-qr_code_2:before { content: "\f00a"; }
.icon-info:before { content: "\e88e"; }
.icon-add:before { content: "\e145"; }
.icon-task_alt:before { content: "\e2e6"; }
.icon-edit:before { content: "\e3c9"; }
.icon-delete:before { content: "\e872"; }
@font-face {
font-family: 'Material Icons Local';
src: url('/static/fonts/MaterialIcons-Regular.ttf') format('truetype');
font-weight: normal;
font-style: normal;
font-display: swap;
}
.mi{font-family:'Material Icons Local','Material Icons';font-weight:normal;font-style:normal;font-size:inherit;display:inline-block;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-feature-settings:'liga';-webkit-font-feature-settings:'liga';text-rendering:optimizeLegibility}

BIN
static/uni.ttf Normal file

Binary file not shown.

1
static/web/image-resize-3.0.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

7
static/web/quill-1.3.7.min.js vendored Normal file

File diff suppressed because one or more lines are too long

61
template.h5.html Normal file
View File

@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
<!-- 正式发布的时候使用,开发期间不启用。↓ -->
<!-- <script src="/h5/touch-emulator.js"></script>
<script>
TouchEmulator();
if (document.documentElement.clientWidth > 1024) {
window.location.href = '/h5/pcguide.html#'+location.pathname+location.search;
}
</script>
<style>
::-webkit-scrollbar{
display: none;
}
</style>
<script>
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?";// 百度统计key
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script> -->
<!-- 正式发布的时候使用,开发期间不启用。↑ -->
<script>
// document.addEventListener('DOMContentLoaded', function() {
// document.documentElement.style.fontSize = document.documentElement.clientWidth / 20 + 'px'
// })
</script>
<script src="<%= BASE_URL %>static/web/image-resize-3.0.1.min.js"></script>
<script src="<%= BASE_URL %>static/web/quill-1.3.7.min.js"></script>
<link rel="stylesheet" href="<%= BASE_URL %>static/index.<%= VUE_APP_INDEX_CSS_HASH %>.css" />
</head>
<body>
<!-- 该文件为 H5 平台的模板 HTML并非应用入口。 -->
<!-- 请勿在此文件编写页面代码或直接运行此文件。 -->
<!-- 详见文档https://uniapp.dcloud.io/collocation/manifest?id=h5-template -->
<noscript>
<strong>Please enable JavaScript to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<script>
/*BAIDU_STAT*/
</script>
</body>
</html>

86
uni.scss Normal file
View File

@ -0,0 +1,86 @@
/**
* 这里是uni-app内置的常用样式变量
*
* uni-app 官方扩展插件及插件市场https://ext.dcloud.net.cn上很多三方插件均使用了这些样式变量
* 如果你是插件开发者建议你使用scss预处理并在插件代码中直接使用这些变量无需 import 这个文件方便用户通过搭积木的方式开发整体风格一致的App
*
*/
/**
* 如果你是App开发者插件使用者你可以通过修改这些变量来定制自己的插件主题实现自定义主题功能
*
* 如果你的项目同样使用了scss预处理你也可以直接在你的 scss 代码中使用如下变量同时无需 import 这个文件
*/
/* 颜色变量 */
/* 行为相关颜色 */
$uni-color-primary: #007aff;
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
/* 文字基本颜色 */
$uni-text-color:#333;//基本色
$uni-text-color-inverse:#fff;//反色
$uni-text-color-grey:#999;//辅助灰色如加载更多的提示信息
$uni-text-color-placeholder: #808080;
$uni-text-color-disable:#c0c0c0;
/* 背景颜色 */
$uni-bg-color:#ffffff;
$uni-bg-color-grey:#f8f8f8;
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
/* 边框颜色 */
$uni-border-color:#e5e5e5;
/* 尺寸变量 */
/* 文字尺寸 */
$uni-font-size-sm:12px;
$uni-font-size-base:14px;
$uni-font-size-lg:16;
/* 图片尺寸 */
$uni-img-size-sm:20px;
$uni-img-size-base:26px;
$uni-img-size-lg:40px;
/* Border Radius */
$uni-border-radius-sm: 2px;
$uni-border-radius-base: 3px;
$uni-border-radius-lg: 6px;
$uni-border-radius-circle: 50%;
/* 水平间距 */
$uni-spacing-row-sm: 5px;
$uni-spacing-row-base: 10px;
$uni-spacing-row-lg: 15px;
/* 垂直间距 */
$uni-spacing-col-sm: 4px;
$uni-spacing-col-base: 8px;
$uni-spacing-col-lg: 12px;
/* 透明度 */
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
/* 文章场景相关 */
$uni-color-title: #2C405A; // 文章标题颜色
$uni-font-size-title:20px;
$uni-color-subtitle: #555555; // 二级标题颜色
$uni-font-size-subtitle:26px;
$uni-color-paragraph: #3F536E; // 文章段落颜色
$uni-font-size-paragraph:15px;
:root {
--grad-primary-start: #05DCEF;
--grad-primary-mid: #7DE2F5;
--grad-primary-end: #B8F2FF;
--grad-contrast-start: #00F5A0;
--grad-contrast-end: #00D9F5;
--bg-gray: #F5F5F5;
--text-color: #333333;
--text-light: #666666;
}

142
utils/dialog.js Normal file
View File

@ -0,0 +1,142 @@
/**
* 弹窗工具类 - 基于 Uniapp 原生 API 的简洁实现
*
* 使用系统原生弹窗 API无需自定义组件
* 提供统一的接口用于显示成功警告错误消息和 Toast
*
* @example
* // 显示成功消息
* DialogUtils.showSuccessMessage('成功', '数据已保存');
*
* // 显示确认对话框
* uni.showModal({
* title: '确认',
* content: '确定删除吗?',
* success: (res) => {
* if (res.confirm) {
* // 执行删除
* }
* }
* });
*
* // 显示 Toast 提示
* DialogUtils.toast('保存中...', 'loading');
*/
export const DialogUtils = {
/**
* 显示成功消息弹窗
* @param {string} title - 弹窗标题
* @param {string} message - 消息内容
* @param {string} confirmText - 确认按钮文本
*/
showSuccessMessage(title = '成功', message = '', confirmText = '我知道了') {
uni.showModal({
title,
content: message,
showCancel: false,
confirmText
});
},
/**
* 显示警告消息弹窗
* @param {string} title - 弹窗标题
* @param {string} message - 消息内容
* @param {string} confirmText - 确认按钮文本
*/
showWarningMessage(title = '提示', message = '', confirmText = '我知道了') {
uni.showModal({
title,
content: message,
showCancel: false,
confirmText
});
},
/**
* 显示错误消息弹窗
* @param {string} title - 弹窗标题
* @param {string} message - 消息内容
* @param {string} confirmText - 确认按钮文本
*/
showErrorMessage(title = '错误', message = '', confirmText = '我知道了') {
uni.showModal({
title,
content: message,
showCancel: false,
confirmText
});
},
/**
* 显示确认对话框带取消按钮
* @param {string} title - 弹窗标题
* @param {string} message - 消息内容
* @param {string} confirmText - 确认按钮文本
* @param {string} cancelText - 取消按钮文本
* @returns {Promise<boolean>} 用户是否点击了确认
*
* @example
* const confirmed = await DialogUtils.showConfirm('确认删除', '确定要删除这条记录吗?');
* if (confirmed) {
* // 执行删除操作
* }
*/
async showConfirm(
title = '确认',
message = '',
confirmText = '确定',
cancelText = '取消'
) {
return new Promise((resolve) => {
uni.showModal({
title,
content: message,
showCancel: true,
confirmText,
cancelText,
success: (res) => {
resolve(res.confirm);
},
fail: () => {
resolve(false);
}
});
});
},
/**
* 显示 Toast 提示
* @param {string} message - 提示消息
* @param {string} icon - 图标类型 (success/error/loading/none)
* @param {number} duration - 显示时长毫秒默认 2000
*
* @example
* // 显示加载中提示
* DialogUtils.toast('加载中...', 'loading');
*
* // 显示成功提示2秒后自动隐藏
* DialogUtils.toast('保存成功', 'success');
*/
toast(message, icon = 'none', duration = 2000) {
uni.showToast({
title: message,
icon,
duration,
mask: false
});
},
/**
* 隐藏 Toast 提示
*
* @example
* DialogUtils.toast('加载中...', 'loading', 0);
* // ... 执行某些操作
* DialogUtils.hideToast();
*/
hideToast() {
uni.hideToast();
}
};