配置 eslint 与 prettier

This commit is contained in:
tao
2026-01-14 16:35:46 +08:00
parent 09b921009e
commit 317f2847ac
65 changed files with 6469 additions and 3487 deletions

5
.prettierignore Normal file
View File

@@ -0,0 +1,5 @@
# 忽略打包输出目录
dist
# 忽略依赖目录
node_modules

25
.prettierrc Normal file
View File

@@ -0,0 +1,25 @@
{
"arrowParens": "always",
"bracketSameLine": false,
"objectWrap": "preserve",
"bracketSpacing": true,
"semi": false,
"experimentalOperatorPosition": "end",
"experimentalTernaries": false,
"singleQuote": true,
"jsxSingleQuote": false,
"quoteProps": "preserve",
"trailingComma": "all",
"singleAttributePerLine": false,
"htmlWhitespaceSensitivity": "ignore",
"vueIndentScriptAndStyle": false,
"proseWrap": "preserve",
"endOfLine": "lf",
"insertPragma": false,
"printWidth": 80,
"requirePragma": false,
"tabWidth": 2,
"useTabs": true,
"embeddedLanguageFormatting": "auto",
"cursorOffset": -1
}

3
commitlint.config.js Normal file
View File

@@ -0,0 +1,3 @@
module.exports = {
extends: ['@commitlint/config-conventional'],
}

53
eslint.config.mjs Normal file
View File

@@ -0,0 +1,53 @@
/**
* ESLint Flat 配置ESLint 9
* 适用于Vue 3 + TypeScript + Prettier
*/
import pluginVue from 'eslint-plugin-vue' // Vue 官方 ESLint 插件
import {
defineConfigWithVueTs, // 用于组合 Vue + TS 的 Flat 配置
vueTsConfigs, // 官方提供的 TypeScript 推荐规则集合
} from '@vue/eslint-config-typescript'
import prettierPlugin from 'eslint-plugin-prettier' // 将 Prettier 作为 ESLint 规则使用
import prettierConfig from 'eslint-config-prettier' // 关闭与 Prettier 冲突的 ESLint 规则
export default defineConfigWithVueTs(
// 1. 全局忽略配置(必须放在最前面)
{
// 不对构建产物和依赖目录进行 ESLint 校验
ignores: ['dist/**', 'node_modules/**'],
},
// 2. Vue 相关基础规则(包含模板语法支持)
pluginVue.configs['flat/essential'],
// 3. TypeScript 推荐规则(脚本逻辑部分)
vueTsConfigs.recommended,
// 4. Prettier 相关配置:只负责格式化,不干扰语义规则
{
// 注册需要用到的插件
plugins: {
prettier: prettierPlugin, // Prettier 插件
},
// 自定义规则
rules: {
// 关闭与 Prettier 冲突的 ESLint 规则
...prettierConfig.rules,
// 将 Prettier 的格式化结果作为 ESLint 的告警输出
'prettier/prettier': 'warn',
},
},
// 5. 自定义规则
{
rules: {
// Vue 规则
'vue/multi-word-component-names': 'off',
// TypeScript 规则
'@typescript-eslint/no-explicit-any': 'off',
},
},
)

6
global.d.ts vendored
View File

@@ -1,4 +1,4 @@
// declare module '@/*';
declare module '@/components/*';
declare module '@/views/*';
declare module '@/api/*';
declare module '@/components/*'
declare module '@/views/*'
declare module '@/api/*'

10
lint-staged.config.cjs Normal file
View File

@@ -0,0 +1,10 @@
module.exports = {
'*.{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'],
'{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': [
'prettier --write--parser json',
],
'package.json': ['prettier --write'],
'*.vue': ['eslint --fix', 'prettier --write', 'stylelint --fix'],
'*.{scss,less,style,html}': ['stylelint --fix', 'prettier --write'],
'*.md': ['prettier --write'],
}

2248
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,10 @@
"scripts": {
"dev": "vite --host --open",
"build": "vite build",
"preview": "vite preview"
"preview": "vite preview",
"lint": "eslint \"src/**/*.{ts,vue}\"",
"lint:fix": "eslint \"src/**/*.{ts,vue}\" --fix",
"format": "prettier --write \"src/**/*.{ts,vue,js,json,css,md}\""
},
"dependencies": {
"@types/lodash": "^4.17.21",
@@ -24,14 +27,29 @@
"@types/event-source-polyfill": "^1.0.5",
"@types/js-cookie": "^3.0.6",
"@types/node": "^24.0.3",
"@typescript-eslint/eslint-plugin": "^8.53.0",
"@typescript-eslint/parser": "^8.53.0",
"@vitejs/plugin-vue": "^5.2.3",
"@vue/eslint-config-typescript": "^14.6.0",
"@vue/tsconfig": "^0.7.0",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-vue": "^10.6.2",
"event-source-polyfill": "^1.0.31",
"js-cookie": "^3.0.5",
"lint-staged": "^16.2.7",
"prettier": "^3.7.4",
"sass-embedded": "^1.89.2",
"typescript": "~5.8.3",
"unplugin-icons": "^22.2.0",
"vite": "^6.3.5",
"vue-tsc": "^2.2.8"
},
"lint-staged": {
"*.{ts,js,vue}": [
"eslint --fix",
"prettier --write"
]
}
}

View File

@@ -4,7 +4,7 @@
</a-config-provider>
</template>
<script setup>
import zhCN from 'ant-design-vue/es/locale/zh_CN';
import 'dayjs/locale/zh-cn';
<script setup lang="ts">
import zhCN from 'ant-design-vue/es/locale/zh_CN'
import 'dayjs/locale/zh-cn'
</script>

40
src/api/common.d.ts vendored
View File

@@ -1,11 +1,11 @@
export type ID = number | string;
export type IDS = (number | string)[];
export type ID = number | string
export type IDS = (number | string)[]
export interface BaseEntity {
createBy?: string;
createTime?: string;
updateBy?: string;
updateTime?: string;
createBy?: string
createTime?: string
updateBy?: string
updateTime?: string
}
/**
@@ -16,24 +16,24 @@ export interface BaseEntity {
* @param isAsc 是否升序
*/
export interface PageQuery {
isAsc?: string;
orderByColumn?: string;
pageNum?: number;
pageSize?: number;
[key: string]: any;
isAsc?: string
orderByColumn?: string
pageNum?: number
pageSize?: number
[key: string]: any
}
export interface ApiResponse<T = any> {
code?: number;
msg?: string;
code?: number
msg?: string
data?: T;
rows: T[];
total?: number;
data?: T
rows: T[]
total?: number
token?: string;
token?: string
img?: string;
uuid?: string;
captchaOnOff?: boolean;
img?: string
uuid?: string
captchaOnOff?: boolean
}

View File

@@ -1,93 +1,93 @@
import axios from "axios";
import router from "@/router";
import { getToken, removeToken } from "@/utils/auth";
import { Modal, notification } from "ant-design-vue";
import type { AxiosInstance, AxiosRequestConfig } from "axios";
import type { ApiResponse } from "@/api/common";
import axios from 'axios'
import router from '@/router'
import { getToken, removeToken } from '@/utils/auth'
import { Modal, notification } from 'ant-design-vue'
import type { AxiosInstance, AxiosRequestConfig } from 'axios'
import type { ApiResponse } from '@/api/common'
const errCodeMap: { [key: string]: string } = {
"403": "当前操作没有权限",
"404": "访问资源不存在",
default: "系统未知错误,请反馈给管理员",
};
'403': '当前操作没有权限',
'404': '访问资源不存在',
default: '系统未知错误,请反馈给管理员',
}
// 创建axios实例
const service: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 10000,
});
})
// 请求拦截器
service.interceptors.request.use(
(config) => {
config.headers = config.headers || {};
config.headers["Accept-Language"] = "zh-CN";
config.headers = config.headers || {}
config.headers['Accept-Language'] = 'zh-CN'
const token = getToken();
const token = getToken()
if (token) {
config.headers["Authorization"] = `Bearer ${token}`;
config.headers['Authorization'] = `Bearer ${token}`
}
return config;
return config
},
(error) => {
return Promise.reject(error);
}
);
return Promise.reject(error)
},
)
// 响应拦截器
service.interceptors.response.use(
(response) => {
if (response.config.responseType === "blob") {
return response.data;
if (response.config.responseType === 'blob') {
return response.data
}
const code = response.data.code || 200;
const data = response.data;
const code = response.data.code || 200
const data = response.data
switch (code) {
case 200:
return data;
return data
case 401:
Modal.error({
title: "系统提示",
content: "登录状态已过期,请重新登录",
title: '系统提示',
content: '登录状态已过期,请重新登录',
onOk: () => {
removeToken();
router.replace({ name: 'Login' });
removeToken()
router.replace({ name: 'Login' })
},
});
return Promise.reject(data);
})
return Promise.reject(data)
case 500:
return Promise.reject(new Error(data.msg));
return Promise.reject(new Error(data.msg))
default:
notification.error({
message: "请求错误",
message: '请求错误',
description: errCodeMap[code] || data?.msg || errCodeMap.default,
});
return Promise.reject(data);
})
return Promise.reject(data)
}
},
(error) => {
if (!error.__handled) {
const message =
error.code === "ECONNABORTED"
? "请求超时,请稍后重试"
: error.message || "网络异常";
error.code === 'ECONNABORTED'
? '请求超时,请稍后重试'
: error.message || '网络异常'
notification.error({
message: "网络错误",
message: '网络错误',
description: message,
});
})
error.__handled = true;
error.__handled = true
}
return Promise.reject(error);
}
);
return Promise.reject(error)
},
)
function request<T = any>(config: AxiosRequestConfig): Promise<ApiResponse<T>> {
return service(config) as Promise<ApiResponse<T>>;
return service(config) as Promise<ApiResponse<T>>
}
export default request;
export default request

View File

@@ -5,7 +5,7 @@ export function listData(params: any) {
return request({
url: '/system/dict/data/list',
method: 'get',
params
params,
})
}
@@ -13,7 +13,7 @@ export function listData(params: any) {
export function getData(dictCode: string) {
return request({
url: '/system/dict/data/' + dictCode,
method: 'get'
method: 'get',
})
}
@@ -21,7 +21,7 @@ export function getData(dictCode: string) {
export function getDicts(dictType: string) {
return request({
url: '/system/dict/data/type/' + dictType,
method: 'get'
method: 'get',
})
}
@@ -30,7 +30,7 @@ export function addData(data: any) {
return request({
url: '/system/dict/data',
method: 'post',
data
data,
})
}
@@ -39,7 +39,7 @@ export function updateData(data: any) {
return request({
url: '/system/dict/data',
method: 'put',
data
data,
})
}
@@ -47,6 +47,6 @@ export function updateData(data: any) {
export function delData(dictCode: string) {
return request({
url: '/system/dict/data/' + dictCode,
method: 'delete'
method: 'delete',
})
}

View File

@@ -4,58 +4,58 @@ import type { ID } from '@/api/common'
// 查询字典类型列表
export function listType(params: any) {
return request({
url: "/system/dict/type/list",
method: "get",
url: '/system/dict/type/list',
method: 'get',
params,
});
})
}
// 查询字典类型详细
export function getType(dictId: ID) {
return request({
url: "/system/dict/type/" + dictId,
method: "get",
});
url: '/system/dict/type/' + dictId,
method: 'get',
})
}
// 新增字典类型
export function addType(data: any) {
return request({
url: "/system/dict/type",
method: "post",
url: '/system/dict/type',
method: 'post',
data,
});
})
}
// 修改字典类型
export function updateType(data: any) {
return request({
url: "/system/dict/type",
method: "put",
url: '/system/dict/type',
method: 'put',
data,
});
})
}
// 删除字典类型
export function delType(dictId: ID) {
return request({
url: "/system/dict/type/" + dictId,
method: "delete",
});
url: '/system/dict/type/' + dictId,
method: 'delete',
})
}
// 刷新字典缓存
export function refreshCache() {
return request({
url: "/system/dict/type/refreshCache",
method: "delete",
});
url: '/system/dict/type/refreshCache',
method: 'delete',
})
}
// 获取字典选择框列表
export function getOptions() {
return request({
url: "/system/dict/type/optionselect",
method: "get",
});
url: '/system/dict/type/optionselect',
method: 'get',
})
}

View File

@@ -1,12 +1,12 @@
import request from '@/api/request'
import type { LoginInfo } from './model';
import type { LoginInfo } from './model'
// 用户登录
export function login(data: LoginInfo) {
return request({
url: '/login',
method: 'post',
data
data,
})
}
@@ -14,7 +14,7 @@ export function login(data: LoginInfo) {
export function getCaptcha() {
return request({
url: '/captchaImage',
method: 'get'
method: 'get',
})
}
@@ -22,6 +22,6 @@ export function getCaptcha() {
export function logout() {
return request({
url: '/logout',
method: 'post'
method: 'post',
})
}

View File

@@ -1,36 +1,36 @@
import request from "@/api/request";
import type { ID } from "@/api/common";
import request from '@/api/request'
import type { ID } from '@/api/common'
// 查询设备管理详细
export function getEquipmentByCode(code: string) {
return request({
url: '/basic/equipment/code/' + code,
method: 'get'
method: 'get',
})
}
// 查询设备进站记录列表
export function listEquipmentEntry(params: any) {
return request({
url: "/mes/equipment-entry-log/list",
method: "get",
url: '/mes/equipment-entry-log/list',
method: 'get',
params,
});
})
}
// 新增设备进站记录
export function addEquipmentEntry(data: any) {
return request({
url: "/mes/equipment-entry-log",
method: "post",
url: '/mes/equipment-entry-log',
method: 'post',
data,
});
})
}
// 删除设备进站记录
export function delEquipmentEntry(id: ID) {
return request({
url: "/mes/equipment-entry-log/" + id,
method: "delete",
});
url: '/mes/equipment-entry-log/' + id,
method: 'delete',
})
}

View File

@@ -1,72 +1,72 @@
import request from "@/api/request";
import type { LotTraceOrderData, LotTraceOrderQuery } from "./model";
import type { ID } from "@/api/common";
import request from '@/api/request'
import type { LotTraceOrderData, LotTraceOrderQuery } from './model'
import type { ID } from '@/api/common'
// 查询随工单列表
export function listLotTraceOrder(params: LotTraceOrderQuery) {
return request({
url: "/mes/lot-trace-order/list",
method: "get",
url: '/mes/lot-trace-order/list',
method: 'get',
params,
});
})
}
// 高级查询随工单列表
export function advListLotTraceOrder(params: LotTraceOrderQuery) {
return request({
url: "/mes/lot-trace-order/advList",
method: "get",
url: '/mes/lot-trace-order/advList',
method: 'get',
params,
});
})
}
// 查询随工单详细
export function getLotTraceOrder(id: ID) {
return request({
url: "/mes/lot-trace-order/" + id,
method: "get",
});
url: '/mes/lot-trace-order/' + id,
method: 'get',
})
}
// 新增随工单
export function addLotTraceOrder(data: LotTraceOrderData) {
return request({
url: "/mes/lot-trace-order",
method: "post",
url: '/mes/lot-trace-order',
method: 'post',
data,
});
})
}
// 修改随工单
export function updateLotTraceOrder(data: LotTraceOrderData) {
return request({
url: "/mes/lot-trace-order",
method: "put",
url: '/mes/lot-trace-order',
method: 'put',
data,
});
})
}
// 删除随工单
export function delLotTraceOrder(id: ID) {
return request({
url: "/mes/lot-trace-order/" + id,
method: "delete",
});
url: '/mes/lot-trace-order/' + id,
method: 'delete',
})
}
// 关闭随工单
export function closeLotTraceOrder(id: ID) {
return request({
url: "/mes/lot-trace-order/close/" + id,
method: "put",
});
url: '/mes/lot-trace-order/close/' + id,
method: 'put',
})
}
// 新增质量异常联络单
export function addQualityAbnormalContact(data: any) {
return request({
url: "/mes/qualityAbnormalContact",
method: "post",
url: '/mes/qualityAbnormalContact',
method: 'post',
data,
});
})
}

View File

@@ -1,54 +1,54 @@
import request from "@/api/request";
import type { ID } from "@/api/common";
import request from '@/api/request'
import type { ID } from '@/api/common'
// 查询库位列表
export function listStorageLocation(params: any) {
return request({
url: "/wip/storageLocation/list",
method: "get",
url: '/wip/storageLocation/list',
method: 'get',
params,
});
})
}
// 高级查询库位列表
export function advListStorageLocation(params: any) {
return request({
url: "/wip/storageLocation/advList",
method: "get",
url: '/wip/storageLocation/advList',
method: 'get',
params,
});
})
}
// 查询库位详细
export function getStorageLocation(id: ID) {
return request({
url: "/wip/storageLocation/" + id,
method: "get",
});
url: '/wip/storageLocation/' + id,
method: 'get',
})
}
// 新增库位
export function addStorageLocation(data: any) {
return request({
url: "/wip/storageLocation",
method: "post",
url: '/wip/storageLocation',
method: 'post',
data,
});
})
}
// 修改库位
export function updateStorageLocation(data: any) {
return request({
url: "/wip/storageLocation",
method: "put",
url: '/wip/storageLocation',
method: 'put',
data,
});
})
}
// 删除库位
export function delStorageLocation(id: ID) {
return request({
url: "/wip/storageLocation/" + id,
method: "delete",
});
url: '/wip/storageLocation/' + id,
method: 'delete',
})
}

View File

@@ -1,37 +1,37 @@
import request from "@/api/request";
import type { ID } from "@/api/common";
import request from '@/api/request'
import type { ID } from '@/api/common'
// 查询站点绑定治具列表
export function listStationBindMask(params: any) {
return request({
url: "/mes/station/mask/list",
method: "get",
url: '/mes/station/mask/list',
method: 'get',
params,
});
})
}
// 为站点绑定治具
export function batchBindMasksToStation(stationId: ID, maskIds: ID[]) {
return request({
url: "/mes/station/" + stationId + "/bind/mask",
method: "put",
url: '/mes/station/' + stationId + '/bind/mask',
method: 'put',
data: maskIds,
});
})
}
// 为站点绑定治具组
export function bindMaskCombinationToStations(data: any) {
return request({
url: "/mes/station/bind/mask-combination",
method: "put",
url: '/mes/station/bind/mask-combination',
method: 'put',
data,
});
})
}
// 站点解绑治具
export function unbindStationMask(stationMaskId: ID) {
return request({
url: "/mes/station/bind-mask/" + stationMaskId,
method: "delete",
});
url: '/mes/station/bind-mask/' + stationMaskId,
method: 'delete',
})
}

