Compare commits

..

4 Commits

Author SHA1 Message Date
tao
f724b40e24 增加出站功能 2025-12-30 15:51:25 +08:00
tao
7b36211f04 添加 Title 组件 2025-12-30 15:51:13 +08:00
tao
1b4c9bcc8d 增加站点完工接口
增加库位查询接口
2025-12-30 15:46:22 +08:00
tao
bea5487180 修复用户登录 bug 2025-12-30 15:42:56 +08:00
15 changed files with 198 additions and 506 deletions

15
components.d.ts vendored
View File

@@ -10,7 +10,6 @@ declare module 'vue' {
export interface GlobalComponents {
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']
@@ -21,7 +20,6 @@ declare module 'vue' {
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']
AMenu: typeof import('ant-design-vue/es')['Menu']
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
AModal: typeof import('ant-design-vue/es')['Modal']
@@ -35,20 +33,19 @@ declare module 'vue' {
ATable: typeof import('ant-design-vue/es')['Table']
ATableSummaryCell: typeof import('ant-design-vue/es')['TableSummaryCell']
ATableSummaryRow: typeof import('ant-design-vue/es')['TableSummaryRow']
ATag: typeof import('ant-design-vue/es')['Tag']
ATypographyText: typeof import('ant-design-vue/es')['TypographyText']
Header: typeof import('./src/components/Header/index.vue')['default']
DictTag: typeof import('./src/components/common/DictTag/index.vue')['default']
Header: typeof import('./src/components/common/Header/index.vue')['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']
ILucideRefreshCw: typeof import('~icons/lucide/refresh-cw')['default']
ILucideRotateCcw: typeof import('~icons/lucide/rotate-ccw')['default']
ILucideRotateCw: typeof import('~icons/lucide/rotate-cw')['default']
ILucideShieldCheck: typeof import('~icons/lucide/shield-check')['default']
ILucideUser: typeof import('~icons/lucide/user')['default']
Lucide: typeof import('./src/components/common/Lucide/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Subtitle: typeof import('./src/components/common/Subtitle/index.vue')['default']
Title: typeof import('./src/components/common/Title/index.vue')['default']
}
}

View File

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

View File

@@ -61,3 +61,12 @@ export function startStation(id: ID) {
method: "put",
});
}
// 站点完工
export function completeStation(id: ID, location: any) {
return request({
url: '/mes/station/' + id + '/complete',
method: 'put',
data: location
})
}

View File

@@ -1,394 +0,0 @@
<template>
<div class="bottom-actions">
<div class="pagination-container">
<div class="pagination-control left" v-if="showPagination">
<div class="pagination-btn-wrapper">
<a-button :disabled="currentPage === 1" @click="goToPrevPage" class="pagination-btn prev-btn">
<template #icon>
<LeftOutlined />
</template>
</a-button>
<div class="page-tooltip" v-if="currentPage > 1">
{{ currentPage - 1 }} / {{ totalPages }}
</div>
</div>
</div>
<div class="buttons-container">
<a-row :gutter="16" justify="center">
<a-col v-for="button in currentPageButtons" :key="button.label">
<a-button :type="button.type" size="large" @click="executeButtonAction(button.handler)"
:class="['action-button', getButtonStatusClass(button)]">
<component :is="getButtonStatusIcon(button)" v-if="getButtonStatusIcon(button)"
class="status-icon-btn" />
{{ button.label }}
</a-button>
</a-col>
</a-row>
</div>
<div class="pagination-control right" v-if="showPagination">
<div class="pagination-btn-wrapper">
<a-button :disabled="currentPage === totalPages" @click="goToNextPage" class="pagination-btn next-btn">
<template #icon>
<RightOutlined />
</template>
</a-button>
<div class="page-tooltip" v-if="currentPage < totalPages">
{{ currentPage + 1 }} / {{ totalPages }}
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, nextTick, onMounted, onBeforeUnmount } from 'vue';
import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
type ButtonType = 'primary' | 'default' | 'dashed' | 'text' | 'link';
interface ActionButton {
label: string;
handler: string;
type: ButtonType;
status?: 'running' | 'paused' | 'idle';
}
interface Props {
buttons: ActionButton[];
buttonsPerPage?: number;
}
interface Emits {
(e: 'execute', handlerName: string): void;
}
const props = withDefaults(defineProps<Props>(), {
buttonsPerPage: 6
});
const emit = defineEmits<Emits>();
// 底部按钮分页控制
const currentPage = ref(1);
const buttonsPerPage = ref(props.buttonsPerPage);
// 获取按钮状态样式类
const getButtonStatusClass = (button: ActionButton) => {
const statusClasses = {
running: 'status-running',
paused: 'status-paused',
idle: 'status-idle'
};
return statusClasses[button.status || 'idle'];
};
// 获取按钮状态图标
const getButtonStatusIcon = (button: ActionButton) => {
const statusIcons = {
running: 'i-play-circle',
paused: 'i-pause-circle',
idle: ''
};
return statusIcons[button.status || 'idle'];
};
// 计算当前页显示的按钮
const currentPageButtons = computed(() => {
const start = (currentPage.value - 1) * buttonsPerPage.value;
const end = start + buttonsPerPage.value;
return props.buttons.slice(start, end);
});
// 计算总页数
const totalPages = computed(() => {
return Math.ceil(props.buttons.length / buttonsPerPage.value);
});
// 是否显示分页控制
const showPagination = computed(() => {
return totalPages.value > 1;
});
// 分页控制函数
const goToPrevPage = () => {
if (currentPage.value > 1) {
currentPage.value--;
}
};
const goToNextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++;
}
};
// 执行按钮操作
const executeButtonAction = (handlerName: string) => {
emit('execute', handlerName);
};
// 响应式调整按钮数量
const updateButtonsPerPage = () => {
nextTick(() => {
const buttonsContainer = document.querySelector('.buttons-container') as HTMLElement;
if (!buttonsContainer) {
buttonsPerPage.value = props.buttonsPerPage; // 默认值
return;
}
const containerWidth = buttonsContainer.offsetWidth;
const buttonWidth = 100; // 单个操作按钮最小宽度
const buttonGap = 16; // 按钮间距
// 计算可以放置的按钮数量
const maxButtons = Math.floor((containerWidth + buttonGap) / (buttonWidth + buttonGap));
// 设置最小和最大按钮数量
buttonsPerPage.value = Math.max(3, maxButtons);
});
};
// 初始化
onMounted(() => {
// 初始化按钮数量
updateButtonsPerPage();
// 监听窗口大小变化
window.addEventListener('resize', updateButtonsPerPage);
});
// 清理事件监听
onBeforeUnmount(() => {
window.removeEventListener('resize', updateButtonsPerPage);
});
</script>
<style scoped lang="scss">
// 变量定义
$primary-color: #4a90e2;
$primary-light: #5ba0f2;
$success-color: #52c41a;
$success-light: #73d13d;
$success-bg: rgba(82, 196, 26, 0.1);
$success-bg-hover: rgba(82, 196, 26, 0.2);
$warning-color: #faad14;
$warning-light: #ffc53d;
$warning-bg: rgba(250, 173, 20, 0.1);
$warning-bg-hover: rgba(250, 173, 20, 0.2);
$error-color: #ff4d4f;
$error-light: #ff7875;
$bg-dark: rgba(0, 0, 0, 0.4);
$bg-overlay: rgba(255, 255, 255, 0.1);
$bg-overlay-hover: rgba(255, 255, 255, 0.2);
$text-light: #cccccc;
$text-success: #52c41a;
$text-warning: #faad14;
$white: #ffffff;
$spacing-xs: 4px;
$spacing-sm: 8px;
$spacing-md: 12px;
$spacing-lg: 16px;
$border-radius: 6px;
$border-radius-lg: 8px;
$transition: all 0.3s ease;
// 混合器
@mixin flex-center {
display: flex;
align-items: center;
justify-content: center;
}
@mixin button-base {
border-radius: $border-radius;
font-weight: 500;
transition: $transition;
cursor: pointer;
user-select: none;
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
&:active {
transform: translateY(0);
}
}
/* 底部操作按钮区域 */
.bottom-actions {
height: 80px;
background: $bg-dark;
border-top: 2px solid $primary-color;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: $spacing-sm $spacing-lg;
flex-shrink: 0;
gap: 6px;
}
.pagination-container {
@include flex-center;
width: 100%;
gap: $spacing-lg;
}
.pagination-control {
display: flex;
align-items: center;
}
.pagination-btn-wrapper {
position: relative;
display: flex;
align-items: center;
}
.pagination-btn {
width: 35px;
height: 50px;
border-radius: $border-radius-lg;
@include flex-center;
background: rgba(74, 144, 226, 0.1);
border: 1px solid $primary-color;
color: $text-light;
transition: $transition;
&:hover {
background: rgba(74, 144, 226, 0.2);
border-color: $primary-light;
color: $white;
}
&:disabled {
background: $bg-overlay;
border-color: rgba(255, 255, 255, 0.2);
color: rgba(255, 255, 255, 0.3);
transform: none;
}
}
.page-tooltip {
position: absolute;
top: -30px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.8);
color: $white;
padding: $spacing-xs $spacing-sm;
border-radius: $border-radius;
font-size: 12px;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease;
z-index: 1000;
&::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 4px solid transparent;
border-top-color: rgba(0, 0, 0, 0.8);
}
}
.pagination-btn-wrapper:hover .page-tooltip {
opacity: 1;
}
.buttons-container {
flex: 1;
@include flex-center;
}
.action-button {
min-width: 100px;
height: 36px;
font-size: 14px;
@include button-base;
}
/* 按钮状态样式 */
.action-button {
&.status-running {
background: $success-bg !important;
border-color: $success-color !important;
color: $text-success !important;
box-shadow: 0 0 8px rgba(82, 196, 26, 0.3);
&:hover {
background: $success-bg-hover !important;
border-color: $success-light !important;
color: $white !important;
box-shadow: 0 0 12px rgba(82, 196, 26, 0.5);
}
}
&.status-paused {
background: $warning-bg !important;
border-color: $warning-color !important;
color: $text-warning !important;
box-shadow: 0 0 8px rgba(250, 173, 20, 0.3);
&:hover {
background: $warning-bg-hover !important;
border-color: $warning-light !important;
color: $white !important;
box-shadow: 0 0 12px rgba(250, 173, 20, 0.5);
}
}
&.status-idle {
background: $bg-overlay;
border-color: rgba(255, 255, 255, 0.3);
color: $text-light;
&:hover {
background: $bg-overlay-hover;
border-color: rgba(255, 255, 255, 0.5);
color: $white;
}
}
}
.status-icon-btn {
width: 14px;
height: 14px;
margin-right: 6px;
}
/* 响应式设计 */
@media (max-width: 1366px) {
.bottom-actions .ant-btn {
min-width: 90px;
height: 36px;
font-size: 13px;
}
}
@media (max-width: 1024px) {
.bottom-actions {
height: 70px;
.ant-row {
flex-wrap: wrap;
gap: $spacing-sm;
}
.ant-btn {
min-width: 80px;
height: 32px;
font-size: 12px;
}
}
}
</style>

