first commit

This commit is contained in:
2026-09-02 16:31:50 +08:00
commit a461727193
911 changed files with 692450 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
# 配置项文档:https://editorconfig.org/
# 告知 EditorConfig 插件,当前即是根文件
root = true
# 适用全部文件
[*]
## 设置字符集
charset = utf-8
## 缩进风格 space | tab,建议 space
indent_style = space
## 缩进的空格数(修改这里的话需要将 prettier.config.js 和 .vscode -> settings.json 也同步修改)
indent_size = 2
## 换行符类型 lf | cr | crlf,一般都是设置为 lf
end_of_line = lf
## 是否在文件末尾插入空白行
insert_final_newline = true
## 是否删除一行中的前后空格
trim_trailing_whitespace = true
# 适用 .md 文件
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false
+10
View File
@@ -0,0 +1,10 @@
# 自定义的环境变量(命名必须以 VITE_ 开头)
## 后端接口公共路径(如果解决跨域问题采用反向代理就只需写公共路径)
VITE_BASE_API = 'http://127.0.0.1:8078/api/'
## 路由模式 hash 或 html5
VITE_ROUTER_HISTORY = 'hash'
## 开发环境地址前缀(一般 '/','./' 都可以)
VITE_PUBLIC_PATH = '/'
+10
View File
@@ -0,0 +1,10 @@
# 自定义的环境变量(命名必须以 VITE_ 开头)
## 后端接口公共路径(如果解决跨域问题采用 CORS 就需要写全路径)
VITE_BASE_API = 'http://127.0.0.1:8078/api/'
## 路由模式 hash 或 html5
VITE_ROUTER_HISTORY = 'hash'
## 打包路径(就是网站前缀,例如部署到 https://un-pany.github.io/v3-admin-vite/ 域名下,就需要填写 /v3-admin-vite/)
VITE_PUBLIC_PATH = '/'
+11
View File
@@ -0,0 +1,11 @@
# 自定义的环境变量(命名必须以 VITE_ 开头)
## 后端接口公共路径(如果解决跨域问题采用 CORS 就需要写全路径)
# VITE_BASE_API = 'https://mock.mengxuegu.com/mock/63218b5fb4c53348ed2bc212/api/v1'
VITE_BASE_API = 'http://192.168.2.212:5000/api/'
## 路由模式 hash 或 html5
VITE_ROUTER_HISTORY = 'hash'
## 打包路径(就是网站前缀,例如部署到 https://un-pany.github.io/v3-admin-vite/ 域名下,就需要填写 /v3-admin-vite/)
VITE_PUBLIC_PATH = '/acs-office/'
+8
View File
@@ -0,0 +1,8 @@
# Eslint 会忽略的文件
.DS_Store
node_modules
dist
dist-ssr
*.local
.npmrc
+69
View File
@@ -0,0 +1,69 @@
module.exports = {
root: true,
env: {
browser: true,
node: true,
es6: true
},
extends: [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/typescript/recommended",
"@vue/prettier",
"@vue/eslint-config-typescript"
],
parser: "vue-eslint-parser",
parserOptions: {
parser: "@typescript-eslint/parser",
ecmaVersion: 2020,
sourceType: "module",
jsxPragma: "React",
ecmaFeatures: {
jsx: true,
tsx: true
}
},
rules: {
// TS
"@typescript-eslint/no-explicit-any": "off",
"no-debugger": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_"
}
],
"no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_"
}
],
// Vue
"vue/no-v-html": "off",
"vue/require-default-prop": "off",
"vue/require-explicit-emits": "off",
"vue/multi-word-component-names": "off",
"vue/html-self-closing": [
"error",
{
html: {
void: "always",
normal: "always",
component: "always"
},
svg: "always",
math: "always"
}
],
// Prettier
"prettier/prettier": "off"
}
}
+37
View File
@@ -0,0 +1,37 @@
# Git 会忽略的文件
.DS_Store
node_modules
dist
dist-ssr
.eslintcache
# Local env files
*.local
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Editor directories and files
.vscode/*
!.vscode/extensions.json
!.vscode/settings.json
!.vscode/*.code-snippets
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.zip
# Use the PNPM
package-lock.json
yarn.lock
1013-JSHS-ACS_WEB_dist.zip
+2
View File
@@ -0,0 +1,2 @@
# 通过该配置兜底解决组件没有类型提示的问题
shamefully-hoist = true
+8
View File
@@ -0,0 +1,8 @@
# Prettier 会忽略的文件
.DS_Store
node_modules
dist
dist-ssr
*.local
.npmrc
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022-present pany <https://github.com/pany-ang>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+3
View File
@@ -0,0 +1,3 @@
# PaperPreparation
RFID 原纸在线管理系统-叉车备纸 @2024
+3
View File
@@ -0,0 +1,3 @@
1、运行项目:npm run dev
2、.env.development是开发环境、.env.production是发布环境
3、发布项目:npm run build:dev
+1
View File
@@ -0,0 +1 @@
declare module 'file-saver'
+22
View File
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="/favicon.ico" />
<link rel="stylesheet" href="/app-loading.css" />
<title>WMS PDA</title>
</head>
<body>
<div id="app">
<div id="app-loading"></div>
</div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
+79
View File
@@ -0,0 +1,79 @@
{
"scripts": {
"dev": "vite",
"build:dev": "vite build",
"build:stage": "vue-tsc --noEmit && vite build --mode staging",
"build:prod": "vue-tsc --noEmit && vite build",
"preview:stage": "pnpm build:stage && vite preview",
"preview:prod": "pnpm build:prod && vite preview",
"lint:eslint": "eslint --cache --max-warnings 0 \"{src,tests,types}/**/*.{vue,js,jsx,ts,tsx}\" --fix",
"lint:prettier": "prettier --write \"{src,tests,types}/**/*.{vue,js,jsx,ts,tsx,json,css,less,scss,html,md}\"",
"lint": "pnpm lint:eslint && pnpm lint:prettier",
"test": "vitest"
},
"dependencies": {
"@element-plus/icons-vue": "2.1.0",
"@microsoft/signalr": "^7.0.10",
"axios": "1.5.0",
"dayjs": "^1.11.9",
"echarts": "^5.4.3",
"element-plus": "2.3.12",
"file-saver": "^2.0.5",
"js-cookie": "3.0.5",
"lodash-es": "4.17.21",
"mitt": "3.0.1",
"normalize.css": "8.0.1",
"nprogress": "0.2.0",
"path-browserify": "1.0.1",
"path-to-regexp": "6.2.1",
"pinia": "2.1.6",
"screenfull": "6.0.2",
"vue": "3.3.4",
"vue-router": "4.2.4",
"vxe-table": "4.4.1",
"vxe-table-plugin-element": "3.0.7",
"xe-utils": "3.5.11",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@types/js-cookie": "3.0.3",
"@types/lodash-es": "4.17.8",
"@types/node": "20.5.7",
"@types/nprogress": "0.2.0",
"@types/path-browserify": "1.0.0",
"@typescript-eslint/eslint-plugin": "6.4.1",
"@typescript-eslint/parser": "6.4.1",
"@vitejs/plugin-vue": "4.3.3",
"@vitejs/plugin-vue-jsx": "3.0.2",
"@vue/eslint-config-prettier": "8.0.0",
"@vue/eslint-config-typescript": "11.0.3",
"@vue/test-utils": "2.4.1",
"eslint": "8.48.0",
"eslint-plugin-prettier": "5.0.0",
"eslint-plugin-vue": "9.17.0",
"jsdom": "22.1.0",
"lint-staged": "14.0.1",
"prettier": "3.0.2",
"sass": "1.66.1",
"typescript": "5.2.2",
"unocss": "0.55.3",
"vite": "4.4.9",
"vite-plugin-svg-icons": "2.0.1",
"vite-svg-loader": "4.0.0",
"vitest": "0.34.3",
"vue-eslint-parser": "9.3.1",
"vue-tsc": "1.8.8"
},
"lint-staged": {
"*.{vue,js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{css,less,scss,html,md}": [
"prettier --write"
],
"package.json": [
"prettier --write"
]
}
}
+5323
View File
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
/** 配置项文档:https://prettier.io/docs/en/configuration.html */
module.exports = {
/** 每一行的宽度 */
printWidth: 120,
/** Tab 键的空格数 */
tabWidth: 2,
/** 在对象中的括号之间是否用空格来间隔 */
bracketSpacing: true,
/** 箭头函数的参数无论有几个,都要括号包裹 */
arrowParens: "always",
/** 换行符的使用 */
endOfLine: "auto",
/** 是否采用单引号 */
singleQuote: false,
/** 对象或者数组的最后一个元素后面不要加逗号 */
trailingComma: "none",
/** 是否加分号 */
semi: false,
/** 是否使用 Tab 格式化 */
useTabs: false
}
+45
View File
@@ -0,0 +1,45 @@
/** 白屏阶段会执行的 CSS 加载动画 */
#app-loading {
position: relative;
top: 45vh;
margin: 0 auto;
color: #409eff;
font-size: 12px;
}
#app-loading,
#app-loading::before,
#app-loading::after {
width: 2em;
height: 2em;
border-radius: 50%;
animation: 2s ease-in-out infinite app-loading-animation;
}
#app-loading::before,
#app-loading::after {
content: "";
position: absolute;
}
#app-loading::before {
left: -4em;
animation-delay: -0.2s;
}
#app-loading::after {
left: 4em;
animation-delay: 0.2s;
}
@keyframes app-loading-animation {
0%,
80%,
100% {
box-shadow: 0 2em 0 -2em;
}
40% {
box-shadow: 0 2em 0 0;
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 951 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

+3
View File
@@ -0,0 +1,3 @@
{
"baseApiUrl":"http://127.0.0.1:8078/api/"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

+31
View File
@@ -0,0 +1,31 @@
<script lang="ts" setup>
// import { h } from "vue"
import { useTheme } from "@/hooks/useTheme"
// import { ElNotification } from "element-plus"
// 将 Element Plus 的语言设置为中文
import zhCn from "element-plus/es/locale/lang/zh-cn"
const { initTheme } = useTheme()
/** 初始化主题 */
initTheme()
/** 作者小心思 */
// ElNotification({
// title: "Hello",
// type: "success",
// message: h(
// "a",
// { style: "color: teal", target: "_blank", href: "https://github.com/un-pany/v3-admin-vite" },
// "小项目获取 star 不易,如果你喜欢这个项目的话,欢迎点击这里支持一个 star !这是作者持续维护的唯一动力(小声:毕竟是免费的)"
// ),
// duration: 0,
// position: "bottom-right"
// })
</script>
<template>
<el-config-provider :locale="zhCn">
<router-view />
</el-config-provider>
</template>
+47
View File
@@ -0,0 +1,47 @@
import { request } from "@/utils/service"
import * as agvTask from '@/dto/agvTask/agvTask'
export function GetAgvTask() {
return request<AgvTaskResponseData>({
url: "AgvTask/GetAll",
method: "get",
})
}
export function GetAlarmInfoes() {
return request<AgvTaskAlarmResponseData>({
url: "AgvTask/GetAlarmInfoes",
method: "get",
})
}
export function PostAlarmHandleResult(agvTaskId: string) {
return request<PostAlarmHandleResultResponseData>({
url: "AgvTask/PostAlarmHandleResult?agvTaskId=" + agvTaskId,
method: "post",
})
}
/**
* 获取搬运物料的AgvTask
* @returns
*/
export function GetCarryAgvTask() {
return request<AgvTaskResponseData>({
url: "AgvTask/GetCarryAgvTask",
method: "get",
})
}
//判断是否重复入库(30分钟内同一库位存在入库任务,则弹窗提示二次确认)
export function IsReStockIn(wareHouse: string) {
return request<IsReStockInResponseData>({
url: "AgvTask/IsReStockIn?wareHouse=" + wareHouse,
method: "post",
})
}
export type AgvTaskResponseData = ApiResponseDataArray<agvTask.agvTaskInfo>
export type AgvTaskAlarmResponseData = ApiResponseDataArray<agvTask.agvTaskFailedAlarmInfo>
export type PostAlarmHandleResultResponseData = ApiResponseData<boolean>
export type IsReStockInResponseData = ApiResponseData<boolean>
@@ -0,0 +1,44 @@
import { request } from "@/utils/service"
import * as carryIn from '@/dto/carryMaterial/carryMaterial'
/**
* 物料搬运,起点库位验证
* @param startRankNo
* @returns
*/
export function CheckCarryStartRankNo(startRankNo: string) {
return request<CheckRanNoResponseData>({
url: "Warehouse/CheckCarryStartRankNo?startRankNo="+startRankNo,
method: "post",
})
}
/**
* 物料搬运,终点库位验证
* @param startRankNo
* @returns
*/
export function CheckCarryEndRankNo(endRankNo: string) {
return request<CheckRanNoResponseData>({
url: "Warehouse/CheckCarryEndRankNo?endRankNo="+endRankNo,
method: "post",
})
}
/**
* 搬运物料
* @param agvTaskId
* @returns
*/
export function PostCarryMaterial(data:carryIn.CarryInParam) {
return request<CarryMaterialResponseData>({
url: "Warehouse/CarryMaterial",
method: "post",
data
})
}
export type CheckRanNoResponseData = ApiResponseData<string>
export type CarryMaterialResponseData = ApiResponseData<string>
+11
View File
@@ -0,0 +1,11 @@
import { request } from "@/utils/service"
/** 获取 班组信息 */
export function GetGroupNames() {
return request<GetGroupNameResponseData>({
url: "Report/GetGroupNames",
method: "get"
})
}
export type GetGroupNameResponseData = ApiResponseDataArray<String>
@@ -0,0 +1,11 @@
import * as elevatorCount from '@/dto/elevatorCount/elevatorCount'
import { request } from "@/utils/service"
export function GetElevatorCountToday() {
return request<GetElevatorCountResponseData>({
url: "ElevatorCount/GetElevatorCountToday",
method: "get",
})
}
export type GetElevatorCountResponseData = ApiResponseDataArray<elevatorCount.elevatorCount>
@@ -0,0 +1,11 @@
import * as elevatorQueue from '@/dto/elevatorQueue/elevatorQueue'
import { request } from "@/utils/service"
export function GetElevatorQueToday() {
return request<GetElevatorQueTodayResponseData>({
url: "ElevatorQue/GetElevatorQueToday",
method: "get",
})
}
export type GetElevatorQueTodayResponseData = ApiResponseDataArray<elevatorQueue.elevatorQueue>
+49
View File
@@ -0,0 +1,49 @@
import { request } from "@/utils/service"
import * as erpTask from '@/dto/erpTask/erpTask'
//2-PDA入库 5-尾拖回库
export function GetErpTask(taskType:number){
return request<ErpTaskResponseData>({
url: "Erp/GetLatestErpTasks?taskType="+taskType,
method: "get"
})
}
//合格证是否存在 taskType 2-入库 5-尾拖回库 6-搬运
export function IsBarCodeExist(barCode:string,taskType:number){
return request<BarCodeExistResponseData>({
url: "Erp/IsBarCodeExist?barCode="+barCode+"&taskType="+taskType,
method: "post"
})
}
//更新单个任务状态 taskStatus 4-暂停 -1 取消
export function UpdateTaskStatus(id: string, taskStatus: number) {
return request<UpdateTaskStatusResponseData>({
url: "Erp/UpdateTaskStatus?id=" + id + "&taskStatus=" + taskStatus,
method: "post"
})
}
//暂停所有待执行的任务
export function StopAll(taskType:number) {
return request<StopAllResponseData>({
url: "Erp/StopAll?taskType="+taskType,
method: "get"
})
}
//恢复所有暂停的任务
export function ResumeAll(taskType:number) {
return request<ResumeAllResponseData>({
url: "Erp/ResumeAll?taskType="+taskType,
method: "get"
})
}
export type ErpTaskResponseData = ApiResponseDataArray<erpTask.erpTaskInfo>
export type BarCodeExistResponseData = ApiResponseData<boolean>
export type UpdateTaskStatusResponseData = ApiResponseData<boolean>
export type StopAllResponseData = ApiResponseData<boolean>
export type ResumeAllResponseData = ApiResponseData<boolean>
@@ -0,0 +1,36 @@
/** 模拟接口响应数据 */
const SELECT_RESPONSE_DATA = {
code: 0,
data: [
{
label: "苹果",
value: 1
},
{
label: "香蕉",
value: 2
},
{
label: "橘子",
value: 3,
disabled: true
}
],
message: "获取 Select 数据成功"
}
/** 模拟接口 */
export function getSelectDataApi() {
return new Promise<typeof SELECT_RESPONSE_DATA>((resolve, reject) => {
// 模拟接口响应时间 2s
setTimeout(() => {
// 模拟接口调用成功
if (Math.random() < 0.8) {
resolve(SELECT_RESPONSE_DATA)
} else {
// 模拟接口调用出错
reject(new Error("接口发生错误"))
}
}, 2000)
})
}
@@ -0,0 +1,26 @@
/** 模拟接口响应数据 */
const SUCCESS_RESPONSE_DATA = {
code: 0,
data: {
list: [] as number[]
},
message: "获取成功"
}
/** 模拟请求接口成功 */
export function getSuccessApi(list: number[]) {
return new Promise<typeof SUCCESS_RESPONSE_DATA>((resolve) => {
setTimeout(() => {
resolve({ ...SUCCESS_RESPONSE_DATA, data: { list } })
}, 1000)
})
}
/** 模拟请求接口失败 */
export function getErrorApi() {
return new Promise((_resolve, reject) => {
setTimeout(() => {
reject(new Error("发生错误"))
}, 1000)
})
}
+26
View File
@@ -0,0 +1,26 @@
import { request } from "@/utils/service"
import * as Login from "./types/login"
/** 获取所有用户 Token */
export function getAllUser() {
return request<Login.AllUserInfo>({
url: "SysUserInfo/GetAll",
method: "get"
})
}
/** 登录并返回 Token */
export function loginApi(data: Login.LoginRequestData) {
return request<Login.LoginResponseData>({
url: `SysUserInfo/Login?loginId=${data.loginId}&pwd=${data.pwd}`,
method: "get"
})
}
/** 获取用户详情 */
export function getUserInfoApi() {
return request<Login.UserInfoResponseData>({
url: "users/info",
method: "get"
})
}
+19
View File
@@ -0,0 +1,19 @@
import { UserPermissionDto } from "@/dto/user/userPermissionDto"
import { UserDto } from "@/dto/user/userdto"
export interface LoginRequestData {
/** admin 或 editor */
loginId: string
/** 密码 */
pwd: string
// /** 验证码 */
// code: string
}
export type AllUserInfo = ApiResponseData<UserDto[]>
//export type LoginCodeResponseData = ApiResponseData<string>
export type LoginResponseData = ApiResponseData<UserPermissionDto>
export type UserInfoResponseData = ApiResponseData<{ username: string; roles: string[] }>
@@ -0,0 +1,76 @@
import { request } from "@/utils/service"
import * as materialInfo from '@/dto/materiaInfo/materiaInfo'
//根据库位查询wms库存信息
export function QueryMaterialInfo(wareHouse: string) {
return request<QueryMaterialInfoResponseData>({
url: "Warehouse/QueryMaterialInfo?wareHouse=" + wareHouse,
method: "post",
})
}
//更新wms库存信息
export function Update(materialInfo: materialInfo.MaterialInfo) {
return request<UpdateResponseData>({
url: "Warehouse/Update",
method: "post",
data: materialInfo
})
}
//根据合格证号从erp查询物料信息
export function GetMaterialInfoFromErp(barCode: string) {
return request<GetMaterialInfoFromErpResponseData>({
url: "Erp/GetMaterialInfoFromErp?barCode=" + barCode,
method: "post",
})
}
//根据合格证号查询入库物料已上传erp的数量
export function QueryUploadErpNum(barCode: string) {
return request<QueryUploadErpNumResponseData>({
url: "Warehouse/QueryUploadErpNum?barcode=" + barCode,
method: "post",
})
}
/**
* 解锁南区码头
* @returns
*/
export function UnlockSourthStorageRack() {
return request<UnlockStorageRackResponseData>({
url: "Warehouse/UnlockSourthStorageRack",
method: "post",
})
}
/**
* 解锁北区码头
* @returns
*/
export function UnlockNorthAreaStorageRack() {
return request<UnlockStorageRackResponseData>({
url: "Warehouse/UnlockNorthAreaStorageRack",
method: "post",
})
}
/**
* 清空出库码头
* @returns
*/
export function ClearWharf(transport: number) {
return request<ClearWharfResponseData>({
url: "Warehouse/ClearWharf?transport=" + transport,
method: "post",
})
}
export type QueryMaterialInfoResponseData = ApiResponseData<materialInfo.MaterialInfo>
export type UpdateResponseData = ApiResponseData<boolean>
export type GetMaterialInfoFromErpResponseData = ApiResponseData<materialInfo.MaterialInfo>
export type UnlockStorageRackResponseData = ApiResponseData<string>
export type QueryUploadErpNumResponseData = ApiResponseData<number>
export type ClearWharfResponseData = ApiResponseData<boolean>
@@ -0,0 +1,25 @@
import { request } from "@/utils/service"
import * as md from "@/dto/report/MonthReportDto"
/** */
export function GetMonthReport(data: md.MonthReportSearch) {
return request<MonthReportResponseData>({
url: "Report/MonthReport",
method: "post",
data
})
}
/** */
export function ExportMonthReport(data: md.MonthReportSearch) {
return request<any>({
url: "Report/ExportMonthReport",
method: "post",
responseType: "blob",
data
})
}
/** */
export type MonthReportResponseData = ApiResponseData<md.MonthReportDto>
@@ -0,0 +1,26 @@
import { request } from "@/utils/service"
import * as md from "@/dto/report/WetPaperReportDto"
/** */
export function GetWetPaperReport(data: md.WetPaperReportSearch) {
return request<WetPaperReportResponseData>({
url: "Report/WetPaperReport",
method: "post",
data
})
}
/** */
export function ExportWetPaperReport(data: md.WetPaperReportSearch) {
return request<any>({
url: "Report/ExportWetPaperReport",
method: "post",
responseType: "blob",
data
})
}
/** */
export type WetPaperReportResponseData = ApiResponseData<md.WetPaperReportDto>
+12
View File
@@ -0,0 +1,12 @@
import { request } from "@/utils/service"
import * as stockIn from '@/dto/stockIn/stockIn'
export function AddMaterialStockIn(data:stockIn.stockInParam){
return request<StockInResponseData>({
url: "Wms/StockInByPda",
method: "post",
data
})
}
export type StockInResponseData = ApiResponseData<boolean>
@@ -0,0 +1,11 @@
import * as stockInDetail from '@/dto/stockInDetail/stockInDetail'
import { request } from "@/utils/service"
export function GetStockinDetailToday() {
return request<GetStockinDetailTodayResponseData>({
url: "Warehouse/GetStockinDetailToday",
method: "get",
})
}
export type GetStockinDetailTodayResponseData = ApiResponseDataArray<stockInDetail.stockInDetail>
+37
View File
@@ -0,0 +1,37 @@
import { request } from "@/utils/service"
import type * as Table from "./types/table"
/** 增 */
export function createTableDataApi(data: Table.CreateTableRequestData) {
return request({
url: "table",
method: "post",
data
})
}
/** 删 */
export function deleteTableDataApi(id: string) {
return request({
url: `table/${id}`,
method: "delete"
})
}
/** 改 */
export function updateTableDataApi(data: Table.UpdateTableRequestData) {
return request({
url: "table",
method: "put",
data
})
}
/** 查 */
export function getTableDataApi(params: Table.GetTableRequestData) {
return request<Table.GetTableResponseData>({
url: "table",
method: "get",
params
})
}
+36
View File
@@ -0,0 +1,36 @@
export interface CreateTableRequestData {
username: string
password: string
}
export interface UpdateTableRequestData {
id: string
username: string
password?: string
}
export interface GetTableRequestData {
/** 当前页码 */
currentPage: number
/** 查询条数 */
size: number
/** 查询参数:用户名 */
username?: string
/** 查询参数:手机号 */
phone?: string
}
export interface GetTableData {
createTime: string
email: string
id: string
phone: string
roles: string
status: boolean
username: string
}
export type GetTableResponseData = ApiResponseData<{
list: GetTableData[]
total: number
}>
Binary file not shown.

After

Width:  |  Height:  |  Size: 953 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

+97
View File
@@ -0,0 +1,97 @@
// 颜色
$colors: (
"primary": #db9e3f,
"info-1": #4394e4,
"info": #4b67af,
"white": #ffffff,
"light": #f9f9f9,
"grey-1": #999999,
"grey": #666666,
"dark-1": #5f5f5f,
"dark": #222222,
"black-1": #171823,
"black": #000000,
);
// 字体大小
$base-font-size: 0.2rem;
$font-sizes: (
xxs: 0.1,
//8px
xs: 0.125,
//10px
sm: 0.2875,
//12px
md: 0.1625,
//13px
lg: 0.175,
//14px
xl: 0.2,
//16px
xxl: 0.225,
//18px
xxxl: 0.25 //20px,,,,
);
// 宽高
.w-100 {
width: 100%;
}
.h-100 {
height: 100%;
}
//flex
.d-flex {
display: flex;
}
.flex-column {
flex-direction: column;
}
.flex-wrap {
flex-wrap: wrap;
}
.flex-nowrap {
flex-wrap: nowrap;
}
$flex-jc: (
start: flex-start,
end: flex-end,
center: center,
between: space-between,
around: space-around,
evenly: space-evenly,
);
$flex-ai: (
start: flex-start,
end: flex-end,
center: center,
stretch: stretch,
);
.flex-1 {
flex: 1;
}
//.mt-1 => margin top
//spacing
$spacing-types: (
m: margin,
p: padding,
);
$spacing-directions: (
t: top,
r: right,
b: bottom,
l: left,
);
$spacing-base-size: 0.2rem;
$spacing-sizes: (
0: 0,
1: 0.25,
2: 0.5,
3: 1,
4: 1.5,
5: 3,
);
+139
View File
@@ -0,0 +1,139 @@
#index {
color: #d3d6dd;
// background-color: black;
width: 100%;
height: 100vh;
// position: absolute;
transform-origin: left top;
overflow: hidden;
.bg {
width: 100%;
height: 100%;
padding: 0.2rem 0.2rem 0 0.2rem;
background-image:url("src/assets/pageBg.png") ;
background-size: cover;
background-position: center center;
}
.host-body {
.bg{
color: red($color: red);
}
.title {
position: relative;
width: 6.25rem;
text-align: center;
background-size: cover;
background-repeat: no-repeat;
.title-text {
font-size: 0.3rem;
position: absolute;
bottom: 0;
left: 50%;
transform: translate(-50%);
}
.title-bototm {
position: absolute;
bottom: -0.375rem;
left: 50%;
transform: translate(-50%);
}
}
// 第二行
.aside-width {
width: 40%;
}
.react-r-s,
.react-l-s {
// background-color: #0f1325;
background-color: #1a5cd7;
}
// 平行四边形
.react-right {
&.react-l-s {
text-align: right;
width: 430px;
}
font-size: 18px;
width: 300px;
line-height: 50px;
text-align: center;
transform: skewX(-45deg);
.react-after {
position: absolute;
right: -25px;
top: 0;
height: 50px;
width: 50px;
// background-color: #0f1325;
background-color: #1a5cd7;
transform: skewX(45deg);
}
.text {
display: inline-block;
transform: skewX(45deg);
}
}
.react-left {
&.react-l-s {
width: 500px;
text-align: left;
}
font-size: 18px;
width: 300px;
height: 50px;
line-height: 50px;
text-align: center;
transform: skewX(45deg);
// background-color: #0f1325;
background-color: #1a5cd7;
.react-left {
position: absolute;
left: -25px;
top: 0;
height: 50px;
width: 50px;
// background-color: #0f1325;
background-color: #1a5cd7;
transform: skewX(-45deg);
}
.text {
display: inline-block;
transform: skewX(-45deg);
}
}
.body-box {
margin-top: 0.2rem;
display: flex;
flex-direction: column;
//下方区域的布局
.content-box {
display: grid;
grid-template-columns: 4fr 7fr 4fr;
}
// 底部数据
.bototm-box {
margin-top: 0.125rem;
display: grid;
grid-template-columns: repeat(2, 50%);
}
}
}
}
+185
View File
@@ -0,0 +1,185 @@
@import "./variables";
// 全局样式
* {
margin: 0;
padding: 0;
list-style-type: none;
box-sizing: border-box;
outline: none;
}
html {
margin: 0;
padding: 0;
}
body {
font-family: Arial, Helvetica, sans-serif;
line-height: 1.2em;
background-color: #f1f1f1;
margin: 0;
padding: 0;
}
a {
color: #343440;
text-decoration: none;
}
.clearfix {
&::after {
content: "";
display: table;
height: 0;
line-height: 0;
visibility: hidden;
clear: both;
}
}
//浮动
.float-r {
float: right;
}
//浮动
.float-l {
float: left;
}
// 字体加粗
.fw-b {
font-weight: bold;
}
//文章一行显示,多余省略号显示
.title-item {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// .bg-color-black {
// background-color: rgba(19, 25, 47, 0.6);
// }
.bg-color-blue {
background-color: #1a5cd7;
}
.colorBlack {
color: #272727 !important;
&:hover {
color: #272727 !important;
}
}
.colorGrass {
color: #33cea0;
&:hover {
color: #33cea0 !important;
}
}
.colorRed {
color: #ff5722;
&:hover {
color: #ff5722 !important;
}
}
.colorText {
color: #d3d6dd !important;
&:hover {
color: #d3d6dd !important;
}
}
.colorBlue {
color: #257dff !important;
&:hover {
color: #257dff !important;
}
}
//颜色
@each $colorkey, $color in $colors {
.text-#{$colorkey} {
color: $color;
}
.bg-#{$colorkey} {
background-color: $color;
}
}
//对齐
@each $var in (left, center, right) {
.text-#{$var} {
text-align: $var !important;
}
}
//flex
@each $key, $value in $flex-jc {
.jc-#{$key} {
justify-content: $value;
}
}
@each $key, $value in $flex-ai {
.ai-#{$key} {
align-items: $value;
}
}
//字体
@each $fontkey, $fontvalue in $font-sizes {
.fs-#{$fontkey} {
font-size: $fontvalue * $base-font-size;
}
}
//.mt-1 => margin top
//spacing
@each $typekey, $type in $spacing-types {
//.m-1
@each $sizekey, $size in $spacing-sizes {
.#{$typekey}-#{$sizekey} {
#{$type}: $size * $spacing-base-size;
}
}
//.mx-1
@each $sizekey, $size in $spacing-sizes {
.#{$typekey}x-#{$sizekey} {
#{$type}-left: $size * $spacing-base-size;
#{$type}-right: $size * $spacing-base-size;
}
.#{$typekey}y-#{$sizekey} {
#{$type}-top: $size * $spacing-base-size;
#{$type}-bottom: $size * $spacing-base-size;
}
}
//.mt-1
@each $directionkey, $direction in $spacing-directions {
@each $sizekey, $size in $spacing-sizes {
.#{$typekey}#{$directionkey}-#{$sizekey} {
#{$type}-#{$direction}: $size * $spacing-base-size;
}
}
}
.#{$typekey} {
#{$type}: 0;
}
}
@@ -0,0 +1,58 @@
<script lang="ts" setup>
import { type ListItem } from "./data"
interface Props {
list: ListItem[]
}
const props = defineProps<Props>()
</script>
<template>
<el-empty v-if="props.list.length === 0" />
<el-card v-else v-for="(item, index) in props.list" :key="index" shadow="never" class="card-container">
<template #header>
<div class="card-header">
<div>
<span>
<span class="card-title">{{ item.title }}</span>
<el-tag v-if="item.extra" :type="item.status" effect="plain" size="small">{{ item.extra }}</el-tag>
</span>
<div class="card-time">{{ item.datetime }}</div>
</div>
<div v-if="item.avatar" class="card-avatar">
<img :src="item.avatar" width="34" />
</div>
</div>
</template>
<div class="card-body">
{{ item.description ?? "No Data" }}
</div>
</el-card>
</template>
<style lang="scss" scoped>
.card-container {
margin-bottom: 10px;
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
.card-title {
font-weight: bold;
margin-right: 10px;
}
.card-time {
font-size: 12px;
color: grey;
}
.card-avatar {
display: flex;
align-items: center;
}
}
.card-body {
font-size: 12px;
}
}
</style>
+66
View File
@@ -0,0 +1,66 @@
export interface ListItem {
avatar?: string
title: string
datetime?: string
description?: string
status?: "" | "success" | "info" | "warning" | "danger"
extra?: string
}
export const notifyData: ListItem[] = [
{
avatar: "https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png",
title: "V3 Admin Vite 上线啦",
datetime: "一年前",
description:
"一个免费开源的中后台管理系统基础解决方案,基于 Vue3、TypeScript、Element Plus、Pinia 和 Vite 等主流技术"
},
{
avatar: "https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png",
title: "V3 Admin 上线啦",
datetime: "两年前",
description: "一个中后台管理系统基础解决方案,基于 Vue3、TypeScript、Element Plus 和 Pinia"
}
]
export const messageData: ListItem[] = [
{
avatar: "https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png",
title: "来自楚门的世界",
description: "如果再也不能见到你,祝你早安、午安和晚安",
datetime: "1998-06-05"
},
{
avatar: "https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png",
title: "来自大话西游",
description: "如果非要在这份爱上加上一个期限,我希望是一万年",
datetime: "1995-02-04"
},
{
avatar: "https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png",
title: "来自龙猫",
description: "心存善意,定能途遇天使",
datetime: "1988-04-16"
}
]
export const todoData: ListItem[] = [
{
title: "任务名称",
description: "这家伙很懒,什么都没留下",
extra: "未开始",
status: "info"
},
{
title: "任务名称",
description: "这家伙很懒,什么都没留下",
extra: "进行中",
status: ""
},
{
title: "任务名称",
description: "这家伙很懒,什么都没留下",
extra: "已超时",
status: "danger"
}
]
+95
View File
@@ -0,0 +1,95 @@
<script lang="ts" setup>
import { ref, computed } from "vue"
import { ElMessage } from "element-plus"
import { Bell } from "@element-plus/icons-vue"
import NotifyList from "./NotifyList.vue"
import { type ListItem, notifyData, messageData, todoData } from "./data"
type TabName = "通知" | "消息" | "待办"
interface DataItem {
name: TabName
type: "primary" | "success" | "warning" | "danger" | "info"
list: ListItem[]
}
/** 角标当前值 */
const badgeValue = computed(() => {
return data.value.reduce((sum, item) => sum + item.list.length, 0)
})
/** 角标最大值 */
const badgeMax = 99
/** 面板宽度 */
const popoverWidth = 350
/** 当前 Tab */
const activeName = ref<TabName>("通知")
/** 所有数据 */
const data = ref<DataItem[]>([
// 通知数据
{
name: "通知",
type: "primary",
list: notifyData
},
// 消息数据
{
name: "消息",
type: "danger",
list: messageData
},
// 待办数据
{
name: "待办",
type: "warning",
list: todoData
}
])
const handleHistory = () => {
ElMessage.success(`跳转到${activeName.value}历史页面`)
}
</script>
<template>
<div class="notify">
<el-popover placement="bottom" :width="popoverWidth" trigger="click">
<template #reference>
<el-badge :value="badgeValue" :max="badgeMax" :hidden="badgeValue === 0">
<el-tooltip effect="dark" content="消息通知" placement="bottom">
<el-icon :size="20">
<Bell />
</el-icon>
</el-tooltip>
</el-badge>
</template>
<template #default>
<el-tabs v-model="activeName" class="demo-tabs" stretch>
<el-tab-pane v-for="(item, index) in data" :name="item.name" :key="index">
<template #label>
{{ item.name }}
<el-badge :value="item.list.length" :max="badgeMax" :type="item.type" />
</template>
<el-scrollbar height="400px">
<NotifyList :list="item.list" />
</el-scrollbar>
</el-tab-pane>
</el-tabs>
<div class="notify-history">
<el-button link @click="handleHistory">查看{{ activeName }}历史</el-button>
</div>
</template>
</el-popover>
</div>
</template>
<style lang="scss" scoped>
.notify {
margin-right: 10px;
color: var(--el-text-color-regular);
}
.notify-history {
text-align: center;
padding-top: 12px;
border-top: 1px solid var(--el-border-color);
}
</style>
@@ -0,0 +1,92 @@
<script lang="ts" setup>
import { computed, ref, watchEffect } from "vue"
import { ElMessage } from "element-plus"
import screenfull from "screenfull"
interface Props {
/** 全屏的元素,默认是 html */
element?: string
/** 打开全屏提示语 */
openTips?: string
/** 关闭全屏提示语 */
exitTips?: string
/** 是否只针对内容区 */
content?: boolean
}
const props = withDefaults(defineProps<Props>(), {
element: "html",
openTips: "全屏",
exitTips: "退出全屏",
content: false
})
//#region 全屏
const isFullscreen = ref<boolean>(false)
const fullscreenTips = computed(() => {
return isFullscreen.value ? props.exitTips : props.openTips
})
const fullscreenSvgName = computed(() => {
return isFullscreen.value ? "fullscreen-exit" : "fullscreen"
})
const handleFullscreenClick = () => {
const dom = document.querySelector(props.element) || undefined
screenfull.isEnabled ? screenfull.toggle(dom) : ElMessage.warning("您的浏览器无法工作")
}
const handleFullscreenChange = () => {
isFullscreen.value = screenfull.isFullscreen
}
watchEffect((onCleanup) => {
// 挂载组件时自动执行
screenfull.on("change", handleFullscreenChange)
// 卸载组件时自动执行
onCleanup(() => {
screenfull.isEnabled && screenfull.off("change", handleFullscreenChange)
})
})
//#endregion
//#region 内容区
const isContentLarge = ref<boolean>(false)
const contentLargeTips = computed(() => {
return isContentLarge.value ? "内容区复原" : "内容区放大"
})
const contentLargeSvgName = computed(() => {
return isContentLarge.value ? "fullscreen-exit" : "fullscreen"
})
const handleContentLargeClick = () => {
document.body.className = !isContentLarge.value ? "content-large" : ""
isContentLarge.value = !isContentLarge.value
}
//#endregion
</script>
<template>
<div>
<!-- 全屏 -->
<el-tooltip v-if="!content" effect="dark" :content="fullscreenTips" placement="bottom">
<SvgIcon :name="fullscreenSvgName" @click="handleFullscreenClick" />
</el-tooltip>
<!-- 内容区 -->
<el-dropdown v-else>
<SvgIcon :name="contentLargeSvgName" />
<template #dropdown>
<el-dropdown-menu>
<!-- 内容区放大 -->
<el-dropdown-item @click="handleContentLargeClick">{{ contentLargeTips }}</el-dropdown-item>
<!-- 内容区全屏 -->
<el-dropdown-item @click="handleFullscreenClick" :disabled="isFullscreen">内容区全屏</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<style lang="scss" scoped>
.svg-icon {
font-size: 20px;
&:focus {
outline: none;
}
}
</style>
@@ -0,0 +1,57 @@
<script lang="ts" setup>
import { computed } from "vue"
import { useAppStore } from "@/store/modules/app"
import { DeviceEnum } from "@/constants/app-key"
interface Props {
total: number
}
const props = defineProps<Props>()
const appStore = useAppStore()
const isMobile = computed(() => appStore.device === DeviceEnum.Mobile)
</script>
<template>
<div class="search-footer">
<template v-if="!isMobile">
<span class="search-footer-item">
<SvgIcon name="keyboard-enter" />
<span>确认</span>
</span>
<span class="search-footer-item">
<SvgIcon name="keyboard-up" />
<SvgIcon name="keyboard-down" />
<span>切换</span>
</span>
<span class="search-footer-item">
<SvgIcon name="keyboard-esc" />
<span>关闭</span>
</span>
</template>
<span class="search-footer-total">共 {{ props.total }} 项</span>
</div>
</template>
<style lang="scss" scoped>
.search-footer {
display: flex;
color: var(--el-text-color-secondary);
font-size: 14px;
&-item {
display: flex;
align-items: center;
margin-right: 12px;
.svg-icon {
margin-right: 5px;
padding: 2px;
font-size: 20px;
background-color: var(--el-fill-color);
}
}
&-total {
margin: 0 0 0 auto;
}
}
</style>
@@ -0,0 +1,219 @@
<script lang="ts" setup>
import { computed, ref, shallowRef } from "vue"
import { type RouteRecordName, type RouteRecordRaw, useRouter } from "vue-router"
import { useAppStore } from "@/store/modules/app"
import { usePermissionStore } from "@/store/modules/permission"
import SearchResult from "./SearchResult.vue"
import SearchFooter from "./SearchFooter.vue"
import { ElMessage, ElScrollbar } from "element-plus"
import { cloneDeep, debounce } from "lodash-es"
import { DeviceEnum } from "@/constants/app-key"
import { isExternal } from "@/utils/validate"
interface Props {
/** 控制 modal 显隐 */
modelValue: boolean
}
const props = defineProps<Props>()
const emit = defineEmits<{
"update:modelValue": [boolean]
}>()
const appStore = useAppStore()
const router = useRouter()
const inputRef = ref<HTMLInputElement | null>(null)
const scrollbarRef = ref<InstanceType<typeof ElScrollbar> | null>(null)
const searchResultRef = ref<InstanceType<typeof SearchResult> | null>(null)
const keyword = ref<string>("")
const resultList = shallowRef<RouteRecordRaw[]>([])
const activeRouteName = ref<RouteRecordName | undefined>(undefined)
/** 是否按下了上键或下键(用于解决和 mouseenter 事件的冲突) */
const isPressUpOrDown = ref<boolean>(false)
/** 控制搜索对话框宽度 */
const modalWidth = computed(() => (appStore.device === DeviceEnum.Mobile ? "80vw" : "40vw"))
/** 控制搜索对话框显隐 */
const modalVisible = computed({
get() {
return props.modelValue
},
set(value: boolean) {
emit("update:modelValue", value)
}
})
/** 树形菜单 */
const menusData = computed(() => cloneDeep(usePermissionStore().routes))
/** 搜索(防抖) */
const handleSearch = debounce(() => {
const flatMenusData = flatTree(menusData.value)
resultList.value = flatMenusData.filter((menu) =>
keyword.value ? menu.meta?.title?.toLocaleLowerCase().includes(keyword.value.toLocaleLowerCase().trim()) : false
)
// 默认选中搜索结果的第一项
const length = resultList.value?.length
activeRouteName.value = length > 0 ? resultList.value[0].name : undefined
}, 500)
/** 将树形菜单扁平化为一维数组,用于菜单搜索 */
const flatTree = (arr: RouteRecordRaw[], result: RouteRecordRaw[] = []) => {
arr.forEach((item) => {
result.push(item)
item.children && flatTree(item.children, result)
})
return result
}
/** 关闭搜索对话框 */
const handleClose = () => {
modalVisible.value = false
// 延时处理防止用户看到重置数据的操作
setTimeout(() => {
keyword.value = ""
resultList.value = []
}, 200)
}
/** 根据下标位置进行滚动 */
const scrollTo = (index: number) => {
if (!searchResultRef.value) return
const scrollTop = searchResultRef.value.getScrollTop(index)
// 手动控制 el-scrollbar 滚动条滚动,设置滚动条到顶部的距离
scrollbarRef.value?.setScrollTop(scrollTop)
}
/** 键盘上键 */
const handleUp = () => {
isPressUpOrDown.value = true
const { length } = resultList.value
if (length === 0) return
// 获取该 name 在菜单中第一次出现的位置
const index = resultList.value.findIndex((item) => item.name === activeRouteName.value)
// 如果已处在顶部
if (index === 0) {
const bottomName = resultList.value[length - 1].name
// 如果顶部和底部的 bottomName 相同,且长度大于 1,就再跳一个位置(可解决遇到首尾两个相同 name 导致的上键不能生效的问题)
if (activeRouteName.value === bottomName && length > 1) {
activeRouteName.value = resultList.value[length - 2].name
scrollTo(length - 2)
} else {
// 跳转到底部
activeRouteName.value = bottomName
scrollTo(length - 1)
}
} else {
activeRouteName.value = resultList.value[index - 1].name
scrollTo(index - 1)
}
}
/** 键盘下键 */
const handleDown = () => {
isPressUpOrDown.value = true
const { length } = resultList.value
if (length === 0) return
// 获取该 name 在菜单中最后一次出现的位置(可解决遇到连续两个相同 name 导致的下键不能生效的问题)
const index = resultList.value.map((item) => item.name).lastIndexOf(activeRouteName.value)
// 如果已处在底部
if (index === length - 1) {
const topName = resultList.value[0].name
// 如果底部和顶部的 topName 相同,且长度大于 1,就再跳一个位置(可解决遇到首尾两个相同 name 导致的下键不能生效的问题)
if (activeRouteName.value === topName && length > 1) {
activeRouteName.value = resultList.value[1].name
scrollTo(1)
} else {
// 跳转到顶部
activeRouteName.value = topName
scrollTo(0)
}
} else {
activeRouteName.value = resultList.value[index + 1].name
scrollTo(index + 1)
}
}
/** 键盘回车键 */
const handleEnter = () => {
const { length } = resultList.value
if (length === 0) return
const name = activeRouteName.value
const path = resultList.value.find((item) => item.name === name)?.path
if (path && isExternal(path)) {
window.open(path, "_blank", "noopener, noreferrer")
return
}
if (!name) {
ElMessage.warning("无法通过搜索进入该菜单,请为对应的路由设置唯一的 Name")
return
}
try {
router.push({ name })
} catch {
ElMessage.error("该菜单有必填的动态参数,无法通过搜索进入")
return
}
handleClose()
}
/** 释放上键或下键 */
const handleReleaseUpOrDown = () => {
isPressUpOrDown.value = false
}
</script>
<template>
<el-dialog
v-model="modalVisible"
@opened="inputRef?.focus()"
@closed="inputRef?.blur()"
@keydown.up="handleUp"
@keydown.down="handleDown"
@keydown.enter="handleEnter"
@keyup.up.down="handleReleaseUpOrDown"
:before-close="handleClose"
:width="modalWidth"
top="5vh"
class="search-modal__private"
append-to-body
>
<el-input ref="inputRef" v-model="keyword" @input="handleSearch" placeholder="搜索菜单" size="large" clearable>
<template #prefix>
<SvgIcon name="search" />
</template>
</el-input>
<el-empty v-if="resultList.length === 0" description="暂无搜索结果" :image-size="100" />
<template v-else>
<p>搜索结果</p>
<el-scrollbar ref="scrollbarRef" max-height="40vh" always>
<SearchResult
ref="searchResultRef"
v-model="activeRouteName"
:list="resultList"
:isPressUpOrDown="isPressUpOrDown"
@click="handleEnter"
/>
</el-scrollbar>
</template>
<template #footer>
<SearchFooter :total="resultList.length" />
</template>
</el-dialog>
</template>
<style lang="scss">
.search-modal__private {
.svg-icon {
font-size: 18px;
}
.el-dialog__header {
display: none;
}
.el-dialog__footer {
border-top: 1px solid var(--el-border-color);
padding: var(--el-dialog-padding-primary);
}
}
</style>
@@ -0,0 +1,122 @@
<script lang="ts" setup>
import { computed, getCurrentInstance, onBeforeMount, onBeforeUnmount, onMounted, ref } from "vue"
import { type RouteRecordName, type RouteRecordRaw } from "vue-router"
interface Props {
modelValue: RouteRecordName | undefined
list: RouteRecordRaw[]
isPressUpOrDown: boolean
}
const props = defineProps<Props>()
const emit = defineEmits<{
"update:modelValue": [RouteRecordName | undefined]
}>()
const instance = getCurrentInstance()
const scrollbarHeight = ref<number>(0)
/** 选中的菜单 */
const activeRouteName = computed({
get() {
return props.modelValue
},
set(value: RouteRecordName | undefined) {
emit("update:modelValue", value)
}
})
/** 菜单的样式 */
const itemStyle = (item: RouteRecordRaw) => {
const flag = item.name === activeRouteName.value
return {
background: flag ? "var(--el-color-primary)" : "",
color: flag ? "#fff" : ""
}
}
/** 鼠标移入 */
const handleMouseenter = (item: RouteRecordRaw) => {
// 如果上键或下键与 mouseenter 事件同时生效,则以上下键为准,不执行该函数的赋值逻辑
if (props.isPressUpOrDown) return
activeRouteName.value = item.name
}
/** 计算滚动可视区高度 */
const getScrollbarHeight = () => {
// el-scrollbar max-height="40vh"
scrollbarHeight.value = Number((window.innerHeight * 0.4).toFixed(1))
}
/** 根据下标计算到顶部的距离 */
const getScrollTop = (index: number) => {
const currentInstance = instance?.proxy?.$refs[`resultItemRef${index}`] as HTMLDivElement[]
if (!currentInstance) return 0
const currentRef = currentInstance[0]
const scrollTop = currentRef.offsetTop + 128 // 128 = 两个 result-item (56 + 56 = 112)高度与上下 margin(8 + 8 = 16)大小之和
return scrollTop > scrollbarHeight.value ? scrollTop - scrollbarHeight.value : 0
}
/** 在组件挂载前添加窗口大小变化事件监听器 */
onBeforeMount(() => {
window.addEventListener("resize", getScrollbarHeight)
})
/** 在组件挂载时立即计算滚动可视区高度 */
onMounted(() => {
getScrollbarHeight()
})
/** 在组件卸载前移除窗口大小变化事件监听器 */
onBeforeUnmount(() => {
window.removeEventListener("resize", getScrollbarHeight)
})
defineExpose({ getScrollTop })
</script>
<template>
<!-- 外层 div 不能删除,是用来接收父组件 click 事件的 -->
<div>
<div
v-for="(item, index) in list"
:key="index"
:ref="`resultItemRef${index}`"
class="result-item"
:style="itemStyle(item)"
@mouseenter="handleMouseenter(item)"
>
<SvgIcon v-if="item.meta?.svgIcon" :name="item.meta.svgIcon" />
<component v-else-if="item.meta?.elIcon" :is="item.meta.elIcon" class="el-icon" />
<span class="result-item-title">
{{ item.meta?.title }}
</span>
<SvgIcon v-if="activeRouteName && activeRouteName === item.name" name="keyboard-enter" />
</div>
</div>
</template>
<style lang="scss" scoped>
.result-item {
display: flex;
align-items: center;
height: 56px;
padding: 0 15px;
margin-top: 8px;
border: 1px solid var(--el-border-color);
border-radius: 4px;
cursor: pointer;
.svg-icon {
min-width: 1em;
font-size: 18px;
}
.el-icon {
width: 1em;
font-size: 18px;
}
&-title {
flex: 1;
margin-left: 12px;
}
}
</style>
@@ -0,0 +1,29 @@
<script lang="ts" setup>
import { ref } from "vue"
import SearchModal from "./SearchModal.vue"
/** 控制 modal 显隐 */
const modalVisible = ref<boolean>(false)
/** 打开 modal */
const handleOpen = () => {
modalVisible.value = true
}
</script>
<template>
<div>
<el-tooltip effect="dark" content="搜索菜单" placement="bottom">
<SvgIcon name="search" @click="handleOpen" />
</el-tooltip>
<SearchModal v-model="modalVisible" />
</div>
</template>
<style lang="scss" scoped>
.svg-icon {
font-size: 20px;
&:focus {
outline: none;
}
}
</style>
@@ -0,0 +1,29 @@
<script lang="ts" setup>
import { computed } from "vue"
interface Props {
prefix?: string
name: string
}
const props = withDefaults(defineProps<Props>(), {
prefix: "icon"
})
const symbolId = computed(() => `#${props.prefix}-${props.name}`)
</script>
<template>
<svg class="svg-icon" aria-hidden="true">
<use :href="symbolId" />
</svg>
</template>
<style lang="scss" scoped>
.svg-icon {
width: 1em;
height: 1em;
fill: currentColor;
overflow: hidden;
}
</style>
@@ -0,0 +1,30 @@
<script lang="ts" setup>
import { useTheme } from "@/hooks/useTheme"
import { MagicStick } from "@element-plus/icons-vue"
const { themeList, activeThemeName, setTheme } = useTheme()
</script>
<template>
<el-dropdown trigger="click" @command="setTheme">
<div>
<el-tooltip effect="dark" content="主题模式" placement="bottom">
<el-icon :size="20">
<MagicStick />
</el-icon>
</el-tooltip>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="(theme, index) in themeList"
:key="index"
:disabled="activeThemeName === theme.name"
:command="theme.name"
>
<span>{{ theme.title }}</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
+12
View File
@@ -0,0 +1,12 @@
export interface IGlobalConfig {
baseApiUrl: string
}
export const loadConfigFile = async () => {
const response = await fetch('/js/globalConfig.json');
if (!response.ok) {
throw new Error('Failed to load configuration file');
}
return response.json();
};
+48
View File
@@ -0,0 +1,48 @@
import { getConfigLayout } from "@/utils/cache/local-storage"
/** 项目配置类型 */
export interface LayoutSettings {
/** 是否显示 Settings Panel */
showSettings: boolean
/** 布局模式 */
layoutMode: "left" | "top" | "left-top"
/** 是否显示标签栏 */
showTagsView: boolean
/** 是否显示 Logo */
showLogo: boolean
/** 是否固定 Header */
fixedHeader: boolean
/** 是否显示消息通知 */
showNotify: boolean
/** 是否显示切换主题按钮 */
showThemeSwitch: boolean
/** 是否显示全屏按钮 */
showScreenfull: boolean
/** 是否显示搜索按钮 */
showSearchMenu: boolean
/** 是否缓存标签栏 */
cacheTagsView: boolean
/** 是否显示灰色模式 */
showGreyMode: boolean
/** 是否显示色弱模式 */
showColorWeakness: boolean
}
/** 默认配置 */
const defaultSettings: LayoutSettings = {
layoutMode: "left",
showSettings: true,
showTagsView: true,
fixedHeader: true,
showLogo: true,
showNotify: true,
showThemeSwitch: true,
showScreenfull: true,
showSearchMenu: true,
cacheTagsView: false,
showGreyMode: false,
showColorWeakness: false
}
/** 项目配置 */
export const layoutSettings: LayoutSettings = { ...defaultSettings, ...getConfigLayout() }
+28
View File
@@ -0,0 +1,28 @@
/** 动态路由配置 */
interface RouteSettings {
/**
* 是否开启动态路由功能?
* 1. 开启后需要后端配合,在查询用户详情接口返回当前用户可以用来判断并加载动态路由的字段(该项目用的是角色 roles 字段)
* 2. 假如项目不需要根据不同的用户来显示不同的页面,则应该将 async: false
*/
async: boolean
/** 当动态路由功能关闭时:
* 1. 应该将所有路由都写到常驻路由里面(表明所有登陆的用户能访问的页面都是一样的)
* 2. 系统自动给当前登录用户赋值一个没有任何作用的默认角色
*/
defaultRoles: Array<string>
/**
* 是否开启三级及其以上路由缓存功能?
* 1. 开启后会进行路由降级(把三级及其以上的路由转化为二级路由)
* 2. 由于都会转成二级路由,所以二级及其以上路由有内嵌子路由将会失效
*/
thirdLevelRouteCache: boolean
}
const routeSettings: RouteSettings = {
async: false,
defaultRoles: ["DEFAULT_ROLE"],
thirdLevelRouteCache: false
}
export default routeSettings
+16
View File
@@ -0,0 +1,16 @@
import { type RouteLocationNormalized } from "vue-router"
/** 免登录白名单(匹配路由 path) */
const whiteListByPath: string[] = ["/login",'/stockIn-page','/tailIn-page',
'/materialMaintance-page','/carryMaterial-page','/sysSetting-page','/stockInDetail-page']
/** 免登录白名单(匹配路由 name) */
const whiteListByName: string[] = []
/** 判断是否在白名单 */
const isWhiteList = (to: RouteLocationNormalized) => {
// path 和 name 任意一个匹配上即可
return whiteListByPath.indexOf(to.path) !== -1 || whiteListByName.indexOf(to.name as any) !== -1
}
export default isWhiteList
+13
View File
@@ -0,0 +1,13 @@
/** 设备类型 */
export enum DeviceEnum {
Mobile,
Desktop
}
/** 侧边栏打开状态常量 */
export const SIDEBAR_OPENED = "opened"
/** 侧边栏关闭状态常量 */
export const SIDEBAR_CLOSED = "closed"
export type SidebarOpened = typeof SIDEBAR_OPENED
export type SidebarClosed = typeof SIDEBAR_CLOSED
+15
View File
@@ -0,0 +1,15 @@
const SYSTEM_NAME = "acs-office"
/** 缓存数据时用到的 Key */
class CacheKey {
static readonly TOKEN = `${SYSTEM_NAME}-token-key`
static readonly CONFIG_LAYOUT = `${SYSTEM_NAME}-config-layout-key`
static readonly SIDEBAR_STATUS = `${SYSTEM_NAME}-sidebar-status-key`
static readonly ACTIVE_THEME_NAME = `${SYSTEM_NAME}-active-theme-name-key`
static readonly VISITED_VIEWS = `${SYSTEM_NAME}-visited-views-key`
static readonly CACHED_VIEWS = `${SYSTEM_NAME}-cached-views-key`
static readonly PRODUCTION_CONFIG = `${SYSTEM_NAME}-cached-production-config-key`
static readonly USER_NAME = `${SYSTEM_NAME}-cached-username-key`
}
export default CacheKey
+7
View File
@@ -0,0 +1,7 @@
import { type App } from "vue"
import { permission } from "./permission"
/** 挂载自定义指令 */
export function loadDirectives(app: App) {
app.directive("permission", permission)
}
@@ -0,0 +1,17 @@
import { type Directive } from "vue"
import { useUserStoreHook } from "@/store/modules/user"
/** 权限指令,和权限判断函数 checkPermission 功能类似 */
export const permission: Directive = {
mounted(el, binding) {
const { value: permissionRoles } = binding
const { roles } = useUserStoreHook()
if (Array.isArray(permissionRoles) && permissionRoles.length > 0) {
const hasPermission = roles.some((role) => permissionRoles.includes(role))
// hasPermission || (el.style.display = "none") // 隐藏
hasPermission || el.parentNode?.removeChild(el) // 销毁
} else {
throw new Error(`need roles! Like v-permission="['admin','editor']"`)
}
}
}
+29
View File
@@ -0,0 +1,29 @@
export interface agvTaskInfo {
Id: string; // 在TypeScript中,我们通常使用string来模拟Guid
ErpTaskId: string; // 同上
BarCode: string;
OrderNo: string;
CustomName: string;
MateriaName: string;
MateriaType: string;
MateriaNum: number;
MateriaBatch: string;
MateriaCode: string;
EquipmentNo: string;
TaskStatus: number;
TaskType: number;
Priority: number;
Floor: number;
StartPositionCode: string;
EndPositionCode: string;
Transport: number;
IsLock: boolean;
GroupId: string; // 同上,使用string来模拟Guid
CreationTime?: Date; // 可选属性,使用Date类型
LastModifiedTime?: Date; // 同上
}
export interface agvTaskFailedAlarmInfo{
AgvTaskId:string,
Operation:string
}
@@ -0,0 +1,4 @@
export interface CarryInParam {
StartStorageRackNo: string; // 起点库位编码
EndStorageRackNo: string; // 终点库位编码
}
@@ -0,0 +1,28 @@
export class ProductionConfig {
web!: number
isDoubleLevel!: boolean
maxLevel!: number
position?: string
mainKnife?: string
maxSpeed?: number
cycleDayTime?: number
paperCodeWordMaxLen?: number
maxSecNum?: number
maxLineNum?: number
rcvOrderFilePath?: string
historyFilePath?: string
recycleFilePath?: string
showInchWeb?: boolean
continuousStop?: number
minSecWidth?: number
minCutlength?: number
localIpAdderss?: string
trimHighLightMinValue?: number
lineCode?: string
enableSpeed?: boolean
stkSecWidth?: number
threeBestSpd?: number
fiveBestSpd?: number
sevenBestSpd?: number
minBestSpd?: number
}
@@ -0,0 +1,8 @@
export interface elevatorCount {
Id: string,
ElevatorNo: number,
ElevatorChannel: number,
Count: number,
CreationTime: Date,
LastModifiedTime: Date
}
@@ -0,0 +1,9 @@
export interface elevatorQueue{
Id:string,
ElevatorNo:number,
ElevatorChannel:number,
AgvTaskId:string,
IsLock:boolean,
CreationTime: Date,
LastModifiedTime: Date
}
+26
View File
@@ -0,0 +1,26 @@
export interface erpTaskInfo {
Id: string; // 假设Guid在TypeScript中作为字符串处理
StorageRackNo: string;
OrderNo: string;
ProduceReturnCode: string;
SaleNo: string;
SaleReturnCode: string;
BarCode: string;
CustomName: string;
MateriaName: string; // 注意:原始C#属性名可能有误,通常应为 MaterialName
MateriaType: string; // 注意:同上
MateriaBatch: string;
MateriaCode: string;
EquipmentNo: string;
ErpOrderNum: number;
TaskStatus: number;
TaskType: number;
Transport: number;
Priority: number;
IsLock: boolean;
IsCallBackErpEnd: boolean;
ErpCallWmsInterface: string;
WmsCallErpInterface: string;
CreationTime: Date | null;
LastModifiedTime: Date | null;
}
@@ -0,0 +1,32 @@
export interface MaterialInfo {
Id: string; // 假设Guid用string表示
StorageRackNo: string; // 库位编号
AgvPassPointNum: string; // 途径提升机指定点的坐标
ElevatorChannel?: number; // 提升机通道号(PLC定义的网带编号),提升机库位专用
IsLock: boolean; // 是否锁定
ContainStatus: number; // 库位状态
StorageRackName: string; // 库位名称
AreaNumSort: number; // 所在的库区排序
NumSort: number; // 库位排序
ReservoirAreaId?: number; // 所属库区
ReservoirAreaColumnId?: number; // 所属库区列
Remark: string; // 备注
Floor: number; // 所属楼层编号
MachineName: string; // AGV名称
ConfirmDate?: Date; // 确认时间
IsDisabled: boolean; // 是否禁用
BarCode: string; // 合格证号
OrderNo: string; // 订单号
CustomName: string; // 客户名称
MateriaName: string; // 物料名称
MateriaType: string; // 物料类型(注意:这里与MateriaName可能重复,需确认是否为笔误)
MateriaNum: number; // 单个托盘上的物料数量
MateriaBatch: string; // 批次号
MateriaCode: string; // 物料编码
EquipmentNo: string; // 联动线出口,机械手设备唯一号
Transport: number; // 码头位置
ErpCallWmsInterface: string; // Erp回调Wms接口地址
WmsCallErpInterface: string; // Wms回调Erp接口地址
CreationTime?: Date; // 创建时间
LastModifiedTime?: Date; // 修改时间
}
@@ -0,0 +1,148 @@
export interface WareHouse {
Id: string; // 假设Guid在TypeScript中使用string表示
/// <summary>
/// 库位编号
/// </summary>
StorageRackNo: string;
/// <summary>
/// 途径提升机指定点的坐标
/// </summary>
AgvPassPointNum: string;
/// <summary>
/// 提升机通道号(PLC定义的网带编号),提升机库位专用
/// </summary>
ElevatorChannel: number | null;
/// <summary>
/// 是否锁定
/// </summary>
IsLock: boolean;
/// <summary>
/// 库位状态
/// </summary>
ContainStatus: number;
/// <summary>
/// 库位名称
/// </summary>
StorageRackName: string;
/// <summary>
/// 所在的库区排序
/// </summary>
AreaNumSort: number;
/// <summary>
/// 库位排序(同一列的库位排序号越大越在里面(以车前进方向为准))
/// </summary>
NumSort: number;
/// <summary>
/// 所属库区
/// </summary>
ReservoirAreaId: number | null;
/// <summary>
/// 所属库区列(小车进入库位的方向为列)
/// </summary>
ReservoirAreaColumnId: number | null;
/// <summary>
/// 备注
/// </summary>
Remark: string;
/// <summary>
/// 所属楼层编号(10-一楼 20-二楼)
/// </summary>
Floor: number;
/// <summary>
/// AGV名称
/// </summary>
MachineName: string;
/// <summary>
/// 确认时间
/// </summary>
ConfirmDate: Date | null;
/// <summary>
/// 是否禁用
/// </summary>
IsDisabled: boolean;
/// <summary>
/// 合格证号
/// </summary>
BarCode: string;
/// <summary>
/// 订单号
/// </summary>
OrderNo: string;
/// <summary>
/// 客户名称
/// </summary>
CustomName: string;
/// <summary>
/// 物料名称
/// </summary>
MateriaName: string;
/// <summary>
/// 物料类型
/// </summary>
MateriaType: string;
/// <summary>
/// 单个托盘上的物料数量
/// </summary>
MateriaNum: number;
/// <summary>
/// 批次号的生成规则为: 作业单号+日期+班次
/// </summary>
MateriaBatch: string;
/// <summary>
/// 物料编码
/// </summary>
MateriaCode: string;
/// <summary>
/// 联动线出口,机械手设备唯一号
/// </summary>
EquipmentNo: string;
/// <summary>
/// 码头位置
/// </summary>
Transport: number;
/// <summary>
/// Erp回调Wms接口地址
/// </summary>
ErpCallWmsInterface: string;
/// <summary>
/// Wms回调Erp接口地址
/// </summary>
WmsCallErpInterface: string;
/// <summary>
/// 创建时间
/// </summary>
CreationTime: Date | null;
/// <summary>
/// 修改时间
/// </summary>
LastModifiedTime: Date | null;
}
@@ -0,0 +1,96 @@
import { UUID } from "crypto"
import { IOrderDryRequest } from "@/dto/search/PagedRequestDto"
export interface MonthReportDto {
/** */
rpCompleteMonthReportSum: MonthReportDetailDto
/** */
rpCompleteMonthReportDetails: MonthReportDetailDto[]
}
export interface MonthReportDetailDto {
[key: string]: string | number | undefined | null | void| Date|UUID|boolean
/** */
id: UUID
/** */
productDate: string
/** */
startTime: string
/** */
startTimeStr: string
/**订单数量 */
orderCount: number
/** */
stopTimes: number
/**停机时间 */
stopTime: number
/** */
stopTimeStr: string
/**结束时间 */
endTime: string
/** */
endTimeStr: string
/**生产时间 */
runTime: number
/** */
runTimeStr: string
/**班组 */
groupName: string
/** */
/**平均速度 */
avgSpd: number
/** 运行平均速度*/
runAvgSpd: number
/**蒸汽 */
steam: number
/**总蒸汽 */
accSteam: number
/**空压 */
airpressure: number
/**总空压 */
accAirpressure: number
/**总功率 */
kw: number
/**总功耗 */
kwh: number
/** */
ordLen: number
/** */
ordArea: number
/**实际米长 */
prodLen: number
/** */
prodArea: number
wastLen: number
/** */
wastArea: number
/**修边面积 */
trimArea: number
/**废品率 */
wastRate: number
/** 修边率*/
trimRate: number
/**损耗 */
prodRate: number
}
export interface MonthReportSearch extends IOrderDryRequest {
groupNames: string[]
}
@@ -0,0 +1,129 @@
import { UUID } from "crypto"
import { IOrderDryRequest } from "@/dto/search/PagedRequestDto"
export interface WetPaperReportDto {
rpPaperStaticsReportDetails:WetPaperReportDetailDto[]
}
export interface WetPaperReportDetailDto {
[key: string]: string | number | undefined | null | void| Date|UUID|boolean
id :UUID
groupName: string
/** 生产日期 */
productDate :string
startTime :string
endTime :string
/**门幅 */
web :number
/** 米长;*/
len :number
/** 材质 */
paperMasseCode: string
/** 材质 */
paperMasseCodeGrid :string
/** 楞型*/
fluteCode :string
/** */
sC1Rate :number
sC2Rate :number
sC3Rate :number
/** 废品率*/
wastRate :number
/**原纸 */
dcPaperCode :string
/**DC克重 g */
dcUnitWeight :number
/**DC米长 */
dcLength :number
/** DC重量kg*/
dcWeight :number
sC1Flute :string
/** */
sc1CorrPaperCode :string
/** SC1Corr克重*/
sc1CorrUnitWeight :number
/**SC1Corr米长 */
sc1CorrLength :number
/** SC1Corr重量kg*/
sc1CorrWeight :number
/** */
sc1SurfPaperCode :string
/** SC1Surf克重 */
sc1SurfUnitWeight :number
/** SC1Surf米长*/
sc1SurfLength :number
/** SC1Surf克重 */
sc1SurfWeight :number
sC2Flute :string
/** */
sc2CorrPaperCode :string
/** SC1Corr克重*/
sc2CorrUnitWeight :number
/**SC1Corr米长 */
sc2CorrLength :number
/** SC1Corr重量kg*/
sc2CorrWeight :number
/** */
sc2SurfPaperCode :string
/** SC1Surf克重 */
sc2SurfUnitWeight :number
/** SC1Surf米长*/
sc2SurfLength :number
/** SC1Surf克重 */
sc2SurfWeight :number
sC3Flute :string
/** */
sc3CorrPaperCode :string
/** SC1Corr克重*/
sc3CorrUnitWeight :number
/**SC1Corr米长 */
sc3CorrLength :number
/** SC1Corr重量kg*/
sc3CorrWeight :number
/** */
sc3SurfPaperCode :string
/** SC1Surf克重 */
sc3SurfUnitWeight :number
/** SC1Surf米长*/
sc3SurfLength :number
/** SC1Surf克重 */
sc3SurfWeight :number
}
export interface WetPaperReportSearch extends IOrderDryRequest {
groupNames: string[]
}
+7
View File
@@ -0,0 +1,7 @@
export interface stockInParam {
BarCode: string,
StorageRackNo: string,
TaskType: number, //2-入库 5-尾拖回库
Floor:number,//楼层 10-一楼 20- 二楼
MertialNum: number //物料数量(仅尾拖入库需要传递此值)
}
@@ -0,0 +1,53 @@
export interface stockInDetail {
/// <summary>
/// ErpTask的Id
/// </summary>
Id: string; // 注意:Guid在TypeScript中不是内置类型,你可能需要定义一个类型或使用string代替
/// <summary>
/// 库位编号(起点)
/// </summary>
StorageRackNo: string;
/// <summary>
/// 合格证号
/// </summary>
BarCode: string;
/// <summary>
/// 用户名称
/// </summary>
CustomName: string;
/// <summary>
/// 物料名称
/// </summary>
MateriaName: string; // 注意:这里可能存在拼写错误,通常应为MaterialName
/// <summary>
/// 物料类型
/// </summary>
MateriaType: string; // 同样,这里可能存在拼写错误,通常应为MaterialType
/// <summary>
/// 批次号的生成规则为: 作业单号+日期+班次
/// </summary>
MateriaBatch: string;
/// <summary>
/// 物料编号
/// </summary>
MateriaCode: string;
ErpOrderNum:number;
/// <summary>
/// 已上传给Erp的数量
/// </summary>
AlreadyUploadNum: number;
/// <summary>
/// 物料入库最终存放库位
/// </summary>
StoragePosition: string;
}
+14
View File
@@ -0,0 +1,14 @@
export class SysUserInfo {
/** 登录名 */
loginId!: string
/** 密码 */
password?: string
/** 用户姓名 */
userName?: string
/** 电话号码 */
phoneNum?: string
/** 性别 */
sex?: string
/** 班组 */
groupName?: string
}
@@ -0,0 +1,7 @@
import { ProductionConfig } from "../config/productionConfig"
import { SysUserInfo } from "./sysUserInfo"
export class UserPermissionDto {
token!: string
userInfo!: SysUserInfo
productionConfig!: ProductionConfig
}
+32
View File
@@ -0,0 +1,32 @@
import { UUID } from "crypto"
/** 用户信息 */
export class UserDto {
/** 主键Id */
id!: UUID
/** 登录名 */
loginId!: string
/** 密码 */
password?: string
/** 用户姓名 */
userName?: string
/** 电话号码 */
phoneNum?: string
/** 性别 */
sex?: string
/** 班组 */
groupName?: string
/** 添加时间 */
createdTime?: Date
/** 最近一次修改时间 */
modifiedTime?: Date
/** 添加人用户名 */
creationUserName?: string
/** 修改人用户名 */
modifierUserName?: string
/** 角色名id */
roleId?: UUID
/** 角色名 */
roleName?: string
/** 是否修改密码 */
isMidPwd?: boolean
}
+53
View File
@@ -0,0 +1,53 @@
import { ref, onMounted } from "vue"
type OptionValue = string | number
/** Select 需要的数据格式 */
interface SelectOption {
value: OptionValue
label: string
disabled?: boolean
}
/** 接口响应格式 */
interface ApiData {
code: number
data: SelectOption[]
message: string
}
/** 入参格式,暂时只需要传递 api 函数即可 */
interface FetchSelectProps {
api: () => Promise<ApiData>
}
export function useFetchSelect(props: FetchSelectProps) {
const { api } = props
const loading = ref<boolean>(false)
const options = ref<SelectOption[]>([])
const value = ref<OptionValue>("")
/** 调用接口获取数据 */
const loadData = () => {
loading.value = true
options.value = []
api()
.then((res) => {
options.value = res.data
})
.finally(() => {
loading.value = false
})
}
onMounted(() => {
loadData()
})
return {
loading,
options,
value
}
}
@@ -0,0 +1,35 @@
import { type LoadingOptions, ElLoading } from "element-plus"
const defaultOptions = {
lock: true,
text: "加载中..."
}
interface LoadingInstance {
close: () => void
}
interface UseFullscreenLoading {
<T extends (...args: any[]) => ReturnType<T>>(
fn: T,
options?: LoadingOptions
): (...args: Parameters<T>) => Promise<ReturnType<T>>
}
/**
* 传入一个函数 fn,在它执行周期内,加上「全屏」loading
* @param fn 要执行的函数
* @param options LoadingOptions
* @returns 返回一个新的函数,该函数返回一个 Promise
*/
export const useFullscreenLoading: UseFullscreenLoading = (fn, options = {}) => {
let loadingInstance: LoadingInstance
return async (...args) => {
try {
loadingInstance = ElLoading.service({ ...defaultOptions, ...options })
return await fn(...args)
} finally {
loadingInstance?.close()
}
}
}
+41
View File
@@ -0,0 +1,41 @@
import { reactive } from "vue"
interface DefaultPaginationData {
total: number
currentPage: number
pageSizes: number[]
pageSize: number
layout: string
}
interface PaginationData {
total?: number
currentPage?: number
pageSizes?: number[]
pageSize?: number
layout?: string
}
/** 默认的分页参数 */
const defaultPaginationData: DefaultPaginationData = {
total: 0,
currentPage: 1,
pageSizes: [10, 20, 50],
pageSize: 10,
layout: "total, sizes, prev, pager, next, jumper"
}
export function usePagination(initialPaginationData: PaginationData = {}) {
/** 合并分页参数 */
const paginationData = reactive({ ...defaultPaginationData, ...initialPaginationData })
/** 改变当前页码 */
const handleCurrentChange = (value: number) => {
paginationData.currentPage = value
}
/** 改变页面大小 */
const handleSizeChange = (value: number) => {
paginationData.pageSize = value
}
return { paginationData, handleCurrentChange, handleSizeChange }
}
+48
View File
@@ -0,0 +1,48 @@
import { onBeforeUnmount } from "vue"
import mitt, { type Handler } from "mitt"
import { type RouteLocationNormalized } from "vue-router"
/** 回调函数的类型 */
type Callback = (route: RouteLocationNormalized) => void
const emitter = mitt()
const key = Symbol("ROUTE_CHANGE")
let latestRoute: RouteLocationNormalized
/** 设置最新的路由信息,触发路由变化事件 */
export const setRouteChange = (to: RouteLocationNormalized) => {
// 触发事件
emitter.emit(key, to)
// 缓存最新的路由信息
latestRoute = to
}
/** 单独监听路由会浪费渲染性能,使用发布订阅模式去进行分发管理 */
export function useRouteListener() {
/** 回调函数集合 */
const callbackList: Callback[] = []
/** 监听路由变化(可以选择立即执行) */
const listenerRouteChange = (callback: Callback, immediate = false) => {
// 缓存回调函数
callbackList.push(callback)
// 监听事件
emitter.on(key, callback as Handler)
// 可以选择立即执行一次回调函数
immediate && latestRoute && callback(latestRoute)
}
/** 移除路由变化事件监听器 */
const removeRouteListener = (callback: Callback) => {
emitter.off(key, callback as Handler)
}
/** 组件销毁前移除监听器 */
onBeforeUnmount(() => {
for (let i = 0; i < callbackList.length; i++) {
removeRouteListener(callbackList[i])
}
})
return { listenerRouteChange, removeRouteListener }
}
+57
View File
@@ -0,0 +1,57 @@
import { ref, watchEffect } from "vue"
import { getActiveThemeName, setActiveThemeName } from "@/utils/cache/local-storage"
const DEFAULT_THEME_NAME = "normal"
type DefaultThemeName = typeof DEFAULT_THEME_NAME
/** 注册的主题名称, 其中 DefaultThemeName 是必填的 */
export type ThemeName = DefaultThemeName | "dark" | "dark-blue"
interface ThemeList {
title: string
name: ThemeName
}
/** 主题列表 */
const themeList: ThemeList[] = [
{
title: "默认",
name: DEFAULT_THEME_NAME
},
{
title: "黑暗",
name: "dark"
},
{
title: "深蓝",
name: "dark-blue"
}
]
/** 正在应用的主题名称 */
const activeThemeName = ref<ThemeName>(getActiveThemeName() || "dark-blue")
/** 设置主题 */
const setTheme = (value: ThemeName) => {
activeThemeName.value = value
}
/** 在 html 根元素上挂载 class */
const setHtmlRootClassName = (value: ThemeName) => {
document.documentElement.className = value
}
/** 初始化 */
const initTheme = () => {
// watchEffect 来收集副作用
watchEffect(() => {
const value = activeThemeName.value
setHtmlRootClassName(value)
setActiveThemeName(value)
})
}
/** 主题 hook */
export function useTheme() {
return { themeList, activeThemeName, initTheme, setTheme }
}
+7
View File
@@ -0,0 +1,7 @@
import { type App } from "vue"
import SvgIcon from "@/components/SvgIcon/index.vue" // Svg Component
import "virtual:svg-icons-register"
export function loadSvg(app: App) {
app.component("SvgIcon", SvgIcon)
}
+1
View File
@@ -0,0 +1 @@
<svg t="1651119499039" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9021" width="200" height="200"><path d="M512 720m-48 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0Z" p-id="9022"></path><path d="M480 416v184c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V416c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8z" p-id="9023"></path><path d="M955.7 856l-416-720c-6.2-10.7-16.9-16-27.7-16s-21.6 5.3-27.7 16l-416 720C56 877.4 71.4 904 96 904h832c24.6 0 40-26.6 27.7-48z m-783.5-27.9L512 239.9l339.8 588.2H172.2z" p-id="9024"></path></svg>

After

Width:  |  Height:  |  Size: 548 B

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