View File

@@ -1,81 +1,84 @@
import request from "@/api/request";
import type { MaskCombinationData, MaskCombinationQuery } from "./model";
import type { ID } from "@/api/common";
import request from '@/api/request'
import type { MaskCombinationData, MaskCombinationQuery } from './model'
import type { ID } from '@/api/common'
// 查询治具组合列表
export function listMaskCombination(params: MaskCombinationQuery) {
return request({
url: "/tpm/mask/combination/list",
method: "get",
url: '/tpm/mask/combination/list',
method: 'get',
params,
});
})
}
// 查询治具组合包含的治具列表
export function listCombinationAssignMask(id: ID, params: MaskCombinationQuery) {
export function listCombinationAssignMask(
id: ID,
params: MaskCombinationQuery,
) {
return request({
url: "/tpm/mask/combination/" + id + "/masks",
method: "get",
url: '/tpm/mask/combination/' + id + '/masks',
method: 'get',
params,
});
})
}
// 高级查询治具组合列表
export function advListMaskCombination(params: MaskCombinationQuery) {
return request({
url: "/tpm/mask/combination/advList",
method: "get",
url: '/tpm/mask/combination/advList',
method: 'get',
params,
});
})
}
// 查询治具组合详细
export function getMaskCombination(id: ID) {
return request({
url: "/tpm/mask/combination/" + id,
method: "get",
});
url: '/tpm/mask/combination/' + id,
method: 'get',
})
}
// 新增治具组合
export function addMaskCombination(data: MaskCombinationData) {
return request({
url: "/tpm/mask/combination",
method: "post",
url: '/tpm/mask/combination',
method: 'post',
data,
});
})
}
// 修改治具组合
export function updateMaskCombination(data: MaskCombinationData) {
return request({
url: "/tpm/mask/combination",
method: "put",
url: '/tpm/mask/combination',
method: 'put',
data,
});
})
}
// 删除治具组合
export function delMaskCombination(id: ID) {
return request({
url: "/tpm/mask/combination/" + id,
method: "delete",
});
url: '/tpm/mask/combination/' + id,
method: 'delete',
})
}
// 新增治具组合与治具关联关系
export function addMaskCombinationAssignment(data: MaskCombinationData) {
return request({
url: "/tpm/mask/combination/assignment",
method: "post",
url: '/tpm/mask/combination/assignment',
method: 'post',
data,
});
})
}
// 删除治具组合与治具关联关系
export function delMaskCombinationAssignment(id: ID) {
return request({
url: "/tpm/mask/combination/assignment/" + id,
method: "delete",
});
url: '/tpm/mask/combination/assignment/' + id,
method: 'delete',
})
}

View File

@@ -1,26 +1,26 @@
import { BaseEntity, PageQuery, type ID } from "@/api/common";
import { BaseEntity, PageQuery, type ID } from '@/api/common'
/**
* 治具组合查询参数
*/
export interface MaskCombinationQuery extends PageQuery {
combinationName?: string;
combinationCode?: string;
combinationStatus?: string;
remark?: string;
searchValue?: string;
tempId?: string;
timeRange?: [string, string];
combinationName?: string
combinationCode?: string
combinationStatus?: string
remark?: string
searchValue?: string
tempId?: ID
timeRange?: [string, string]
}
/**
* 治具组合数据
*/
export interface MaskCombinationData extends BaseEntity {
combinationName?: string;
combinationCode?: string;
combinationStatus?: string;
remark?: string;
searchValue?: string;
tempId?: string;
timeRange?: [string, string];
combinationName?: string
combinationCode?: string
combinationStatus?: string
remark?: string
searchValue?: string
tempId?: ID
timeRange?: [string, string]
}

View File

@@ -1,94 +1,94 @@
import { BaseEntity, PageQuery, type ID } from "@/api/common";
import { BaseEntity, PageQuery, type ID } from '@/api/common'
/**
* 随工单查询参数
*/
export interface LotTraceOrderQuery extends PageQuery {
/** 编码 */
code?: string;
code?: string
/** 状态 */
status?: string;
status?: string
/** 主生产计划的ID */
mpsId?: number;
mpsId?: number
/** 主生产计划编码 */
mpsCode?: string;
mpsCode?: string
/** 订单类型 */
orderType?: string;
orderType?: string
/** 主生产计划明细ID */
mpsDetailId?: number;
mpsDetailId?: number
/** 批号 */
batchNo?: string;
batchNo?: string
/** 主生产计划明细序号 */
mpsDetailSeq?: number;
mpsDetailSeq?: number
/** 目标产品ID */
tarMaterialId?: number;
tarMaterialId?: number
/** 主物料ID */
masterMaterialId?: number;
masterMaterialId?: number
/** 生产版本ID */
prodVersionId?: number;
prodVersionId?: number
/** 计划数量 */
planQty?: number;
planQty?: number
/** OK数量 */
okQty?: number;
okQty?: number
/** NG数量 */
ngQty?: number;
ngQty?: number
/** 未完成数量 */
unfinishedQty?: number;
unfinishedQty?: number
/** 单位ID */
unitId?: number;
unitId?: number
/** 计划开始时间范围 */
planStartTimeRange?: [string, string];
planStartTimeRange?: [string, string]
/** 计划结束时间范围 */
planEndTimeRange?: [string, string];
planEndTimeRange?: [string, string]
/** 扩展字段1 */
extStr1?: string;
extStr1?: string
/** 扩展字段2 */
extStr2?: string;
extStr2?: string
/** 扩展字段3 */
extStr3?: string;
extStr3?: string
/** 扩展字段4 */
extStr4?: string;
extStr4?: string
/** 扩展字段5 */
extStr5?: string;
extStr5?: string
/** 扩展字段6 */
extStr6?: string;
extStr6?: string
/** 扩展字段7 */
extStr7?: string;
extStr7?: string
/** 扩展字段8 */
extStr8?: string;
extStr8?: string
/** 扩展字段9 */
extStr9?: string;
extStr9?: string
/** 扩展字段10 */
extStr10?: string;
extStr10?: string
/** 扩展字段11 */
extStr11?: string;
extStr11?: string
/** 扩展字段12 */
extStr12?: string;
extStr12?: string
/** 扩展字段13 */
extStr13?: string;
extStr13?: string
/** 扩展字段14 */
extStr14?: string;
extStr14?: string
/** 扩展字段15 */
extStr15?: string;
extStr15?: string
/** 扩展字段16 */
extStr16?: string;
extStr16?: string
/** 扩展整型1 */
extInt1?: number;
extInt1?: number
/** 扩展整型2 */
extInt2?: number;
extInt2?: number
/** 扩展小数1 */
extDec1?: number;
extDec1?: number
/** 扩展小数2 */
extDec2?: number;
extDec2?: number
/** 扩展日期1范围 */
extDate1Range?: [string, string];
extDate1Range?: [string, string]
/** 扩展日期2范围 */
extDate2Range?: [string, string];
extDate2Range?: [string, string]
/** 删除标志 */
delStatus?: string;
delStatus?: string
/** 创建时间范围 */
createTimeRange?: [string, string];
createTimeRange?: [string, string]
/** 更新时间范围 */
updateTimeRange?: [string, string];
updateTimeRange?: [string, string]
}
/**
@@ -96,93 +96,93 @@ export interface LotTraceOrderQuery extends PageQuery {
*/
export interface LotTraceOrderData extends BaseEntity {
/** 主键ID */
id?: ID;
id?: ID
/** 编码 */
code?: string;
code?: string
/** 状态 */
status?: string;
status?: string
/** 主生产计划的ID */
mpsId?: number;
mpsId?: number
/** 主生产计划编码 */
mpsCode?: string;
mpsCode?: string
/** 订单类型 */
orderType?: string;
orderType?: string
/** 主生产计划明细ID */
mpsDetailId?: number;
mpsDetailId?: number
/** 批号 */
batchNo?: string;
batchNo?: string
/** 主生产计划明细序号 */
mpsDetailSeq?: number;
mpsDetailSeq?: number
/** 目标产品ID */
tarMaterialId?: number;
tarMaterialId?: number
/** 目标产品名称 */
tarMaterialName?: string;
tarMaterialName?: string
/** 目标产品编码 */
tarMaterialCode?: string;
tarMaterialCode?: string
/** 主物料ID */
masterMaterialId?: number;
masterMaterialId?: number
/** 生产版本ID */
prodVersionId?: number;
prodVersionId?: number
/** 计划数量 */
planQty?: number;
planQty?: number
/** OK数量 */
okQty?: number;
okQty?: number
/** NG数量 */
ngQty?: number;
ngQty?: number
/** 未完成数量 */
unfinishedQty?: number;
unfinishedQty?: number
/** 单位ID */
unitId?: number;
unitId?: number
/** 计划开始时间 */
planStartTime?: string;
planStartTime?: string
/** 计划结束时间 */
planEndTime?: string;
planEndTime?: string
/** 扩展字段1 */
extStr1?: string;
extStr1?: string
/** 扩展字段2 */
extStr2?: string;
extStr2?: string
/** 扩展字段3 */
extStr3?: string;
extStr3?: string
/** 扩展字段4 */
extStr4?: string;
extStr4?: string
/** 扩展字段5 */
extStr5?: string;
extStr5?: string
/** 扩展字段6 */
extStr6?: string;
extStr6?: string
/** 扩展字段7 */
extStr7?: string;
extStr7?: string
/** 扩展字段8 */
extStr8?: string;
extStr8?: string
/** 扩展字段9 */
extStr9?: string;
extStr9?: string
/** 扩展字段10 */
extStr10?: string;
extStr10?: string
/** 扩展字段11 */
extStr11?: string;
extStr11?: string
/** 扩展字段12 */
extStr12?: string;
extStr12?: string
/** 扩展字段13 */
extStr13?: string;
extStr13?: string
/** 扩展字段14 */
extStr14?: string;
extStr14?: string
/** 扩展字段15 */
extStr15?: string;
extStr15?: string
/** 扩展字段16 */
extStr16?: string;
extStr16?: string
/** 扩展整型1 */
extInt1?: number;
extInt1?: number
/** 扩展整型2 */
extInt2?: number;
extInt2?: number
/** 扩展小数1 */
extDec1?: number;
extDec1?: number
/** 扩展小数2 */
extDec2?: number;
extDec2?: number
/** 扩展日期1 */
extDate1?: string;
extDate1?: string
/** 扩展日期2 */
extDate2?: string;
extDate2?: string
/** 备注 */
remark?: string;
remark?: string
/** 删除标志 */
delStatus?: string;
delStatus?: string
}

View File

@@ -1,61 +1,61 @@
import request from "@/api/request";
import type { ID } from "@/api/common";
import request from '@/api/request'
import type { ID } from '@/api/common'
// 查询主材进站列表
export function listMainMaterialEntryLog(params: any) {
return request({
url: "/mes/station/entry-log/main-material/list",
method: "get",
url: '/mes/station/entry-log/main-material/list',
method: 'get',
params,
});
})
}
// 查询主材进站详细
export function getMainMaterialEntryLog(id: ID) {
return request({
url: "/mes/station/entry-log/main-material/" + id,
method: "get",
});
url: '/mes/station/entry-log/main-material/' + id,
method: 'get',
})
}
// 新增主材Wafer进站
export function addWaferEntryLog(data: any) {
return request({
url: "/mes/station/entry-log/main-material/wafer",
method: "post",
url: '/mes/station/entry-log/main-material/wafer',
method: 'post',
data,
});
})
}
// 通过载具新增主材Wafer进站
export function addWaferEntryLogByCarrier(data: any) {
return request({
url: "/mes/station/entry-log/main-material/wafer/by-carrier",
method: "post",
url: '/mes/station/entry-log/main-material/wafer/by-carrier',
method: 'post',
data,
});
})
}
// 新增主材Die进站
export function addDieEntryLog(data: any) {
return request({
url: "/mes/station/entry-log/main-material/die",
method: "post",
url: '/mes/station/entry-log/main-material/die',
method: 'post',
data,
});
})
}
// 通过载具新增主材Die进站
export function addDieEntryLogByCarrier(data: any) {
return request({
url: "/mes/station/entry-log/main-material/die/by-carrier",
method: "post",
url: '/mes/station/entry-log/main-material/die/by-carrier',
method: 'post',
data,
});
})
}
// 删除主材进站
export function delMainMaterialEntryLog(id: ID) {
return request({
url: "/mes/station/entry-log/main-material/" + id,
method: "delete",
});
url: '/mes/station/entry-log/main-material/' + id,
method: 'delete',
})
}

View File

@@ -1,39 +1,39 @@
import request from "@/api/request";
import type { ID } from "@/api/common";
import request from '@/api/request'
import type { ID } from '@/api/common'
// 查询主材出站列表
export function listMainMaterialOutboundLog(params: any) {
return request({
url: "/mes/station/out-log/main-material/list",
method: "get",
url: '/mes/station/out-log/main-material/list',
method: 'get',
params,
});
})
}
// 查询主材出站详细
export function getMainMaterialOutboundLog(id: ID) {
return request({
url: "/mes/station/out-log/main-material/" + id,
method: "get",
});
url: '/mes/station/out-log/main-material/' + id,
method: 'get',
})
}
// 新增主材出站
export function batchAddMainMaterialOutboundLog(data: any) {
return request({
url: "/mes/station/out-log/main-material/batch",
method: "post",
url: '/mes/station/out-log/main-material/batch',
method: 'post',
data,
});
})
}
// 新增主材出站
export function addWaferDieOutboundLogBySpecifiedNg(data: any) {
return request({
url: "/mes/station/out-log/main-material/wafer-die/by-specified-ng",
method: "post",
url: '/mes/station/out-log/main-material/wafer-die/by-specified-ng',
method: 'post',
data,
});
})
}
// 修改主材出站
@@ -41,14 +41,14 @@ export function updateMainMaterialOutboundLog(data: any) {
return request({
url: '/mes/station/out-log/main-material',
method: 'put',
data
data,
})
}
// 删除主材出站
export function delMainMaterialOutboundLog(id: ID) {
return request({
url: "/mes/station/out-log/main-material/" + id,
method: "delete",
});
url: '/mes/station/out-log/main-material/' + id,
method: 'delete',
})
}

View File

@@ -1,73 +1,73 @@
import request from "@/api/request";
import type { ID } from "@/api/common";
import type { MesStationQuery, MesStationData } from "./model";
import request from '@/api/request'
import type { ID } from '@/api/common'
import type { MesStationQuery, MesStationData } from './model'
// 查询站点列表
export function listStation(params: MesStationQuery) {
return request({
url: "/mes/station/list",
method: "get",
url: '/mes/station/list',
method: 'get',
params,
});
})
}
// 高级查询站点列表
export function advListStation(params: MesStationQuery) {
return request({
url: "/mes/station/advList",
method: "get",
url: '/mes/station/advList',
method: 'get',
params,
});
})
}
// 查询站点详细
export function getStation(id: ID) {
return request({
url: "/mes/station/" + id,
method: "get",
});
url: '/mes/station/' + id,
method: 'get',
})
}
// 判断站点是否为最后一个站点
export function isLastStation(id: ID) {
return request({
url: '/mes/station/' + id + '/isLast',
method: 'get'
method: 'get',
})
}
// 新增站点
export function addStation(data: MesStationData) {
return request({
url: "/mes/station",
method: "post",
url: '/mes/station',
method: 'post',
data,
});
})
}
// 修改站点
export function updateStation(data: MesStationData) {
return request({
url: "/mes/station",
method: "put",
url: '/mes/station',
method: 'put',
data,
});
})
}
// 删除站点
export function delStation(id: ID) {
return request({
url: "/mes/station/" + id,
method: "delete",
});
url: '/mes/station/' + id,
method: 'delete',
})
}
// 站点开工
export function startStation(id: ID) {
return request({
url: "/mes/station/" + id + "/start",
method: "put",
});
url: '/mes/station/' + id + '/start',
method: 'put',
})
}
// 站点完工
@@ -75,6 +75,6 @@ export function completeStation(id: ID, location: any) {
return request({
url: '/mes/station/' + id + '/complete',
method: 'put',
data: location
data: location,
})
}

View File

@@ -1,43 +1,43 @@
import { BaseEntity, PageQuery, type ID } from "@/api/common";
import { BaseEntity, PageQuery, type ID } from '@/api/common'
/**
* 站点查询参数
*/
export interface MesStationQuery extends PageQuery {
/** 随工单ID */
traceOrderId?: number;
traceOrderId?: ID
/** 随工单编码 */
traceOrderCode?: string;
traceOrderCode?: string
/** 站点序号 */
seqNo?: number;
seqNo?: number
/** 站点名称 */
name?: string;
name?: string
/** 站点编码 */
code?: string;
code?: string
/** 状态 */
status?: string;
status?: string
/** 计划数量最小值 */
planQtyMin?: number;
planQtyMin?: number
/** 计划数量最大值 */
planQtyMax?: number;
planQtyMax?: number
/** 合格数量最小值 */
okQtyMin?: number;
okQtyMin?: number
/** 合格数量最大值 */
okQtyMax?: number;
okQtyMax?: number
/** 不合格数量最小值 */
ngQtyMin?: number;
ngQtyMin?: number
/** 不合格数量最大值 */
ngQtyMax?: number;
ngQtyMax?: number
/** 进站时间范围开始 */
arrivalTimeStart?: string;
arrivalTimeStart?: string
/** 进站时间范围结束 */
arrivalTimeEnd?: string;
arrivalTimeEnd?: string
/** 出战时间范围开始 */
departureTimeStart?: string;
departureTimeStart?: string
/** 出战时间范围结束 */
departureTimeEnd?: string;
departureTimeEnd?: string
/** 删除标志 */
delStatus?: string;
delStatus?: string
}
/**
@@ -45,27 +45,27 @@ export interface MesStationQuery extends PageQuery {
*/
export interface MesStationData extends BaseEntity {
/** 主键ID */
id?: number;
id?: ID
/** 随工单ID */
traceOrderId?: number;
traceOrderId?: ID
/** 站点序号 */
seqNo?: number;
seqNo?: number
/** 站点名称 */
name?: string;
name?: string
/** 站点编码 */
code?: string;
code?: string
/** 状态 */
status?: string;
status?: string
/** 计划数量 */
planQty?: number;
planQty?: number
/** 合格数量 */
okQty?: number;
okQty?: number
/** 不合格数量 */
ngQty?: number;
ngQty?: number
/** 进站时间 */
arrivalTime?: string;
arrivalTime?: string
/** 出战时间 */
departureTime?: string;
departureTime?: string
/** 删除标志 */
delStatus?: string;
delStatus?: string
}