View File

@@ -1,20 +0,0 @@
<script setup lang="ts">
defineOptions({
name: 'Lucide',
})
const props = defineProps({
name: {
type: String,
default: '',
},
})
</script>
<template>
<div>
<component :is="props.name" />
</div>
</template>
<style scoped lang="scss"></style>

View File

@@ -0,0 +1,46 @@
<template>
<div class="title">
<span>{{ name }}</span>
<a-space class="subtitle">
<slot name="extra" />
<a-button v-if="showRefresh" size="small" type="primary" @click="handleRefresh">
<template #icon><i-lucide-rotate-ccw /></template>刷新
</a-button>
</a-space>
</div>
</template>
<script setup lang="ts">
defineProps({
name: {
type: String,
default: '',
},
showRefresh: {
type: Boolean,
default: false,
},
});
const slots = defineSlots<{
'default'(): any;
'extra'(): any;
}>();
const emit = defineEmits(['refresh']);
const handleRefresh = () => {
emit('refresh');
};
</script>
<style scoped lang="scss">
.title {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 20px;
font-weight: 600;
border-bottom: 1px solid #c9c9c9;
padding-bottom: 7px;
}
</style>

View File

@@ -29,14 +29,19 @@ export const useAuthStore = defineStore("auth", () => {
duration: 3,
});
let userInfo = {};
if (rememberMe) {
userStore.setUserInfo({
userInfo = {
...params,
rememberMe
});
};
} else {
userStore.clearUserInfo();
userInfo = {
username: params.username
};
}
userStore.clearUserInfo();
userStore.setUserInfo(userInfo);
const redirect = route.query.redirect || "/";
router.replace(redirect as string);
return res;

