调整部分前端布局和后端增删改逻辑
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version" : "1.0",
|
||||
"configurations" : [
|
||||
{
|
||||
"customPlaygroundType" : "device",
|
||||
"packageName" : "",
|
||||
"playground" : "standard",
|
||||
"type" : "uni-app:app-android"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
export function listScanExceptions() {
|
||||
return request({
|
||||
url: "/business/dashboard/scan-exceptions",
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
export function listPalletInventory(query) {
|
||||
return request({
|
||||
url: "/report/pallet-inventory/list",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
export function listShipmentReport(query) {
|
||||
return request({
|
||||
url: "/report/shipment/list",
|
||||
method: "get",
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
<template>
|
||||
<view
|
||||
v-if="testBatchStore.isTestMode"
|
||||
class="flex items-start gap-3 rounded-xl border border-red-300 bg-red-50 px-3 py-3"
|
||||
>
|
||||
<view class="mt-0.5 flex size-6 shrink-0 items-center justify-center rounded-full bg-red-500 text-white">
|
||||
<view class="i-mdi-flask-outline text-sm"></view>
|
||||
<view v-if="testBatchStore.isTestMode" class="test-batch-banner">
|
||||
<view class="test-batch-icon">
|
||||
<view class="i-mdi-flask-outline test-batch-icon-glyph"></view>
|
||||
</view>
|
||||
<view class="min-w-0">
|
||||
<text class="block text-sm font-bold text-red-700">测试数据</text>
|
||||
<text class="mt-0.5 block break-all text-xs leading-5 text-red-600">
|
||||
<view class="test-batch-copy">
|
||||
<text class="test-batch-title">测试数据</text>
|
||||
<text class="test-batch-value">
|
||||
当前批次:{{ testBatchStore.currentBatchName || "已选测试批次" }}
|
||||
</text>
|
||||
</view>
|
||||
@@ -20,3 +17,62 @@ import { useTestBatchStore } from "@/store";
|
||||
|
||||
const testBatchStore = useTestBatchStore();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.test-batch-banner {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
align-items: flex-start;
|
||||
gap: 12rpx;
|
||||
margin-top: 18rpx;
|
||||
border: 1rpx solid #f1b8b4;
|
||||
border-radius: 16rpx;
|
||||
padding: 16rpx;
|
||||
background: #fff5f4;
|
||||
}
|
||||
|
||||
.test-batch-icon {
|
||||
display: flex;
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
flex: 0 0 52rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: #d9534f;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.test-batch-icon-glyph {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.test-batch-copy {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.test-batch-title,
|
||||
.test-batch-value {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.test-batch-title {
|
||||
color: #b42318;
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.test-batch-value {
|
||||
margin-top: 5rpx;
|
||||
color: #c2413b;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.55;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
<template>
|
||||
<view class="space-y-3 rounded-2xl bg-white p-4 shadow-sm">
|
||||
<view class="flex items-center justify-between gap-3">
|
||||
<view class="min-w-0">
|
||||
<text class="block text-sm font-bold text-gray-800">数据模式</text>
|
||||
<text class="mt-1 block text-xs text-gray-500">
|
||||
<view class="test-batch-switch">
|
||||
<view class="mode-switch-header">
|
||||
<view class="mode-switch-copy">
|
||||
<text class="mode-switch-title">数据模式</text>
|
||||
<text class="mode-switch-description">
|
||||
{{ testBatchStore.isTestMode ? "当前操作隔离在测试批次" : "当前使用正式数据" }}
|
||||
</text>
|
||||
</view>
|
||||
<text
|
||||
:class="testBatchStore.isTestMode ? 'border-red-300 bg-red-50 text-red-700' : 'border-gray-200 bg-gray-50 text-gray-600'"
|
||||
class="shrink-0 rounded-full border px-2.5 py-1 text-xs font-semibold"
|
||||
class="mode-switch-status"
|
||||
:class="testBatchStore.isTestMode ? 'is-test' : 'is-formal'"
|
||||
>
|
||||
{{ testBatchStore.isTestMode ? "测试数据" : "正式数据" }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="flex rounded-xl bg-gray-100 p-1">
|
||||
<view class="mode-switch-controls">
|
||||
<button
|
||||
class="flex-1 rounded-lg py-2 text-xs transition-colors"
|
||||
:class="!testBatchStore.isTestMode ? 'bg-white font-semibold text-gray-800 shadow-sm' : 'text-gray-500'"
|
||||
class="mode-switch-button"
|
||||
:class="{ active: !testBatchStore.isTestMode }"
|
||||
:disabled="loading"
|
||||
size="mini"
|
||||
@click="handleModeClick('formal')"
|
||||
@@ -26,8 +26,8 @@
|
||||
正式
|
||||
</button>
|
||||
<button
|
||||
class="flex-1 rounded-lg py-2 text-xs transition-colors"
|
||||
:class="testBatchStore.isTestMode ? 'bg-red-500 font-semibold text-white shadow-sm' : 'text-gray-500'"
|
||||
class="mode-switch-button"
|
||||
:class="{ 'active-test': testBatchStore.isTestMode }"
|
||||
:disabled="loading || !activeOptions.length"
|
||||
size="mini"
|
||||
@click="handleModeClick('test')"
|
||||
@@ -36,8 +36,11 @@
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view v-if="testBatchStore.isTestMode || activeOptions.length" class="space-y-1">
|
||||
<text class="block text-xs text-gray-500">测试批次</text>
|
||||
<view
|
||||
v-if="testBatchStore.isTestMode || activeOptions.length"
|
||||
class="test-batch-picker-row"
|
||||
>
|
||||
<text class="test-batch-picker-label-title">测试批次</text>
|
||||
<picker
|
||||
:range="activeOptions"
|
||||
range-key="batchName"
|
||||
@@ -45,15 +48,23 @@
|
||||
:disabled="loading || !activeOptions.length"
|
||||
@change="handleBatchChange"
|
||||
>
|
||||
<view class="rounded-xl bg-gray-50 px-3 py-3 text-sm text-gray-700">
|
||||
{{ selectedBatch?.batchName || (loading ? "加载中..." : "暂无可用测试批次") }}
|
||||
<view class="test-batch-picker">
|
||||
<text class="test-batch-picker-label">
|
||||
{{ selectedBatch?.batchName || (loading ? "加载中..." : "暂无可用测试批次") }}
|
||||
</text>
|
||||
<view class="i-mdi-chevron-down test-batch-picker-arrow"></view>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<view v-if="loadError" class="flex items-center justify-between gap-2 rounded-lg bg-orange-50 px-3 py-2">
|
||||
<text class="min-w-0 text-xs leading-5 text-orange-700">{{ loadError }}</text>
|
||||
<button class="shrink-0 text-xs text-orange-700" size="mini" :disabled="loading" @click="loadOptions">
|
||||
<view v-if="loadError" class="mode-switch-error">
|
||||
<text class="mode-switch-error-text">{{ loadError }}</text>
|
||||
<button
|
||||
class="mode-switch-retry"
|
||||
size="mini"
|
||||
:disabled="loading"
|
||||
@click="loadOptions"
|
||||
>
|
||||
重试
|
||||
</button>
|
||||
</view>
|
||||
@@ -151,3 +162,191 @@ async function handleBatchChange(event) {
|
||||
|
||||
onMounted(loadOptions);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.test-batch-switch {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
border: 1rpx solid #e5eeee;
|
||||
border-radius: 20rpx;
|
||||
padding: 22rpx;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 8rpx 24rpx rgba(40, 34, 92, 0.06);
|
||||
}
|
||||
|
||||
.mode-switch-header {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 14rpx;
|
||||
}
|
||||
|
||||
.mode-switch-copy {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mode-switch-title,
|
||||
.mode-switch-description,
|
||||
.test-batch-picker-label-title,
|
||||
.test-batch-picker-label,
|
||||
.mode-switch-error-text {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.mode-switch-title {
|
||||
color: #28225c;
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.mode-switch-description {
|
||||
margin-top: 5rpx;
|
||||
color: #8b969d;
|
||||
font-size: 21rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.mode-switch-status {
|
||||
flex: 0 0 auto;
|
||||
border: 1rpx solid #dce7e7;
|
||||
border-radius: 999rpx;
|
||||
padding: 7rpx 12rpx;
|
||||
font-size: 20rpx;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.mode-switch-status.is-formal {
|
||||
border-color: #dce7e7;
|
||||
background: #f5f8f8;
|
||||
color: #64727a;
|
||||
}
|
||||
|
||||
.mode-switch-status.is-test {
|
||||
border-color: #f1b8b4;
|
||||
background: #fff1f0;
|
||||
color: #b42318;
|
||||
}
|
||||
|
||||
.mode-switch-controls {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
gap: 10rpx;
|
||||
margin-top: 18rpx;
|
||||
border-radius: 12rpx;
|
||||
padding: 5rpx;
|
||||
background: #f1f5f5;
|
||||
}
|
||||
|
||||
.mode-switch-button {
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
height: 64rpx;
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
border-radius: 9rpx;
|
||||
background: transparent;
|
||||
color: #748087;
|
||||
font-size: 23rpx;
|
||||
line-height: 64rpx;
|
||||
}
|
||||
|
||||
.mode-switch-button::after,
|
||||
.mode-switch-retry::after {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.mode-switch-button.active {
|
||||
background: #ffffff;
|
||||
box-shadow: 0 4rpx 10rpx rgba(40, 34, 92, 0.08);
|
||||
color: #28225c;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.mode-switch-button.active-test {
|
||||
background: #d9534f;
|
||||
box-shadow: 0 4rpx 10rpx rgba(217, 83, 79, 0.18);
|
||||
color: #ffffff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.test-batch-picker-row {
|
||||
min-width: 0;
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
|
||||
.test-batch-picker-label-title {
|
||||
color: #6c7786;
|
||||
font-size: 21rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.test-batch-picker {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12rpx;
|
||||
margin-top: 8rpx;
|
||||
border: 1rpx solid #e0eaea;
|
||||
border-radius: 12rpx;
|
||||
padding: 16rpx;
|
||||
background: #fbfdfd;
|
||||
}
|
||||
|
||||
.test-batch-picker-label {
|
||||
flex: 1;
|
||||
color: #47545c;
|
||||
font-size: 23rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.test-batch-picker-arrow {
|
||||
flex: 0 0 30rpx;
|
||||
color: #96a5a9;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.mode-switch-error {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
margin-top: 14rpx;
|
||||
border-radius: 10rpx;
|
||||
padding: 10rpx 12rpx;
|
||||
background: #fff8ed;
|
||||
}
|
||||
|
||||
.mode-switch-error-text {
|
||||
flex: 1;
|
||||
color: #a15c00;
|
||||
font-size: 21rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.mode-switch-retry {
|
||||
width: auto;
|
||||
min-width: 74rpx;
|
||||
height: 52rpx;
|
||||
flex: 0 0 auto;
|
||||
margin: 0;
|
||||
padding: 0 12rpx;
|
||||
border: 0;
|
||||
border-radius: 8rpx;
|
||||
background: #fff0d6;
|
||||
color: #a15c00;
|
||||
font-size: 20rpx;
|
||||
line-height: 52rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
export const businessMenuEntries = [
|
||||
{
|
||||
path: "/pages/business/inbound/index",
|
||||
title: "仓库入库",
|
||||
description: "扫描托盘登记入库",
|
||||
icon: "i-mdi-package-down",
|
||||
iconClass: "bg-blue-50 text-blue-600",
|
||||
permission: "business:inbound:list",
|
||||
},
|
||||
{
|
||||
path: "/pages/business/filling/index",
|
||||
title: "工厂装填",
|
||||
description: "录入菌种和包数",
|
||||
icon: "i-mdi-package-variant-closed-plus",
|
||||
iconClass: "bg-green-50 text-green-600",
|
||||
permission: "business:filling:list",
|
||||
},
|
||||
{
|
||||
path: "/pages/business/shipment/index",
|
||||
title: "发货出库",
|
||||
description: "扫码创建发货单",
|
||||
icon: "i-mdi-truck-delivery",
|
||||
iconClass: "bg-orange-50 text-orange-600",
|
||||
permission: "business:shipment:list",
|
||||
},
|
||||
{
|
||||
path: "/pages/business/usage/index",
|
||||
title: "客户现场使用",
|
||||
description: "绑定产线记录消耗",
|
||||
icon: "i-mdi-factory",
|
||||
iconClass: "bg-purple-50 text-purple-600",
|
||||
permission: "business:usage:list",
|
||||
},
|
||||
{
|
||||
path: "/pages/business/return/index",
|
||||
title: "退回仓库",
|
||||
description: "申请和确认退回",
|
||||
icon: "i-mdi-undo-variant",
|
||||
iconClass: "bg-red-50 text-red-600",
|
||||
permission: "business:return:list",
|
||||
},
|
||||
];
|
||||
@@ -1,70 +1,72 @@
|
||||
{
|
||||
"name": "智链云枢管理系统",
|
||||
"appid": "__UNI__25A9D80",
|
||||
"description": "",
|
||||
"versionName": "1.9.0",
|
||||
"versionCode": "100",
|
||||
"transformPx": false,
|
||||
"app-plus": {
|
||||
"usingComponents": true,
|
||||
"nvueCompiler": "uni-app",
|
||||
"splashscreen": {
|
||||
"alwaysShowBeforeRender": true,
|
||||
"waiting": true,
|
||||
"autoclose": true,
|
||||
"delay": 0
|
||||
"name" : "智链云枢管理系统",
|
||||
"appid" : "__UNI__F8DBA93",
|
||||
"description" : "",
|
||||
"versionName" : "1.9.0",
|
||||
"versionCode" : "100",
|
||||
"transformPx" : false,
|
||||
"app-plus" : {
|
||||
"usingComponents" : true,
|
||||
"nvueCompiler" : "uni-app",
|
||||
"splashscreen" : {
|
||||
"alwaysShowBeforeRender" : true,
|
||||
"waiting" : true,
|
||||
"autoclose" : true,
|
||||
"delay" : 0
|
||||
},
|
||||
"modules" : {},
|
||||
"distribute" : {
|
||||
"android" : {
|
||||
"permissions" : [
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||
]
|
||||
},
|
||||
"ios" : {
|
||||
"dSYMs" : false
|
||||
},
|
||||
"sdkConfigs" : {}
|
||||
}
|
||||
},
|
||||
"modules": {},
|
||||
"distribute": {
|
||||
"android": {
|
||||
"permissions": [
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||
]
|
||||
},
|
||||
"ios": {
|
||||
"dSYMs": false
|
||||
},
|
||||
"sdkConfigs": {}
|
||||
}
|
||||
},
|
||||
"quickapp": {},
|
||||
"mp-weixin": {
|
||||
"appid": "wxccd7e2a0911b3397",
|
||||
"setting": {
|
||||
"urlCheck": false,
|
||||
"es6": false,
|
||||
"minified": true,
|
||||
"postcss": true
|
||||
"quickapp" : {},
|
||||
"mp-weixin" : {
|
||||
"appid" : "wxccd7e2a0911b3397",
|
||||
"setting" : {
|
||||
"urlCheck" : false,
|
||||
"es6" : false,
|
||||
"minified" : true,
|
||||
"postcss" : true
|
||||
},
|
||||
"optimization" : {
|
||||
"subPackages" : true
|
||||
},
|
||||
"usingComponents" : true
|
||||
},
|
||||
"optimization": {
|
||||
"subPackages": true
|
||||
"vueVersion" : "3",
|
||||
"h5" : {
|
||||
"devServer" : {
|
||||
"port" : 9090,
|
||||
"https" : false
|
||||
},
|
||||
"title" : "智链云枢管理系统",
|
||||
"router" : {
|
||||
"mode" : "hash",
|
||||
"base" : "./"
|
||||
}
|
||||
},
|
||||
"usingComponents": true
|
||||
},
|
||||
"vueVersion": "3",
|
||||
"h5": {
|
||||
"devServer": {
|
||||
"port": 9090,
|
||||
"https": false
|
||||
},
|
||||
"title": "智链云枢管理系统",
|
||||
"router": {
|
||||
"mode": "hash",
|
||||
"base": "./"
|
||||
}
|
||||
}
|
||||
"locale" : "zh-Hans",
|
||||
"fallbackLocale" : "zh-Hans"
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
{
|
||||
"path": "pages/business/filling/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "托盘装填"
|
||||
"navigationBarTitleText": "工厂装填"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -158,5 +158,15 @@
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "智链云枢管理系统",
|
||||
"navigationBarBackgroundColor": "#FFFFFF"
|
||||
},
|
||||
"condition" : { //模式配置,仅开发期间生效
|
||||
"current": 0, //当前激活的模式(list 的索引项)
|
||||
"list": [
|
||||
{
|
||||
"name": "", //模式名称
|
||||
"path": "", //启动页面,必选
|
||||
"query": "" //启动参数,在页面的onLoad函数里面得到
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<scroll-view scroll-y class="h-screen">
|
||||
<view class="space-y-4 p-4 pb-24">
|
||||
<view>
|
||||
<text class="block text-xl font-bold text-gray-800">托盘装填</text>
|
||||
<text class="block text-xl font-bold text-gray-800">工厂装填</text>
|
||||
<text class="mt-1 block text-xs text-gray-500">
|
||||
扫描空托盘,录入菌种、批次、规格和初始包数
|
||||
</text>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,118 +1,94 @@
|
||||
<template>
|
||||
<view
|
||||
class="flex h-full flex-col items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100 px-6 pb-20 overflow-hidden"
|
||||
>
|
||||
<!-- Logo Section -->
|
||||
<view class="mb-6 flex flex-col items-center">
|
||||
<view
|
||||
class="mb-4 flex h-20 w-60 items-center justify-center rounded-2xl bg-gray-800 px-4 shadow-lg"
|
||||
>
|
||||
<image
|
||||
class="h-16 w-full"
|
||||
:src="globalConfig.appInfo.logo"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<text class="text-xl font-bold tracking-wide text-gray-800"
|
||||
>智链云枢管理系统登录</text
|
||||
>
|
||||
</view>
|
||||
<view class="login-page">
|
||||
<image
|
||||
class="login-background"
|
||||
src="/static/login-background.jpg"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="login-scrim" />
|
||||
|
||||
<!-- Form Section -->
|
||||
<view class="w-full rounded-3xl bg-white/80 p-6 shadow-xl backdrop-blur-md">
|
||||
<!-- Username -->
|
||||
<view class="group relative mb-5">
|
||||
<view
|
||||
class="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 transition-colors group-focus-within:text-blue-500"
|
||||
>
|
||||
<view class="i-mdi-account text-xl"></view>
|
||||
</view>
|
||||
<input
|
||||
v-model="loginForm.username"
|
||||
class="h-12 w-full rounded-xl bg-gray-50 pl-12 pr-4 text-sm text-gray-700 outline-none transition-all focus:bg-white focus:ring-2 focus:ring-blue-400"
|
||||
type="text"
|
||||
placeholder="请输入账号"
|
||||
maxlength="30"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- Password -->
|
||||
<view class="group relative mb-5">
|
||||
<view
|
||||
class="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 transition-colors group-focus-within:text-blue-500"
|
||||
>
|
||||
<view class="i-mdi-lock text-xl"></view>
|
||||
</view>
|
||||
<input
|
||||
v-model="loginForm.password"
|
||||
type="password"
|
||||
class="h-12 w-full rounded-xl bg-gray-50 pl-12 pr-4 text-sm text-gray-700 outline-none transition-all focus:bg-white focus:ring-2 focus:ring-blue-400"
|
||||
placeholder="请输入密码"
|
||||
maxlength="20"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- Captcha -->
|
||||
<view
|
||||
class="mb-8 flex items-center justify-between"
|
||||
v-if="captchaEnabled"
|
||||
>
|
||||
<view class="group relative mr-3 flex-1">
|
||||
<view
|
||||
class="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 transition-colors group-focus-within:text-blue-500"
|
||||
>
|
||||
<view class="i-mdi-security text-xl"></view>
|
||||
<view class="login-content">
|
||||
<view class="login-card">
|
||||
<view class="brand-lockup">
|
||||
<view class="brand-logo-frame">
|
||||
<image
|
||||
class="brand-logo"
|
||||
:src="globalConfig.appInfo.logo"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<input
|
||||
v-model="loginForm.code"
|
||||
type="number"
|
||||
class="h-12 w-full rounded-xl bg-gray-50 pl-12 pr-4 text-sm text-gray-700 outline-none transition-all focus:bg-white focus:ring-2 focus:ring-blue-400"
|
||||
placeholder="验证码"
|
||||
maxlength="4"
|
||||
/>
|
||||
<text class="brand-title">{{ globalConfig.appInfo.name }}</text>
|
||||
<text class="brand-subtitle">仓储运营管理平台</text>
|
||||
</view>
|
||||
<view
|
||||
class="h-12 w-28 overflow-hidden rounded-xl bg-gray-100 shadow-sm transition-opacity active:opacity-80"
|
||||
@click="getCode"
|
||||
>
|
||||
<image :src="codeUrl" class="size-full object-cover"></image>
|
||||
|
||||
<view class="login-form">
|
||||
<view class="field-control">
|
||||
<view class="field-icon i-mdi-account" />
|
||||
<input
|
||||
v-model="loginForm.username"
|
||||
class="field-input"
|
||||
type="text"
|
||||
placeholder="请输入您的账号"
|
||||
maxlength="30"
|
||||
confirm-type="next"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="field-control">
|
||||
<view class="field-icon i-mdi-lock" />
|
||||
<input
|
||||
v-model="loginForm.password"
|
||||
class="field-input"
|
||||
type="password"
|
||||
placeholder="请输入您的密码"
|
||||
maxlength="20"
|
||||
confirm-type="done"
|
||||
@confirm="handleLogin"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view v-if="captchaEnabled" class="captcha-row">
|
||||
<view class="field-control captcha-input">
|
||||
<view class="field-icon i-mdi-security" />
|
||||
<input
|
||||
v-model="loginForm.code"
|
||||
class="field-input"
|
||||
type="number"
|
||||
placeholder="请输入验证码"
|
||||
maxlength="4"
|
||||
confirm-type="done"
|
||||
@confirm="handleLogin"
|
||||
/>
|
||||
</view>
|
||||
<view class="captcha-image-wrap" @click="getCode">
|
||||
<image
|
||||
:src="codeUrl"
|
||||
class="captcha-image"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<button
|
||||
class="login-button"
|
||||
:loading="loading"
|
||||
:disabled="loading"
|
||||
@click="handleLogin"
|
||||
>
|
||||
{{ loading ? "登录中..." : "登 录" }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Login Button -->
|
||||
<button
|
||||
@click="handleLogin"
|
||||
class="flex h-12 w-full items-center justify-center rounded-xl bg-gradient-to-r from-blue-500 to-indigo-600 text-base font-semibold text-white shadow-lg shadow-blue-500/30 transition-transform active:scale-95"
|
||||
>
|
||||
登 录
|
||||
</button>
|
||||
|
||||
<!-- Footer Links -->
|
||||
<view class="mt-6 flex flex-col items-center space-y-3">
|
||||
<view class="flex items-center text-sm text-gray-500" v-if="register">
|
||||
<view v-if="register" class="register-tip">
|
||||
<text>没有账号?</text>
|
||||
<text
|
||||
@click="handleUserRegister"
|
||||
class="ml-1 font-medium text-blue-600 active:opacity-70"
|
||||
>立即注册</text
|
||||
>
|
||||
<text class="action-link" @click="handleUserRegister">立即注册</text>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="flex flex-wrap items-center justify-center text-xs text-gray-400"
|
||||
>
|
||||
<view class="agreement-tip">
|
||||
<text>登录即代表同意</text>
|
||||
<text
|
||||
@click="handleUserAgrement"
|
||||
class="mx-1 text-blue-500 active:opacity-70"
|
||||
>《用户协议》</text
|
||||
>
|
||||
<text class="action-link" @click="handleUserAgrement">《用户协议》</text>
|
||||
<text>和</text>
|
||||
<text
|
||||
@click="handlePrivacy"
|
||||
class="mx-1 text-blue-500 active:opacity-70"
|
||||
>《隐私协议》</text
|
||||
>
|
||||
<text class="action-link" @click="handlePrivacy">《隐私协议》</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -129,6 +105,7 @@ import { useConfigStore, useUserStore } from "@/store";
|
||||
const { proxy } = getCurrentInstance();
|
||||
const globalConfig = useConfigStore().config;
|
||||
const codeUrl = ref("");
|
||||
const loading = ref(false);
|
||||
// 验证码开关
|
||||
const captchaEnabled = ref(true);
|
||||
// 用户注册开关
|
||||
@@ -160,6 +137,8 @@ function getCode() {
|
||||
getCodeImg().then((res) => {
|
||||
captchaEnabled.value =
|
||||
res.captchaEnabled === undefined ? true : res.captchaEnabled;
|
||||
register.value =
|
||||
res.registerEnabled === undefined ? false : res.registerEnabled;
|
||||
if (captchaEnabled.value) {
|
||||
codeUrl.value = "data:image/gif;base64," + res.img;
|
||||
loginForm.value.uuid = res.uuid;
|
||||
@@ -176,6 +155,7 @@ async function handleLogin() {
|
||||
} else if (loginForm.value.code === "" && captchaEnabled.value) {
|
||||
proxy.$modal.msgError("请输入验证码");
|
||||
} else {
|
||||
loading.value = true;
|
||||
proxy.$modal.loading("登录中,请耐心等待...");
|
||||
pwdLogin();
|
||||
}
|
||||
@@ -183,25 +163,26 @@ async function handleLogin() {
|
||||
|
||||
// 密码登录
|
||||
async function pwdLogin() {
|
||||
useUserStore()
|
||||
.login(loginForm.value)
|
||||
.then(() => {
|
||||
proxy.$modal.closeLoading();
|
||||
loginSuccess();
|
||||
})
|
||||
.catch(() => {
|
||||
if (captchaEnabled.value) {
|
||||
getCode();
|
||||
}
|
||||
});
|
||||
try {
|
||||
await useUserStore().login(loginForm.value);
|
||||
proxy.$modal.closeLoading();
|
||||
loading.value = false;
|
||||
loginSuccess();
|
||||
} catch (error) {
|
||||
proxy.$modal.closeLoading();
|
||||
loading.value = false;
|
||||
if (captchaEnabled.value) {
|
||||
getCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 登录成功后,处理函数
|
||||
function loginSuccess(result) {
|
||||
function loginSuccess() {
|
||||
// 设置用户信息
|
||||
useUserStore()
|
||||
.getInfo()
|
||||
.then((res) => {
|
||||
.then(() => {
|
||||
proxy.$tab.reLaunch("/pages/index");
|
||||
});
|
||||
}
|
||||
@@ -219,6 +200,239 @@ getCode();
|
||||
|
||||
<style lang="scss" scoped>
|
||||
page {
|
||||
background-color: #ffffff;
|
||||
background-color: #eef8f8;
|
||||
}
|
||||
|
||||
.login-page {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
padding: 48rpx 32rpx;
|
||||
background: #eef8f8;
|
||||
}
|
||||
|
||||
.login-background,
|
||||
.login-scrim {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.login-background {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.login-scrim {
|
||||
z-index: 1;
|
||||
background: rgba(241, 250, 250, 0.22);
|
||||
}
|
||||
|
||||
.login-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-width: 720rpx;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
width: 100%;
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.88);
|
||||
border-radius: 20rpx;
|
||||
padding: 56rpx 44rpx 42rpx;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
box-shadow: 0 20rpx 60rpx rgba(40, 34, 92, 0.18);
|
||||
}
|
||||
|
||||
.brand-lockup {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 46rpx;
|
||||
text-align: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.brand-logo-frame {
|
||||
display: flex;
|
||||
width: 400rpx;
|
||||
max-width: 100%;
|
||||
height: 136rpx;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 18rpx;
|
||||
padding: 16rpx 22rpx;
|
||||
background: #28225c;
|
||||
box-shadow: 0 10rpx 22rpx rgba(40, 34, 92, 0.16);
|
||||
}
|
||||
|
||||
.brand-logo {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.brand-title {
|
||||
margin-top: 18rpx;
|
||||
color: #28225c;
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.brand-subtitle {
|
||||
margin-top: 10rpx;
|
||||
color: #6c7786;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.field-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 96rpx;
|
||||
margin-bottom: 24rpx;
|
||||
border: 1rpx solid #d9e6e7;
|
||||
border-radius: 10rpx;
|
||||
padding: 0 24rpx;
|
||||
background: #ffffff;
|
||||
transition: border-color 160ms ease, box-shadow 160ms ease;
|
||||
}
|
||||
|
||||
.field-control:focus-within {
|
||||
border-color: #008596;
|
||||
box-shadow: 0 0 0 4rpx rgba(0, 133, 150, 0.1);
|
||||
}
|
||||
|
||||
.field-icon {
|
||||
flex: 0 0 36rpx;
|
||||
margin-right: 18rpx;
|
||||
color: #008596;
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.field-input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 96rpx;
|
||||
color: #28333f;
|
||||
font-size: 28rpx;
|
||||
line-height: 96rpx;
|
||||
}
|
||||
|
||||
.captcha-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18rpx;
|
||||
}
|
||||
|
||||
.captcha-input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.captcha-image-wrap {
|
||||
flex: 0 0 220rpx;
|
||||
width: 220rpx;
|
||||
height: 96rpx;
|
||||
margin-bottom: 24rpx;
|
||||
overflow: hidden;
|
||||
border: 1rpx solid #d9e6e7;
|
||||
border-radius: 10rpx;
|
||||
background: #f4f8f8;
|
||||
}
|
||||
|
||||
.captcha-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.login-button {
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
margin: 8rpx 0 0;
|
||||
border: 0;
|
||||
border-radius: 10rpx;
|
||||
background: #008596;
|
||||
box-shadow: 0 12rpx 24rpx rgba(0, 133, 150, 0.2);
|
||||
color: #ffffff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
line-height: 96rpx;
|
||||
transition: opacity 160ms ease, transform 160ms ease;
|
||||
}
|
||||
|
||||
.login-button::after {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.login-button:active {
|
||||
opacity: 0.84;
|
||||
transform: translateY(1rpx);
|
||||
}
|
||||
|
||||
.login-button[disabled] {
|
||||
opacity: 0.72;
|
||||
}
|
||||
|
||||
.register-tip,
|
||||
.agreement-tip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #7b8791;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.6;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.register-tip {
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
|
||||
.agreement-tip {
|
||||
flex-wrap: wrap;
|
||||
margin-top: 22rpx;
|
||||
color: #9aa5ad;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.action-link {
|
||||
margin: 0 6rpx;
|
||||
color: #008596;
|
||||
}
|
||||
|
||||
.action-link:active {
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 420px) {
|
||||
.login-page {
|
||||
padding: 32rpx 24rpx;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
padding: 44rpx 28rpx 34rpx;
|
||||
}
|
||||
|
||||
.brand-logo-frame {
|
||||
width: 360rpx;
|
||||
height: 122rpx;
|
||||
padding: 14rpx 20rpx;
|
||||
}
|
||||
|
||||
.captcha-image-wrap {
|
||||
flex-basis: 190rpx;
|
||||
width: 190rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,231 +1,62 @@
|
||||
<template>
|
||||
<view class="flex h-full flex-col bg-gray-50 overflow-hidden">
|
||||
<scroll-view scroll-y class="flex-1" :show-scrollbar="false">
|
||||
<view class="p-4 pb-24 space-y-6">
|
||||
<view class="work-page">
|
||||
<scroll-view
|
||||
class="work-scroll"
|
||||
scroll-y
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<view class="work-content">
|
||||
<view class="work-heading">
|
||||
<view class="work-heading-copy">
|
||||
<text class="work-kicker">业务工作台</text>
|
||||
<text class="work-subtitle">
|
||||
根据当前账号权限,快速进入现场业务流程
|
||||
</text>
|
||||
</view>
|
||||
<view class="work-permission-count">
|
||||
<text class="work-permission-value">{{ visibleBusinessEntries.length }}</text>
|
||||
<text class="work-permission-label">项可用</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<TestBatchModeSwitch />
|
||||
|
||||
<!-- Banner -->
|
||||
<view
|
||||
class="relative h-40 w-full overflow-hidden rounded-2xl shadow-lg shadow-indigo-100"
|
||||
>
|
||||
<swiper
|
||||
class="h-40 w-full"
|
||||
:current="swiperDotIndex"
|
||||
@change="changeSwiper"
|
||||
autoplay
|
||||
interval="3000"
|
||||
circular
|
||||
>
|
||||
<swiper-item v-for="(item, index) in data" :key="index">
|
||||
<view class="h-full w-full" @click="clickBannerItem(item)">
|
||||
<image
|
||||
:src="item.image"
|
||||
mode="scaleToFill"
|
||||
class="block h-full w-full"
|
||||
/>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<!-- Custom Dots -->
|
||||
<view
|
||||
class="absolute bottom-3 right-0 left-0 flex justify-center space-x-2 pointer-events-none"
|
||||
>
|
||||
<view
|
||||
v-for="(item, index) in data"
|
||||
:key="index"
|
||||
class="h-1.5 rounded-full transition-all duration-300"
|
||||
:class="current === index ? 'bg-white w-4' : 'bg-white/50 w-1.5'"
|
||||
>
|
||||
<view class="work-section">
|
||||
<view class="work-section-header">
|
||||
<view class="work-section-marker"></view>
|
||||
<view class="work-section-copy">
|
||||
<text class="work-section-title">业务管理</text>
|
||||
<text class="work-section-description">选择需要执行的现场流程</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- AI Tools Section -->
|
||||
<view>
|
||||
<view class="mb-4 flex items-center space-x-2">
|
||||
<view class="h-4 w-1 rounded-full bg-indigo-500"></view>
|
||||
<text class="text-base font-bold text-gray-800">AI 生产力</text>
|
||||
</view>
|
||||
<view class="grid grid-cols-2 gap-4">
|
||||
<view v-if="visibleBusinessEntries.length" class="work-entry-list">
|
||||
<view
|
||||
class="group relative overflow-hidden rounded-2xl bg-white p-5 shadow-sm transition-all active:scale-95"
|
||||
@click="handleToAiChat"
|
||||
v-for="entry in visibleBusinessEntries"
|
||||
:key="entry.path"
|
||||
class="work-entry"
|
||||
@click="handleBusinessEntry(entry)"
|
||||
>
|
||||
<view
|
||||
class="absolute -right-4 -top-4 size-20 rounded-full bg-indigo-50 opacity-50"
|
||||
></view>
|
||||
<view class="relative z-10 flex flex-col">
|
||||
<view
|
||||
class="mb-3 flex size-10 items-center justify-center rounded-xl bg-indigo-500 text-white shadow-md shadow-indigo-200"
|
||||
>
|
||||
<view class="i-mdi-chat text-xl"></view>
|
||||
</view>
|
||||
<text class="font-bold text-gray-800">智能对话</text>
|
||||
<text class="mt-1 text-xs text-gray-500">智能助手</text>
|
||||
class="work-entry-icon"
|
||||
:class="entry.iconClass"
|
||||
>
|
||||
<view :class="entry.icon" class="work-entry-icon-glyph"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="group relative overflow-hidden rounded-2xl bg-white p-5 shadow-sm transition-all active:scale-95"
|
||||
@click="handleBuilding"
|
||||
>
|
||||
<view
|
||||
class="absolute -right-4 -top-4 size-20 rounded-full bg-purple-50 opacity-50"
|
||||
></view>
|
||||
<view class="relative z-10 flex flex-col">
|
||||
<view
|
||||
class="mb-3 flex size-10 items-center justify-center rounded-xl bg-purple-500 text-white shadow-md shadow-purple-200"
|
||||
>
|
||||
<view class="i-mdi-image-multiple text-xl"></view>
|
||||
</view>
|
||||
<text class="font-bold text-gray-800">图像生成</text>
|
||||
<text class="mt-1 text-xs text-gray-500">创意工坊</text>
|
||||
<view class="work-entry-copy">
|
||||
<text class="work-entry-title">{{ entry.title }}</text>
|
||||
<text class="work-entry-description">{{ entry.description }}</text>
|
||||
</view>
|
||||
<view class="i-mdi-chevron-right work-entry-arrow"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- PDA Business Section -->
|
||||
<view v-if="visibleBusinessEntries.length">
|
||||
<view class="mb-4 flex items-center space-x-2">
|
||||
<view class="h-4 w-1 rounded-full bg-emerald-500"></view>
|
||||
<text class="text-base font-bold text-gray-800">现场业务</text>
|
||||
</view>
|
||||
<view class="rounded-2xl bg-white p-5 shadow-sm">
|
||||
<view class="grid grid-cols-3 gap-4">
|
||||
<view
|
||||
v-for="entry in visibleBusinessEntries"
|
||||
:key="entry.path"
|
||||
class="flex flex-col items-center space-y-1.5 active:opacity-60"
|
||||
@click="handleBusinessEntry(entry)"
|
||||
>
|
||||
<view
|
||||
class="flex size-10 items-center justify-center rounded-xl bg-emerald-50 text-emerald-600"
|
||||
>
|
||||
<view :class="entry.icon" class="text-xl"></view>
|
||||
</view>
|
||||
<text class="text-center text-[11px] font-medium text-gray-600">
|
||||
{{ entry.title }}
|
||||
</text>
|
||||
<text class="text-center text-[10px] text-gray-400">
|
||||
{{ entry.description }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- System Management Section -->
|
||||
<view>
|
||||
<view class="mb-4 flex items-center space-x-2">
|
||||
<view class="h-4 w-1 rounded-full bg-blue-500"></view>
|
||||
<text class="text-base font-bold text-gray-800">系统管理</text>
|
||||
</view>
|
||||
<view class="rounded-2xl bg-white p-5 shadow-sm">
|
||||
<view class="flex flex-wrap justify-between gap-y-4">
|
||||
<view
|
||||
class="w-[22%] flex flex-col items-center space-y-1.5 active:opacity-60"
|
||||
@click="handleToUserList"
|
||||
>
|
||||
<view
|
||||
class="flex size-10 items-center justify-center rounded-xl bg-blue-50 text-blue-500 transition-colors group-active:bg-blue-100"
|
||||
>
|
||||
<view class="i-mdi-account text-xl"></view>
|
||||
</view>
|
||||
<text class="text-[11px] font-medium text-gray-600"
|
||||
>用户管理</text
|
||||
>
|
||||
</view>
|
||||
<view
|
||||
class="w-[22%] flex flex-col items-center space-y-1.5 active:opacity-60"
|
||||
@click="handleBuilding"
|
||||
>
|
||||
<view
|
||||
class="flex size-10 items-center justify-center rounded-xl bg-orange-50 text-orange-500 transition-colors group-active:bg-orange-100"
|
||||
>
|
||||
<view class="i-mdi-badge-account text-xl"></view>
|
||||
</view>
|
||||
<text class="text-[11px] font-medium text-gray-600"
|
||||
>角色管理</text
|
||||
>
|
||||
</view>
|
||||
<view
|
||||
class="w-[22%] flex flex-col items-center space-y-1.5 active:opacity-60"
|
||||
@click="handleBuilding"
|
||||
>
|
||||
<view
|
||||
class="flex size-10 items-center justify-center rounded-xl bg-green-50 text-green-500 transition-colors group-active:bg-green-100"
|
||||
>
|
||||
<view class="i-mdi-office-building text-xl"></view>
|
||||
</view>
|
||||
<text class="text-[11px] font-medium text-gray-600"
|
||||
>部门管理</text
|
||||
>
|
||||
</view>
|
||||
<view
|
||||
class="w-[22%] flex flex-col items-center space-y-1.5 active:opacity-60"
|
||||
@click="handleBuilding"
|
||||
>
|
||||
<view
|
||||
class="flex size-10 items-center justify-center rounded-xl bg-red-50 text-red-500 transition-colors group-active:bg-red-100"
|
||||
>
|
||||
<view class="i-mdi-cog text-xl"></view>
|
||||
</view>
|
||||
<text class="text-[11px] font-medium text-gray-600"
|
||||
>配置管理</text
|
||||
>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="w-[22%] flex flex-col items-center space-y-1.5 active:opacity-60"
|
||||
@click="handleBuilding"
|
||||
>
|
||||
<view
|
||||
class="flex size-10 items-center justify-center rounded-xl bg-cyan-50 text-cyan-500 transition-colors group-active:bg-cyan-100"
|
||||
>
|
||||
<view class="i-mdi-book-open-page-variant text-xl"></view>
|
||||
</view>
|
||||
<text class="text-[11px] font-medium text-gray-600"
|
||||
>字典管理</text
|
||||
>
|
||||
</view>
|
||||
<view
|
||||
class="w-[22%] flex flex-col items-center space-y-1.5 active:opacity-60"
|
||||
@click="handleBuilding"
|
||||
>
|
||||
<view
|
||||
class="flex size-10 items-center justify-center rounded-xl bg-yellow-50 text-yellow-500 transition-colors group-active:bg-yellow-100"
|
||||
>
|
||||
<view class="i-mdi-bullhorn text-xl"></view>
|
||||
</view>
|
||||
<text class="text-[11px] font-medium text-gray-600"
|
||||
>通知公告</text
|
||||
>
|
||||
</view>
|
||||
<view
|
||||
class="w-[22%] flex flex-col items-center space-y-1.5 active:opacity-60"
|
||||
@click="handleBuilding"
|
||||
>
|
||||
<view
|
||||
class="flex size-10 items-center justify-center rounded-xl bg-pink-50 text-pink-500 transition-colors group-active:bg-pink-100"
|
||||
>
|
||||
<view class="i-mdi-file-document-outline text-xl"></view>
|
||||
</view>
|
||||
<text class="text-[11px] font-medium text-gray-600"
|
||||
>日志管理</text
|
||||
>
|
||||
</view>
|
||||
<view
|
||||
class="w-[22%] flex flex-col items-center space-y-1.5 active:opacity-60"
|
||||
@click="handleBuilding"
|
||||
>
|
||||
<view
|
||||
class="flex size-10 items-center justify-center rounded-xl bg-gray-50 text-gray-500 transition-colors group-active:bg-gray-100"
|
||||
>
|
||||
<view class="i-mdi-dots-horizontal text-xl"></view>
|
||||
</view>
|
||||
<text class="text-[11px] font-medium text-gray-600">更多</text>
|
||||
</view>
|
||||
<view v-else class="work-empty-state">
|
||||
<view class="work-empty-icon">
|
||||
<view class="i-mdi-lock-outline work-empty-icon-glyph"></view>
|
||||
</view>
|
||||
<text class="work-empty-title">暂无已授权业务</text>
|
||||
<text class="work-empty-description">请联系管理员分配业务菜单权限</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -234,113 +65,291 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, getCurrentInstance } from "vue";
|
||||
import { computed, getCurrentInstance } from "vue";
|
||||
import TestBatchModeSwitch from "@/components/business/TestBatchModeSwitch.vue";
|
||||
import { businessMenuEntries } from "@/config/businessMenu";
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const current = ref(0);
|
||||
const swiperDotIndex = ref(0);
|
||||
const data = ref([
|
||||
{ image: "/static/images/banner/banner01.jpg" },
|
||||
{ image: "/static/images/banner/banner02.jpg" },
|
||||
{ image: "/static/images/banner/banner03.jpg" },
|
||||
]);
|
||||
const businessEntries = [
|
||||
{
|
||||
path: "/pages/business/inbound/index",
|
||||
title: "仓库入库",
|
||||
description: "扫描托盘登记入库",
|
||||
icon: "i-mdi-package-down",
|
||||
color: "blue",
|
||||
permissions: [
|
||||
"business:inbound:list",
|
||||
"business:inbound:query",
|
||||
"business:inbound:add",
|
||||
"business:inbound:edit",
|
||||
"business:inbound:confirm",
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/pages/business/filling/index",
|
||||
title: "托盘装填",
|
||||
description: "录入菌种和包数",
|
||||
icon: "i-mdi-package-variant-closed-plus",
|
||||
color: "green",
|
||||
permissions: [
|
||||
"business:filling:list",
|
||||
"business:filling:query",
|
||||
"business:filling:add",
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/pages/business/shipment/index",
|
||||
title: "发货出库",
|
||||
description: "扫码创建发货单",
|
||||
icon: "i-mdi-truck-delivery",
|
||||
color: "orange",
|
||||
permissions: [
|
||||
"business:shipment:list",
|
||||
"business:shipment:query",
|
||||
"business:shipment:add",
|
||||
"business:shipment:edit",
|
||||
"business:shipment:confirm",
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/pages/business/usage/index",
|
||||
title: "现场使用",
|
||||
description: "绑定产线记录消耗",
|
||||
icon: "i-mdi-factory",
|
||||
color: "purple",
|
||||
permissions: [
|
||||
"business:usage:list",
|
||||
"business:usage:query",
|
||||
"business:usage:add",
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/pages/business/return/index",
|
||||
title: "退回仓库",
|
||||
description: "申请和确认退回",
|
||||
icon: "i-mdi-undo-variant",
|
||||
color: "red",
|
||||
permissions: [
|
||||
"business:return:list",
|
||||
"business:return:query",
|
||||
"business:return:request",
|
||||
"business:return:confirm",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const visibleBusinessEntries = computed(() =>
|
||||
businessEntries.filter((entry) => proxy.$auth.hasPermiOr(entry.permissions)),
|
||||
businessMenuEntries.filter((entry) => proxy.$auth.hasPermi(entry.permission)),
|
||||
);
|
||||
|
||||
function clickBannerItem(item) {
|
||||
console.info(item);
|
||||
}
|
||||
|
||||
function changeSwiper(e) {
|
||||
current.value = e.detail.current;
|
||||
}
|
||||
|
||||
function handleToAiChat() {
|
||||
handleBuilding();
|
||||
}
|
||||
|
||||
function handleToUserList() {
|
||||
handleBuilding();
|
||||
}
|
||||
|
||||
function handleBusinessEntry(entry) {
|
||||
if (!proxy.$auth.hasPermiOr(entry.permissions)) {
|
||||
proxy.$modal.msgError("当前账号没有该业务权限");
|
||||
if (!proxy.$auth.hasPermi(entry.permission)) {
|
||||
proxy.$modal.msgError("当前账号没有该业务菜单权限");
|
||||
return;
|
||||
}
|
||||
proxy.$tab.navigateTo(entry.path);
|
||||
}
|
||||
|
||||
function handleBuilding() {
|
||||
proxy.$modal.msg("模块建设中~");
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.work-page {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
background: #f4f8f8;
|
||||
}
|
||||
|
||||
.work-scroll {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.work-content {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
padding: 24rpx 28rpx calc(140rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.work-heading {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 18rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.work-heading-copy {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.work-kicker {
|
||||
display: block;
|
||||
color: #28225c;
|
||||
font-size: 38rpx;
|
||||
font-weight: 700;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.work-subtitle {
|
||||
display: block;
|
||||
margin-top: 10rpx;
|
||||
color: #6c7786;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.55;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.work-permission-count {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: baseline;
|
||||
border-radius: 14rpx;
|
||||
padding: 12rpx 16rpx;
|
||||
background: #e9f7f6;
|
||||
color: #008596;
|
||||
flex-direction: column;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.work-permission-value {
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.work-permission-label {
|
||||
margin-top: 4rpx;
|
||||
font-size: 20rpx;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.work-section {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
margin-top: 20rpx;
|
||||
border-radius: 20rpx;
|
||||
padding: 24rpx;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 8rpx 24rpx rgba(40, 34, 92, 0.06);
|
||||
}
|
||||
|
||||
.work-section-header {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: flex-start;
|
||||
gap: 14rpx;
|
||||
}
|
||||
|
||||
.work-section-marker {
|
||||
width: 8rpx;
|
||||
height: 38rpx;
|
||||
flex: 0 0 8rpx;
|
||||
border-radius: 999rpx;
|
||||
background: #008596;
|
||||
}
|
||||
|
||||
.work-section-copy {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.work-section-title {
|
||||
display: block;
|
||||
color: #28225c;
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.work-section-description {
|
||||
display: block;
|
||||
margin-top: 6rpx;
|
||||
color: #8b969d;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.work-entry-list {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
margin-top: 20rpx;
|
||||
gap: 12rpx;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.work-entry {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
border: 1rpx solid #e5eeee;
|
||||
border-radius: 16rpx;
|
||||
padding: 18rpx;
|
||||
background: #fbfdfd;
|
||||
}
|
||||
|
||||
.work-entry:active {
|
||||
border-color: #8ccbc5;
|
||||
background: #f0faf9;
|
||||
}
|
||||
|
||||
.work-entry-icon {
|
||||
display: flex;
|
||||
width: 82rpx;
|
||||
height: 82rpx;
|
||||
flex: 0 0 82rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 18rpx;
|
||||
}
|
||||
|
||||
.work-entry-icon-glyph {
|
||||
font-size: 42rpx;
|
||||
}
|
||||
|
||||
.work-entry-copy {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.work-entry-title,
|
||||
.work-entry-description {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.work-entry-title {
|
||||
color: #27313a;
|
||||
font-size: 27rpx;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.work-entry-description {
|
||||
margin-top: 7rpx;
|
||||
color: #7b8791;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.work-entry-arrow {
|
||||
flex: 0 0 34rpx;
|
||||
color: #a9b7bb;
|
||||
font-size: 34rpx;
|
||||
}
|
||||
|
||||
.work-empty-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 70rpx 16rpx 54rpx;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.work-empty-icon {
|
||||
display: flex;
|
||||
width: 84rpx;
|
||||
height: 84rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: #f1f5f5;
|
||||
color: #9aa5ad;
|
||||
}
|
||||
|
||||
.work-empty-icon-glyph {
|
||||
font-size: 42rpx;
|
||||
}
|
||||
|
||||
.work-empty-title {
|
||||
display: block;
|
||||
margin-top: 20rpx;
|
||||
color: #4b5563;
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.work-empty-description {
|
||||
display: block;
|
||||
margin-top: 8rpx;
|
||||
color: #9aa5ad;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.5;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 420px) {
|
||||
.work-content {
|
||||
padding-right: 20rpx;
|
||||
padding-left: 20rpx;
|
||||
}
|
||||
|
||||
.work-kicker {
|
||||
font-size: 34rpx;
|
||||
}
|
||||
|
||||
.work-section {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.work-entry {
|
||||
gap: 12rpx;
|
||||
padding: 14rpx;
|
||||
}
|
||||
|
||||
.work-entry-icon {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
flex-basis: 72rpx;
|
||||
}
|
||||
|
||||
.work-entry-icon-glyph {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
@@ -159,21 +159,95 @@ test("PDA 路由和工作台入口覆盖五类现场业务", async () => {
|
||||
assert.equal(pagePaths.has(pagePath), true, `${pagePath} 路由缺失`);
|
||||
}
|
||||
|
||||
const workSource = await readFile(
|
||||
path.join(appRoot, "src/pages/work/index.vue"),
|
||||
"utf8",
|
||||
const [workSource, menuSource] = await Promise.all([
|
||||
readFile(path.join(appRoot, "src/pages/work/index.vue"), "utf8"),
|
||||
readFile(path.join(appRoot, "src/config/businessMenu.js"), "utf8"),
|
||||
]);
|
||||
assert.match(workSource, /hasPermi\(entry\.permission\)/);
|
||||
assert.match(menuSource, /business:inbound:list/);
|
||||
assert.match(menuSource, /business:filling:list/);
|
||||
assert.match(menuSource, /business:shipment:list/);
|
||||
assert.match(menuSource, /business:usage:list/);
|
||||
assert.match(menuSource, /business:return:list/);
|
||||
assert.match(menuSource, /pages\/business\/inbound\/index/);
|
||||
assert.match(menuSource, /pages\/business\/filling\/index/);
|
||||
assert.match(menuSource, /pages\/business\/shipment\/index/);
|
||||
assert.match(menuSource, /pages\/business\/usage\/index/);
|
||||
assert.match(menuSource, /pages\/business\/return\/index/);
|
||||
});
|
||||
|
||||
test("首页展示真实运营驾驶舱,不保留模板入口和虚假统计", async () => {
|
||||
const source = await readFile(path.join(appRoot, "src/pages/index.vue"), "utf8");
|
||||
assert.match(source, /listScanExceptions/);
|
||||
assert.match(source, /最近扫码异常/);
|
||||
assert.match(source, /TestBatchBanner/);
|
||||
assert.doesNotMatch(
|
||||
source,
|
||||
/快捷入口|新闻|计划|请假申请|周报|工作效率|新通知|待办任务|85%/,
|
||||
);
|
||||
assert.match(workSource, /hasPermiOr/);
|
||||
assert.match(workSource, /business:inbound/);
|
||||
assert.match(workSource, /business:filling/);
|
||||
assert.match(workSource, /business:shipment/);
|
||||
assert.match(workSource, /business:usage/);
|
||||
assert.match(workSource, /business:return/);
|
||||
assert.match(workSource, /pages\/business\/inbound\/index/);
|
||||
assert.match(workSource, /pages\/business\/filling\/index/);
|
||||
assert.match(workSource, /pages\/business\/shipment\/index/);
|
||||
assert.match(workSource, /pages\/business\/usage\/index/);
|
||||
assert.match(workSource, /pages\/business\/return\/index/);
|
||||
});
|
||||
|
||||
test("工作台只展示真实业务入口并使用后端列表权限", async () => {
|
||||
const [workSource, menuModule] = await Promise.all([
|
||||
readFile(path.join(appRoot, "src/pages/work/index.vue"), "utf8"),
|
||||
importSource("src/config/businessMenu.js"),
|
||||
]);
|
||||
|
||||
assert.deepEqual(
|
||||
menuModule.businessMenuEntries.map(({ title, permission, path: entryPath }) => ({
|
||||
title,
|
||||
permission,
|
||||
path: entryPath,
|
||||
})),
|
||||
[
|
||||
{
|
||||
title: "仓库入库",
|
||||
permission: "business:inbound:list",
|
||||
path: "/pages/business/inbound/index",
|
||||
},
|
||||
{
|
||||
title: "工厂装填",
|
||||
permission: "business:filling:list",
|
||||
path: "/pages/business/filling/index",
|
||||
},
|
||||
{
|
||||
title: "发货出库",
|
||||
permission: "business:shipment:list",
|
||||
path: "/pages/business/shipment/index",
|
||||
},
|
||||
{
|
||||
title: "客户现场使用",
|
||||
permission: "business:usage:list",
|
||||
path: "/pages/business/usage/index",
|
||||
},
|
||||
{
|
||||
title: "退回仓库",
|
||||
permission: "business:return:list",
|
||||
path: "/pages/business/return/index",
|
||||
},
|
||||
],
|
||||
);
|
||||
assert.match(workSource, /businessMenuEntries/);
|
||||
assert.match(workSource, /hasPermi\(entry\.permission\)/);
|
||||
assert.match(workSource, /navigateTo\(entry\.path\)/);
|
||||
assert.doesNotMatch(workSource, /AI 生产力|智能对话|图像生成|系统管理|handleBuilding|模块建设中/);
|
||||
});
|
||||
|
||||
test("运营驾驶舱 API 对接现有扫码异常接口", async () => {
|
||||
const source = await readFile(path.join(appRoot, "src/api/business/dashboard.js"), "utf8");
|
||||
assert.match(source, /export function listScanExceptions\(/);
|
||||
assert.match(source, /url:\s*["']\/business\/dashboard\/scan-exceptions["']/);
|
||||
assert.match(source, /method:\s*["']get["']/);
|
||||
});
|
||||
|
||||
test("APP 页面标题与后端业务菜单名称一致", async () => {
|
||||
const [pages, fillingSource] = await Promise.all([
|
||||
JSON.parse(await readFile(path.join(appRoot, "src/pages.json"), "utf8")),
|
||||
readFile(path.join(appRoot, "src/pages/business/filling/index.vue"), "utf8"),
|
||||
]);
|
||||
const fillingPage = pages.pages.find((page) => page.path === "pages/business/filling/index");
|
||||
assert.equal(fillingPage?.style?.navigationBarTitleText, "工厂装填");
|
||||
assert.match(fillingSource, />工厂装填</);
|
||||
});
|
||||
|
||||
test("PDA 测试模式附加批次请求头并在五个业务页持续提示", async () => {
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import test from "node:test";
|
||||
|
||||
const appRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const readSource = (relativePath) =>
|
||||
readFileSync(resolve(appRoot, relativePath), "utf8");
|
||||
|
||||
test("APP 首页展示 PC 端核心运营统计并保护移动端横向空间", () => {
|
||||
const source = readSource("src/pages/index.vue");
|
||||
|
||||
for (const className of [
|
||||
"dashboard-overview",
|
||||
"dashboard-kpi-grid",
|
||||
"dashboard-trend-card",
|
||||
"dashboard-distribution-card",
|
||||
"dashboard-trend-range",
|
||||
"dashboard-status-list",
|
||||
]) {
|
||||
assert.match(source, new RegExp(`class=\\"[^\\"]*${className}[^\\"]*\\"`));
|
||||
}
|
||||
|
||||
for (const label of [
|
||||
"当前库存托盘",
|
||||
"剩余包数",
|
||||
"今日入库单",
|
||||
"今日出库单",
|
||||
"待确认单据",
|
||||
"入库 / 出库趋势",
|
||||
"托盘流转状态分布",
|
||||
]) {
|
||||
assert.match(source, new RegExp(label));
|
||||
}
|
||||
|
||||
assert.match(source, /listPalletInventory/);
|
||||
assert.match(source, /listShipmentReport/);
|
||||
assert.match(source, /listInbound/);
|
||||
assert.match(source, /listShipment/);
|
||||
assert.match(source, /listReturn/);
|
||||
assert.match(source, /TREND_RANGE_OPTIONS/);
|
||||
assert.match(source, /overflow-x:\s*hidden/);
|
||||
assert.match(source, /min-width:\s*0/);
|
||||
});
|
||||
|
||||
test("APP 首页统计保留加载、空数据和部分失败状态", () => {
|
||||
const source = readSource("src/pages/index.vue");
|
||||
|
||||
assert.match(source, /statsLoading/);
|
||||
assert.match(source, /statsError/);
|
||||
assert.match(source, /dashboard-stat-empty/);
|
||||
assert.match(source, /dashboard-stat-error/);
|
||||
assert.match(source, /formatMetric/);
|
||||
assert.match(source, /--/);
|
||||
});
|
||||
|
||||
test("APP 首页扫码异常支持默认收起并通过标题展开", () => {
|
||||
const source = readSource("src/pages/index.vue");
|
||||
|
||||
assert.match(source, /const exceptionsExpanded = ref\(false\)/);
|
||||
assert.match(source, /function toggleExceptionList\(\)/);
|
||||
assert.match(source, /class="[^"]*exception-header[^"]*"/);
|
||||
assert.match(source, /@click="toggleExceptionList"/);
|
||||
assert.match(source, /exception-toggle-icon/);
|
||||
assert.match(source, /v-else-if="exceptionsExpanded" class="exception-list"/);
|
||||
});
|
||||
|
||||
test("APP 首页报表 API 复用现有只读报表路径", () => {
|
||||
const inventorySource = readSource("src/api/report/palletInventory.js");
|
||||
const shipmentSource = readSource("src/api/report/shipment.js");
|
||||
|
||||
assert.match(inventorySource, /export function listPalletInventory\(/);
|
||||
assert.match(inventorySource, /url:\s*["']\/report\/pallet-inventory\/list["']/);
|
||||
assert.match(inventorySource, /method:\s*["']get["']/);
|
||||
assert.match(shipmentSource, /export function listShipmentReport\(/);
|
||||
assert.match(shipmentSource, /url:\s*["']\/report\/shipment\/list["']/);
|
||||
assert.match(shipmentSource, /method:\s*["']get["']/);
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import test from "node:test";
|
||||
|
||||
const appRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const loginPagePath = resolve(appRoot, "src/pages/login.vue");
|
||||
const loginPageSource = readFileSync(loginPagePath, "utf8");
|
||||
|
||||
test("APP 登录页沿用 PC 端的品牌背景与登录卡片结构", () => {
|
||||
assert.match(loginPageSource, /class="login-page"/);
|
||||
assert.match(loginPageSource, /\/static\/login-background\.jpg/);
|
||||
assert.match(loginPageSource, /class="login-card"/);
|
||||
assert.match(loginPageSource, /globalConfig\.appInfo\.name/);
|
||||
assert.doesNotMatch(
|
||||
loginPageSource,
|
||||
/bg-gradient-to-br from-blue-50 to-indigo-100/,
|
||||
);
|
||||
assert.ok(
|
||||
existsSync(resolve(appRoot, "src/static/login-background.jpg")),
|
||||
"APP 登录页需要包含与 PC 端一致的背景资源",
|
||||
);
|
||||
});
|
||||
|
||||
test("APP 登录页保留验证码刷新与回车登录交互", () => {
|
||||
assert.match(loginPageSource, /@click="getCode"/);
|
||||
assert.match(loginPageSource, /@confirm="handleLogin"/);
|
||||
assert.match(loginPageSource, /captchaEnabled/);
|
||||
});
|
||||
|
||||
test("APP 登录页为白色 Sylvan 字标提供深色承载层", () => {
|
||||
assert.match(loginPageSource, /class="brand-logo-frame"/);
|
||||
assert.match(
|
||||
loginPageSource,
|
||||
/\.brand-logo-frame\s*\{[\s\S]*background:\s*#28225c/,
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import test from "node:test";
|
||||
|
||||
const appRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const readSource = (relativePath) =>
|
||||
readFileSync(resolve(appRoot, relativePath), "utf8");
|
||||
|
||||
const homeSource = readSource("src/pages/index.vue");
|
||||
const workSource = readSource("src/pages/work/index.vue");
|
||||
const bannerSource = readSource("src/components/business/TestBatchBanner.vue");
|
||||
const modeSwitchSource = readSource(
|
||||
"src/components/business/TestBatchModeSwitch.vue",
|
||||
);
|
||||
|
||||
test("首页使用受约束的运营布局并保护横向空间", () => {
|
||||
assert.match(homeSource, /class="dashboard-page"/);
|
||||
assert.match(homeSource, /class="dashboard-scroll"/);
|
||||
assert.match(homeSource, /class="dashboard-content"/);
|
||||
assert.match(homeSource, /class="dashboard-hero"/);
|
||||
assert.match(homeSource, /class="dashboard-metric-grid"/);
|
||||
assert.match(homeSource, /class="exception-list"/);
|
||||
assert.match(homeSource, /class="exception-item"/);
|
||||
assert.match(homeSource, /overflow-x:\s*hidden/);
|
||||
assert.match(homeSource, /overflow-wrap:\s*anywhere/);
|
||||
});
|
||||
|
||||
test("工作台入口在窄屏下保持单列和可换行", () => {
|
||||
assert.match(workSource, /class="work-page"/);
|
||||
assert.match(workSource, /class="work-scroll"/);
|
||||
assert.match(workSource, /class="work-content"/);
|
||||
assert.match(workSource, /class="work-entry-list"/);
|
||||
assert.match(workSource, /class="work-entry"/);
|
||||
assert.match(workSource, /class="work-entry-copy"/);
|
||||
assert.match(workSource, /overflow-x:\s*hidden/);
|
||||
assert.match(workSource, /overflow-wrap:\s*anywhere/);
|
||||
});
|
||||
|
||||
test("测试批次提示和切换区不会撑开页面", () => {
|
||||
assert.match(bannerSource, /class="test-batch-banner"/);
|
||||
assert.match(bannerSource, /overflow-wrap:\s*anywhere/);
|
||||
assert.match(modeSwitchSource, /class="test-batch-switch"/);
|
||||
assert.match(modeSwitchSource, /class="test-batch-picker"/);
|
||||
assert.match(modeSwitchSource, /overflow-wrap:\s*anywhere/);
|
||||
});
|
||||
Reference in New Issue
Block a user