View File

@@ -3,10 +3,26 @@
<template v-for="(item, index) in options">
<template v-if="isValueMatch(item.value)">
<span
v-if="(item.elTagType == 'default' || item.elTagType == '') && (item.elTagClass == '' || item.elTagClass == null)"
:key="item.value" :index="index" :class="item.elTagClass">{{ item.label + " " }}</span>
<a-tag v-else :disable-transitions="true" :key="item.value + ''" :index="index" :color="getColor(item.elTagType)"
:class="item.elTagClass + ' ' + size">{{ item.label + " " }}</a-tag>
v-if="
(item.elTagType == 'default' || item.elTagType == '') &&
(item.elTagClass == '' || item.elTagClass == null)
"
:key="item.value"
:index="index"
:class="item.elTagClass"
>
{{ item.label + ' ' }}
</span>
<a-tag
v-else
:disable-transitions="true"
:key="item.value + ''"
:index="index"
:color="getColor(item.elTagType)"
:class="item.elTagClass + ' ' + size"
>
{{ item.label + ' ' }}
</a-tag>
</template>
</template>
<template v-if="unmatch && showValue">
@@ -19,10 +35,10 @@
import { computed, ref } from 'vue'
interface DictOption {
label: string;
value: string;
elTagType?: string;
elTagClass?: string;
label: string
value: string
elTagType?: string
elTagClass?: string
}
// 记录未匹配的项
@@ -43,7 +59,7 @@ const props = defineProps({
},
separator: {
type: String,
default: ",",
default: ',',
},
size: {
type: String,
@@ -52,19 +68,35 @@ const props = defineProps({
})
const values = computed(() => {
if (props.value === null || typeof props.value === 'undefined' || props.value === '') return []
if (typeof props.value === 'number' || typeof props.value === 'boolean') return [props.value]
return Array.isArray(props.value) ? props.value.map(item => '' + item) : String(props.value).split(props.separator)
if (
props.value === null ||
typeof props.value === 'undefined' ||
props.value === ''
)
return []
if (typeof props.value === 'number' || typeof props.value === 'boolean')
return [props.value]
return Array.isArray(props.value)
? props.value.map((item) => '' + item)
: String(props.value).split(props.separator)
})
const unmatch = computed(() => {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
unmatchArray.value = []
// 没有value不显示
if (props.value === null || typeof props.value === 'undefined' || props.value === '' || !Array.isArray(props.options) || props.options.length === 0) return false
if (
props.value === null ||
typeof props.value === 'undefined' ||
props.value === '' ||
!Array.isArray(props.options) ||
props.options.length === 0
)
return false
// 传入值为数组
let unmatch = false // 添加一个标志来判断是否有未匹配项
values.value.forEach(item => {
if (!props.options.some(v => v.value == item)) {
values.value.forEach((item) => {
if (!props.options.some((v) => v.value == item)) {
unmatchArray.value.push(item)
unmatch = true // 如果有未匹配项将标志设置为true
}
@@ -73,14 +105,14 @@ const unmatch = computed(() => {
})
function handleArray(array: string[]) {
if (array.length === 0) return ""
if (array.length === 0) return ''
return array.reduce((pre, cur) => {
return pre + " " + cur
return pre + ' ' + cur
})
}
function isValueMatch(itemValue: string) {
return values.value.some(val => val == itemValue)
return values.value.some((val) => val == itemValue)
}
function getColor(tagType: string | undefined) {

View File

@@ -1,7 +1,13 @@
<template>
<header class="header-container" :class="{ 'hide-shadow': hideShadow }"
:style="{ height, zIndex, lineHeight: height }">
<div class="opts left-opts" v-if="$slots['left-opts'] || title || $slots.title">
<header
class="header-container"
:class="{ 'hide-shadow': hideShadow }"
:style="{ height, zIndex, lineHeight: height }"
>
<div
class="opts left-opts"
v-if="$slots['left-opts'] || title || $slots.title"
>
<a-button v-if="showBack" @click="back">返回</a-button>
<a-button v-if="showHome" @click="backToHome">首页</a-button>
<slot name="left-opts" />
@@ -11,20 +17,29 @@
{{ title }}
</slot>
</div>
<div class="opts right-opts" v-if="$slots['right-opts'] || title || $slots.title">
<div
class="opts right-opts"
v-if="$slots['right-opts'] || title || $slots.title"
>
<slot name="right-opts" />
<a-button @click="handleLogout" type="primary" danger
v-if="showLogout && username">退出{{ username }}</a-button>
<a-button
@click="handleLogout"
type="primary"
danger
v-if="showLogout && username"
>
退出{{ username }}
</a-button>
</div>
<slot />
</header>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router';
import { Modal } from 'ant-design-vue';
import { useAuthStore, useUserStore } from '@/store';
import { storeToRefs } from 'pinia';
import { useRouter } from 'vue-router'
import { Modal } from 'ant-design-vue'
import { useAuthStore, useUserStore } from '@/store'
import { storeToRefs } from 'pinia'
defineProps({
showHome: {
@@ -45,7 +60,7 @@ defineProps({
},
hideShadow: {
type: Boolean,
default: false
default: false,
},
height: {
type: String,
@@ -54,23 +69,23 @@ defineProps({
zIndex: {
type: Number,
default: 999,
}
});
},
})
defineSlots<{
'default'(): any;
'left-opts'(): any;
'title'(): any;
'right-opts'(): any;
}>();
'default'(): any
'left-opts'(): any
'title'(): any
'right-opts'(): any
}>()
const emit = defineEmits(['back']);
const router = useRouter();
const emit = defineEmits(['back'])
const router = useRouter()
const userStore = useUserStore();
const { username } = storeToRefs(userStore);
const userStore = useUserStore()
const { username } = storeToRefs(userStore)
const authStore = useAuthStore();
const authStore = useAuthStore()
const handleLogout = () => {
Modal.confirm({
title: '提示',
@@ -78,23 +93,23 @@ const handleLogout = () => {
okText: '确定',
cancelText: '取消',
onOk: () => {
authStore.logout();
authStore.logout()
},
});
};
})
}
const back = () => {
emit('back');
defaultBack();
};
emit('back')
defaultBack()
}
const defaultBack = () => {
router.go(-1);
};
router.go(-1)
}
const backToHome = () => {
router.push({ name: 'Index' });
};
router.push({ name: 'Index' })
}
</script>
<style scoped lang="scss">
@@ -106,7 +121,9 @@ const backToHome = () => {
gap: 10px;
background-color: #1f2e54;
color: #fff;
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;
box-shadow:
rgba(0, 0, 0, 0.1) 0px 4px 6px -1px,
rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;
&.hide-shadow {
box-shadow: none;

View File

@@ -4,7 +4,8 @@
<a-space class="subtitle">
<slot />
<a-button v-if="showRefresh" size="small" @click="handleRefresh">
<template #icon><i-lucide-rotate-ccw /></template>刷新
<template #icon><i-lucide-rotate-ccw /></template>
刷新
</a-button>
</a-space>
</div>
@@ -20,16 +21,16 @@ defineProps({
type: Boolean,
default: false,
},
});
})
const slots = defineSlots<{
'default'(): any;
}>();
defineSlots<{
'default'(): any
}>()
const emit = defineEmits(['refresh']);
const emit = defineEmits(['refresh'])
const handleRefresh = () => {
emit('refresh');
};
emit('refresh')
}
</script>
<style scoped lang="scss">

View File

@@ -2,68 +2,87 @@
<a-button class="action-btn" @click="handleHold">Hold</a-button>
<!-- Hold Modal -->
<a-modal v-model:open="openHoldModal" title="Hold 操作" @cancel="handleCloseHold" @ok="handleSubmitHold">
<a-modal
v-model:open="openHoldModal"
title="Hold 操作"
@cancel="handleCloseHold"
@ok="handleSubmitHold"
>
<a-form :colon="false" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
<a-form-item label="工单编码">
<a-input v-model:value="traceOrderStore.traceOrderInfo.code" readonly />
</a-form-item>
<a-form-item label="目标产品编码">
<a-input v-model:value="traceOrderStore.traceOrderInfo.tarMaterialCode" readonly />
<a-input
v-model:value="traceOrderStore.traceOrderInfo.tarMaterialCode"
readonly
/>
</a-form-item>
<a-form-item label="目标产品名称">
<a-input v-model:value="traceOrderStore.traceOrderInfo.tarMaterialName" readonly />
<a-input
v-model:value="traceOrderStore.traceOrderInfo.tarMaterialName"
readonly
/>
</a-form-item>
<a-form-item label="发起工序名称">
<a-input v-model:value="traceOrderStore.stationInfo.operationTitle" readonly />
<a-input
v-model:value="traceOrderStore.stationInfo.operationTitle"
readonly
/>
</a-form-item>
<a-form-item label="产品规格">
<a-input readonly />
</a-form-item>
<a-form-item label="计划完成日期">
<a-date-picker v-model:value="planFinishDate" placeholder="选择计划完成日期" valueFormat="YYYY-MM-DD HH:mm:ss" show-time
style="width: 100%" />
<a-date-picker
v-model:value="planFinishDate"
placeholder="选择计划完成日期"
valueFormat="YYYY-MM-DD HH:mm:ss"
show-time
style="width: 100%"
/>
</a-form-item>
</a-form>
</a-modal>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useDialog } from '@/utils/useDialog';
import { useTraceOrderStore } from '@/store';
import { message } from 'ant-design-vue';
import { addQualityAbnormalContact } from '@/api/traceOrderManage';
import { ref } from 'vue'
import { useDialog } from '@/utils/useDialog'
import { useTraceOrderStore } from '@/store'
import { message } from 'ant-design-vue'
import { addQualityAbnormalContact } from '@/api/traceOrderManage'
const traceOrderStore = useTraceOrderStore();
const traceOrderStore = useTraceOrderStore()
// useDialog 管理弹窗状态
const { visible: openHoldModal, show, hide } = useDialog();
const { visible: openHoldModal, show, hide } = useDialog()
const planFinishDate = ref('');
const planFinishDate = ref('')
const handleHold = () => {
if (!traceOrderStore.currentTraceOrderCode) {
message.error('请先选择工单!');
return;
message.error('请先选择工单!')
return
}
show()
}
show();
};
const handleCloseHold = () => {
planFinishDate.value = '';
hide();
};
planFinishDate.value = ''
hide()
}
const handleSubmitHold = async () => {
const tmpPlanFinishDate = planFinishDate.value;
const tmpPlanFinishDate = planFinishDate.value
// 修改随工单状态
try {
message.success('Hold 成功!')
planFinishDate.value = '';
openHoldModal.value = false;
planFinishDate.value = ''
openHoldModal.value = false
} catch (error: any) {
message.error(error.message || 'Hold 失败');
return;
message.error(error.message || 'Hold 失败')
return
}
// 添加修改记录
@@ -72,11 +91,11 @@ const handleSubmitHold = async () => {
materialCode: traceOrderStore.traceOrderInfo.tarMaterialCode,
abnormalOperation: traceOrderStore.stationInfo.operationCode,
planFinishDate: tmpPlanFinishDate,
status: "Hold",
status: 'Hold',
})
} catch (error: any) {
message.error(error.message || '添加记录异常');
return;
message.error(error.message || '添加记录异常')
return
}
}
};
</script>

View File

@@ -1,21 +1,21 @@
import { createApp } from "vue";
import App from "./App.vue";
import { createApp } from 'vue'
import App from './App.vue'
// Pinia 状态管理
import { createPinia } from "pinia";
const pinia = createPinia();
import { createPinia } from 'pinia'
const pinia = createPinia()
// Vue Router
import router from "./router";
import router from './router'
// 样式文件
import "ant-design-vue/dist/reset.css";
import "@/assets/styles/index.scss";
import 'ant-design-vue/dist/reset.css'
import '@/assets/styles/index.scss'
const app = createApp(App);
const app = createApp(App)
// 字典方法
import { useDict } from "@/utils/dict";
app.config.globalProperties.useDict = useDict;
import { useDict } from '@/utils/dict'
app.config.globalProperties.useDict = useDict
app.use(pinia).use(router).mount("#app");
app.use(pinia).use(router).mount('#app')

View File

@@ -1,120 +1,119 @@
import { createRouter, createWebHistory } from 'vue-router';
import { getToken } from "@/utils/auth";
import { createRouter, createWebHistory } from 'vue-router'
import { getToken } from '@/utils/auth'
const whiteList = ["/login"];
const whiteList = ['/login']
const routes = [
{
path: "/",
name: "Index",
component: () => import("@/views/index.vue"),
path: '/',
name: 'Index',
component: () => import('@/views/index.vue'),
},
{
path: "/login",
name: "Login",
component: () => import("@/views/login.vue"),
path: '/login',
name: 'Login',
component: () => import('@/views/login.vue'),
},
{
path: "/traceOrderManage",
name: "TraceOrderManage",
component: () => import("@/views/traceOrderManage/layout.vue"),
redirect: { name: "TraceOrderManageIndex" },
path: '/traceOrderManage',
name: 'TraceOrderManage',
component: () => import('@/views/traceOrderManage/layout.vue'),
redirect: { name: 'TraceOrderManageIndex' },
children: [
{
path: "",
name: "TraceOrderManageIndex",
component: () => import("@/views/traceOrderManage/index.vue"),
path: '',
name: 'TraceOrderManageIndex',
component: () => import('@/views/traceOrderManage/index.vue'),
},
{
path: "infeed",
name: "Infeed",
component: () => import("@/views/traceOrderManage/infeed/layout.vue"),
redirect: { name: "PrimaryMaterial" },
path: 'infeed',
name: 'Infeed',
component: () => import('@/views/traceOrderManage/infeed/layout.vue'),
redirect: { name: 'PrimaryMaterial' },
children: [
{
path: "primaryMaterial",
name: "PrimaryMaterial",
path: 'primaryMaterial',
name: 'PrimaryMaterial',
component: () =>
import("@/views/traceOrderManage/infeed/primaryMaterial/index.vue"),
import('@/views/traceOrderManage/infeed/primaryMaterial/index.vue'),
},
{
path: "rawMaterial",
name: "RawMaterial",
path: 'rawMaterial',
name: 'RawMaterial',
component: () =>
import("@/views/traceOrderManage/infeed/rawMaterial/index.vue"),
import('@/views/traceOrderManage/infeed/rawMaterial/index.vue'),
},
{
path: "mask",
name: "Mask",
component: () => import("@/views/traceOrderManage/infeed/mask/index.vue"),
path: 'mask',
name: 'Mask',
component: () =>
import('@/views/traceOrderManage/infeed/mask/index.vue'),
},
{
path: "equipment",
name: "Equipment",
path: 'equipment',
name: 'Equipment',
component: () =>
import("@/views/traceOrderManage/infeed/equipment/index.vue"),
import('@/views/traceOrderManage/infeed/equipment/index.vue'),
},
],
},
{
path: "outfeed",
name: "Outfeed",
component: () => import("@/views/traceOrderManage/outfeed/layout.vue"),
redirect: { name: "JobReport" },
path: 'outfeed',
name: 'Outfeed',
component: () => import('@/views/traceOrderManage/outfeed/layout.vue'),
redirect: { name: 'JobReport' },
children: [
{
path: "jobReport",
name: "JobReport",
path: 'jobReport',
name: 'JobReport',
component: () =>
import("@/views/traceOrderManage/outfeed/jobReport/index.vue"),
import('@/views/traceOrderManage/outfeed/jobReport/index.vue'),
},
{
path: "parameterConfiguration",
name: "ParameterConfiguration",
path: 'parameterConfiguration',
name: 'ParameterConfiguration',
component: () =>
import(
"@/views/traceOrderManage/outfeed/parameterConfiguration/index.vue"
),
import('@/views/traceOrderManage/outfeed/parameterConfiguration/index.vue'),
},
{
path: "processGuidance",
name: "ProcessGuidance",
path: 'processGuidance',
name: 'ProcessGuidance',
component: () =>
import("@/views/traceOrderManage/outfeed/processGuidance/index.vue"),
import('@/views/traceOrderManage/outfeed/processGuidance/index.vue'),
},
],
},
],
},
];
]
const router = createRouter({
history: createWebHistory(),
routes
});
routes,
})
router.beforeEach(async (to, from, next) => {
const token = getToken();
const token = getToken()
// 已登录,访问 login → 跳首页
if (token && to.path === "/login") {
return next("/");
if (token && to.path === '/login') {
return next('/')
}
// 白名单放行
if (whiteList.includes(to.path)) {
return next();
return next()
}
// 未登录,访问受保护路由
if (!token) {
return next({
path: "/login",
path: '/login',
query: { redirect: to.fullPath },
});
})
}
// 已登录,正常访问
next();
});
next()
})
export default router;
export default router

10
src/shim-vue.d.ts vendored
View File