View File

@@ -109,18 +109,15 @@ fetchBoundEquipmentList()
<template>
<div class="equipment__container">
<div class="main-title">绑定设备列表</div>
<Title name="绑定设备列表" showRefresh @refresh="fetchBoundEquipmentList" />
<a-row class="main-content" :gutter="24">
<a-col :span="10" class="input__container">
<a-row>
<a-col :span="17">
<a-row :gutter="12">
<a-col :span="21">
<a-input size="large" v-model:value="equipmentInput" @pressEnter="fetchEquipmentInfo" placeholder="按下回车搜索" />
</a-col>
<a-col :span="6" :offset="1">
<a-space>
<a-button type="primary" size="large" @click="handleBind">绑定</a-button>
<a-button size="large" @click="fetchBoundEquipmentList">刷新</a-button>
</a-space>
<a-col :span="3">
<a-button type="primary" size="large" @click="handleBind">绑定</a-button>
</a-col>
</a-row>
<div class="description-wrapper">
@@ -173,13 +170,6 @@ fetchBoundEquipmentList()
}
}
.main-title {
font-size: 20px;
font-weight: 600;
border-bottom: 1px solid #c9c9c9;
padding: 0 0 8px;
}
.table-wrapper {
height: 100%;
overflow: auto;

View File

@@ -136,11 +136,8 @@ fetchBoundMaskList()
<div class="mask__container">
<a-row class="main-content" :gutter="24">
<a-col :span="14" class="mask-item__container">
<div class="main-title">治具组合信息</div>
<a-space>
<a-button size="large" @click="() => openMaskModal = true">绑定治具组合</a-button>
<a-button size="large" @click="fetchBoundMaskList">刷新</a-button>
</a-space>
<Title name="治具组合信息" showRefresh @refresh="fetchBoundMaskList" />
<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 size="middle" :scroll="{ y: tableHeight }" :loading="loadingMask">
@@ -154,7 +151,7 @@ fetchBoundMaskList()
</div>
</a-col>
<a-col :span="10" class="mask-item__container">
<div class="main-title">治具校验</div>
<Title name="治具校验" />
<a-row>
<a-col :span="20">
<a-input size="large" v-model:value="maskInput" @pressEnter="" placeholder="按下回车校验" />
@@ -208,13 +205,6 @@ fetchBoundMaskList()
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;

View File

@@ -3,7 +3,7 @@ import { ref, onMounted } from 'vue';
import { usePwoStore, useJobStore } from '@/store'
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/pwoManage/station/primaryMaterial'
import { listMainMaterialEntryLog, addWaferEntryLogByCarrier, addDieEntryLogByCarrier, addWaferEntryLog, addDieEntryLog, delMainMaterialEntryLog } from '@/api/pwoManage/primaryMaterial'
const pwoStore = usePwoStore();
const jobStore = useJobStore();
@@ -133,7 +133,7 @@ fetchPrimaryMaterialList()
<template>
<div class="primary-material__container">
<div class="main-title">主材料进站</div>
<Title name="主材料进站" showRefresh @refresh="fetchPrimaryMaterialList" />
<a-form layout="inline" size="large">
<a-form-item label="主材类型">
<a-input v-model:value="materialType" disabled />
@@ -141,15 +141,15 @@ fetchPrimaryMaterialList()
<a-form-item label="载具 ID">
<a-input v-model:value="carrierInput" @pressEnter="insertCarrier" placeholder="按下回车录入" allow-clear />
</a-form-item>
<a-form-item>
<a-button type="primary" @click="insertCarrier">录入</a-button>
</a-form-item>
<a-form-item label="物料编码">
<a-input v-model:value="materialInput" @pressEnter="insertMaterial" placeholder="按下回车录入" allow-clear />
</a-form-item>
<a-form-item>
<a-button type="primary" @click="insertMaterial">录入</a-button>
</a-form-item>
<a-form-item>
<a-button @click="fetchPrimaryMaterialList">刷新</a-button>
</a-form-item>
</a-form>
<div class="table-wrapper" ref="customTable">
<a-table :dataSource="materialTableData" :columns="materialColumns as ColumnsType<MaterialTableItem>"
@@ -175,13 +175,6 @@ fetchPrimaryMaterialList()
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;

View File

@@ -63,7 +63,7 @@ defineExpose({
<template>
<div class="raw-material__container">
<div class="main-title">材料确认</div>
<Title name="材料确认" showRefresh @refresh="" />
<div class="table-wrapper" ref="customTable">
<a-table :dataSource="materialTableData" :columns="materialColumns as ColumnsType<MaterialTableItem>"
:pagination="false" bordered sticky :scroll="{ y: tableHeight }">
@@ -88,13 +88,6 @@ defineExpose({
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;

View File

@@ -398,7 +398,7 @@ onUnmounted(() => {
.bottom-section {
background: #fff;
padding: 16px;
padding: 12px 14px;
border-radius: 8px;
flex: 1;
min-height: 0;

View File

@@ -1,9 +1,13 @@
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue';
import { ref, onMounted, computed, getCurrentInstance } from 'vue';
import { message } from 'ant-design-vue';
import { useJobStore } from '@/store';
import type { ColumnsType } from 'ant-design-vue/es/table/interface';
import { delay } from '@/utils/mock';
import { listMainMaterialEntryLog } from '@/api/pwoManage/primaryMaterial'
import { listMainMaterialOutboundLog } from '@/api/pwoManage/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 jobStore = useJobStore();
@@ -20,11 +24,12 @@ const primaryMaterialColumns = [
{ 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', filters: [{ text: 'OK', value: 'OK', }, { text: 'NG', value: 'NG', }], onFilter: (value: string, record: any) => record.result.indexOf(value) === 0, align: 'center' },
{ title: '质量等级', dataIndex: 'qualityLevel', key: 'qualityLevel', filters: [{ text: 'A', value: 'A', }, { text: 'B', value: 'B', }], onFilter: (value: string, record: any) => record.qualityLevel.indexOf(value) === 0, align: 'center' },
{ title: '结果', dataIndex: 'result', key: 'result', align: 'center' },
{ title: '质量等级', dataIndex: 'qualityLevel', key: 'qualityLevel' },
{ title: '操作', key: 'action', align: 'center', width: 120 },
];
// 总计数据
const totals = computed(() => {
let okNum = 0;
let ngNum = 0;
@@ -43,15 +48,11 @@ const primaryMaterialTableData = ref<PrimaryMaterialTableItem[]>([]);
const loadingPrimaryMaterialList = ref(false);
const fetchPrimaryMaterialList = async () => {
// const stationId = jobStore.jobInfo.id;
// if (!stationId) return;
const stationId = jobStore.jobInfo.id;
if (!stationId) return;
loadingPrimaryMaterialList.value = true;
try {
const rows = await delay(1000, [
{ key: '1', waferCode: 'WF-20250401-0001', dieCode: 'DIE-20250401-0001', result: 'OK', qualityLevel: 'A' },
{ key: '2', waferCode: 'WF-20250401-0001', dieCode: 'DIE-20250401-0001', result: 'NG', qualityLevel: 'A' },
{ key: '3', waferCode: 'WF-20250401-0001', dieCode: 'DIE-20250401-0001', result: 'OK', qualityLevel: 'B' }
]);
const { rows } = await listMainMaterialEntryLog({ stationId });
primaryMaterialTableData.value = rows;
} catch (error: any) {
message.error(error.message || '查询治具列表失败');
@@ -83,7 +84,7 @@ fetchPrimaryMaterialList()
<template>
<div class="equipment__container">
<div class="main-title">主材报工</div>
<Title name="主材报工" showRefresh @refresh="fetchPrimaryMaterialList" />
<div class="table-wrapper" ref="customTable">
<a-table :dataSource="primaryMaterialTableData" :columns="primaryMaterialColumns as ColumnsType<PrimaryMaterialTableItem>"
:pagination="false" bordered sticky size="middle" :scroll="{ y: tableHeight }"
@@ -91,12 +92,14 @@ fetchPrimaryMaterialList()
<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="OK" checkedValue="OK" un-checked-children="NG" unCheckedValue="NG" />
<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%">
<a-select-option value="A">A</a-select-option>
<a-select-option value="B">B</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" :value="dict.value">{{ dict.label }}</a-select-option>
</a-select>
<a-select v-model:value="record.qualityLevel" style="width: 80%" v-else>
<a-select-option v-for="dict in main_material_ng_level" :value="dict.value">{{ dict.label }}</a-select-option>
</a-select>
</template>
<template v-if="column.key === 'action'">
@@ -145,29 +148,21 @@ fetchPrimaryMaterialList()
}
}
.main-title {
font-size: 20px;
font-weight: 600;
border-bottom: 1px solid #c9c9c9;
padding: 0 0 8px;
}
.table-wrapper {
height: 100%;
overflow: auto;
}
::v-deep(.ant-switch) {
background-color: #ff0000;
background-color: #04d903;
&:hover {
background-color: #ff2525;
background-color: #02eb02;
}
&.ant-switch-checked {
background-color: #04d903;
background-color: #ff0000;
&:hover {
background-color: #02eb02;
background-color: #ff2525;
}
}
}

View File

@@ -1,11 +1,16 @@
<script setup lang="ts">
import { ref, computed } from 'vue';
import { ref, computed, reactive } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { message } from 'ant-design-vue';
import { listStorageLocation } from '@/api/pwoManage/location';
import { completeStation } from '@/api/pwoManage/station';
import { useJobStore } from '@/store';
const router = useRouter();
const route = useRoute();
const jobStore = useJobStore();
const menuItems = [
{ label: '主材报工', key: 'JobReport', progress: 30 },
{ label: '工序参数', key: 'ParameterConfiguration', progress: 80 },
@@ -18,12 +23,35 @@ const handleMenuClick = (name: string) => {
router.push({ name });
};
const locationOptions = ref<any>([]);
const fetchLocationList = async () => {
try {
const { rows } = await listStorageLocation({});
locationOptions.value = rows;
} catch (error: any) {
message.error(error.message || '获取库位列表失败');
}
};
const openSelectLocation = ref(false);
const handleSelectLocation = () => {
fetchLocationList();
openSelectLocation.value = true;
};
const storage = reactive({});
const handleChangeLocation = (value: any, option: any) => {
Object.assign(storage, option);
};
// 确认出站
const outfeeding = ref(false);
const handleOutfeed = async () => {
outfeeding.value = true;
try {
router.push({ name: 'JobReport' });
await completeStation(jobStore.jobInfo.id, storage);
openSelectLocation.value = false;
message.success('出站成功');
} catch (error: any) {
message.error(error.message || '出站失败');
} finally {
@@ -41,6 +69,8 @@ function renderTableHeight() {
defineExpose({
renderTableHeight
});
fetchLocationList();
</script>
<template>
@@ -62,10 +92,14 @@ defineExpose({
: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="handleSelectLocation">确认出站</a-button>
</div>
</a-col>
</a-row>
<a-modal title="选择出站库位" :open="openSelectLocation" @ok="handleOutfeed" @cancel="openSelectLocation = false">
<a-select style="width: 100%" @change="handleChangeLocation" :options="locationOptions" :fieldNames="{ label: 'storageLocationCode', value: 'id' }" />
</a-modal>
</a-spin>
</template>