Compare commits
10 Commits
e7f30f2788
...
cef3725a6e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cef3725a6e | ||
|
|
edaa445b63 | ||
|
|
9e45b78f31 | ||
|
|
7025a5304f | ||
|
|
90d6b542ee | ||
|
|
200e57931a | ||
|
|
652ebfb5ef | ||
|
|
448a22df5e | ||
|
|
e8a69a3536 | ||
|
|
fb55334a1d |
29
components.d.ts
vendored
29
components.d.ts
vendored
@@ -8,17 +8,46 @@ export {}
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
AAvatar: typeof import('ant-design-vue/es')['Avatar']
|
||||
AButton: typeof import('ant-design-vue/es')['Button']
|
||||
ACard: typeof import('ant-design-vue/es')['Card']
|
||||
ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
|
||||
ACol: typeof import('ant-design-vue/es')['Col']
|
||||
AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
|
||||
ActionButtons: typeof import('./src/components/common/ActionButtons/index.vue')['default']
|
||||
ADescriptions: typeof import('ant-design-vue/es')['Descriptions']
|
||||
ADescriptionsItem: typeof import('ant-design-vue/es')['DescriptionsItem']
|
||||
ADivider: typeof import('ant-design-vue/es')['Divider']
|
||||
ADropdown: typeof import('ant-design-vue/es')['Dropdown']
|
||||
ADropdownButton: typeof import('ant-design-vue/es')['DropdownButton']
|
||||
AForm: typeof import('ant-design-vue/es')['Form']
|
||||
AFormItem: typeof import('ant-design-vue/es')['FormItem']
|
||||
AInput: typeof import('ant-design-vue/es')['Input']
|
||||
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
|
||||
AInputSearch: typeof import('ant-design-vue/es')['InputSearch']
|
||||
AList: typeof import('ant-design-vue/es')['List']
|
||||
AListItem: typeof import('ant-design-vue/es')['ListItem']
|
||||
AListItemMeta: typeof import('ant-design-vue/es')['ListItemMeta']
|
||||
AMenu: typeof import('ant-design-vue/es')['Menu']
|
||||
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
|
||||
AModal: typeof import('ant-design-vue/es')['Modal']
|
||||
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
|
||||
AProgress: typeof import('ant-design-vue/es')['Progress']
|
||||
ARadioButton: typeof import('ant-design-vue/es')['RadioButton']
|
||||
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
|
||||
ARow: typeof import('ant-design-vue/es')['Row']
|
||||
ASpace: typeof import('ant-design-vue/es')['Space']
|
||||
ASpin: typeof import('ant-design-vue/es')['Spin']
|
||||
ASteps: typeof import('ant-design-vue/es')['Steps']
|
||||
ATable: typeof import('ant-design-vue/es')['Table']
|
||||
ATag: typeof import('ant-design-vue/es')['Tag']
|
||||
Header: typeof import('./src/components/Header/index.vue')['default']
|
||||
ILucideArrow: typeof import('~icons/lucide/arrow')['default']
|
||||
ILucideArrowDown: typeof import('~icons/lucide/arrow-down')['default']
|
||||
ILucideBarChart3: typeof import('~icons/lucide/bar-chart3')['default']
|
||||
ILucideBuilding: typeof import('~icons/lucide/building')['default']
|
||||
ILucideChevronDown: typeof import('~icons/lucide/chevron-down')['default']
|
||||
ILucideCopy: typeof import('~icons/lucide/copy')['default']
|
||||
ILucideCpu: typeof import('~icons/lucide/cpu')['default']
|
||||
ILucideLoader: typeof import('~icons/lucide/loader')['default']
|
||||
ILucideLock: typeof import('~icons/lucide/lock')['default']
|
||||
|
||||
8
src/api/common/model.d.ts
vendored
8
src/api/common/model.d.ts
vendored
@@ -1,8 +0,0 @@
|
||||
export interface ApiResponse<T = any> {
|
||||
code?: number;
|
||||
msg?: string;
|
||||
data?: T;
|
||||
rows?: T[];
|
||||
total?: number;
|
||||
token?: string;
|
||||
}
|
||||
81
src/api/pwoManage/fixtureCombination/index.ts
Normal file
81
src/api/pwoManage/fixtureCombination/index.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import request from "@/api/request";
|
||||
import type { FixtureCombinationData, FixtureCombinationQuery } from "./model";
|
||||
import type { ID } from "@/api/common";
|
||||
|
||||
// 查询治具组合列表
|
||||
export function listMaskCombination(params: FixtureCombinationQuery) {
|
||||
return request({
|
||||
url: "/tpm/mask/combination/list",
|
||||
method: "get",
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 查询治具组合包含的治具列表
|
||||
export function listCombinationAssignMask(id: ID, params: FixtureCombinationQuery) {
|
||||
return request({
|
||||
url: "/tpm/mask/combination/" + id + "/masks",
|
||||
method: "get",
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 高级查询治具组合列表
|
||||
export function advListMaskCombination(params: FixtureCombinationQuery) {
|
||||
return request({
|
||||
url: "/tpm/mask/combination/advList",
|
||||
method: "get",
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 查询治具组合详细
|
||||
export function getMaskCombination(id: ID) {
|
||||
return request({
|
||||
url: "/tpm/mask/combination/" + id,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
// 新增治具组合
|
||||
export function addMaskCombination(data: FixtureCombinationData) {
|
||||
return request({
|
||||
url: "/tpm/mask/combination",
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 修改治具组合
|
||||
export function updateMaskCombination(data: FixtureCombinationData) {
|
||||
return request({
|
||||
url: "/tpm/mask/combination",
|
||||
method: "put",
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 删除治具组合
|
||||
export function delMaskCombination(id: ID) {
|
||||
return request({
|
||||
url: "/tpm/mask/combination/" + id,
|
||||
method: "delete",
|
||||
});
|
||||
}
|
||||
|
||||
// 新增治具组合与治具关联关系
|
||||
export function addMaskCombinationAssignment(data: FixtureCombinationData) {
|
||||
return request({
|
||||
url: "/tpm/mask/combination/assignment",
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 删除治具组合与治具关联关系
|
||||
export function delMaskCombinationAssignment(id: ID) {
|
||||
return request({
|
||||
url: "/tpm/mask/combination/assignment/" + id,
|
||||
method: "delete",
|
||||
});
|
||||
}
|
||||
26
src/api/pwoManage/fixtureCombination/model.d.ts
vendored
Normal file
26
src/api/pwoManage/fixtureCombination/model.d.ts
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
import { BaseEntity, PageQuery, type ID } from "@/api/common";
|
||||
/**
|
||||
* 治具组合查询参数
|
||||
*/
|
||||
export interface FixtureCombinationQuery extends PageQuery {
|
||||
combinationName?: string;
|
||||
combinationCode?: string;
|
||||
combinationStatus?: string;
|
||||
remark?: string;
|
||||
searchValue?: string;
|
||||
tempId?: string;
|
||||
timeRange?: [string, string];
|
||||
}
|
||||
|
||||
/**
|
||||
* 治具组合数据
|
||||
*/
|
||||
export interface FixtureCombinationData extends BaseEntity {
|
||||
combinationName?: string;
|
||||
combinationCode?: string;
|
||||
combinationStatus?: string;
|
||||
remark?: string;
|
||||
searchValue?: string;
|
||||
tempId?: string;
|
||||
timeRange?: [string, string];
|
||||
}
|
||||
63
src/api/pwoManage/index.ts
Normal file
63
src/api/pwoManage/index.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
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",
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 高级查询随工单列表
|
||||
export function advListLotTraceOrder(params: LotTraceOrderQuery) {
|
||||
return request({
|
||||
url: "/mes/lot-trace-order/advList",
|
||||
method: "get",
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 查询随工单详细
|
||||
export function getLotTraceOrder(id: ID) {
|
||||
return request({
|
||||
url: "/mes/lot-trace-order/" + id,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
// 新增随工单
|
||||
export function addLotTraceOrder(data: LotTraceOrderData) {
|
||||
return request({
|
||||
url: "/mes/lot-trace-order",
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 修改随工单
|
||||
export function updateLotTraceOrder(data: LotTraceOrderData) {
|
||||
return request({
|
||||
url: "/mes/lot-trace-order",
|
||||
method: "put",
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 删除随工单
|
||||
export function delLotTraceOrder(id: ID) {
|
||||
return request({
|
||||
url: "/mes/lot-trace-order/" + id,
|
||||
method: "delete",
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭随工单
|
||||
export function closeLotTraceOrder(id: ID) {
|
||||
return request({
|
||||
url: "/mes/lot-trace-order/close/" + id,
|
||||
method: "put",
|
||||
});
|
||||
}
|
||||
188
src/api/pwoManage/model.d.ts
vendored
Normal file
188
src/api/pwoManage/model.d.ts
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
import { BaseEntity, PageQuery, type ID } from "@/api/common";
|
||||
/**
|
||||
* 随工单查询参数
|
||||
*/
|
||||
export interface LotTraceOrderQuery extends PageQuery {
|
||||
/** 编码 */
|
||||
code?: string;
|
||||
/** 状态 */
|
||||
status?: string;
|
||||
/** 主生产计划的ID */
|
||||
mpsId?: number;
|
||||
/** 主生产计划编码 */
|
||||
mpsCode?: string;
|
||||
/** 订单类型 */
|
||||
orderType?: string;
|
||||
/** 主生产计划明细ID */
|
||||
mpsDetailId?: number;
|
||||
/** 批号 */
|
||||
batchNo?: string;
|
||||
/** 主生产计划明细序号 */
|
||||
mpsDetailSeq?: number;
|
||||
/** 目标产品ID */
|
||||
tarMaterialId?: number;
|
||||
/** 主物料ID */
|
||||
masterMaterialId?: number;
|
||||
/** 生产版本ID */
|
||||
prodVersionId?: number;
|
||||
/** 计划数量 */
|
||||
planQty?: number;
|
||||
/** OK数量 */
|
||||
okQty?: number;
|
||||
/** NG数量 */
|
||||
ngQty?: number;
|
||||
/** 未完成数量 */
|
||||
unfinishedQty?: number;
|
||||
/** 单位ID */
|
||||
unitId?: number;
|
||||
/** 计划开始时间范围 */
|
||||
planStartTimeRange?: [string, string];
|
||||
/** 计划结束时间范围 */
|
||||
planEndTimeRange?: [string, string];
|
||||
/** 扩展字段1 */
|
||||
extStr1?: string;
|
||||
/** 扩展字段2 */
|
||||
extStr2?: string;
|
||||
/** 扩展字段3 */
|
||||
extStr3?: string;
|
||||
/** 扩展字段4 */
|
||||
extStr4?: string;
|
||||
/** 扩展字段5 */
|
||||
extStr5?: string;
|
||||
/** 扩展字段6 */
|
||||
extStr6?: string;
|
||||
/** 扩展字段7 */
|
||||
extStr7?: string;
|
||||
/** 扩展字段8 */
|
||||
extStr8?: string;
|
||||
/** 扩展字段9 */
|
||||
extStr9?: string;
|
||||
/** 扩展字段10 */
|
||||
extStr10?: string;
|
||||
/** 扩展字段11 */
|
||||
extStr11?: string;
|
||||
/** 扩展字段12 */
|
||||
extStr12?: string;
|
||||
/** 扩展字段13 */
|
||||
extStr13?: string;
|
||||
/** 扩展字段14 */
|
||||
extStr14?: string;
|
||||
/** 扩展字段15 */
|
||||
extStr15?: string;
|
||||
/** 扩展字段16 */
|
||||
extStr16?: string;
|
||||
/** 扩展整型1 */
|
||||
extInt1?: number;
|
||||
/** 扩展整型2 */
|
||||
extInt2?: number;
|
||||
/** 扩展小数1 */
|
||||
extDec1?: number;
|
||||
/** 扩展小数2 */
|
||||
extDec2?: number;
|
||||
/** 扩展日期1范围 */
|
||||
extDate1Range?: [string, string];
|
||||
/** 扩展日期2范围 */
|
||||
extDate2Range?: [string, string];
|
||||
/** 删除标志 */
|
||||
delStatus?: string;
|
||||
/** 创建时间范围 */
|
||||
createTimeRange?: [string, string];
|
||||
/** 更新时间范围 */
|
||||
updateTimeRange?: [string, string];
|
||||
}
|
||||
|
||||
/**
|
||||
* 随工单数据
|
||||
*/
|
||||
export interface LotTraceOrderData extends BaseEntity {
|
||||
/** 主键ID */
|
||||
id?: ID;
|
||||
/** 编码 */
|
||||
code?: string;
|
||||
/** 状态 */
|
||||
status?: string;
|
||||
/** 主生产计划的ID */
|
||||
mpsId?: number;
|
||||
/** 主生产计划编码 */
|
||||
mpsCode?: string;
|
||||
/** 订单类型 */
|
||||
orderType?: string;
|
||||
/** 主生产计划明细ID */
|
||||
mpsDetailId?: number;
|
||||
/** 批号 */
|
||||
batchNo?: string;
|
||||
/** 主生产计划明细序号 */
|
||||
mpsDetailSeq?: number;
|
||||
/** 目标产品ID */
|
||||
tarMaterialId?: number;
|
||||
/** 目标产品名称 */
|
||||
tarMaterialName?: string;
|
||||
/** 目标产品编码 */
|
||||
tarMaterialCode?: string;
|
||||
/** 主物料ID */
|
||||
masterMaterialId?: number;
|
||||
/** 生产版本ID */
|
||||
prodVersionId?: number;
|
||||
/** 计划数量 */
|
||||
planQty?: number;
|
||||
/** OK数量 */
|
||||
okQty?: number;
|
||||
/** NG数量 */
|
||||
ngQty?: number;
|
||||
/** 未完成数量 */
|
||||
unfinishedQty?: number;
|
||||
/** 单位ID */
|
||||
unitId?: number;
|
||||
/** 计划开始时间 */
|
||||
planStartTime?: string;
|
||||
/** 计划结束时间 */
|
||||
planEndTime?: string;
|
||||
/** 扩展字段1 */
|
||||
extStr1?: string;
|
||||
/** 扩展字段2 */
|
||||
extStr2?: string;
|
||||
/** 扩展字段3 */
|
||||
extStr3?: string;
|
||||
/** 扩展字段4 */
|
||||
extStr4?: string;
|
||||
/** 扩展字段5 */
|
||||
extStr5?: string;
|
||||
/** 扩展字段6 */
|
||||
extStr6?: string;
|
||||
/** 扩展字段7 */
|
||||
extStr7?: string;
|
||||
/** 扩展字段8 */
|
||||
extStr8?: string;
|
||||
/** 扩展字段9 */
|
||||
extStr9?: string;
|
||||
/** 扩展字段10 */
|
||||
extStr10?: string;
|
||||
/** 扩展字段11 */
|
||||
extStr11?: string;
|
||||
/** 扩展字段12 */
|
||||
extStr12?: string;
|
||||
/** 扩展字段13 */
|
||||
extStr13?: string;
|
||||
/** 扩展字段14 */
|
||||
extStr14?: string;
|
||||
/** 扩展字段15 */
|
||||
extStr15?: string;
|
||||
/** 扩展字段16 */
|
||||
extStr16?: string;
|
||||
/** 扩展整型1 */
|
||||
extInt1?: number;
|
||||
/** 扩展整型2 */
|
||||
extInt2?: number;
|
||||
/** 扩展小数1 */
|
||||
extDec1?: number;
|
||||
/** 扩展小数2 */
|
||||
extDec2?: number;
|
||||
/** 扩展日期1 */
|
||||
extDate1?: string;
|
||||
/** 扩展日期2 */
|
||||
extDate2?: string;
|
||||
/** 备注 */
|
||||
remark?: string;
|
||||
/** 删除标志 */
|
||||
delStatus?: string;
|
||||
}
|
||||
55
src/api/station/index.ts
Normal file
55
src/api/station/index.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
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",
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 高级查询站点列表
|
||||
export function advListStation(params: MesStationQuery) {
|
||||
return request({
|
||||
url: "/mes/station/advList",
|
||||
method: "get",
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 查询站点详细
|
||||
export function getStation(id: ID) {
|
||||
return request({
|
||||
url: "/mes/station/" + id,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
// 新增站点
|
||||
export function addStation(data: MesStationData) {
|
||||
return request({
|
||||
url: "/mes/station",
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 修改站点
|
||||
export function updateStation(data: MesStationData) {
|
||||
return request({
|
||||
url: "/mes/station",
|
||||
method: "put",
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 删除站点
|
||||
export function delStation(id: ID) {
|
||||
return request({
|
||||
url: "/mes/station/" + id,
|
||||
method: "delete",
|
||||
});
|
||||
}
|
||||
71
src/api/station/model.d.ts
vendored
Normal file
71
src/api/station/model.d.ts
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
import { BaseEntity, PageQuery, type ID } from "@/api/common";
|
||||
|
||||
/**
|
||||
* 站点查询参数
|
||||
*/
|
||||
export interface MesStationQuery extends PageQuery {
|
||||
/** 随工单ID */
|
||||
traceOrderId?: number;
|
||||
/** 随工单编码 */
|
||||
traceOrderCode?: string;
|
||||
/** 站点序号 */
|
||||
seqNo?: number;
|
||||
/** 站点名称 */
|
||||
name?: string;
|
||||
/** 站点编码 */
|
||||
code?: string;
|
||||
/** 状态 */
|
||||
status?: string;
|
||||
/** 计划数量最小值 */
|
||||
planQtyMin?: number;
|
||||
/** 计划数量最大值 */
|
||||
planQtyMax?: number;
|
||||
/** 合格数量最小值 */
|
||||
okQtyMin?: number;
|
||||
/** 合格数量最大值 */
|
||||
okQtyMax?: number;
|
||||
/** 不合格数量最小值 */
|
||||
ngQtyMin?: number;
|
||||
/** 不合格数量最大值 */
|
||||
ngQtyMax?: number;
|
||||
/** 进站时间范围开始 */
|
||||
arrivalTimeStart?: string;
|
||||
/** 进站时间范围结束 */
|
||||
arrivalTimeEnd?: string;
|
||||
/** 出战时间范围开始 */
|
||||
departureTimeStart?: string;
|
||||
/** 出战时间范围结束 */
|
||||
departureTimeEnd?: string;
|
||||
/** 删除标志 */
|
||||
delStatus?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 站点数据
|
||||
*/
|
||||
export interface MesStationData extends BaseEntity {
|
||||
/** 主键ID */
|
||||
id?: number;
|
||||
/** 随工单ID */
|
||||
traceOrderId?: number;
|
||||
/** 站点序号 */
|
||||
seqNo?: number;
|
||||
/** 站点名称 */
|
||||
name?: string;
|
||||
/** 站点编码 */
|
||||
code?: string;
|
||||
/** 状态 */
|
||||
status?: string;
|
||||
/** 计划数量 */
|
||||
planQty?: number;
|
||||
/** 合格数量 */
|
||||
okQty?: number;
|
||||
/** 不合格数量 */
|
||||
ngQty?: number;
|
||||
/** 进站时间 */
|
||||
arrivalTime?: string;
|
||||
/** 出战时间 */
|
||||
departureTime?: string;
|
||||
/** 删除标志 */
|
||||
delStatus?: string;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import request from '../request'
|
||||
import request from '@/api/request'
|
||||
import type { LoginInfo } from './model';
|
||||
|
||||
// 用户登录
|
||||
@@ -17,3 +17,11 @@ export function getCaptcha() {
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 退出登录
|
||||
export function logout() {
|
||||
return request({
|
||||
url: '/logout',
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
134
src/assets/styles/custom/button.scss
Normal file
134
src/assets/styles/custom/button.scss
Normal file
@@ -0,0 +1,134 @@
|
||||
// Color variables
|
||||
$orange: #ff9800;
|
||||
$orange-hover: #ffb74d;
|
||||
|
||||
$blue: #2196f3;
|
||||
$blue-hover: #42a5f5;
|
||||
|
||||
$red: #f44336;
|
||||
$red-hover: #ef5350;
|
||||
|
||||
$green: #4caf50;
|
||||
$green-hover: #66bb6a;
|
||||
|
||||
$purple: #9c27b0;
|
||||
$purple-hover: #ab47bc;
|
||||
|
||||
$teal: #009688;
|
||||
$teal-hover: #26a69a;
|
||||
|
||||
$indigo: #3f51b5;
|
||||
$indigo-hover: #5c6bc0;
|
||||
|
||||
$pink: #e91e63;
|
||||
$pink-hover: #ec407a;
|
||||
|
||||
$cyan: #00bcd4;
|
||||
$cyan-hover: #26c6da;
|
||||
|
||||
$amber: #ffc107;
|
||||
$amber-hover: #ffd54f;
|
||||
|
||||
$brown: #795548;
|
||||
$brown-hover: #8d6e63;
|
||||
|
||||
$grey: #9e9e9e;
|
||||
$grey-hover: #bdbdbd;
|
||||
|
||||
.action-btn {
|
||||
&.orange-btn {
|
||||
background-color: $orange;
|
||||
|
||||
& &:hover {
|
||||
background-color: $orange-hover;
|
||||
}
|
||||
}
|
||||
|
||||
&.blue-btn {
|
||||
background-color: $blue;
|
||||
|
||||
&:hover {
|
||||
background-color: $blue-hover;
|
||||
}
|
||||
}
|
||||
|
||||
&.red-btn {
|
||||
background-color: $red;
|
||||
|
||||
&:hover {
|
||||
background-color: $red-hover;
|
||||
}
|
||||
}
|
||||
|
||||
&.green-btn {
|
||||
background-color: $green;
|
||||
|
||||
&:hover {
|
||||
background-color: $green-hover;
|
||||
}
|
||||
}
|
||||
|
||||
&.purple-btn {
|
||||
background-color: $purple;
|
||||
|
||||
&:hover {
|
||||
background-color: $purple-hover;
|
||||
}
|
||||
}
|
||||
|
||||
&.teal-btn {
|
||||
background-color: $teal;
|
||||
|
||||
&:hover {
|
||||
background-color: $teal-hover;
|
||||
}
|
||||
}
|
||||
|
||||
&.indigo-btn {
|
||||
background-color: $indigo;
|
||||
|
||||
&:hover {
|
||||
background-color: $indigo-hover;
|
||||
}
|
||||
}
|
||||
|
||||
&.pink-btn {
|
||||
background-color: $pink;
|
||||
|
||||
&:hover {
|
||||
background-color: $pink-hover;
|
||||
}
|
||||
}
|
||||
|
||||
&.cyan-btn {
|
||||
background-color: $cyan;
|
||||
|
||||
&:hover {
|
||||
background-color: $cyan-hover;
|
||||
}
|
||||
}
|
||||
|
||||
&.amber-btn {
|
||||
background-color: $amber;
|
||||
|
||||
&:hover {
|
||||
background-color: $amber-hover;
|
||||
}
|
||||
}
|
||||
|
||||
&.brown-btn {
|
||||
background-color: $brown;
|
||||
|
||||
&:hover {
|
||||
background-color: $brown-hover;
|
||||
}
|
||||
}
|
||||
|
||||
&.grey-btn {
|
||||
background-color: $grey;
|
||||
|
||||
&:hover {
|
||||
background-color: $grey-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/assets/styles/custom/index.scss
Normal file
1
src/assets/styles/custom/index.scss
Normal file
@@ -0,0 +1 @@
|
||||
@forward './button';
|
||||
@@ -1,3 +1,4 @@
|
||||
@forward './base';
|
||||
@forward './variables';
|
||||
@forward './ant-design';
|
||||
@forward './custom/index';
|
||||
@@ -2,8 +2,8 @@
|
||||
<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="showHome" class="header-btn" @click="backToHome">首页</a-button>
|
||||
<a-button v-if="showBack" class="header-btn" @click="back">返回</a-button>
|
||||
<a-button v-if="showBack" @click="back">返回</a-button>
|
||||
<a-button v-if="showHome" @click="backToHome">首页</a-button>
|
||||
<slot name="left-opts" />
|
||||
</div>
|
||||
<div class="title" v-if="title || $slots.title">
|
||||
@@ -13,6 +13,8 @@
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<slot />
|
||||
</header>
|
||||
@@ -20,6 +22,9 @@
|
||||
|
||||
<script setup>
|
||||
import { useRouter } from 'vue-router';
|
||||
import { Modal } from 'ant-design-vue';
|
||||
import { useAuthStore, useUserStore } from '@/store';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
defineProps({
|
||||
showHome: {
|
||||
@@ -30,6 +35,10 @@ defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showLogout: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: null,
|
||||
@@ -51,6 +60,22 @@ defineProps({
|
||||
const emit = defineEmits(['back']);
|
||||
const router = useRouter();
|
||||
|
||||
const userStore = useUserStore();
|
||||
const { username } = storeToRefs(userStore);
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const handleLogout = () => {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: `是否确认退出登录:${ username.value }`,
|
||||
okText: '确定',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
authStore.logout();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const back = () => {
|
||||
emit('back');
|
||||
defaultBack();
|
||||
@@ -61,7 +86,7 @@ const defaultBack = () => {
|
||||
};
|
||||
|
||||
const backToHome = () => {
|
||||
router.push({ name: 'ipc' });
|
||||
router.push({ name: 'Index' });
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -72,7 +97,8 @@ const backToHome = () => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background-color: #fff;
|
||||
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;
|
||||
|
||||
&.hide-shadow {
|
||||
@@ -81,6 +107,7 @@ const backToHome = () => {
|
||||
|
||||
.title {
|
||||
flex: 14;
|
||||
color: #fff;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
@@ -105,12 +132,5 @@ const backToHome = () => {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.header-btn.ant-btn) {
|
||||
height: 80%;
|
||||
text-align: center;
|
||||
font-size: clamp(14px, 1vw, 18px);
|
||||
padding: 0 0.75rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import Cookies from "js-cookie";
|
||||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { message } from "ant-design-vue";
|
||||
import { message, notification } from "ant-design-vue";
|
||||
|
||||
import { useUserStore } from "./user";
|
||||
import { login, logout as logoutApi } from "@/api/system";
|
||||
import { TokenKey as TOKEN_KEY } from "@/utils/auth";
|
||||
import { setToken, getToken, removeToken, setAccount, removeAccount } from "@/utils/auth";
|
||||
import type { LoginInfo } from "@/api/system/model";
|
||||
|
||||
export const useAuthStore = defineStore("auth", () => {
|
||||
@@ -15,26 +14,29 @@ export const useAuthStore = defineStore("auth", () => {
|
||||
const userStore = useUserStore();
|
||||
|
||||
const loginLoading = ref(false);
|
||||
const token = ref(Cookies.get(TOKEN_KEY) || null);
|
||||
const token = ref(getToken() || null);
|
||||
|
||||
function setToken(newToken: string) {
|
||||
token.value = newToken;
|
||||
Cookies.set(TOKEN_KEY, newToken);
|
||||
}
|
||||
|
||||
function clearToken() {
|
||||
token.value = null;
|
||||
Cookies.remove(TOKEN_KEY);
|
||||
}
|
||||
|
||||
async function authLogin(params: LoginInfo) {
|
||||
async function authLogin(params: LoginInfo, rememberMe: boolean) {
|
||||
try {
|
||||
loginLoading.value = true;
|
||||
const res = await login(params);
|
||||
if (res.code === 200) {
|
||||
setToken(res.token as string);
|
||||
await userStore.fetchUserInfo();
|
||||
message.success("登录成功");
|
||||
notification.success({
|
||||
message: "登录成功",
|
||||
description: `欢迎回来,${params.username}`,
|
||||
duration: 3,
|
||||
});
|
||||
|
||||
if (rememberMe) {
|
||||
userStore.setUserInfo({
|
||||
...params,
|
||||
rememberMe
|
||||
});
|
||||
} else {
|
||||
userStore.clearUserInfo();
|
||||
}
|
||||
const redirect = route.query.redirect || "/";
|
||||
router.replace(redirect as string);
|
||||
return res;
|
||||
@@ -52,7 +54,7 @@ export const useAuthStore = defineStore("auth", () => {
|
||||
async function logout() {
|
||||
// 在实际应用中,这里可以调用后端的退出登录接口
|
||||
await logoutApi();
|
||||
clearToken();
|
||||
removeToken();
|
||||
userStore.clearUserInfo();
|
||||
await router.push("/login");
|
||||
message.success("已成功退出");
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import Cookies from 'js-cookie';
|
||||
import { setAccount, removeAccount } from "@/utils/auth";
|
||||
|
||||
const USERNAME_KEY = 'username';
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
export const useUserStore = defineStore("user", {
|
||||
state: () => ({
|
||||
username: Cookies.get(USERNAME_KEY) || null,
|
||||
username: Cookies.get('username') || null,
|
||||
}),
|
||||
actions: {
|
||||
async fetchUserInfo() {
|
||||
// Simulate API call
|
||||
const fetchedUsername = 'mock_user';
|
||||
this.username = fetchedUsername;
|
||||
Cookies.set(USERNAME_KEY, fetchedUsername);
|
||||
},
|
||||
setUserInfo(params: any) {
|
||||
setAccount({
|
||||
username: params.username,
|
||||
password: params.password,
|
||||
rememberMe: params.rememberMe ? "true" : "false",
|
||||
});
|
||||
},
|
||||
clearUserInfo() {
|
||||
this.username = null;
|
||||
Cookies.remove(USERNAME_KEY);
|
||||
removeAccount();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import Cookies from 'js-cookie';
|
||||
import { encrypt } from '@/utils/jsencrypt';
|
||||
|
||||
// 30 天
|
||||
export const expiresTime = 30;
|
||||
|
||||
export const TokenKey = 'Admin-Token'
|
||||
|
||||
@@ -13,3 +17,29 @@ export function setToken(token: string) {
|
||||
export function removeToken() {
|
||||
return Cookies.remove(TokenKey)
|
||||
}
|
||||
|
||||
export interface AccountInfo {
|
||||
username?: string;
|
||||
password?: string;
|
||||
rememberMe?: 'true' | 'false';
|
||||
}
|
||||
|
||||
export function getAccount() {
|
||||
return {
|
||||
username: Cookies.get("username"),
|
||||
password: Cookies.get("password"),
|
||||
rememberMe: Cookies.get("rememberMe"),
|
||||
};
|
||||
}
|
||||
|
||||
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});
|
||||
}
|
||||
|
||||
export function removeAccount() {
|
||||
Cookies.remove("username");
|
||||
Cookies.remove("password");
|
||||
Cookies.remove("rememberMe");
|
||||
}
|
||||
|
||||
31
src/utils/jsencrypt.ts
Normal file
31
src/utils/jsencrypt.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import JSEncrypt from "jsencrypt";
|
||||
|
||||
// 密钥对生成 http://web.chacuo.net/netrsakeypair
|
||||
|
||||
const publicKey =
|
||||
"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=";
|
||||
|
||||
// 加密
|
||||
export function encrypt(txt: string) {
|
||||
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); // 对数据进行解密
|
||||
}
|
||||
@@ -1,13 +1,9 @@
|
||||
<template>
|
||||
<div class="ipc-dashboard">
|
||||
<Header title="过站工控机">
|
||||
<template #right>
|
||||
<a-button @click="handleLogout">退出登录</a-button>
|
||||
</template>
|
||||
</Header>
|
||||
<Header title="过站工控机" showLogout />
|
||||
|
||||
<div class="menu-grid">
|
||||
<a-card class="menu-card" shadow="hover" @click="handleJumpTo('/pwoManage')">
|
||||
<a-card class="menu-card" shadow="hover" @click="handleJumpTo('PwoManage')">
|
||||
<div class="icon-wrap">
|
||||
<i-lucide-building />
|
||||
</div>
|
||||
@@ -16,8 +12,8 @@
|
||||
<div class="desc">管理生产工单和进度</div>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<a-card class="menu-card" shadow="hover" @click="handleJumpTo('/stationControl')">
|
||||
<!--
|
||||
<a-card class="menu-card" shadow="hover" @click="handleJumpTo('stationControl')">
|
||||
<div class="icon-wrap">
|
||||
<i-lucide-monitor />
|
||||
</div>
|
||||
@@ -27,7 +23,7 @@
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<a-card class="menu-card" shadow="hover" @click="handleJumpTo('/dispatch')">
|
||||
<a-card class="menu-card" shadow="hover" @click="handleJumpTo('dispatch')">
|
||||
<div class="icon-wrap">
|
||||
<i-lucide-package />
|
||||
</div>
|
||||
@@ -37,7 +33,7 @@
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<a-card class="menu-card" shadow="hover" @click="handleJumpTo('/hold')">
|
||||
<a-card class="menu-card" shadow="hover" @click="handleJumpTo('hold')">
|
||||
<div class="icon-wrap">
|
||||
<i-lucide-server />
|
||||
</div>
|
||||
@@ -47,7 +43,7 @@
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<a-card class="menu-card" shadow="hover" @click="handleJumpTo('/dataAnalysis')">
|
||||
<a-card class="menu-card" shadow="hover" @click="handleJumpTo('dataAnalysis')">
|
||||
<div class="icon-wrap">
|
||||
<i-lucide-bar-chart-3 />
|
||||
</div>
|
||||
@@ -57,7 +53,7 @@
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<a-card class="menu-card" shadow="hover" @click="handleJumpTo('/maintenance')">
|
||||
<a-card class="menu-card" shadow="hover" @click="handleJumpTo('maintenance')">
|
||||
<div class="icon-wrap">
|
||||
<i-lucide-wrench />
|
||||
</div>
|
||||
@@ -67,7 +63,7 @@
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<a-card class="menu-card" shadow="hover" @click="handleJumpTo('/personnel')">
|
||||
<a-card class="menu-card" shadow="hover" @click="handleJumpTo('personnel')">
|
||||
<div class="icon-wrap">
|
||||
<i-lucide-users />
|
||||
</div>
|
||||
@@ -77,7 +73,7 @@
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<a-card class="menu-card" shadow="hover" @click="handleJumpTo('/settings')">
|
||||
<a-card class="menu-card" shadow="hover" @click="handleJumpTo('settings')">
|
||||
<div class="icon-wrap">
|
||||
<i-lucide-settings />
|
||||
</div>
|
||||
@@ -85,7 +81,7 @@
|
||||
<div class="title">系统设置</div>
|
||||
<div class="desc">系统参数配置</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-card> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -113,20 +109,17 @@ const handleLogout = () => {
|
||||
okText: '确定',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
message.success('已退出');
|
||||
authStore.logout();
|
||||
},
|
||||
onCancel: () => {
|
||||
message.error('操作失败');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleJumpTo = (path) => {
|
||||
const handleJumpTo = (name) => {
|
||||
if (!loggedIn.value) {
|
||||
message.warning("尚未登录,请先登录");
|
||||
return;
|
||||
}
|
||||
router.push({ path });
|
||||
router.push({ name });
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -136,6 +129,7 @@ const handleJumpTo = (path) => {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.menu-grid {
|
||||
|
||||
@@ -6,6 +6,8 @@ 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 } from '@/utils/auth';
|
||||
import { decrypt } from '@/utils/jsencrypt';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const { loginLoading } = storeToRefs(authStore);
|
||||
@@ -15,13 +17,14 @@ const formData = reactive<LoginInfo>({
|
||||
username: '',
|
||||
password: '',
|
||||
uuid: '',
|
||||
code: ''
|
||||
code: '',
|
||||
})
|
||||
|
||||
// 验证码图片
|
||||
const captchaImg = ref('')
|
||||
const captchaLoading = ref(false)
|
||||
const loadCaptchaFail = ref(false)
|
||||
const rememberMe = ref(false)
|
||||
|
||||
// 表单验证规则
|
||||
const rules: Record<string, Rule[]> = {
|
||||
@@ -61,16 +64,27 @@ const refreshCaptcha = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
const initLoginForm = () => {
|
||||
const { username, password, rememberMe: isRememberMe } = getAccount();
|
||||
if (isRememberMe && isRememberMe === 'true') {
|
||||
rememberMe.value = true;
|
||||
formData.username = username || '';
|
||||
formData.password = decrypt(password || '') || '';
|
||||
}
|
||||
}
|
||||
|
||||
// 登录处理
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
await authStore.authLogin(formData);
|
||||
await authStore.authLogin(formData, rememberMe.value);
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '登录失败');
|
||||
message.error(error.message || error.msg || '登录失败');
|
||||
await refreshCaptcha();
|
||||
}
|
||||
}
|
||||
|
||||
initLoginForm()
|
||||
// 组件挂载时获取验证码
|
||||
refreshCaptcha()
|
||||
</script>
|
||||
@@ -104,6 +118,7 @@ refreshCaptcha()
|
||||
placeholder="请输入用户名"
|
||||
size="large"
|
||||
class="login-input"
|
||||
allow-clear
|
||||
>
|
||||
<template #prefix>
|
||||
<i-lucide-user class="input-icon" />
|
||||
@@ -118,6 +133,8 @@ refreshCaptcha()
|
||||
placeholder="请输入密码"
|
||||
size="large"
|
||||
class="login-input"
|
||||
:visibility-toggle="false"
|
||||
allow-clear
|
||||
>
|
||||
<template #prefix>
|
||||
<i-lucide-lock class="input-icon" />
|
||||
@@ -133,6 +150,7 @@ refreshCaptcha()
|
||||
placeholder="请输入验证码"
|
||||
size="large"
|
||||
class="login-input captcha-input"
|
||||
allow-clear
|
||||
>
|
||||
<template #prefix>
|
||||
<i-lucide-shield-check class="input-icon" />
|
||||
@@ -159,6 +177,10 @@ refreshCaptcha()
|
||||
</div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item name="rememberMe" class="form-item">
|
||||
<a-checkbox v-model:checked="rememberMe" style="color: #ddd;">记住密码</a-checkbox>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 登录按钮 -->
|
||||
<a-form-item class="form-item">
|
||||
<a-button
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { message } from 'ant-design-vue';
|
||||
import type { ColumnsType } from 'ant-design-vue/es/table/interface';
|
||||
import { listStation } from '@/api/station';
|
||||
import { usePwoStore } from '@/store';
|
||||
|
||||
interface TableItem {
|
||||
key: string;
|
||||
index: number;
|
||||
operationTitle: string;
|
||||
planQty: number;
|
||||
okQty: number;
|
||||
ngQty: number;
|
||||
status: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const route = useRoute();
|
||||
const pwoStore = usePwoStore();
|
||||
const tableData = ref<TableItem[]>([]);
|
||||
|
||||
const columns = [
|
||||
{ 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', width: 120 },
|
||||
];
|
||||
|
||||
const loadingStations = ref(false);
|
||||
const fetchStations = async (traceOrderCode: string) => {
|
||||
try {
|
||||
loadingStations.value = true;
|
||||
const res = await listStation({ traceOrderCode });
|
||||
tableData.value = (res.rows || []).map((item: any, idx: number) => {
|
||||
return {
|
||||
key: String(item.id ?? idx),
|
||||
index: item.seqNo ?? idx + 1,
|
||||
...item,
|
||||
} as TableItem;
|
||||
});
|
||||
} catch (error: any) {
|
||||
message.error(error?.msg || error?.message || '查询站点失败');
|
||||
} finally {
|
||||
loadingStations.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (record: TableItem) => {
|
||||
message.success(`提交了序号: ${record.index}`);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
const traceOrderCode = route.query.traceOrderCode as string;
|
||||
if (traceOrderCode) {
|
||||
fetchStations(traceOrderCode);
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => route.query.t,
|
||||
(val) => {
|
||||
if (val) {
|
||||
fetchStations(route.query.traceOrderCode as string);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function rowClick(record: TableItem) {
|
||||
return {
|
||||
onClick: () => {
|
||||
pwoStore.setCurrentJob(record);
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="table-wrapper">
|
||||
<a-table
|
||||
:dataSource="tableData"
|
||||
:columns="columns as ColumnsType<TableItem>"
|
||||
:pagination="false"
|
||||
:loading="loadingStations"
|
||||
bordered
|
||||
sticky
|
||||
size="middle"
|
||||
rowKey="key"
|
||||
class="custom-table"
|
||||
:customRow="rowClick"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-button type="primary" @click="handleSubmit(record as TableItem)">进入</a-button>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.table-wrapper {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
|
||||
.custom-table {
|
||||
:deep(.ant-table-thead > tr > th) {
|
||||
background-color: #e6f7ff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr > td) {
|
||||
padding: 12px 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
184
src/views/pwoManage/infeed/fixture/index.vue
Normal file
184
src/views/pwoManage/infeed/fixture/index.vue
Normal file
@@ -0,0 +1,184 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import type { ColumnsType } from 'ant-design-vue/es/table/interface';
|
||||
import { listMaskCombination, listCombinationAssignMask } from "@/api/pwoManage/fixtureCombination";
|
||||
|
||||
interface FixtureTableItem {
|
||||
key: string;
|
||||
operationTitle: string;
|
||||
operationCode: string;
|
||||
maskName: string;
|
||||
maskCode: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface FixtureCombinationItem {
|
||||
key: string;
|
||||
combinationCode: string;
|
||||
combinationName: string;
|
||||
combinationStatus: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const fixtureColumns = [
|
||||
{ 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' },
|
||||
];
|
||||
|
||||
// 治具表格
|
||||
const fixtureTableData = ref<FixtureTableItem[]>([]);
|
||||
|
||||
const fixtureInput = ref<string>('');
|
||||
|
||||
const openFixtureModal = ref(false)
|
||||
const fixtureCombinationColumns = [
|
||||
{ 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 fixtureCombinationTableData = ref<FixtureCombinationItem[]>([])
|
||||
const fetchCombinationList = async () => {
|
||||
try {
|
||||
const { rows, total } = await listMaskCombination({})
|
||||
if (total as number <= 0) throw new Error('未查询到组合列表');
|
||||
fixtureCombinationTableData.value = rows;
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '获取工单信息失败');
|
||||
}
|
||||
}
|
||||
|
||||
const existingCombination = ref(new Set())
|
||||
const handleBind = async (id: number) => {
|
||||
const { rows } = await listCombinationAssignMask(id, {
|
||||
maskStatus: "AVAILABLE",
|
||||
orderByColumn: 'id',
|
||||
orderBy: 'desc',
|
||||
})
|
||||
|
||||
const newCombination = rows.filter(item => !existingCombination.value.has(item.id))
|
||||
newCombination.forEach(item => {
|
||||
fixtureTableData.value.push({
|
||||
key: item.id,
|
||||
operationCode: item.operationCode,
|
||||
operationTitle: item.operationTitle,
|
||||
maskCode: item.maskCode,
|
||||
maskName: item.maskName
|
||||
})
|
||||
existingCombination.value.add(item.id)
|
||||
})
|
||||
|
||||
message.success('绑定成功')
|
||||
openFixtureModal.value = false
|
||||
}
|
||||
|
||||
// 计算表格高度
|
||||
const customTable = ref<HTMLElement | null>(null)
|
||||
const tableHeight = ref(200)
|
||||
const renderTableHeight = () => {
|
||||
if (customTable.value) {
|
||||
tableHeight.value = customTable.value.clientHeight - 50
|
||||
console.log('元素高度:', tableHeight.value)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
renderTableHeight()
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
renderTableHeight
|
||||
});
|
||||
|
||||
fetchCombinationList()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="fixture__container">
|
||||
<a-row class="main-content" :gutter="24">
|
||||
<a-col :span="14" class="fixture-item__container">
|
||||
<div class="main-title">治具组合信息</div>
|
||||
<a-button size="large" @click="() => openFixtureModal = true">绑定治具组合</a-button>
|
||||
<div class="table-wrapper" ref="customTable">
|
||||
<a-table :dataSource="fixtureTableData" :columns="fixtureColumns as ColumnsType<FixtureTableItem>"
|
||||
:pagination="false" bordered sticky size="middle" :scroll="{ y: tableHeight }">
|
||||
<template #bodyCell="{ column, index }">
|
||||
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="10" class="fixture-item__container">
|
||||
<div class="main-title">治具校验</div>
|
||||
<a-row>
|
||||
<a-col :span="20">
|
||||
<a-input size="large" v-model:value="fixtureInput" @pressEnter="" placeholder="按下回车校验" />
|
||||
</a-col>
|
||||
<a-col :span="3" :offset="1">
|
||||
<a-button size="large" @click="">校验</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div class="table-wrapper">
|
||||
<a-descriptions bordered :column="1">
|
||||
<a-descriptions-item label="治具编码">Cloud Database</a-descriptions-item>
|
||||
<a-descriptions-item label="治具名称">Prepaid</a-descriptions-item>
|
||||
<a-descriptions-item label="治具组合">YES</a-descriptions-item>
|
||||
<a-descriptions-item label="标准使用次数">YES</a-descriptions-item>
|
||||
<a-descriptions-item label="当前使用次数">YES</a-descriptions-item>
|
||||
<a-descriptions-item label="关联工序">YES</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-modal v-model:open="openFixtureModal" title="绑定治具组合" width="50vw" :bodyStyle="{ padding: '20px 0' }">
|
||||
<a-table :dataSource="fixtureCombinationTableData" :columns="fixtureCombinationColumns as ColumnsType<FixtureCombinationItem>"
|
||||
: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'">
|
||||
<a-button @click="handleBind(record.id)">绑定</a-button>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.fixture__container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
|
||||
.fixture-item__container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
|
||||
.main-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
border-bottom: 1px solid #c9c9c9;
|
||||
padding: 0 0 8px;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
136
src/views/pwoManage/infeed/layout.vue
Normal file
136
src/views/pwoManage/infeed/layout.vue
Normal file
@@ -0,0 +1,136 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const menuItems = [
|
||||
{ label: '主材', key: 'PrimaryMaterial', progress: 30 },
|
||||
{ label: '材料', key: 'RawMaterial', progress: 50 },
|
||||
{ label: '治具', key: 'Fixture', progress: 80 },
|
||||
];
|
||||
|
||||
const activeKey = computed(() => route.name as string);
|
||||
|
||||
const handleMenuClick = (key: string) => {
|
||||
router.push({ name: key });
|
||||
};
|
||||
|
||||
const infeedChildRef = ref<any>(null);
|
||||
|
||||
/** 父组件对外暴露的方法 */
|
||||
function renderTableHeight() {
|
||||
infeedChildRef.value?.renderTableHeight();
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
renderTableHeight
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-row :gutter="24" class="infeed-layout">
|
||||
<a-col :span="4">
|
||||
<div class="menu-list">
|
||||
<span class="infeed-title">进站</span>
|
||||
<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"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<a-button type="primary" size="large">确认进站</a-button>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="20" class="content-wrapper">
|
||||
<router-view v-slot="{ Component }">
|
||||
<component :is="Component" ref="infeedChildRef" />
|
||||
</router-view>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.infeed-layout {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.menu-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.infeed-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
border-bottom: 1px solid #c9c9c9;
|
||||
padding: 0 0 8px;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
flex: 1;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 1vh 1vw;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
border: 1px solid #f0f0f0;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.02);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&.active {
|
||||
background: #e6f7ff;
|
||||
border-color: #1890ff;
|
||||
|
||||
.menu-title {
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: #47a5fd;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.menu-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.menu-progress {
|
||||
margin-bottom: 0 !important;
|
||||
|
||||
:deep(.ant-progress-bg) {
|
||||
transition: all 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
119
src/views/pwoManage/infeed/primaryMaterial/index.vue
Normal file
119
src/views/pwoManage/infeed/primaryMaterial/index.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import type { ColumnsType } from 'ant-design-vue/es/table/interface';
|
||||
|
||||
interface MaterialTableItem {
|
||||
key: string;
|
||||
carrierCode: string;
|
||||
qrCode: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const materialColumns = [
|
||||
{ title: '序号', dataIndex: 'index', key: 'index', align: 'center', width: 80 },
|
||||
{ title: '载具 ID', dataIndex: 'carrierCode', key: 'carrierCode', align: 'center' },
|
||||
{ title: '二维码', dataIndex: 'qrCode', key: 'qrCode', align: 'center' },
|
||||
{ title: '操作', key: 'action', align: 'center', width: 120 },
|
||||
]
|
||||
|
||||
const carrierInput = ref<string>('');
|
||||
// 录入载具
|
||||
const insertCarrier = () => {
|
||||
if (!carrierInput.value) return;
|
||||
materialTableData.value.push({
|
||||
key: String(Date.now()),
|
||||
carrierCode: String(carrierInput.value),
|
||||
qrCode: String(materialInput.value),
|
||||
})
|
||||
carrierInput.value = '';
|
||||
}
|
||||
|
||||
// 主物料表格
|
||||
const materialTableData = ref<MaterialTableItem[]>([]);
|
||||
const materialInput = ref<string>('');
|
||||
// 录入主物料
|
||||
const insertMaterial = () => {
|
||||
if (!materialInput.value) return;
|
||||
materialTableData.value.push({
|
||||
key: String(Date.now()),
|
||||
carrierCode: String(carrierInput.value),
|
||||
qrCode: String(materialInput.value),
|
||||
})
|
||||
materialInput.value = '';
|
||||
}
|
||||
// 移除主物料
|
||||
const handleRemoveMaterial = (index: number) => {
|
||||
console.log(index)
|
||||
materialTableData.value.splice(index, 1)
|
||||
}
|
||||
|
||||
// 计算表格高度
|
||||
const customTable = ref<HTMLElement | null>(null)
|
||||
const tableHeight = ref(200)
|
||||
const renderTableHeight = () => {
|
||||
if (customTable.value) {
|
||||
tableHeight.value = customTable.value.clientHeight - 50
|
||||
console.log('元素高度:', tableHeight.value)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
renderTableHeight()
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
renderTableHeight
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="primary-material__container">
|
||||
<div class="main-title">主物料进站</div>
|
||||
<a-form layout="inline" :model="{}" size="large">
|
||||
<a-form-item label="载具 ID">
|
||||
<a-input v-model:value="carrierInput" @pressEnter="insertCarrier" placeholder="按下回车录入" />
|
||||
</a-form-item>
|
||||
<a-form-item label="物料编码">
|
||||
<a-input v-model:value="materialInput" @pressEnter="insertMaterial" placeholder="按下回车录入" />
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @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 }">
|
||||
<template #bodyCell="{ column, index }">
|
||||
<template v-if="column.key === 'index'">{{ index + 1 }}</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-button type="text" danger @click="handleRemoveMaterial(index)">删除</a-button>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.primary-material__container {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
|
||||
.main-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
border-bottom: 1px solid #c9c9c9;
|
||||
padding: 0 0 8px;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
103
src/views/pwoManage/infeed/rawMaterial/index.vue
Normal file
103
src/views/pwoManage/infeed/rawMaterial/index.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<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';
|
||||
|
||||
interface MaterialTableItem {
|
||||
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: 'num', key: 'num', align: 'center' },
|
||||
{ title: '单位', dataIndex: 'unit', key: 'unit', align: 'center' },
|
||||
{ title: '操作', key: 'action', align: 'center' },
|
||||
]
|
||||
|
||||
// 材料表格
|
||||
const materialTableData = ref<MaterialTableItem[]>([
|
||||
{ key: '1', materialCode: '123', materialName: '物料1', num: 10, unit: '片' },
|
||||
{ key: '2', materialCode: '123', materialName: '物料1', num: 10, unit: '片' },
|
||||
{ key: '3', materialCode: '123', materialName: '物料1', num: 10, unit: '片' },
|
||||
{ key: '4', materialCode: '123', materialName: '物料1', num: 10, unit: '片' },
|
||||
{ key: '5', materialCode: '123', materialName: '物料1', num: 10, unit: '片' },
|
||||
{ key: '6', materialCode: '123', materialName: '物料1', num: 10, unit: '片' },
|
||||
{ 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: '片' },
|
||||
]);
|
||||
|
||||
// 确认材料
|
||||
const handleSubmitMaterial = (index: number) => {
|
||||
message.success(`${ index + 1 } 号进站`)
|
||||
}
|
||||
|
||||
// 计算表格高度
|
||||
const customTable = ref<HTMLElement | null>(null)
|
||||
const tableHeight = ref(200)
|
||||
const renderTableHeight = () => {
|
||||
if (customTable.value) {
|
||||
tableHeight.value = customTable.value.clientHeight - 60
|
||||
console.log('元素高度:', tableHeight.value)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
renderTableHeight()
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
renderTableHeight
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="raw-material__container">
|
||||
<div class="main-title">材料确认</div>
|
||||
<div class="table-wrapper" ref="customTable">
|
||||
<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'">
|
||||
<a-button @click="handleSubmitMaterial(index)">确认</a-button>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.raw-material__container {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
|
||||
.main-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
border-bottom: 1px solid #c9c9c9;
|
||||
padding: 0 0 8px;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
376
src/views/pwoManage/layout.vue
Normal file
376
src/views/pwoManage/layout.vue
Normal file
@@ -0,0 +1,376 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, nextTick } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import Header from '@/components/Header/index.vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { useAuthStore, usePwoStore } from '@/store';
|
||||
import type { WorkOrderInfo } from './model';
|
||||
import { listLotTraceOrder } from '@/api/pwoManage';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const pwoStore = usePwoStore();
|
||||
|
||||
const workOrderInfo = reactive<WorkOrderInfo>({
|
||||
code: '',
|
||||
batchNo: '',
|
||||
status: '',
|
||||
tarMaterialName: '',
|
||||
tarMaterialCode: '',
|
||||
planQty: undefined,
|
||||
});
|
||||
|
||||
const processInfo: any = storeToRefs(pwoStore).currentJob;
|
||||
|
||||
const loadingPwoInfo = ref(false);
|
||||
const fetchLotTraceOrder = async () => {
|
||||
if (!workOrderInfo.code) return;
|
||||
try {
|
||||
loadingPwoInfo.value = true;
|
||||
const res = await listLotTraceOrder({
|
||||
code: workOrderInfo.code,
|
||||
});
|
||||
if (res.total as number <= 0) {
|
||||
throw new Error('未查询到工单信息,请检查工单编码')
|
||||
};
|
||||
workOrderInfo.status = res.rows[0].status;
|
||||
workOrderInfo.batchNo = res.rows[0].batchNo;
|
||||
workOrderInfo.tarMaterialName = res.rows[0].tarMaterialName;
|
||||
workOrderInfo.tarMaterialCode = res.rows[0].tarMaterialCode;
|
||||
workOrderInfo.planQty = res.rows[0].planQty;
|
||||
router.replace({ query: { ...route.query, traceOrderCode: workOrderInfo.code, t: Date.now() } });
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '获取工单信息失败');
|
||||
} finally {
|
||||
loadingPwoInfo.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 孙组件
|
||||
const infeedRef = ref<any>(null);
|
||||
const collapsed = ref(false);
|
||||
const toggleCollapse = async () => {
|
||||
collapsed.value = !collapsed.value;
|
||||
await nextTick();
|
||||
infeedRef.value?.renderTableHeight();
|
||||
}
|
||||
const handlePwoManage = () => {
|
||||
router.push({ name: 'PwoManage' })
|
||||
}
|
||||
|
||||
const handleWaferAppearance = () => {
|
||||
message.info('点击了 Wafer');
|
||||
};
|
||||
|
||||
const handleDieAppearance = () => {
|
||||
message.info('点击了 Die');
|
||||
};
|
||||
|
||||
const openHoldModal = ref(false);
|
||||
const holdForm = reactive({
|
||||
planCompleteDate: '',
|
||||
});
|
||||
const handleHold = () => {
|
||||
openHoldModal.value = true;
|
||||
};
|
||||
const handleCloseHold = () => {
|
||||
holdForm.planCompleteDate = '';
|
||||
openHoldModal.value = false;
|
||||
};
|
||||
const handleSubmitHold = () => {
|
||||
message.success('Hold 住!')
|
||||
holdForm.planCompleteDate = '';
|
||||
openHoldModal.value = false;
|
||||
};
|
||||
|
||||
const handleInfeed = () => {
|
||||
router.push({ name: 'Infeed' });
|
||||
};
|
||||
|
||||
const handleOutfeed = () => {
|
||||
router.push({ name: 'Outfeed' });
|
||||
};
|
||||
|
||||
const handleCopy = async (text: string | number | undefined) => {
|
||||
if (!text) return;
|
||||
await navigator.clipboard.writeText(String(text));
|
||||
message.success(`已复制: ${ text }`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<Header title="过站工控机" showHome showBack showLogout>
|
||||
<template #right-opts>
|
||||
<a-button @click="toggleCollapse">{{ collapsed ? '展开' : '折叠' }}</a-button>
|
||||
</template>
|
||||
</Header>
|
||||
|
||||
<div class="content-wrapper">
|
||||
<!-- Top Section -->
|
||||
<div class="top-section">
|
||||
<a-row :gutter="16" class="full-height-row">
|
||||
<!-- Work Order Info -->
|
||||
<a-col :span="13">
|
||||
<a-spin :spinning="loadingPwoInfo">
|
||||
<a-card title="工单信息" class="info-card" :bordered="false">
|
||||
<a-form :model="workOrderInfo" :colon="false" v-show="!collapsed">
|
||||
<a-row :gutter="36">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="工单批次">
|
||||
<a-input v-model:value="workOrderInfo.batchNo" readonly>
|
||||
<template #suffix>
|
||||
<a-button @click="handleCopy(workOrderInfo.batchNo)">
|
||||
<template #icon><i-lucide-copy /></template>
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="工单状态">
|
||||
<a-input v-model:value="workOrderInfo.status" readonly>
|
||||
<template #suffix>
|
||||
<a-button @click="handleCopy(workOrderInfo.status)">
|
||||
<template #icon><i-lucide-copy /></template>
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="总计数量">
|
||||
<a-input v-model:value="workOrderInfo.planQty" readonly>
|
||||
<template #suffix>
|
||||
<a-button @click="handleCopy(workOrderInfo.planQty)">
|
||||
<template #icon><i-lucide-copy /></template>
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="产品编码">
|
||||
<a-input v-model:value="workOrderInfo.tarMaterialCode" readonly>
|
||||
<template #suffix>
|
||||
<a-button @click="handleCopy(workOrderInfo.tarMaterialCode)">
|
||||
<template #icon><i-lucide-copy /></template>
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="产品名称">
|
||||
<a-input v-model:value="workOrderInfo.tarMaterialName" readonly>
|
||||
<template #suffix>
|
||||
<a-button @click="handleCopy(workOrderInfo.tarMaterialName)">
|
||||
<template #icon><i-lucide-copy /></template>
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="产品规格">
|
||||
<a-input v-model:value="workOrderInfo.planQty" readonly>
|
||||
<template #suffix>
|
||||
<a-button @click="handleCopy(workOrderInfo.planQty)">
|
||||
<template #icon><i-lucide-copy /></template>
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
<template #extra>
|
||||
<a-input-search v-model:value="workOrderInfo.code" @search="fetchLotTraceOrder" placeholder="输入工单编码" enter-button />
|
||||
</template>
|
||||
</a-card>
|
||||
</a-spin>
|
||||
</a-col>
|
||||
|
||||
<!-- Process Info -->
|
||||
<a-col :span="8">
|
||||
<a-card title="工序信息" class="info-card" :bordered="false">
|
||||
<a-form :model="processInfo" :colon="false" :label-col="{ span: 4 }" :wrapper-col="{ span: 20, offset: 2 }"
|
||||
v-show="!collapsed">
|
||||
<a-form-item label="工序名称">
|
||||
<a-input v-model:value="processInfo.operationTitle" readonly>
|
||||
<template #suffix>
|
||||
<a-tag color="#f50" v-if="processInfo.status">{{ processInfo.status }}</a-tag>
|
||||
<a-button @click="handleCopy(processInfo.operationTitle)">
|
||||
<template #icon><i-lucide-copy /></template>
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="作业编码">
|
||||
<a-input v-model:value="processInfo.code" readonly>
|
||||
<template #suffix>
|
||||
<a-button @click="handleCopy(processInfo.code)">
|
||||
<template #icon><i-lucide-copy /></template>
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="设备名称">
|
||||
<a-input v-model:value="processInfo.tarMaterialName" readonly>
|
||||
<template #suffix>
|
||||
<a-button @click="handleCopy(processInfo.tarMaterialName)">
|
||||
<template #icon><i-lucide-copy /></template>
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<a-col :span="3" class="action-buttons-col">
|
||||
<div class="action-buttons" v-show="!collapsed">
|
||||
<a-button class="action-btn green-btn" @click="handlePwoManage">工单管理</a-button>
|
||||
<a-button class="action-btn orange-btn" @click="handleWaferAppearance">Wafer 清单</a-button>
|
||||
<a-button class="action-btn blue-btn" @click="handleDieAppearance">Die 清单</a-button>
|
||||
<a-space>
|
||||
<a-button class="action-btn red-btn" @click="handleHold">Hold</a-button>
|
||||
<a-button class="action-btn orange-btn" @click="handleInfeed">进站</a-button>
|
||||
<a-button class="action-btn blue-btn" @click="handleOutfeed">出站</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
<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="handlePwoManage">工单管理</a-menu-item>
|
||||
<a-menu-item key="2" @click="handleWaferAppearance">Wafer 清单</a-menu-item>
|
||||
<a-menu-item key="3" @click="handleDieAppearance">Die 清单</a-menu-item>
|
||||
<a-menu-item key="4" @click="handleHold">Hold</a-menu-item>
|
||||
<a-menu-item key="5" @click="handleInfeed">进站</a-menu-item>
|
||||
<a-menu-item key="6" @click="handleOutfeed">出站</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button>
|
||||
更多
|
||||
<i-lucide-chevron-down />
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Section -->
|
||||
<main class="bottom-section">
|
||||
<router-view v-slot="{ Component }">
|
||||
<component :is="Component" ref="infeedRef" />
|
||||
</router-view>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Hold Modal -->
|
||||
<a-modal
|
||||
v-model:open="openHoldModal"
|
||||
title="Hold 操作"
|
||||
@cancel="handleCloseHold"
|
||||
@ok="handleSubmitHold"
|
||||
>
|
||||
<a-form :model="holdForm" :colon="false" :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
|
||||
<a-form-item label="工单编码">
|
||||
<a-input v-model:value="workOrderInfo.code" readonly />
|
||||
</a-form-item>
|
||||
<a-form-item label="目标产品编码">
|
||||
<a-input v-model:value="workOrderInfo.tarMaterialCode" readonly />
|
||||
</a-form-item>
|
||||
<a-form-item label="目标产品名称">
|
||||
<a-input v-model:value="workOrderInfo.tarMaterialName" readonly />
|
||||
</a-form-item>
|
||||
<a-form-item label="工序名称">
|
||||
<a-input v-model:value="processInfo.operationTitle" readonly />
|
||||
</a-form-item>
|
||||
<a-form-item label="产品规格">
|
||||
<a-input v-model:value="processInfo.code" readonly />
|
||||
</a-form-item>
|
||||
<a-form-item label="计划完成日期">
|
||||
<a-input v-model:value="holdForm.planCompleteDate" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
height: 100vh;
|
||||
background-color: #f0f2f5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 16px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.top-section {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
:deep(.ant-spin-nested-loading),
|
||||
:deep(.ant-spin-container) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
height: 100%;
|
||||
border-radius: 8px;
|
||||
|
||||
:deep(.ant-card-head) {
|
||||
min-height: 48px;
|
||||
padding: 0 16px;
|
||||
border-bottom: none;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
:deep(.ant-card-body) {
|
||||
padding: 1px 16px;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
height: 100%;
|
||||
|
||||
.action-btn {
|
||||
height: 48px;
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-section {
|
||||
background: #fff;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
16
src/views/pwoManage/model.d.ts
vendored
Normal file
16
src/views/pwoManage/model.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
export interface WorkOrderInfo {
|
||||
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;
|
||||
}
|
||||
9
src/views/pwoManage/outfeed/index.vue
Normal file
9
src/views/pwoManage/outfeed/index.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
outfeed
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
Reference in New Issue
Block a user