@@ -1,5 +1,7 @@
declare module "*.vue" {
import type { DefineComponent } from "vue";
const component: DefineComponent<{}, {}, any>;
export default component;
declare module '*.vue' {
import type { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
const component: DefineComponent<{}, {}, any>
export default component
}

View File

@@ -1,7 +1,7 @@
// 系统管理
export * from './system/auth';
export * from './system/dict';
export * from './system/user';
export * from './system/auth'
export * from './system/dict'
export * from './system/user'
// 工单管理
export * from './traceOrderManage/traceOrder';
export * from './traceOrderManage/traceOrder'

View File

@@ -1,62 +1,68 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { message, notification } from "ant-design-vue";
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { message, notification } from 'ant-design-vue'
import { useUserStore } from "./user";
import { login, logout as logoutApi } from "@/api/system";
import { setToken, getToken, removeToken, removeAccount, getRememberMe } from "@/utils/auth";
import type { LoginInfo } from "@/api/system/model";
import { useUserStore } from './user'
import { login, logout as logoutApi } from '@/api/system'
import {
setToken,
getToken,
removeToken,
removeAccount,
getRememberMe,
} from '@/utils/auth'
import type { LoginInfo } from '@/api/system/model'
export const useAuthStore = defineStore("auth", () => {
const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
export const useAuthStore = defineStore('auth', () => {
const route = useRoute()
const router = useRouter()
const userStore = useUserStore()
const loginLoading = ref(false);
const token = ref(getToken() || null);
const loginLoading = ref(false)
const token = ref(getToken() || null)
async function authLogin(params: LoginInfo, rememberMe: boolean) {
try {
loginLoading.value = true;
const res = await login(params);
loginLoading.value = true
const res = await login(params)
if (res.code === 200) {
setToken(res.token as string);
await userStore.fetchUserInfo();
setToken(res.token as string)
await userStore.fetchUserInfo()
notification.success({
message: "登录成功",
message: '登录成功',
description: `欢迎回来,${params.username}`,
duration: 3,
});
})
if (rememberMe) {
userStore.setUserInfo({ ...params, rememberMe });
userStore.setUserInfo({ ...params, rememberMe })
} else {
userStore.clearUserInfo();
userStore.setUsername(params.username);
userStore.clearUserInfo()
userStore.setUsername(params.username)
}
const redirect = route.query.redirect || "/";
router.replace(redirect as string);
return res;
const redirect = route.query.redirect || '/'
router.replace(redirect as string)
return res
} else {
// 抛出错误,让调用方处理
throw new Error(res.msg || "登录失败");
throw new Error(res.msg || '登录失败')
}
} catch (error) {
throw error;
throw error
} finally {
loginLoading.value = false;
loginLoading.value = false
}
}
async function logout() {
// 在实际应用中,这里可以调用后端的退出登录接口
await logoutApi();
removeToken();
!getRememberMe() && removeAccount();
await router.push("/login");
message.success("已成功退出");
await logoutApi()
removeToken()
if (!getRememberMe()) removeAccount()
await router.push('/login')
message.success('已成功退出')
}
return {
@@ -64,5 +70,5 @@ export const useAuthStore = defineStore("auth", () => {
loginLoading,
authLogin,
logout,
};
});
}
})

View File

@@ -1,53 +1,55 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useDictStore = defineStore("dict", () => {
const dict = ref<any[]>([]);
export const useDictStore = defineStore('dict', () => {
const dict = ref<any[]>([])
// 获取字典
function getDict(_key: string) {
if (_key == null && _key == "") {
return null;
if (_key == null && _key == '') {
return null
}
try {
dict.value.forEach(item => {
dict.value.forEach((item) => {
if (item.key == _key) {
return item.value;
return item.value
}
});
})
} catch (e) {
return null;
console.error(e)
return null
}
}
// 设置字典
function setDict(_key: string, value: string | number) {
if (!_key) return;
if (!_key) return
dict.value.push({
key: _key,
value: value
});
value: value,
})
}
// 删除字典
function removeDict(_key: string) {
var bln = false;
let bln = false
try {
dict.value.forEach((item, index) => {
if (item.key == _key) {
dict.value.splice(index, 1);
bln = true;
dict.value.splice(index, 1)
bln = true
}
});
})
} catch (e) {
bln = false;
console.error(e)
bln = false
}
return bln;
return bln
}
// 清空字典
function cleanDict() {
dict.value = new Array();
dict.value = []
}
// 初始字典
function initDict() {}
@@ -59,5 +61,5 @@ export const useDictStore = defineStore("dict", () => {
removeDict,
cleanDict,
initDict,
};
});
}
})

View File

@@ -1,32 +1,34 @@
import { defineStore } from 'pinia';
import Cookies from 'js-cookie';
import { setAccount, removeAccount } from "@/utils/auth";
import { ref } from 'vue';
import { defineStore } from 'pinia'
import Cookies from 'js-cookie'
import { setAccount, removeAccount } from '@/utils/auth'
import { ref } from 'vue'
export const useUserStore = defineStore("user", () => {
const username = ref(Cookies.get('username') || '');
export const useUserStore = defineStore('user', () => {
const username = ref(Cookies.get('username') || '')
async function fetchUserInfo() {
// Simulate API call
}
function setUsername(name: string) {
username.value = name;
username.value = name
}
function setUserInfo(params: any) {
username.value = params.username || '';
username.value = params.username || ''
params.rememberMe && setAccount({
if (params.rememberMe) {
setAccount({
username: params.username,
password: params.password,
rememberMe: params.rememberMe,
});
})
}
}
function clearUserInfo() {
username.value = '';
removeAccount();
username.value = ''
removeAccount()
}
return {
@@ -36,4 +38,4 @@ export const useUserStore = defineStore("user", () => {
setUserInfo,
clearUserInfo,
}
});
})

View File

@@ -1,128 +1,128 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import { message } from "ant-design-vue";
import { debounce } from 'lodash';
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { message } from 'ant-design-vue'
import { debounce } from 'lodash'
import { listStation, getStation } from "@/api/traceOrderManage/station";
import {
listLotTraceOrder,
getLotTraceOrder,
} from "@/api/traceOrderManage";
import { listStation, getStation } from '@/api/traceOrderManage/station'
import { listLotTraceOrder, getLotTraceOrder } from '@/api/traceOrderManage'
export const useTraceOrderStore = defineStore("traceOrder", () => {
export const useTraceOrderStore = defineStore('traceOrder', () => {
/* ========= 核心业务状态 ========= */
const currentTraceOrderId = ref<string | number>('');
const currentTraceOrderCode = ref<string>('');
const currentStationId = ref<string | number>('');
const currentStationCode = ref<string>('');
const currentTraceOrderId = ref<string | number>('')
const currentTraceOrderCode = ref<string>('')
const currentStationId = ref<string | number>('')
const currentStationCode = ref<string>('')
const traceOrderOptions = ref<any[]>([]);
const traceOrderInfo = ref<any>({});
const stationInfo = ref<any>({});
const stationList = ref<any[]>([]);
const traceOrderOptions = ref<any[]>([])
const traceOrderInfo = ref<any>({})
const stationInfo = ref<any>({})
const stationList = ref<any[]>([])
/* ========= actions ========= */
// 加载工单列表
const fetchTraceOrderOption = debounce(async (code: string) => {
if (!code) return;
if (!code) return
try {
const { rows } = await listLotTraceOrder({
code,
pageSize: 10,
pageNum: 1,
});
})
traceOrderOptions.value = rows.map((item) => {
return {
id: item.id,
label: item.code,
value: item.code,
};
});
} catch (error: any) {
message.error(error.message || "获取工单列表失败");
}
}, 500);
})
} catch (error: any) {
message.error(error.message || '获取工单列表失败')
}
}, 500)
// 选择工单
async function selectTraceOrder(value: string, option: any) {
currentTraceOrderId.value = option.id;
currentTraceOrderCode.value = value;
currentTraceOrderId.value = option.id
currentTraceOrderCode.value = value
try {
loadingTraceOrderInfo.value = true;
if (!option.id) throw new Error();
const { data } = await getLotTraceOrder(option.id);
traceOrderInfo.value = data;
await fetchStationList();
loadingTraceOrderInfo.value = true
if (!option.id) throw new Error()
const { data } = await getLotTraceOrder(option.id)
traceOrderInfo.value = data
await fetchStationList()
} catch (error: any) {
message.error(error.message || "获取工单信息失败");
message.error(error.message || '获取工单信息失败')
} finally {
loadingTraceOrderInfo.value = false;
loadingTraceOrderInfo.value = false
}
}
// 获取工单信息
const loadingTraceOrderInfo = ref(false);
const loadingTraceOrderInfo = ref(false)
async function fetchTraceOrderInfo() {
if (!currentTraceOrderId.value) return;
if (!currentTraceOrderId.value) return
try {
loadingTraceOrderInfo.value = true;
const { data } = await getLotTraceOrder(currentTraceOrderId.value);
traceOrderInfo.value = data;
fetchStationList();
loadingTraceOrderInfo.value = true
const { data } = await getLotTraceOrder(currentTraceOrderId.value)
traceOrderInfo.value = data
fetchStationList()
} catch (error: any) {
message.error(error.message || '刷新工单信息失败');
message.error(error.message || '刷新工单信息失败')
} finally {
loadingTraceOrderInfo.value = false;
loadingTraceOrderInfo.value = false
}
}
// 获取站点列表
const loadingStations = ref(false);
const loadingStations = ref(false)
async function fetchStationList() {
if (!currentTraceOrderCode.value) return;
if (!currentTraceOrderCode.value) return
try {
loadingStations.value = true;
const res = await listStation({ traceOrderCode: currentTraceOrderCode.value });
loadingStations.value = true
const res = await listStation({
traceOrderCode: currentTraceOrderCode.value,
})
stationList.value = (res.rows || []).map((item: any, idx: number) => {
return {
key: String(item.id ?? idx),
index: item.seqNo ?? idx + 1,
...item,
};
});
} catch (error: any) {
message.error(error?.msg || error?.message || "查询站点失败");
} finally {
loadingStations.value = false;
}
};
})
} catch (error: any) {
message.error(error?.msg || error?.message || '查询站点失败')
} finally {
loadingStations.value = false
}
}
// 获取站点信息
const loadingStationInfo = ref(false);
const loadingStationInfo = ref(false)
async function fetchStationInfo() {
if (!currentTraceOrderId.value) return;
if (!currentTraceOrderId.value) return
try {
loadingStationInfo.value = true;
const { data } = await getStation(currentStationId.value);
stationInfo.value = data;
loadingStationInfo.value = true
const { data } = await getStation(currentStationId.value)
stationInfo.value = data
} catch (error: any) {
console.log(error.message || '获取站点信息失败');
console.log(error.message || '获取站点信息失败')
} finally {
loadingStationInfo.value = false;
loadingStationInfo.value = false
}
}
// 选择站点
async function selectStation(stationId: string) {
currentStationId.value = stationId;
stationInfo.value = stationList.value.find((s: any) => s.id === stationId) ?? {};
currentStationCode.value = stationInfo.value.code;
currentStationId.value = stationId
stationInfo.value =
stationList.value.find((s: any) => s.id === stationId) ?? {}
currentStationCode.value = stationInfo.value.code
}
async function refreshAll() {
await Promise.all([fetchTraceOrderInfo(), fetchStationList()]);
await Promise.all([fetchTraceOrderInfo(), fetchStationList()])
}
return {
@@ -144,5 +144,5 @@ export const useTraceOrderStore = defineStore("traceOrder", () => {
fetchTraceOrderInfo,
fetchStationList,
fetchStationInfo,
};
});
}
})

View File

@@ -1,8 +1,8 @@
import Cookies from 'js-cookie';
import { encrypt } from '@/utils/jsencrypt';
import Cookies from 'js-cookie'
import { encrypt } from '@/utils/jsencrypt'
// 30 天
export const expiresTime = 30;
export const expiresTime = 30
export const TokenKey = 'Admin-Token'
@@ -19,30 +19,35 @@ export function removeToken() {
}
export interface AccountInfo {
username?: string;
password?: string;
rememberMe?: 'true' | 'false';
username?: string
password?: string
rememberMe?: 'true' | 'false'
}
export function getRememberMe() {
return Cookies.get("rememberMe");
return Cookies.get('rememberMe')
}
export function getAccount() {
return {
username: Cookies.get("username"),
password: Cookies.get("password"),
};
username: Cookies.get('username'),
password: Cookies.get('password'),
}
}
export function setAccount(account: AccountInfo) {
account.username && Cookies.set("username", account.username, { expires: expiresTime});
account.password && Cookies.set("password", encrypt(account.password) as string, { expires: expiresTime});
account.rememberMe != null && Cookies.set("rememberMe", account.rememberMe, { expires: expiresTime});
if (account.username)
Cookies.set('username', account.username, { expires: expiresTime })
if (account.password)
Cookies.set('password', encrypt(account.password) as string, {
expires: expiresTime,
})
if (account.rememberMe)
Cookies.set('rememberMe', account.rememberMe, { expires: expiresTime })
}
export function removeAccount() {
Cookies.remove("username");
Cookies.remove("password");
Cookies.remove("rememberMe");
Cookies.remove('username')
Cookies.remove('password')
Cookies.remove('rememberMe')
}

View File

@@ -1,8 +1,8 @@
import { message } from "ant-design-vue";
import { message } from 'ant-design-vue'
// 复制到剪贴板
export const handleCopy = async (text: string | number | undefined) => {
if (!text) return;
await navigator.clipboard.writeText(String(text));
message.success(`已复制: ${text}`);
};
if (!text) return
await navigator.clipboard.writeText(String(text))
message.success(`已复制: ${text}`)
}

View File

@@ -1,4 +1,4 @@
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { ref, onMounted, onBeforeUnmount } from 'vue'
/**
* 格式化日期时间
@@ -6,19 +6,22 @@ import { ref, onMounted, onBeforeUnmount } from 'vue';
* @param format 格式字符串,默认 'YYYY-MM-DD HH:mm:ss'
* @returns 格式化后的时间字符串
*/
export function formatDateTime(date: Date | number | string, format = 'YYYY-MM-DD HH:mm:ss'): string {
const d = new Date(date);
export function formatDateTime(
date: Date | number | string,
format = 'YYYY-MM-DD HH:mm:ss',
): string {
const d = new Date(date)
if (isNaN(d.getTime())) {
return '';
return ''
}
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
const hours = String(d.getHours()).padStart(2, '0');
const minutes = String(d.getMinutes()).padStart(2, '0');
const seconds = String(d.getSeconds()).padStart(2, '0');
const year = d.getFullYear()
const month = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
const hours = String(d.getHours()).padStart(2, '0')
const minutes = String(d.getMinutes()).padStart(2, '0')
const seconds = String(d.getSeconds()).padStart(2, '0')
return format
.replace('YYYY', String(year))
@@ -26,7 +29,7 @@ export function formatDateTime(date: Date | number | string, format = 'YYYY-MM-D
.replace('DD', day)
.replace('HH', hours)
.replace('mm', minutes)
.replace('ss', seconds);
.replace('ss', seconds)
}
/**
@@ -35,7 +38,7 @@ export function formatDateTime(date: Date | number | string, format = 'YYYY-MM-D
* @returns 当前时间字符串
*/
export function getCurrentTime(format = 'YYYY-MM-DD HH:mm:ss'): string {
return formatDateTime(new Date(), format);
return formatDateTime(new Date(), format)
}
/**
@@ -44,28 +47,28 @@ export function getCurrentTime(format = 'YYYY-MM-DD HH:mm:ss'): string {
* @returns 响应式时间字符串
*/
export function useRealTime(format = 'YYYY-MM-DD HH:mm:ss') {
const currentTime = ref(getCurrentTime(format));
let timer: NodeJS.Timeout | null = null;
const currentTime = ref(getCurrentTime(format))
let timer: NodeJS.Timeout | null = null
const startTimer = () => {
timer = setInterval(() => {
currentTime.value = getCurrentTime(format);
}, 1000);
};
currentTime.value = getCurrentTime(format)
}, 1000)
}
const stopTimer = () => {
if (timer) {
clearInterval(timer);
timer = null;
clearInterval(timer)
timer = null
}
}
};
onMounted(startTimer);
onBeforeUnmount(stopTimer);
onMounted(startTimer)
onBeforeUnmount(stopTimer)
return {
currentTime,
startTimer,
stopTimer
};
stopTimer,
}
}

View File

@@ -1,4 +1,4 @@
import { useDictStore } from "@/store";
import { useDictStore } from '@/store'
import { getDicts } from '@/api/system/dict'
import { ref, toRefs } from 'vue'
@@ -9,14 +9,19 @@ export function useDict(...args: string[]) {
const res = ref<any>({})
return (() => {
args.forEach((dictType, index) => {
args.forEach((dictType) => {
res.value[dictType] = []
const dicts = useDictStore().getDict(dictType)
if (dicts) {
res.value[dictType] = dicts
} else {
getDicts(dictType).then((resp: any) => {
res.value[dictType] = resp.data.map((p: any) => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass }))
res.value[dictType] = resp.data.map((p: any) => ({
label: p.dictLabel,
value: p.dictValue,
elTagType: p.listClass,
elTagClass: p.cssClass,
}))
useDictStore().setDict(dictType, res.value[dictType])
})
}

View File

@@ -1,31 +1,31 @@
import JSEncrypt from "jsencrypt";
import JSEncrypt from 'jsencrypt'
// 密钥对生成 http://web.chacuo.net/netrsakeypair
const publicKey =
"MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n" +
"nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==";
'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n' +
'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
const privateKey =
"MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n" +
"7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n" +
"PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n" +
"kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n" +
"cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n" +
"DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n" +
"YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n" +
"UP8iWi1Qw0Y=";
'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n' +
'7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n' +
'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\n' +
'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\n' +
'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\n' +
'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n' +
'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n' +
'UP8iWi1Qw0Y='
// 加密
export function encrypt(txt: string) {
const encryptor = new JSEncrypt();
encryptor.setPublicKey(publicKey); // 设置公钥
return encryptor.encrypt(txt); // 对数据进行加密
const encryptor = new JSEncrypt()
encryptor.setPublicKey(publicKey) // 设置公钥
return encryptor.encrypt(txt) // 对数据进行加密
}
// 解密
export function decrypt(txt: string) {
const encryptor = new JSEncrypt();
encryptor.setPrivateKey(privateKey); // 设置私钥
return encryptor.decrypt(txt); // 对数据进行解密
const encryptor = new JSEncrypt()
encryptor.setPrivateKey(privateKey) // 设置私钥
return encryptor.decrypt(txt) // 对数据进行解密
}

View File

@@ -1,7 +1,7 @@
export const delay = (time: number, data?: any) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(data);
}, time);
}) as any;
};
resolve(data)
}, time)
}) as any
}

View File

@@ -1,33 +1,33 @@
import { ref } from 'vue';
import { ref } from 'vue'
/**
* Dialog控制Hook
* @returns visible显隐状态、show显示、hide隐藏、toggle切换
*/
export function useDialog(initialVisible: boolean = false): {
visible: import('vue').Ref<boolean>,
show: () => void,
hide: () => void,
visible: import('vue').Ref<boolean>
show: () => void
hide: () => void
toggle: () => void
} {
const visible = ref(initialVisible);
const visible = ref(initialVisible)
const show = () => {
visible.value = true;
};
visible.value = true
}
const hide = () => {
visible.value = false;
};
visible.value = false
}
const toggle = () => {
visible.value = !visible.value;
};
visible.value = !visible.value
}
return {
visible,
show,
hide,
toggle
};
toggle,
}
}

View File

@@ -1,41 +1,43 @@
import { ref } from 'vue';
import { ref } from 'vue'
/**
* Loading控制Hook
* @returns loading加载状态、startLoading开始加载、stopLoading停止加载、withLoading包装异步函数
*/
export function useLoading(initialLoading = false) {
const loading = ref(initialLoading);
const loading = ref(initialLoading)
const startLoading = () => {
loading.value = true;
};
loading.value = true
}
const stopLoading = () => {
loading.value = false;
};
loading.value = false
}
/**
* 包装异步函数自动控制loading状态
* @param fn 异步函数
* @returns 包装后的函数
*/
const withLoading = <T extends (...args: any[]) => Promise<any>>(fn: T): T => {
const withLoading = <T extends (...args: any[]) => Promise<any>>(
fn: T,
): T => {
return (async (...args: any[]) => {
startLoading();
startLoading()
try {
const result = await fn(...args);
return result;
const result = await fn(...args)
return result
} finally {
stopLoading();
stopLoading()
}
}) as T
}
}) as T;
};
return {
loading,
startLoading,
stopLoading,
withLoading
};
withLoading,
}
}

View File

@@ -4,8 +4,8 @@
*/
export function generateUUID(): string {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
const r = (Math.random() * 16) | 0
const v = c === 'x' ? r : (r & 0x3) | 0x8
return v.toString(16)
})
}

View File

@@ -3,7 +3,11 @@
<Header title="MES 过站平台" showLogout />
<div class="menu-grid">
<a-card class="menu-card" shadow="hover" @click="handleJumpTo('TraceOrderManage')">
<a-card
class="menu-card"
shadow="hover"
@click="handleJumpTo('TraceOrderManage')"
>
<div class="icon-wrap">
<i-lucide-building />
</div>
@@ -86,14 +90,14 @@
</div>
</template>
<script setup>
import { useRouter } from 'vue-router';
<script setup lang="ts">
import { useRouter } from 'vue-router'
const router = useRouter();
const router = useRouter()
const handleJumpTo = (name) => {
router.push({ name });
};
const handleJumpTo = (name: string) => {
router.push({ name })
}
</script>
<style scoped lang="scss">

View File

@@ -3,14 +3,14 @@ import { ref, reactive } from 'vue'
import { message } from 'ant-design-vue'
import { getCaptcha } from '@/api/system'
import type { LoginInfo } from '@/api/system/model'
import type { Rule } from 'ant-design-vue/es/form';
import { useAuthStore } from '@/store';
import { storeToRefs } from 'pinia';
import { getAccount, getRememberMe } from '@/utils/auth';
import { decrypt } from '@/utils/jsencrypt';
import type { Rule } from 'ant-design-vue/es/form'
import { useAuthStore } from '@/store'
import { storeToRefs } from 'pinia'
import { getAccount, getRememberMe } from '@/utils/auth'
import { decrypt } from '@/utils/jsencrypt'
const authStore = useAuthStore();
const { loginLoading } = storeToRefs(authStore);
const authStore = useAuthStore()
const { loginLoading } = storeToRefs(authStore)
// 表单数据
const formData = reactive<LoginInfo>({
@@ -28,15 +28,9 @@ const rememberMe = ref(false)
// 表单验证规则
const rules: Record<string, Rule[]> = {
username: [
{ required: true, message: '请输入用户名', trigger: 'change' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'change' }
],
code: [
{ required: true, message: '请输入验证码', trigger: 'change' }
]
username: [{ required: true, message: '请输入用户名', trigger: 'change' }],
password: [{ required: true, message: '请输入密码', trigger: 'change' }],
code: [{ required: true, message: '请输入验证码', trigger: 'change' }],
}
// 获取验证码
@@ -45,18 +39,18 @@ const refreshCaptcha = async () => {
captchaImg.value = ''
captchaLoading.value = true
await getCaptcha().then((res: any) => {
loadCaptchaFail.value = false;
loadCaptchaFail.value = false
if (res.captchaOnOff) {
captchaImg.value = "data:image/gif;base64," + res.img
rules.code[0].required = true;
captchaImg.value = 'data:image/gif;base64,' + res.img
rules.code[0].required = true
} else {
rules.code[0].required = false;
rules.code[0].required = false
}
formData.uuid = res.uuid
formData.code = '' // 清空验证码输入
})
} catch (error) {
loadCaptchaFail.value = true;
loadCaptchaFail.value = true
message.error('获取验证码失败')
console.error('获取验证码失败:', error)
} finally {
@@ -66,22 +60,22 @@ const refreshCaptcha = async () => {
// 初始化
const initLoginForm = () => {
const { username, password } = getAccount();
const isRememberMe = getRememberMe();
const { username, password } = getAccount()
const isRememberMe = getRememberMe()
if (isRememberMe && isRememberMe === 'true') {
rememberMe.value = true;
formData.username = username || '';
formData.password = decrypt(password || '') || '';
rememberMe.value = true
formData.username = username || ''
formData.password = decrypt(password || '') || ''
}
}
// 登录处理
const handleLogin = async () => {
try {
await authStore.authLogin(formData, rememberMe.value);
await authStore.authLogin(formData, rememberMe.value)
} catch (error: any) {
message.error(error.message || error.msg || '登录失败');
await refreshCaptcha();
message.error(error.message || error.msg || '登录失败')
await refreshCaptcha()
}
}
@@ -144,7 +138,11 @@ refreshCaptcha()
</a-form-item>
<!-- 验证码 -->
<a-form-item name="code" class="form-item" v-if="rules.code[0].required">
<a-form-item
name="code"
class="form-item"
v-if="rules.code[0].required"
>
<div class="captcha-container">
<a-input
v-model:value="formData.code"
@@ -167,7 +165,10 @@ refreshCaptcha()
<div v-else-if="captchaLoading" class="captcha-loading">
<i-lucide-loader class="loading-icon" />
</div>
<div v-else-if="!captchaLoading && loadCaptchaFail" class="captcha-loading">
<div
v-else-if="!captchaLoading && loadCaptchaFail"
class="captcha-loading"
>
获取失败
</div>
<div class="captcha-refresh-hint">
@@ -179,7 +180,9 @@ refreshCaptcha()
</a-form-item>
<a-form-item name="rememberMe" class="form-item">
<a-checkbox v-model:checked="rememberMe" style="color: #ddd;">记住密码</a-checkbox>
<a-checkbox v-model:checked="rememberMe" style="color: #ddd">
记住密码
</a-checkbox>
</a-form-item>
<!-- 登录按钮 -->

View File

@@ -1,52 +1,63 @@
<script setup lang="ts">
import { getCurrentInstance } from 'vue';
import { useRouter } from 'vue-router';
import type { ColumnsType } from 'ant-design-vue/es/table/interface';
import { useTraceOrderStore } from '@/store';
import { getCurrentInstance } from 'vue'
import { useRouter } from 'vue-router'
import type { ColumnsType } from 'ant-design-vue/es/table/interface'
import { useTraceOrderStore } from '@/store'
const { proxy } = getCurrentInstance() as any
const { mes_station_status } = proxy.useDict("mes_station_status", "lot_trace_order_status")
const { mes_station_status } = proxy.useDict('mes_station_status')
interface TableItem {
key: string;
index: number;
operationTitle: string;
planQty: number;
okQty: number;
ngQty: number;
status: string;
[key: string]: any;
key: string
index: number
operationTitle: string
planQty: number
okQty: number
ngQty: number
status: string
[key: string]: any
}
const router = useRouter();
const traceOrderStore = useTraceOrderStore();
const router = useRouter()
const traceOrderStore = useTraceOrderStore()
const columns = [
{ title: '序号', dataIndex: 'index', key: 'index', align: 'center', width: 80 },
{ title: '制程', dataIndex: 'operationTitle', key: 'operationTitle', align: 'center' },
{
title: '序号',
dataIndex: 'index',
key: 'index',
align: 'center',
width: 80,
},
{
title: '制程',
dataIndex: 'operationTitle',
key: 'operationTitle',
align: 'center',
},
{ title: '总数量', dataIndex: 'planQty', key: 'planQty', align: 'center' },
{ title: '合格数量', dataIndex: 'okQty', key: 'okQty', align: 'center' },
{ title: '报废数量', dataIndex: 'ngQty', key: 'ngQty', align: 'center' },
{ title: '状态', dataIndex: 'status', key: 'status', align: 'center' },
{ title: '操作', key: 'action', align: 'center' },
];
]
const handleInfeed = (record: any) => {
router.push({ name: 'Infeed' });
traceOrderStore.selectStation(record.id);
};
router.push({ name: 'Infeed' })
traceOrderStore.selectStation(record.id)
}
const handleOutfeed = (record: any) => {
router.push({ name: 'Outfeed' });
traceOrderStore.selectStation(record.id);
};
router.push({ name: 'Outfeed' })
traceOrderStore.selectStation(record.id)
}
function rowClick(record: TableItem) {
return {
onClick: () => {
traceOrderStore.selectStation(record.id);
traceOrderStore.selectStation(record.id)
},
};
}
}
</script>
@@ -65,12 +76,23 @@ function rowClick(record: TableItem) {
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<DictTag :options="mes_station_status" :value="record.status" size="large" />
<DictTag
:options="mes_station_status"
:value="record.status"
size="large"
/>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a-button size="large" @click="handleInfeed(record)">进站</a-button>
<a-button size="large" type="primary" danger @click="handleOutfeed(record)">出站</a-button>
<a-button
size="large"
type="primary"
danger
@click="handleOutfeed(record)"
>
出站
</a-button>
</a-space>
</template>
</template>

View File

@@ -1,94 +1,110 @@
<script setup lang="ts">
import { ref, onMounted, reactive } from 'vue';
import { message } from 'ant-design-vue';
import { useTraceOrderStore } from '@/store';
import type { ColumnsType } from 'ant-design-vue/es/table/interface';
import { ref, onMounted, reactive } from 'vue'
import { message } from 'ant-design-vue'
import { useTraceOrderStore } from '@/store'
import type { ColumnsType } from 'ant-design-vue/es/table/interface'
import {
getEquipmentByCode,
listEquipmentEntry,
addEquipmentEntry,
delEquipmentEntry,
} from '@/api/traceOrderManage/equipment';
} from '@/api/traceOrderManage/equipment'
const traceOrderStore = useTraceOrderStore();
const traceOrderStore = useTraceOrderStore()
interface EquipmentTableItem {
key: string;
equipmentName: string;
equipmentCode: string;
[key: string]: any;
key: string
equipmentName: string
equipmentCode: string
[key: string]: any
}
const equipmentColumns = [
{ title: '序号', dataIndex: 'index', key: 'index', align: 'center', width: 80 },
{ title: '设备编码', dataIndex: 'equipmentCode', key: 'equipmentCode', align: 'center' },
{ title: '设备名称', dataIndex: 'equipmentTitle', key: 'equipmentTitle', align: 'center' },
{
title: '序号',
dataIndex: 'index',
key: 'index',
align: 'center',
width: 80,
},
{
title: '设备编码',
dataIndex: 'equipmentCode',
key: 'equipmentCode',
align: 'center',
},
{
title: '设备名称',
dataIndex: 'equipmentTitle',
key: 'equipmentTitle',
align: 'center',
},
{ title: '操作', dataIndex: 'action', key: 'action', align: 'center' },
];
]
const equipmentInfo = reactive({
equipmentTitle: '',
equipmentCode: '',
});
})
const fetchEquipmentInfo = async () => {
const { data } = await getEquipmentByCode(equipmentInput.value)
if (!data) {
message.error('设备不存在');
return;
message.error('设备不存在')
return
}
Object.assign(equipmentInfo, data);
Object.assign(equipmentInfo, data)
}
// 治具表格
const equipmentTableData = ref<EquipmentTableItem[]>([]);
const equipmentTableData = ref<EquipmentTableItem[]>([])
const equipmentInput = ref<string>('');
const equipmentInput = ref<string>('')
const handleBind = async () => {
if (!equipmentInput.value) return;
if (!equipmentInput.value) return
try {
await addEquipmentEntry({
equipmentCode: equipmentInput.value,
stationCode: traceOrderStore.currentStationCode,
});
message.success('绑定成功');
equipmentInput.value = '';
fetchBoundEquipmentList();
})
message.success('绑定成功')
equipmentInput.value = ''
fetchBoundEquipmentList()
} catch (error: any) {
message.error(`绑定失败: ${ error.message }`);
message.error(`绑定失败: ${error.message}`)
}
}
const handleUnbind = async (id: number) => {
try {
await delEquipmentEntry(id);
message.success('解绑成功');
fetchBoundEquipmentList();
await delEquipmentEntry(id)
message.success('解绑成功')
fetchBoundEquipmentList()
} catch (error: any) {
message.error(`解绑失败: ${ error.message }`);
message.error(`解绑失败: ${error.message}`)
}
}
// 获取已绑定的设备列表
const loadingEquipmentTableData = ref(false);
const loadingEquipmentTableData = ref(false)
const fetchBoundEquipmentList = async () => {
const stationId = traceOrderStore.currentStationId;
if (!stationId) return;
loadingEquipmentTableData.value = true;
const stationId = traceOrderStore.currentStationId
if (!stationId) return
loadingEquipmentTableData.value = true
try {
const { rows } = await listEquipmentEntry({ stationId });
equipmentTableData.value = rows;
const { rows } = await listEquipmentEntry({ stationId })
equipmentTableData.value = rows
} catch (error: any) {
message.error(error.message || '查询治具列表失败');
message.error(error.message || '查询治具列表失败')
} finally {
loadingEquipmentTableData.value = false;
loadingEquipmentTableData.value = false
}
}
const handleRefresh = () => {
fetchBoundEquipmentList();
renderTableHeight();
fetchBoundEquipmentList()
renderTableHeight()
}
// 计算表格高度
@@ -106,8 +122,8 @@ onMounted(() => {
})
defineExpose({
renderTableHeight
});
renderTableHeight,
})
fetchBoundEquipmentList()
</script>
@@ -119,7 +135,12 @@ fetchBoundEquipmentList()
<a-col :span="10" class="input__container">
<a-row :gutter="12">
<a-col :span="21">
<a-input size="large" v-model:value="equipmentInput" @pressEnter="fetchEquipmentInfo" placeholder="按下回车搜索" />
<a-input
size="large"
v-model:value="equipmentInput"
@pressEnter="fetchEquipmentInfo"
placeholder="按下回车搜索"
/>
</a-col>
<a-col :span="3">
<a-button size="large" @click="handleBind">绑定</a-button>
@@ -127,15 +148,26 @@ fetchBoundEquipmentList()
</a-row>
<div class="description-wrapper">
<a-descriptions :column="1" bordered>
<a-descriptions-item label="设备编码">{{ equipmentInfo.equipmentCode }}</a-descriptions-item>
<a-descriptions-item label="设备名称">{{ equipmentInfo.equipmentTitle }}</a-descriptions-item>
<a-descriptions-item label="设备编码">
{{ equipmentInfo.equipmentCode }}
</a-descriptions-item>
<a-descriptions-item label="设备名称">
{{ equipmentInfo.equipmentTitle }}
</a-descriptions-item>
</a-descriptions>
</div>
</a-col>
<a-col :span="14">
<div class="table-wrapper" ref="customTable">
<a-table :dataSource="equipmentTableData" :columns="equipmentColumns as ColumnsType<EquipmentTableItem>"
:pagination="false" bordered sticky :scroll="{ y: tableHeight }" :loading="loadingEquipmentTableData">
<a-table
:dataSource="equipmentTableData"
:columns="equipmentColumns as ColumnsType<EquipmentTableItem>"
:pagination="false"
bordered
sticky
:scroll="{ y: tableHeight }"
:loading="loadingEquipmentTableData"
>
<template #bodyCell="{ column, index, record }">
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
<template v-if="column.key === 'action'">

View File

@@ -1,54 +1,54 @@
<script setup lang="ts">
import { ref, computed, inject } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { startStation } from '@/api/traceOrderManage/station';
import { useTraceOrderStore } from '@/store';
import { message } from 'ant-design-vue';
import { ref, computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { startStation } from '@/api/traceOrderManage/station'
import { useTraceOrderStore } from '@/store'
import { message } from 'ant-design-vue'
const router = useRouter();
const route = useRoute();
const router = useRouter()
const route = useRoute()
const traceOrderStore = useTraceOrderStore();
const traceOrderStore = useTraceOrderStore()
const menuItems = [
{ label: '主材', key: 'PrimaryMaterial', progress: 30 },
{ label: '治具', key: 'Mask', progress: 80 },
{ label: '设备', key: 'Equipment', progress: 80 },
];
]
const activeKey = computed(() => route.name as string);
const activeKey = computed(() => route.name as string)
const handleMenuClick = (name: string) => {
router.push({ name});
};
router.push({ name })
}
// 确认进站
const infeeding = ref(false);
const infeeding = ref(false)
const handleInfeed = async () => {
infeeding.value = true;
infeeding.value = true
try {
await startStation(traceOrderStore.currentStationId);
message.success('进站成功');
traceOrderStore.fetchStationInfo();
traceOrderStore.fetchStationList();
router.push({ name: 'TraceOrderManage' });
await startStation(traceOrderStore.currentStationId)
message.success('进站成功')
traceOrderStore.fetchStationInfo()
traceOrderStore.fetchStationList()
router.push({ name: 'TraceOrderManage' })
} catch (error: any) {
message.error(error.message || '进站失败');
message.error(error.message || '进站失败')
} finally {
infeeding.value = false;
infeeding.value = false
}
}
};
const infeedChildRef = ref<any>(null);
const infeedChildRef = ref<any>(null)
/** 父组件对外暴露的方法 */
function renderTableHeight() {
infeedChildRef.value?.renderTableHeight();
infeedChildRef.value?.renderTableHeight()
}
defineExpose({
renderTableHeight
});
renderTableHeight,
})
</script>
<template>
@@ -80,7 +80,9 @@ defineExpose({
/>
</div>
</div>
<a-button type="primary" size="large" @click="handleInfeed">确认进站</a-button>
<a-button type="primary" size="large" @click="handleInfeed">
确认进站
</a-button>
</div>
</a-col>
</a-row>

View File

@@ -1,60 +1,117 @@
<script setup lang="ts">
import { ref, onMounted, reactive } from 'vue';
import { message } from 'ant-design-vue';
import type { ColumnsType } from 'ant-design-vue/es/table/interface';
import { listMaskCombination } from "@/api/traceOrderManage/maskCombination";
import { listStationBindMask, bindMaskCombinationToStations, unbindStationMask } from "@/api/traceOrderManage/mask";
import { useTraceOrderStore } from '@/store';
import { ref, onMounted, reactive } from 'vue'
import { message } from 'ant-design-vue'
import type { ColumnsType } from 'ant-design-vue/es/table/interface'
import { listMaskCombination } from '@/api/traceOrderManage/maskCombination'
import {
listStationBindMask,
bindMaskCombinationToStations,
unbindStationMask,
} from '@/api/traceOrderManage/mask'
import { useTraceOrderStore } from '@/store'
const traceOrderStore = useTraceOrderStore();
const traceOrderStore = useTraceOrderStore()
interface MaskTableItem {
key: string;
operationTitle: string;
operationCode: string;
maskName: string;
maskCode: string;
[key: string]: any;
key: string
operationTitle: string
operationCode: string
maskName: string
maskCode: string
[key: string]: any
}
interface MaskCombinationItem {
key: string;
combinationCode: string;
combinationName: string;
combinationStatus: string;
[key: string]: any;
key: string
combinationCode: string
combinationName: string
combinationStatus: string
[key: string]: any
}
const maskColumns = [
{ title: '序号', dataIndex: 'index', key: 'index', align: 'center', width: 80 },
{ title: '工序编码', dataIndex: 'operationCode', key: 'operationCode', align: 'center' },
{ title: '工序名称', dataIndex: 'operationTitle', key: 'operationTitle', align: 'center' },
{ title: '治具编码', dataIndex: 'maskCode', key: 'maskCode', align: 'center' },
{ title: '治具名称', dataIndex: 'maskName', key: 'maskName', align: 'center' },
{
title: '序号',
dataIndex: 'index',
key: 'index',
align: 'center',
width: 80,
},
{
title: '工序编码',
dataIndex: 'operationCode',
key: 'operationCode',
align: 'center',
},
{
title: '工序名称',
dataIndex: 'operationTitle',
key: 'operationTitle',
align: 'center',
},
{
title: '治具编码',
dataIndex: 'maskCode',
key: 'maskCode',
align: 'center',
},
{
title: '治具名称',
dataIndex: 'maskName',
key: 'maskName',
align: 'center',
},
{ title: '操作', dataIndex: 'action', key: 'action', align: 'center' },
];
]
// 治具表格
const maskTableData = ref<MaskTableItem[]>([]);
const maskTableData = ref<MaskTableItem[]>([])
const maskInput = ref<string>('');
const maskInput = ref<string>('')
const openMaskModal = ref(false)
const maskCombinationColumns = [
{ title: '序号', dataIndex: 'index', key: 'index', align: 'center', width: 80 },
{ title: '组合编码', dataIndex: 'combinationCode', key: 'combinationCode', align: 'center' },
{ title: '组合名称', dataIndex: 'combinationName', key: 'combinationName', align: 'center' },
{ title: '组合状态', dataIndex: 'combinationStatus', key: 'combinationStatus', align: 'center' },
{ title: '操作', dataIndex: 'action', key: 'action', align: 'center', width: 100 },
{
title: '序号',
dataIndex: 'index',
key: 'index',
align: 'center',
width: 80,
},
{
title: '组合编码',
dataIndex: 'combinationCode',
key: 'combinationCode',
align: 'center',
},
{
title: '组合名称',
dataIndex: 'combinationName',
key: 'combinationName',
align: 'center',
},
{
title: '组合状态',
dataIndex: 'combinationStatus',
key: 'combinationStatus',
align: 'center',
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
align: 'center',
width: 100,
},
]
const maskCombinationTableData = ref<MaskCombinationItem[]>([])
const fetchCombinationList = async () => {
try {
const { rows, total } = await listMaskCombination({})
if (total as number <= 0) throw new Error('未查询到组合列表');
maskCombinationTableData.value = rows;
if ((total as number) <= 0) throw new Error('未查询到组合列表')
maskCombinationTableData.value = rows
} catch (error: any) {
message.error(error.message || '获取工单信息失败');
message.error(error.message || '获取工单信息失败')
}
}
@@ -62,41 +119,41 @@ const handleBind = async (id: number) => {
try {
await bindMaskCombinationToStations({
lotTraceOrderId: traceOrderStore.currentTraceOrderId,
maskCombinationId: id
});
message.success('绑定成功');
fetchBoundMaskList();
openMaskModal.value = false;
maskCombinationId: id,
})
message.success('绑定成功')
fetchBoundMaskList()
openMaskModal.value = false
} catch (error: any) {
message.error(`绑定失败: ${ error.message }`);
message.error(`绑定失败: ${error.message}`)
}
}
const handleUnbind = async (id: number) => {
try {
await unbindStationMask(id);
message.success('解绑成功');
fetchBoundMaskList();
openMaskModal.value = false;
await unbindStationMask(id)
message.success('解绑成功')
fetchBoundMaskList()
openMaskModal.value = false
} catch (error: any) {
message.error(`解绑失败: ${ error.message }`);
message.error(`解绑失败: ${error.message}`)
}
}
// 获取已绑定的治具列表
const loadingMask = ref(false);
const loadingMask = ref(false)
const fetchBoundMaskList = async () => {
const traceOrderId = traceOrderStore.currentTraceOrderId;
if (!traceOrderId) return;
loadingMask.value = true;
const traceOrderId = traceOrderStore.currentTraceOrderId
if (!traceOrderId) return
loadingMask.value = true
try {
const { rows, total } = await listStationBindMask({ traceOrderId })
if (total as number <= 0) return;
maskTableData.value = rows;
if ((total as number) <= 0) return
maskTableData.value = rows
} catch (error: any) {
message.error(error.message || '查询治具列表失败');
message.error(error.message || '查询治具列表失败')
} finally {
loadingMask.value = false;
loadingMask.value = false
}
}
@@ -106,13 +163,17 @@ const inspectingMask = reactive({
})
const rowClick = (record: MaskTableItem) => {
inspectingMask.maskCode = record.maskCode;
inspectingMask.maskName = record.maskName;
return {
onClick: () => {
inspectingMask.maskCode = record.maskCode
inspectingMask.maskName = record.maskName
},
}
}
const handleRefresh = () => {
fetchBoundMaskList();
renderTableHeight();
fetchBoundMaskList()
renderTableHeight()
}
// 计算表格高度
@@ -130,8 +191,8 @@ onMounted(() => {
})
defineExpose({
renderTableHeight
});
renderTableHeight,
})
fetchCombinationList()
fetchBoundMaskList()
@@ -142,10 +203,20 @@ fetchBoundMaskList()
<a-row class="main-content" :gutter="24">
<a-col :span="14" class="mask-item__container">
<Title name="治具组合信息" showRefresh @refresh="handleRefresh" />
<a-button size="large" @click="() => openMaskModal = true">绑定治具组合</a-button>
<a-button size="large" @click="() => (openMaskModal = true)">
绑定治具组合
</a-button>
<div class="table-wrapper" ref="customTable">
<a-table :dataSource="maskTableData" :columns="maskColumns as ColumnsType<MaskTableItem>"
:pagination="false" bordered sticky :scroll="{ y: tableHeight }" :loading="loadingMask">
<a-table
:dataSource="maskTableData"
:columns="maskColumns as ColumnsType<MaskTableItem>"
:pagination="false"
bordered
sticky
:scroll="{ y: tableHeight }"
:loading="loadingMask"
:customRow="rowClick"
>
<template #bodyCell="{ column, index, record }">
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
<template v-if="column.key === 'action'">
@@ -159,10 +230,14 @@ fetchBoundMaskList()
<Title name="治具校验" />
<a-row>
<a-col :span="20">
<a-input size="large" v-model:value="maskInput" @pressEnter="" placeholder="按下回车校验" />
<a-input
size="large"
v-model:value="maskInput"
placeholder="按下回车校验"
/>
</a-col>
<a-col :span="3" :offset="1">
<a-button size="large" @click="">校验</a-button>
<a-button size="large">校验</a-button>
</a-col>
</a-row>
<div class="table-wrapper">
@@ -178,9 +253,21 @@ fetchBoundMaskList()
</a-col>
</a-row>
<a-modal v-model:open="openMaskModal" title="绑定治具组合" width="50vw" :bodyStyle="{ padding: '20px 0' }" :footer="null">
<a-table :dataSource="maskCombinationTableData" :columns="maskCombinationColumns as ColumnsType<MaskCombinationItem>"
:pagination="false" bordered sticky :scroll="{ y: tableHeight }">
<a-modal
v-model:open="openMaskModal"
title="绑定治具组合"
width="50vw"
:bodyStyle="{ padding: '20px 0' }"
:footer="null"
>
<a-table
:dataSource="maskCombinationTableData"
:columns="maskCombinationColumns as ColumnsType<MaskCombinationItem>"
:pagination="false"
bordered
sticky
:scroll="{ y: tableHeight }"
>
<template #bodyCell="{ column, index, record }">
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
<template v-if="column.key === 'action'">

View File

@@ -1,108 +1,131 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { ref, onMounted } from 'vue'
import { useTraceOrderStore } from '@/store'
import type { ColumnsType } from 'ant-design-vue/es/table/interface';
import type { ColumnsType } from 'ant-design-vue/es/table/interface'
import { message } from 'ant-design-vue'
import { listMainMaterialEntryLog, addWaferEntryLogByCarrier, addDieEntryLogByCarrier, addWaferEntryLog, addDieEntryLog, delMainMaterialEntryLog } from '@/api/traceOrderManage/primaryMaterial'
import {
listMainMaterialEntryLog,
addWaferEntryLogByCarrier,
addDieEntryLogByCarrier,
addWaferEntryLog,
addDieEntryLog,
delMainMaterialEntryLog,
} from '@/api/traceOrderManage/primaryMaterial'
const traceOrderStore = useTraceOrderStore();
const traceOrderStore = useTraceOrderStore()
interface MaterialTableItem {
key: string;
carrierCode: string;
qrCode: string;
dieCode: string;
waferCode: string;
[key: string]: any;
key: string
carrierCode: string
qrCode: string
dieCode: string
waferCode: string
[key: string]: any
}
const materialColumns = [
{ title: '序号', dataIndex: 'index', key: 'index', align: 'center', width: 80 },
{ title: '载具 ID', dataIndex: 'carrierCode', key: 'carrierCode', align: 'center' },
{ title: 'Wafer ID', dataIndex: 'waferCode', key: 'waferCode', align: 'center' },
{
title: '序号',
dataIndex: 'index',
key: 'index',
align: 'center',
width: 80,
},
{
title: '载具 ID',
dataIndex: 'carrierCode',
key: 'carrierCode',
align: 'center',
},
{
title: 'Wafer ID',
dataIndex: 'waferCode',
key: 'waferCode',
align: 'center',
},
{ title: 'Die ID', dataIndex: 'dieCode', key: 'dieCode', align: 'center' },
{ title: '二维码', dataIndex: 'qrCode', key: 'qrCode', align: 'center' },
{ title: '操作', key: 'action', align: 'center', width: 120 },
]
// 查询站点下主材信息
const loadingMaterialTableData = ref(false);
const loadingMaterialTableData = ref(false)
const fetchPrimaryMaterialList = async () => {
const stationId = traceOrderStore.currentStationId;
if (!stationId) return;
loadingMaterialTableData.value = true;
const stationId = traceOrderStore.currentStationId
if (!stationId) return
loadingMaterialTableData.value = true
try {
const { rows } = await listMainMaterialEntryLog({ stationId });
materialTableData.value = rows;
const { rows } = await listMainMaterialEntryLog({ stationId })
materialTableData.value = rows
} catch (error: any) {
message.error(error.message || '查询站点下主材信息失败');
message.error(error.message || '查询站点下主材信息失败')
} finally {
loadingMaterialTableData.value = false;
loadingMaterialTableData.value = false
}
}
};
const carrierInput = ref<string>('');
const carrierInput = ref<string>('')
// 录入载具
const insertCarrier = async () => {
if (!carrierInput.value) return;
if (!carrierInput.value) return
const form = {
carrierCode: carrierInput.value,
stationCode: traceOrderStore.currentStationCode,
};
}
try {
if (materialType.value === "Wafer") {
await addWaferEntryLogByCarrier(form);
} else if (materialType.value === "Die") {
await addDieEntryLogByCarrier(form);
} else throw new Error('主材类型异常');
carrierInput.value = '';
message.success('添加成功');
fetchPrimaryMaterialList();
if (materialType.value === 'Wafer') {
await addWaferEntryLogByCarrier(form)
} else if (materialType.value === 'Die') {
await addDieEntryLogByCarrier(form)
} else throw new Error('主材类型异常')
carrierInput.value = ''
message.success('添加成功')
fetchPrimaryMaterialList()
} catch (error: any) {
message.error(error.message || '添加载具失败');
return;
message.error(error.message || '添加载具失败')
return
}
}
// 主材料表格
const materialTableData = ref<MaterialTableItem[]>([]);
const materialInput = ref<string>('');
const materialTableData = ref<MaterialTableItem[]>([])
const materialInput = ref<string>('')
// 录入主材料
const insertMaterial = async () => {
if (!materialInput.value) return;
if (!materialInput.value) return
const form = {
mainMaterialCodes: [materialInput.value],
stationCode: traceOrderStore.currentStationCode,
};
}
try {
if (materialType.value === "Wafer") {
await addWaferEntryLog(form);
} else if (materialType.value === "Die") {
await addDieEntryLog(form);
} else throw new Error('主材类型异常');
materialInput.value = '';
message.success('添加成功');
fetchPrimaryMaterialList();
if (materialType.value === 'Wafer') {
await addWaferEntryLog(form)
} else if (materialType.value === 'Die') {
await addDieEntryLog(form)
} else throw new Error('主材类型异常')
materialInput.value = ''
message.success('添加成功')
fetchPrimaryMaterialList()
} catch (error: any) {
message.error(error.message || '添加主材失败');
return;
message.error(error.message || '添加主材失败')
return
}
}
// 移除主材料
const handleRemoveMaterial = async (row: MaterialTableItem) => {
try {
await delMainMaterialEntryLog(row.id);
message.success('删除成功');
fetchPrimaryMaterialList();
await delMainMaterialEntryLog(row.id)
message.success('删除成功')
fetchPrimaryMaterialList()
} catch (error: any) {
message.error(error.message || '删除主材失败');
return;
message.error(error.message || '删除主材失败')
return
}
}
const handleRefresh = () => {
fetchPrimaryMaterialList();
renderTableHeight();
fetchPrimaryMaterialList()
renderTableHeight()
}
// 计算表格高度
@@ -120,8 +143,8 @@ onMounted(() => {
})
defineExpose({
renderTableHeight
});
renderTableHeight,
})
// 初始化主材类型
const materialType = ref()
@@ -143,25 +166,48 @@ fetchPrimaryMaterialList()
<a-input v-model:value="materialType" disabled />
</a-form-item>
<a-form-item label="载具 ID">
<a-input v-model:value="carrierInput" @pressEnter="insertCarrier" placeholder="按下回车录入" allow-clear />
<a-input
v-model:value="carrierInput"
@pressEnter="insertCarrier"
placeholder="按下回车录入"
allow-clear
/>
</a-form-item>
<a-form-item>
<a-button @click="insertCarrier">录入</a-button>
</a-form-item>
<a-form-item label="主材 ID">
<a-input v-model:value="materialInput" @pressEnter="insertMaterial" placeholder="按下回车录入" allow-clear />
<a-input
v-model:value="materialInput"
@pressEnter="insertMaterial"
placeholder="按下回车录入"
allow-clear
/>
</a-form-item>
<a-form-item>
<a-button @click="insertMaterial">录入</a-button>
</a-form-item>
</a-form>
<div class="table-wrapper" ref="customTable">
<a-table :dataSource="materialTableData" :columns="materialColumns as ColumnsType<MaterialTableItem>"
:pagination="false" bordered sticky :scroll="{ y: tableHeight }" :loading="loadingMaterialTableData">
<a-table
:dataSource="materialTableData"
:columns="materialColumns as ColumnsType<MaterialTableItem>"
:pagination="false"
bordered
sticky
:scroll="{ y: tableHeight }"
:loading="loadingMaterialTableData"
>
<template #bodyCell="{ column, index, record }">
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
<template v-if="column.key === 'action'">
<a-button type="text" danger @click="handleRemoveMaterial(record as MaterialTableItem)">删除</a-button>
<a-button
type="text"
danger
@click="handleRemoveMaterial(record as MaterialTableItem)"
>
删除
</a-button>
</template>
</template>
</a-table>

View File

@@ -1,21 +1,37 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import type { ColumnsType } from 'ant-design-vue/es/table/interface';
import { message } from 'ant-design-vue';
import { ref, onMounted } from 'vue'
import type { ColumnsType } from 'ant-design-vue/es/table/interface'
import { message } from 'ant-design-vue'
interface MaterialTableItem {
key: string;
materialCode: string;
materialName: string;
num: number;
unit: string;
[key: string]: any;
key: string
materialCode: string
materialName: string
num: number
unit: string
[key: string]: any
}
const materialColumns = [
{ title: '序号', dataIndex: 'index', key: 'index', align: 'center', width: 80 },
{ title: '物料编码', dataIndex: 'materialCode', key: 'materialCode', align: 'center' },
{ title: '物料名称', dataIndex: 'materialName', key: 'materialName', align: 'center' },
{
title: '序号',
dataIndex: 'index',
key: 'index',
align: 'center',
width: 80,
},
{
title: '物料编码',
dataIndex: 'materialCode',
key: 'materialCode',
align: 'center',
},
{
title: '物料名称',
dataIndex: 'materialName',
key: 'materialName',
align: 'center',
},
{ title: '数量', dataIndex: 'num', key: 'num', align: 'center' },
{ title: '单位', dataIndex: 'unit', key: 'unit', align: 'center' },
{ title: '操作', key: 'action', align: 'center' },
@@ -32,10 +48,28 @@ const materialTableData = ref<MaterialTableItem[]>([
{ key: '7', materialCode: '123', materialName: '物料1', num: 10, unit: '片' },
{ key: '8', materialCode: '123', materialName: '物料1', num: 10, unit: '片' },
{ key: '9', materialCode: '123', materialName: '物料1', num: 10, unit: '片' },
{ key: '10', materialCode: '123', materialName: '物料1', num: 10, unit: '片' },
{ key: '11', materialCode: '123', materialName: '物料1', num: 10, unit: '片' },
{ key: '12', materialCode: '123', materialName: '物料1', num: 10, unit: '片' },
]);
{
key: '10',
materialCode: '123',
materialName: '物料1',
num: 10,
unit: '片',
},
{
key: '11',
materialCode: '123',
materialName: '物料1',
num: 10,
unit: '片',
},
{
key: '12',
materialCode: '123',
materialName: '物料1',
num: 10,
unit: '片',
},
])
// 确认材料
const handleSubmitMaterial = (index: number) => {
@@ -57,16 +91,22 @@ onMounted(() => {
})
defineExpose({
renderTableHeight
});
renderTableHeight,
})
</script>
<template>
<div class="raw-material__container">
<Title name="材料确认" showRefresh @refresh="" />
<Title name="材料确认" showRefresh />
<div class="table-wrapper" ref="customTable">
<a-table :dataSource="materialTableData" :columns="materialColumns as ColumnsType<MaterialTableItem>"
:pagination="false" bordered sticky :scroll="{ y: tableHeight }">
<a-table
:dataSource="materialTableData"
:columns="materialColumns as ColumnsType<MaterialTableItem>"
:pagination="false"
bordered
sticky
:scroll="{ y: tableHeight }"
>
<template #bodyCell="{ column, index }">
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
<template v-if="column.key === 'action'">

View File

@@ -1,29 +1,32 @@
<script setup lang="ts">
import { ref, nextTick, getCurrentInstance } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useTraceOrderStore } from '@/store';
import type { LotTraceOrderData } from '@/api/traceOrderManage/model';
import { handleCopy } from '@/utils/copyToClipboard';
import { ref, nextTick, getCurrentInstance } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useTraceOrderStore } from '@/store'
import type { LotTraceOrderData } from '@/api/traceOrderManage/model'
import { handleCopy } from '@/utils/copyToClipboard'
const { proxy } = getCurrentInstance() as any
const { mes_station_status, lot_trace_order_status } = proxy.useDict("mes_station_status", "lot_trace_order_status")
const { mes_station_status, lot_trace_order_status } = proxy.useDict(
'mes_station_status',
'lot_trace_order_status',
)
const route = useRoute();
const router = useRouter();
const traceOrderStore = useTraceOrderStore();
const route = useRoute()
const router = useRouter()
const traceOrderStore = useTraceOrderStore()
const redirectTo = (routeName: string) => {
router.push({ name: routeName });
router.push({ name: routeName })
}
// 孙组件
const infeedRef = ref<any>(null);
const collapsed = ref(false);
const infeedRef = ref<any>(null)
const collapsed = ref(false)
// 折叠
const toggleCollapse = async () => {
collapsed.value = !collapsed.value;
await nextTick();
infeedRef.value?.renderTableHeight();
collapsed.value = !collapsed.value
await nextTick()
infeedRef.value?.renderTableHeight()
}
</script>
@@ -48,13 +51,27 @@ const toggleCollapse = async () => {
<a-col :span="13">
<a-spin :spinning="traceOrderStore.loadingTraceOrderInfo">
<a-card title="工单信息" class="info-card" :bordered="false">
<a-form :model="traceOrderStore.traceOrderInfo" :colon="false" v-show="!collapsed">
<a-form
:model="traceOrderStore.traceOrderInfo"
:colon="false"
v-show="!collapsed"
>
<a-row :gutter="36">
<a-col :span="12">
<a-form-item label="工单批次">
<a-input v-model:value="traceOrderStore.traceOrderInfo.batchNo" readonly>
<a-input
v-model:value="traceOrderStore.traceOrderInfo.batchNo"
readonly
>
<template #suffix>
<a-button @click="handleCopy(traceOrderStore.traceOrderInfo.batchNo)" size="small">
<a-button
@click="
handleCopy(
traceOrderStore.traceOrderInfo.batchNo,
)
"
size="small"
>
<template #icon><i-lucide-copy /></template>
</a-button>
</template>
@@ -63,14 +80,28 @@ const toggleCollapse = async () => {
<a-form-item label="工单状态">
<a-input readonly>
<template #prefix>
<DictTag :options="lot_trace_order_status" :value="traceOrderStore.traceOrderInfo.status" size="medium" />
<DictTag
:options="lot_trace_order_status"
:value="traceOrderStore.traceOrderInfo.status"
size="medium"
/>
</template>
</a-input>
</a-form-item>
<a-form-item label="总计数量">
<a-input v-model:value="traceOrderStore.traceOrderInfo.planQty" readonly>
<a-input
v-model:value="traceOrderStore.traceOrderInfo.planQty"
readonly
>
<template #suffix>
<a-button @click="handleCopy(traceOrderStore.traceOrderInfo.planQty)" size="small">
<a-button
@click="
handleCopy(
traceOrderStore.traceOrderInfo.planQty,
)
"
size="small"
>
<template #icon><i-lucide-copy /></template>
</a-button>
</template>
@@ -79,18 +110,44 @@ const toggleCollapse = async () => {
</a-col>
<a-col :span="12">
<a-form-item label="产品编码">
<a-input v-model:value="traceOrderStore.traceOrderInfo.tarMaterialCode" readonly>
<a-input
v-model:value="
traceOrderStore.traceOrderInfo.tarMaterialCode
"
readonly
>
<template #suffix>
<a-button @click="handleCopy(traceOrderStore.traceOrderInfo.tarMaterialCode)" size="small">
<a-button
@click="
handleCopy(
traceOrderStore.traceOrderInfo
.tarMaterialCode,
)
"
size="small"
>
<template #icon><i-lucide-copy /></template>
</a-button>
</template>
</a-input>
</a-form-item>
<a-form-item label="产品名称">
<a-input v-model:value="traceOrderStore.traceOrderInfo.tarMaterialName" readonly>
<a-input
v-model:value="
traceOrderStore.traceOrderInfo.tarMaterialName
"
readonly
>
<template #suffix>
<a-button @click="handleCopy(traceOrderStore.traceOrderInfo.tarMaterialName)" size="small">
<a-button
@click="
handleCopy(
traceOrderStore.traceOrderInfo
.tarMaterialName,
)
"
size="small"
>
<template #icon><i-lucide-copy /></template>
</a-button>
</template>
@@ -111,10 +168,26 @@ const toggleCollapse = async () => {
<template #extra>
<a-space>
工单编码
<a-select v-model:value="traceOrderStore.currentTraceOrderCode" show-search placeholder="输入工单编码" style="width: 300px"
:show-arrow="false" :options="traceOrderStore.traceOrderOptions" @search="traceOrderStore.fetchTraceOrderOption" :disabled="route.name !== 'TraceOrderManageIndex'"
@change="(val, option) => traceOrderStore.selectTraceOrder(val as string, option as LotTraceOrderData)" />
<a-button @click="traceOrderStore.fetchTraceOrderInfo"><template #icon><i-lucide-rotate-cw /></template></a-button>
<a-select
v-model:value="traceOrderStore.currentTraceOrderCode"
show-search
placeholder="输入工单编码"
style="width: 300px"
:show-arrow="false"
:options="traceOrderStore.traceOrderOptions"
@search="traceOrderStore.fetchTraceOrderOption"
:disabled="route.name !== 'TraceOrderManageIndex'"
@change="
(val, option) =>
traceOrderStore.selectTraceOrder(
val as string,
option as LotTraceOrderData,
)
"
/>
<a-button @click="traceOrderStore.fetchTraceOrderInfo">
<template #icon><i-lucide-rotate-cw /></template>
</a-button>
</a-space>
</template>
</a-card>
@@ -124,11 +197,25 @@ const toggleCollapse = async () => {
<a-col :span="8">
<a-spin :spinning="traceOrderStore.loadingStationInfo">
<a-card title="工序信息" class="info-card" :bordered="false">
<a-form :model="traceOrderStore.stationInfo" :colon="false" v-show="!collapsed">
<a-form
:model="traceOrderStore.stationInfo"
:colon="false"
v-show="!collapsed"
>
<a-form-item label="工序名称">
<a-input v-model:value="traceOrderStore.stationInfo.operationTitle" readonly>
<a-input
v-model:value="traceOrderStore.stationInfo.operationTitle"
readonly
>
<template #suffix>
<a-button @click="handleCopy(traceOrderStore.stationInfo.operationTitle)" size="small">
<a-button
@click="
handleCopy(
traceOrderStore.stationInfo.operationTitle,
)
"
size="small"
>
<template #icon><i-lucide-copy /></template>
</a-button>
</template>
@@ -137,14 +224,24 @@ const toggleCollapse = async () => {
<a-form-item label="工序状态">
<a-input readonly>
<template #prefix>
<DictTag :options="mes_station_status" :value="traceOrderStore.stationInfo.status" size="medium" />
<DictTag
:options="mes_station_status"
:value="traceOrderStore.stationInfo.status"
size="medium"
/>
</template>
</a-input>
</a-form-item>
<a-form-item label="作业编码">
<a-input v-model:value="traceOrderStore.stationInfo.code" readonly>
<a-input
v-model:value="traceOrderStore.stationInfo.code"
readonly
>
<template #suffix>
<a-button @click="handleCopy(traceOrderStore.stationInfo.code)" size="small">
<a-button
@click="handleCopy(traceOrderStore.stationInfo.code)"
size="small"
>
<template #icon><i-lucide-copy /></template>
</a-button>
</template>
@@ -152,7 +249,9 @@ const toggleCollapse = async () => {
</a-form-item>
</a-form>
<template #extra>
<a-button @click="traceOrderStore.fetchStationInfo"><template #icon><i-lucide-rotate-cw /></template></a-button>
<a-button @click="traceOrderStore.fetchStationInfo">
<template #icon><i-lucide-rotate-cw /></template>
</a-button>
</template>
</a-card>
</a-spin>
@@ -161,17 +260,45 @@ const toggleCollapse = async () => {
<!-- Action Buttons -->
<a-col :span="3" class="action-buttons-col">
<div class="action-buttons" v-show="!collapsed">
<a-button class="action-btn" @click="redirectTo('TraceOrderManage')" :disabled="route.name == 'TraceOrderManageIndex'">工单管理</a-button>
<a-button class="action-btn" @click="redirectTo('PrimaryMaterial')" disabled>主材清单</a-button>
<a-button
class="action-btn"
@click="redirectTo('TraceOrderManage')"
:disabled="route.name == 'TraceOrderManageIndex'"
>
工单管理
</a-button>
<a-button
class="action-btn"
@click="redirectTo('PrimaryMaterial')"
disabled
>
主材清单
</a-button>
<HoldTraceOrder />
</div>
<a-card title="操作" class="info-card" :bordered="false" v-show="collapsed">
<a-card
title="操作"
class="info-card"
:bordered="false"
v-show="collapsed"
>
<template #extra>
<a-dropdown>
<template #overlay>
<a-menu>
<a-menu-item key="1" @click="redirectTo('TraceOrderManage')">工单管理</a-menu-item>
<a-menu-item key="2" @click="redirectTo('PrimaryMaterial')" disabled>主材清单</a-menu-item>
<a-menu-item
key="1"
@click="redirectTo('TraceOrderManage')"
>
工单管理
</a-menu-item>
<a-menu-item
key="2"
@click="redirectTo('PrimaryMaterial')"
disabled
>
主材清单
</a-menu-item>
</a-menu>
</template>
<a-button>

View File

@@ -1,16 +1,16 @@
export interface WorkOrderInfo {
code?: string;
batchNo?: string;
status?: string;
tarMaterialName?: string;
tarMaterialCode?: string;
planQty?: number;
code?: string
batchNo?: string
status?: string
tarMaterialName?: string
tarMaterialCode?: string
planQty?: number
}
export interface ProcessInfo {
process?: string;
cut?: string;
jobCode?: string;
status?: string;
equipment?: string;
process?: string
cut?: string
jobCode?: string
status?: string
equipment?: string
}

View File

@@ -1,144 +1,193 @@
<script setup lang="ts">
import { ref, reactive, onMounted, computed, getCurrentInstance } from 'vue';
import { message } from 'ant-design-vue';
import { useTraceOrderStore } from '@/store';
import type { ColumnsType } from 'ant-design-vue/es/table/interface';
import { listMainMaterialEntryLog, listMainMaterialOutboundLog, batchAddMainMaterialOutboundLog, getMainMaterialOutboundLog, updateMainMaterialOutboundLog, delMainMaterialOutboundLog } from "@/api/traceOrderManage/primaryMaterial";
import { ref, reactive, onMounted, computed, getCurrentInstance } from 'vue'
import { message } from 'ant-design-vue'
import { useTraceOrderStore } from '@/store'
import type { ColumnsType } from 'ant-design-vue/es/table/interface'
import {
listMainMaterialEntryLog,
listMainMaterialOutboundLog,
batchAddMainMaterialOutboundLog,
updateMainMaterialOutboundLog,
delMainMaterialOutboundLog,
} from '@/api/traceOrderManage/primaryMaterial'
const { proxy } = getCurrentInstance() as any
const { main_material_ok_level, main_material_ng_level } = proxy.useDict("main_material_ok_level", "main_material_ng_level")
const { main_material_ok_level, main_material_ng_level } = proxy.useDict(
'main_material_ok_level',
'main_material_ng_level',
)
const traceOrderStore = useTraceOrderStore();
const traceOrderStore = useTraceOrderStore()
interface PrimaryMaterialTableItem {
key: string;
waferCode: string;
dieCode: string;
xcoordinates: string;
ycoordinates: string;
outputResult: 'OK' | 'NG';
qualityLevel: string;
createBy: string;
createTime: string;
[key: string]: any;
key: string
waferCode: string
dieCode: string
xcoordinates: string
ycoordinates: string
outputResult: 'OK' | 'NG'
qualityLevel: string
createBy: string
createTime: string
[key: string]: any
}
const primaryMaterialColumns = [
{ title: '序号', dataIndex: 'index', key: 'index', align: 'center', width: 80 },
{ title: 'Wafer ID', dataIndex: 'waferCode', key: 'waferCode', align: 'center' },
{
title: '序号',
dataIndex: 'index',
key: 'index',
align: 'center',
width: 80,
},
{
title: 'Wafer ID',
dataIndex: 'waferCode',
key: 'waferCode',
align: 'center',
},
{ title: 'Die ID', dataIndex: 'dieCode', key: 'dieCode', align: 'center' },
{ title: 'Die X 坐标', dataIndex: 'xcoordinates', key: 'xcoordinates', align: 'center' },
{ title: 'Die Y 坐标', dataIndex: 'ycoordinates', key: 'ycoordinates', align: 'center' },
{ title: '结果', dataIndex: 'outputResult', key: 'outputResult', align: 'center' },
{
title: 'Die X 坐标',
dataIndex: 'xcoordinates',
key: 'xcoordinates',
align: 'center',
},
{
title: 'Die Y 坐标',
dataIndex: 'ycoordinates',
key: 'ycoordinates',
align: 'center',
},
{
title: '结果',
dataIndex: 'outputResult',
key: 'outputResult',
align: 'center',
},
{ title: '质量等级', dataIndex: 'qualityLevel', key: 'qualityLevel' },
{ title: '创建人', dataIndex: 'createBy', key: 'createBy' },
{ title: '创建时间', dataIndex: 'createTime', key: 'createTime' },
{ title: '操作', key: 'action', align: 'center', width: 180 },
];
]
// 总计数据
const totals = computed(() => {
let okNum = 0;
let ngNum = 0;
let okNum = 0
let ngNum = 0
primaryMaterialTableData.value.forEach(({ outputResult, qualityLevel }) => {
primaryMaterialTableData.value.forEach(({ outputResult }) => {
if (outputResult === 'OK') {
okNum++;
okNum++
} else {
ngNum++;
ngNum++
}
});
return { okNum, ngNum };
});
})
return { okNum, ngNum }
})
const primaryMaterialTableData = ref<PrimaryMaterialTableItem[]>([]);
const primaryMaterialTableData = ref<PrimaryMaterialTableItem[]>([])
const loadingPrimaryMaterialList = ref(false);
const loadingPrimaryMaterialList = ref(false)
const fetchPrimaryMaterialList = async () => {
const stationId = traceOrderStore.currentStationId;
if (!stationId) return;
loadingPrimaryMaterialList.value = true;
const stationId = traceOrderStore.currentStationId
if (!stationId) return
loadingPrimaryMaterialList.value = true
try {
const { rows } = await listMainMaterialOutboundLog({ stationId });
primaryMaterialTableData.value = rows;
const { rows } = await listMainMaterialOutboundLog({ stationId })
primaryMaterialTableData.value = rows
} catch (error: any) {
message.error(error.message || '查询失败');
message.error(error.message || '查询失败')
} finally {
loadingPrimaryMaterialList.value = false;
loadingPrimaryMaterialList.value = false
}
}
const openPickPrimaryMaterial = ref(false);
const openPickPrimaryMaterial = ref(false)
interface InfeedMaterialTableItem {
key: string;
waferCode: string;
dieCode: string;
result: 'OK' | 'NG';
qualityLevel: string;
[key: string]: any;
key: string
waferCode: string
dieCode: string
result: 'OK' | 'NG'
qualityLevel: string
[key: string]: any
}
const infeedMaterialColumns = [
{ title: '序号', dataIndex: 'index', key: 'index', align: 'center', width: 80 },
{ title: 'Wafer ID', dataIndex: 'waferCode', key: 'waferCode', align: 'center' },
{
title: '序号',
dataIndex: 'index',
key: 'index',
align: 'center',
width: 80,
},
{
title: 'Wafer ID',
dataIndex: 'waferCode',
key: 'waferCode',
align: 'center',
},
{ title: 'Die ID', dataIndex: 'dieCode', key: 'dieCode', align: 'center' },
{ title: '结果', dataIndex: 'result', key: 'result', align: 'center' },
{ title: '质量等级', dataIndex: 'qualityLevel', key: 'qualityLevel' },
];
const infeedMaterialTableData = ref<InfeedMaterialTableItem[]>([]);
]
const infeedMaterialTableData = ref<InfeedMaterialTableItem[]>([])
// 挑选主材
const handlePickPrimaryMaterial = async () => {
const stationId = traceOrderStore.currentStationId;
if (!stationId) return;
const stationId = traceOrderStore.currentStationId
if (!stationId) return
try {
const { rows } = await listMainMaterialEntryLog({ stationId });
infeedMaterialTableData.value = rows.map(item => {
const { rows } = await listMainMaterialEntryLog({ stationId })
infeedMaterialTableData.value = rows.map((item) => {
return {
...item,
result: 'OK',
qualityLevel: main_material_ok_level.value[0].value || '',
}
});
openPickPrimaryMaterial.value = true;
})
openPickPrimaryMaterial.value = true
} catch (err: any) {
message.error(err.message);
message.error(err.message)
}
}
const openEditRecordModal = ref(false);
const editingRecord = reactive<any>({});
const openEditRecordModal = ref(false)
const editingRecord = reactive<any>({})
// 修改主材出站记录
const handleEditRecord = (record: any) => {
Object.assign(editingRecord, record);
openEditRecordModal.value = true;
Object.assign(editingRecord, record)
openEditRecordModal.value = true
}
// 提交修改主材出站记录
const handleSubmitEdit = async () => {
try {
await updateMainMaterialOutboundLog(editingRecord);
openEditRecordModal.value = false;
await updateMainMaterialOutboundLog(editingRecord)
openEditRecordModal.value = false
} catch (err: any) {
message.error(err.message || '修改失败');
message.error(err.message || '修改失败')
}
}
// 删除主材出站记录
const handleDeleteRecord = async (record: any) => {
try {
await delMainMaterialOutboundLog(record.id);
message.success('删除成功');
await delMainMaterialOutboundLog(record.id)
message.success('删除成功')
} catch (err: any) {
message.error(err.message || '删除失败');
message.error(err.message || '删除失败')
}
}
// 选择主材
const rowSelection = ref<any[]>([]);
const selectedKeys = ref<any>([]);
const handleChangeSelection = (selectedRowKeys: any, selectedRows: InfeedMaterialTableItem[]) => {
selectedKeys.value = selectedRowKeys;
rowSelection.value = selectedRows.map(item => {
const rowSelection = ref<any[]>([])
const selectedKeys = ref<any>([])
const handleChangeSelection = (
selectedRowKeys: any,
selectedRows: InfeedMaterialTableItem[],
) => {
selectedKeys.value = selectedRowKeys
rowSelection.value = selectedRows.map((item) => {
return {
mainMaterialId: item.waferId ?? item.dieId,
result: item.result,
@@ -149,27 +198,30 @@ const handleChangeSelection = (selectedRowKeys: any, selectedRows: InfeedMateria
// 提交主材出站
const submitOutfeed = async () => {
const stationCode = traceOrderStore.currentStationCode;
const stationCode = traceOrderStore.currentStationCode
try {
await batchAddMainMaterialOutboundLog({ stationCode, outbounds: rowSelection.value });
message.success("提交成功");
handleCancel();
fetchPrimaryMaterialList();
await batchAddMainMaterialOutboundLog({
stationCode,
outbounds: rowSelection.value,
})
message.success('提交成功')
handleCancel()
fetchPrimaryMaterialList()
} catch (err: any) {
message.error(err.message || '提交失败');
message.error(err.message || '提交失败')
}
}
// 取消选择主材
const handleCancel = () => {
rowSelection.value = [];
selectedKeys.value = [];
rowSelection.value = []
selectedKeys.value = []
}
const handleRefresh = () => {
fetchPrimaryMaterialList();
renderTableHeight();
fetchPrimaryMaterialList()
renderTableHeight()
}
// 计算表格高度
@@ -188,8 +240,8 @@ onMounted(() => {
defineExpose({
totals,
renderTableHeight
});
renderTableHeight,
})
fetchPrimaryMaterialList()
</script>
@@ -197,28 +249,69 @@ fetchPrimaryMaterialList()
<template>
<div class="equipment__container">
<Title name="主材报工" showRefresh @refresh="handleRefresh" />
<a-button size="large" @click="handlePickPrimaryMaterial">选择主材</a-button>
<a-button size="large" @click="handlePickPrimaryMaterial">
选择主材
</a-button>
<div class="table-wrapper" ref="customTable">
<a-table :dataSource="primaryMaterialTableData" :columns="primaryMaterialColumns as ColumnsType<PrimaryMaterialTableItem>"
:pagination="false" bordered sticky :scroll="{ y: tableHeight }"
:loading="loadingPrimaryMaterialList">
<a-table
:dataSource="primaryMaterialTableData"
:columns="
primaryMaterialColumns as ColumnsType<PrimaryMaterialTableItem>
"
:pagination="false"
bordered
sticky
:scroll="{ y: tableHeight }"
:loading="loadingPrimaryMaterialList"
>
<template #bodyCell="{ column, index, record }">
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
<template v-if="column.key === 'outputResult'">
<a-switch v-model:checked="record.outputResult" checked-children="NG" checkedValue="NG" un-checked-children="OK" unCheckedValue="OK" />
<a-switch
v-model:checked="record.outputResult"
checked-children="NG"
checkedValue="NG"
un-checked-children="OK"
unCheckedValue="OK"
/>
</template>
<template v-if="column.key === 'qualityLevel'">
<a-select v-model:value="record.qualityLevel" style="width: 80%" v-if="record.outputResult === 'NG'">
<a-select-option v-for="dict in main_material_ng_level" :value="dict.value">{{ dict.label }}</a-select-option>
<a-select
v-model:value="record.qualityLevel"
style="width: 80%"
v-if="record.outputResult === 'NG'"
>
<a-select-option
v-for="dict in main_material_ng_level"
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</a-select-option>
</a-select>
<a-select v-model:value="record.qualityLevel" style="width: 80%" v-if="record.outputResult === 'OK'">
<a-select-option v-for="dict in main_material_ok_level" :value="dict.value">{{ dict.label }}</a-select-option>
<a-select
v-model:value="record.qualityLevel"
style="width: 80%"
v-if="record.outputResult === 'OK'"
>
<a-select-option
v-for="dict in main_material_ok_level"
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</a-select-option>
</a-select>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a-button @click="handleEditRecord(record)">修改</a-button>
<a-popconfirm title="确定删除该记录?" ok-text="" cancel-text="" @confirm="handleDeleteRecord(record)">
<a-popconfirm
title="确定删除该记录?"
ok-text=""
cancel-text=""
@confirm="handleDeleteRecord(record)"
>
<a-button danger>删除</a-button>
</a-popconfirm>
</a-space>
@@ -238,8 +331,16 @@ fetchPrimaryMaterialList()
</a-table>
</div>
<a-modal v-model:open="openEditRecordModal" title="修改主材出站记录" @ok="handleSubmitEdit">
<a-form :model="editingRecord" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
<a-modal
v-model:open="openEditRecordModal"
title="修改主材出站记录"
@ok="handleSubmitEdit"
>
<a-form
:model="editingRecord"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 20 }"
>
<a-form-item label="Wafer ID">
<a-input v-model:value="editingRecord.waferCode" disabled />
</a-form-item>
@@ -252,41 +353,109 @@ fetchPrimaryMaterialList()
<a-form-item label="Die Y 坐标">
<a-input v-model:value="editingRecord.ycoordinates" disabled />
</a-form-item>
<a-form-item label="结果" :rules="[{ required: true, message: '请选择结果!' }]">
<a-switch v-model:checked="editingRecord.outputResult" checked-children="NG" checkedValue="NG"
un-checked-children="OK" unCheckedValue="OK" />
<a-form-item
label="结果"
:rules="[{ required: true, message: '请选择结果!' }]"
>
<a-switch
v-model:checked="editingRecord.outputResult"
checked-children="NG"
checkedValue="NG"
un-checked-children="OK"
unCheckedValue="OK"
/>
</a-form-item>
<a-form-item label="质量等级" :rules="[{ required: true, message: '请选择质量等级!' }]">
<a-select v-model:value="editingRecord.qualityLevel" style="width: 80%" v-if="editingRecord.outputResult === 'NG'">
<a-select-option v-for="dict in main_material_ng_level" :value="dict.value">{{ dict.label
}}</a-select-option>
<a-form-item
label="质量等级"
:rules="[{ required: true, message: '请选择质量等级!' }]"
>
<a-select
v-model:value="editingRecord.qualityLevel"
style="width: 80%"
v-if="editingRecord.outputResult === 'NG'"
>
<a-select-option
v-for="dict in main_material_ng_level"
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</a-select-option>
</a-select>
<a-select v-model:value="editingRecord.qualityLevel" style="width: 80%" v-if="editingRecord.outputResult === 'OK'">
<a-select-option v-for="dict in main_material_ok_level" :value="dict.value">{{ dict.label
}}</a-select-option>
<a-select
v-model:value="editingRecord.qualityLevel"
style="width: 80%"
v-if="editingRecord.outputResult === 'OK'"
>
<a-select-option
v-for="dict in main_material_ok_level"
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</a-select-option>
</a-select>
</a-form-item>
</a-form>
</a-modal>
<a-modal v-model:open="openPickPrimaryMaterial" title="选择主材出站" width="50vw" @ok="submitOutfeed" @cancel="handleCancel">
<a-modal
v-model:open="openPickPrimaryMaterial"
title="选择主材出站"
width="50vw"
@ok="submitOutfeed"
@cancel="handleCancel"
>
已选择 {{ rowSelection.length }} 条主材
<a-table :dataSource="infeedMaterialTableData" :row-selection="{ selectedRowKeys: selectedKeys, onChange: handleChangeSelection }" row-key="id"
:columns="infeedMaterialColumns as ColumnsType<InfeedMaterialTableItem>" :pagination="false" bordered sticky>
<a-table
:dataSource="infeedMaterialTableData"
:row-selection="{
selectedRowKeys: selectedKeys,
onChange: handleChangeSelection,
}"
row-key="id"
:columns="infeedMaterialColumns as ColumnsType<InfeedMaterialTableItem>"
:pagination="false"
bordered
sticky
>
<template #bodyCell="{ column, index, record }">
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
<template v-if="column.key === 'result'">
<a-switch v-model:checked="record.result" checked-children="NG" checkedValue="NG"
un-checked-children="OK" unCheckedValue="OK" />
<a-switch
v-model:checked="record.result"
checked-children="NG"
checkedValue="NG"
un-checked-children="OK"
unCheckedValue="OK"
/>
</template>
<template v-if="column.key === 'qualityLevel'">
<a-select v-model:value="record.qualityLevel" style="width: 80%" v-if="record.result === 'NG'">
<a-select-option v-for="dict in main_material_ng_level" :value="dict.value">{{ dict.label
}}</a-select-option>
<a-select
v-model:value="record.qualityLevel"
style="width: 80%"
v-if="record.result === 'NG'"
>
<a-select-option
v-for="dict in main_material_ng_level"
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</a-select-option>
</a-select>
<a-select v-model:value="record.qualityLevel" style="width: 80%" v-if="record.result === 'OK'">
<a-select-option v-for="dict in main_material_ok_level" :value="dict.value">{{ dict.label
}}</a-select-option>
<a-select
v-model:value="record.qualityLevel"
style="width: 80%"
v-if="record.result === 'OK'"
>
<a-select-option
v-for="dict in main_material_ok_level"
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</a-select-option>
</a-select>
</template>
</template>

View File

@@ -1,89 +1,94 @@
<script setup lang="ts">
import { ref, computed, reactive } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { message } from 'ant-design-vue';
import { listStorageLocation } from '@/api/traceOrderManage/location';
import { completeStation, isLastStation as getIsLastStation } from '@/api/traceOrderManage/station';
import { useTraceOrderStore } from '@/store';
import { ref, computed, reactive } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { message } from 'ant-design-vue'
import { listStorageLocation } from '@/api/traceOrderManage/location'
import {
completeStation,
isLastStation as getIsLastStation,
} from '@/api/traceOrderManage/station'
import { useTraceOrderStore } from '@/store'
const router = useRouter();
const route = useRoute();
const router = useRouter()
const route = useRoute()
const traceOrderStore = useTraceOrderStore();
const traceOrderStore = useTraceOrderStore()
const menuItems = [
{ label: '主材报工', key: 'JobReport', progress: 30 },
{ label: '工序参数', key: 'ParameterConfiguration', progress: 80 },
{ label: '流转指引', key: 'ProcessGuidance', progress: 80 },
];
]
const activeKey = computed(() => route.name as string);
const activeKey = computed(() => route.name as string)
const handleMenuClick = (name: string) => {
router.push({ name });
};
router.push({ name })
}
const locationOptions = ref<any>([]);
const locationOptions = ref<any>([])
const fetchLocationList = async () => {
try {
const { rows } = await listStorageLocation({});
locationOptions.value = rows;
const { rows } = await listStorageLocation({})
locationOptions.value = rows
} catch (error: any) {
message.error(error.message || '获取库位列表失败');
message.error(error.message || '获取库位列表失败')
}
}
};
const openSelectLocation = ref(false);
const openSelectLocation = ref(false)
const handleOutfeed = async () => {
// 判断是否为末站点
const { data: isLastStation } = await getIsLastStation(traceOrderStore.currentStationId);
const { data: isLastStation } = await getIsLastStation(
traceOrderStore.currentStationId,
)
if (isLastStation || outfeedChildRef.value?.totals.ngNum) {
fetchLocationList();
openSelectLocation.value = true;
fetchLocationList()
openSelectLocation.value = true
} else {
submitOutfeed();
submitOutfeed()
}
}
};
const storage = reactive({});
const storage = reactive({})
const handleChangeLocation = (value: any, option: any) => {
Object.assign(storage, option);
};
Object.assign(storage, option)
}
const closeOutfeed = () => {
openSelectLocation.value = false;
};
openSelectLocation.value = false
}
// 确认出站
const outfeeding = ref(false);
const outfeeding = ref(false)
const submitOutfeed = async () => {
outfeeding.value = true;
outfeeding.value = true
try {
await completeStation(traceOrderStore.currentStationId, storage);
openSelectLocation.value = false;
message.success('出站成功');
traceOrderStore.fetchStationInfo();
traceOrderStore.fetchStationList();
router.push({ name: 'TraceOrderManage' });
await completeStation(traceOrderStore.currentStationId, storage)
openSelectLocation.value = false
message.success('出站成功')
traceOrderStore.fetchStationInfo()
traceOrderStore.fetchStationList()
router.push({ name: 'TraceOrderManage' })
} catch (error: any) {
message.error(error.message || '出站失败');
message.error(error.message || '出站失败')
} finally {
outfeeding.value = false;
outfeeding.value = false
}
}
};
const outfeedChildRef = ref<any>(null);
const outfeedChildRef = ref<any>(null)
/** 父组件对外暴露的方法 */
function renderTableHeight() {
outfeedChildRef.value?.renderTableHeight();
outfeedChildRef.value?.renderTableHeight()
}
defineExpose({
renderTableHeight
});
renderTableHeight,
})
fetchLocationList();
fetchLocationList()
</script>
<template>
@@ -97,21 +102,43 @@ fetchLocationList();
<a-col :span="4" class="menu-wrapper">
<div class="menu-list">
<span class="outfeed-title">出站</span>
<div v-for="item in menuItems" :key="item.key" class="menu-item" :class="{ active: activeKey === item.key }"
@click="handleMenuClick(item.key)">
<div
v-for="item in menuItems"
:key="item.key"
class="menu-item"
:class="{ active: activeKey === item.key }"
@click="handleMenuClick(item.key)"
>
<div class="menu-content">
<span class="menu-title">{{ item.label }}</span>
<a-progress :percent="item.progress" :show-info="false" size="small"
:stroke-color="activeKey === item.key ? '#1890ff' : undefined" class="menu-progress" />
<a-progress
:percent="item.progress"
:show-info="false"
size="small"
:stroke-color="activeKey === item.key ? '#1890ff' : undefined"
class="menu-progress"
/>
</div>
</div>
<a-button type="primary" size="large" @click="handleOutfeed">确认出站</a-button>
<a-button type="primary" size="large" @click="handleOutfeed">
确认出站
</a-button>
</div>
</a-col>
</a-row>
<a-modal title="选择出站库位" :open="openSelectLocation" @ok="submitOutfeed" @cancel="closeOutfeed">
<a-select style="width: 100%" @change="handleChangeLocation" :options="locationOptions" :fieldNames="{ label: 'storageLocationCode', value: 'id' }" />
<a-modal
title="选择出站库位"
:open="openSelectLocation"
@ok="submitOutfeed"
@cancel="closeOutfeed"
>
<a-select
style="width: 100%"
@change="handleChangeLocation"
:options="locationOptions"
:fieldNames="{ label: 'storageLocationCode', value: 'id' }"
/>
</a-modal>
</a-spin>
</template>

View File

@@ -1,33 +1,32 @@
import { defineConfig, loadEnv } from 'vite';
import Components from "unplugin-vue-components/vite"; // 按需组件自动导入
import { AntDesignVueResolver } from "unplugin-vue-components/resolvers";
import vue from '@vitejs/plugin-vue';
import Icons from 'unplugin-icons/vite';
import IconsResolver from 'unplugin-icons/resolver';
import path from 'path';
import { defineConfig, loadEnv } from 'vite'
import Components from 'unplugin-vue-components/vite' // 按需组件自动导入
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
import vue from '@vitejs/plugin-vue'
import Icons from 'unplugin-icons/vite'
import IconsResolver from 'unplugin-icons/resolver'
import path from 'path'
// https://vite.dev/config/
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd());
const env = loadEnv(mode, process.cwd())
return {
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
'@': path.resolve(__dirname, 'src'),
},
},
server: {
proxy: {
"/api": {
'/api': {
target: env.VITE_APP_BASE_URL,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\//, "/"),
rewrite: (path) => path.replace(/^\/api\//, '/'),
},
"/prod-api": {
'/prod-api': {
target: env.VITE_APP_BASE_URL,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/prod-api\//, "/"),
rewrite: (path) => path.replace(/^\/prod-api\//, '/'),
},
},
},
@@ -42,8 +41,8 @@ export default defineConfig(({ mode }) => {
}),
// 自动导入 lucide 图标
IconsResolver({
prefix: "i", // 比如用 <i-a-arrow-down />
enabledCollections: ["lucide"],
prefix: 'i', // 比如用 <i-a-arrow-down />
enabledCollections: ['lucide'],
}),
],
include: [/\.vue$/, /\.vue\?vue/, /\.md$/, /\.tsx$/], //包含的文件类型
@@ -52,5 +51,5 @@ export default defineConfig(({ mode }) => {
autoInstall: true, // 没安装的图标库会自动下载
}),
],
};
});
}
})