437 lines
12 KiB
Vue
437 lines
12 KiB
Vue
<script setup lang="ts">
|
||
import { ref, reactive, onMounted } from 'vue'
|
||
import type { PaginationInfo } from '@/types'
|
||
import { useRouter } from 'vue-router'
|
||
import { Button, Select, Space, message, Tag, Modal } from 'ant-design-vue'
|
||
import type { ColumnsType } from 'ant-design-vue/es/table'
|
||
import {
|
||
SearchOutlined,
|
||
ReloadOutlined,
|
||
PlusOutlined,
|
||
ExclamationCircleOutlined
|
||
} from '@ant-design/icons-vue'
|
||
import { getSparePartList,
|
||
getSparePartCategories,
|
||
createSparePart,
|
||
updateSparePart,
|
||
deleteSparePart,
|
||
getLowStockSpareParts,
|
||
type SparePart,
|
||
type SparePartCategory,
|
||
type SparePartForm
|
||
} from '@/api/sparepart'
|
||
import { getProjectSelectorList } from '@/api/project'
|
||
|
||
const router = useRouter()
|
||
|
||
// 表格列定义
|
||
const columns: ColumnsType = [
|
||
{ title: '备件编码', dataIndex: 'code', key: 'code', width: 140 },
|
||
{ title: '备件名称', dataIndex: 'name', key: 'name', width: 180 },
|
||
{ title: '分类', dataIndex: 'categoryName', key: 'categoryName', width: 120 },
|
||
{ title: '单位', dataIndex: 'unit', key: 'unit', width: 80 },
|
||
{ title: '当前库存', dataIndex: 'currentStock', key: 'currentStock', width: 100 },
|
||
{ title: '安全库存', dataIndex: 'safeStock', key: 'safeStock', width: 100 },
|
||
{
|
||
title: '库存状态',
|
||
dataIndex: 'lowStockWarning',
|
||
key: 'lowStockWarning',
|
||
width: 100
|
||
},
|
||
{ title: '操作', key: 'action', width: 140, fixed: 'right' as const }
|
||
]
|
||
|
||
// 项目选择选项
|
||
const projectOptions = ref<{ value: string; label: string }[]>([])
|
||
|
||
// 分类选项
|
||
const categoryOptions = ref<{ value: string; label: string }[]>([])
|
||
|
||
// 查询参数
|
||
const queryParams = reactive({
|
||
projectId: '',
|
||
categoryId: undefined as string | undefined
|
||
})
|
||
|
||
// 数据状态
|
||
const loading = ref(false)
|
||
const tableData = ref<SparePart[]>([])
|
||
const lowStockData = ref<SparePart[]>([])
|
||
const pagination = reactive({
|
||
current: 1,
|
||
pageSize: 10,
|
||
total: 0
|
||
})
|
||
|
||
// 模态框状态
|
||
const modalVisible = ref(false)
|
||
const modalTitle = ref('新建备件')
|
||
const modalLoading = ref(false)
|
||
const editingSparePart = ref<SparePart | null>(null)
|
||
|
||
// 表单
|
||
const formRef = ref()
|
||
const formState = reactive<SparePartForm>({
|
||
name: '',
|
||
code: '',
|
||
categoryId: undefined,
|
||
projectId: '',
|
||
unit: '',
|
||
currentStock: 0,
|
||
safeStock: 0,
|
||
description: ''
|
||
})
|
||
|
||
// 表单验证
|
||
const rules = {
|
||
name: [{ required: true, message: '请输入备件名称', trigger: 'blur' }],
|
||
code: [{ required: true, message: '请输入备件编码', trigger: 'blur' }],
|
||
projectId: [{ required: true, message: '请选择项目', trigger: 'change' }]
|
||
}
|
||
|
||
// 获取项目列表
|
||
const fetchProjects = async () => {
|
||
try {
|
||
const res = await getProjectSelectorList()
|
||
projectOptions.value = (res.data.data || []).map((item: any) => ({
|
||
value: item.id,
|
||
label: item.name
|
||
}))
|
||
} catch {
|
||
message.error('获取项目列表失败')
|
||
}
|
||
}
|
||
|
||
// 获取分类列表
|
||
const fetchCategories = async () => {
|
||
try {
|
||
const res = await getSparePartCategories()
|
||
categoryOptions.value = (res.data.data || []).map((item: SparePartCategory) => ({
|
||
value: item.id,
|
||
label: item.name
|
||
}))
|
||
} catch {
|
||
message.error('获取分类列表失败')
|
||
}
|
||
}
|
||
|
||
// 获取备件列表
|
||
const fetchSparePartList = async () => {
|
||
if (!queryParams.projectId) {
|
||
message.warning('请先选择项目')
|
||
return
|
||
}
|
||
loading.value = true
|
||
try {
|
||
const res = await getSparePartList(queryParams.projectId, queryParams.categoryId)
|
||
const data = res.data.data
|
||
tableData.value = data.content || []
|
||
pagination.total = data.totalElements || 0
|
||
} catch {
|
||
message.error('获取备件列表失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 获取低库存备件
|
||
const fetchLowStockSpareParts = async () => {
|
||
if (!queryParams.projectId) return
|
||
loading.value = true
|
||
try {
|
||
const res = await getLowStockSpareParts(queryParams.projectId)
|
||
lowStockData.value = res.data.data || []
|
||
} catch {
|
||
message.error('获取低库存备件失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 搜索
|
||
const handleSearch = () => {
|
||
pagination.current = 1
|
||
fetchSparePartList()
|
||
}
|
||
|
||
// 重置
|
||
const handleReset = () => {
|
||
queryParams.projectId = ''
|
||
queryParams.categoryId = undefined
|
||
pagination.current = 1
|
||
tableData.value = []
|
||
}
|
||
|
||
// 分页变化
|
||
const handlePageChange = (page: number, pageSize: number) => {
|
||
pagination.current = page
|
||
pagination.pageSize = pageSize
|
||
fetchSparePartList()
|
||
}
|
||
|
||
// 打开新建模态框
|
||
const handleAdd = () => {
|
||
editingSparePart.value = null
|
||
modalTitle.value = '新建备件'
|
||
Object.assign(formState, {
|
||
id: undefined,
|
||
name: '',
|
||
code: '',
|
||
categoryId: undefined,
|
||
projectId: queryParams.projectId,
|
||
unit: '',
|
||
currentStock: 0,
|
||
safeStock: 0,
|
||
description: ''
|
||
})
|
||
modalVisible.value = true
|
||
}
|
||
|
||
// 打开编辑模态框
|
||
const handleEdit = (record: SparePart) => {
|
||
editingSparePart.value = record
|
||
modalTitle.value = '编辑备件'
|
||
Object.assign(formState, {
|
||
id: record.id,
|
||
name: record.name,
|
||
code: record.code,
|
||
categoryId: record.categoryId,
|
||
projectId: record.projectId,
|
||
unit: record.unit || '',
|
||
currentStock: record.currentStock || 0,
|
||
safeStock: record.safeStock || 0,
|
||
description: record.description || ''
|
||
})
|
||
modalVisible.value = true
|
||
}
|
||
|
||
// 查看详情
|
||
const handleView = (record: SparePart) => {
|
||
router.push(`/sparepart/detail/${record.id}`)
|
||
}
|
||
|
||
// 删除备件
|
||
const handleDelete = (record: SparePart) => {
|
||
Modal.confirm({
|
||
title: '确认删除',
|
||
content: `确定要删除备件 "${record.name}" 吗?`,
|
||
okText: '确认',
|
||
cancelText: '取消',
|
||
onOk: async () => {
|
||
try {
|
||
await deleteSparePart(record.id)
|
||
message.success('删除成功')
|
||
fetchSparePartList()
|
||
} catch {
|
||
message.error('删除失败')
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
// 提交表单
|
||
const handleSubmit = async () => {
|
||
try {
|
||
await formRef.value.validate()
|
||
modalLoading.value = true
|
||
if (editingSparePart.value) {
|
||
await updateSparePart(editingSparePart.value.id, formState)
|
||
message.success('更新成功')
|
||
} else {
|
||
await createSparePart(formState)
|
||
message.success('创建成功')
|
||
}
|
||
modalVisible.value = false
|
||
fetchSparePartList()
|
||
} catch (error) {
|
||
// 表单验证失败,不显示错误信息(验证组件会处理)
|
||
} finally {
|
||
modalLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 入库
|
||
const handleInStock = (record: SparePart) => {
|
||
router.push(`/sparepart/stock/in?sparePartId=${record.id}`)
|
||
}
|
||
|
||
// 出库
|
||
const handleOutStock = (record: SparePart) => {
|
||
router.push(`/sparepart/stock/out?sparePartId=${record.id}`)
|
||
}
|
||
|
||
onMounted(() => {
|
||
fetchProjects()
|
||
fetchCategories()
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<div class="page-container">
|
||
<!-- 页面标题 -->
|
||
<div class="page-header">
|
||
<h2 class="page-title">备件管理</h2>
|
||
</div>
|
||
|
||
<!-- 筛选区 -->
|
||
<div class="filter-bar">
|
||
<Space>
|
||
<Select
|
||
v-model:value="queryParams.projectId"
|
||
placeholder="请选择项目"
|
||
style="width: 240px"
|
||
allow-clear
|
||
:options="projectOptions"
|
||
@change="handleSearch"
|
||
/>
|
||
<Select
|
||
v-model:value="queryParams.categoryId"
|
||
placeholder="请选择分类"
|
||
style="width: 160px"
|
||
allow-clear
|
||
:options="categoryOptions"
|
||
@change="handleSearch"
|
||
/>
|
||
<Button type="primary" @click="handleSearch">
|
||
<SearchOutlined /> 查询
|
||
</Button>
|
||
<Button @click="handleReset">
|
||
<ReloadOutlined /> 重置
|
||
</Button>
|
||
</Space>
|
||
</div>
|
||
|
||
<!-- 表格 -->
|
||
<div class="table-card">
|
||
<div style="margin-bottom: 16px">
|
||
<Space>
|
||
<Button type="primary" @click="handleAdd" :disabled="!queryParams.projectId">
|
||
<PlusOutlined /> 新建备件
|
||
</Button>
|
||
<Button @click="fetchLowStockSpareParts" :disabled="!queryParams.projectId">
|
||
<ExclamationCircleOutlined /> 低库存备件
|
||
</Button>
|
||
</Space>
|
||
</div>
|
||
|
||
<a-table
|
||
:columns="columns"
|
||
:data-source="tableData"
|
||
:loading="loading"
|
||
:row-key="(record: SparePart) => record.id"
|
||
:pagination="{
|
||
current: pagination.current,
|
||
pageSize: pagination.pageSize,
|
||
total: pagination.total,
|
||
showSizeChanger: true,
|
||
showQuickJumper: true,
|
||
showTotal: (total: number) => `共 ${total} 条`
|
||
}"
|
||
@change="(pag: PaginationInfo) => handlePageChange(pag.current, pag.pageSize)"
|
||
>
|
||
<template #bodyCell="{ column, record }">
|
||
<template v-if="column.key === 'lowStockWarning'">
|
||
<Tag v-if="record.lowStockWarning" color="red">
|
||
{{ record.lowStockWarning ? '低库存' : '正常' }}
|
||
</Tag>
|
||
<Tag v-else color="green">正常</Tag>
|
||
</template>
|
||
<template v-else-if="column.key === 'action'">
|
||
<Space>
|
||
<Button type="link" size="small" @click="handleView(record)">查看</Button>
|
||
<Button type="link" size="small" @click="handleEdit(record)">编辑</Button>
|
||
<Button type="link" size="small" @click="handleInStock(record)">入库</Button>
|
||
<Button type="link" size="small" @click="handleOutStock(record)">出库</Button>
|
||
<Button type="link" size="small" danger @click="handleDelete(record)">删除</Button>
|
||
</Space>
|
||
</template>
|
||
</template>
|
||
</a-table>
|
||
|
||
<!-- 未选择项目提示 -->
|
||
<a-empty v-if="!queryParams.projectId && tableData.length === 0" description="请先选择项目" />
|
||
</div>
|
||
|
||
<!-- 新建/编辑模态框 -->
|
||
<a-modal
|
||
v-model:open="modalVisible"
|
||
:title="modalTitle"
|
||
:confirm-loading="modalLoading"
|
||
@ok="handleSubmit"
|
||
width="600px"
|
||
>
|
||
<a-form
|
||
ref="formRef"
|
||
:model="formState"
|
||
:rules="rules"
|
||
:label-col="{ span: 6 }"
|
||
:wrapper-col="{ span: 16 }"
|
||
>
|
||
<a-form-item label="备件编码" name="code">
|
||
<a-input v-model:value="formState.code" placeholder="请输入备件编码" />
|
||
</a-form-item>
|
||
<a-form-item label="备件名称" name="name">
|
||
<a-input v-model:value="formState.name" placeholder="请输入备件名称" />
|
||
</a-form-item>
|
||
<a-form-item label="分类" name="categoryId">
|
||
<a-select
|
||
v-model:value="formState.categoryId"
|
||
placeholder="请选择分类"
|
||
:options="categoryOptions"
|
||
allow-clear
|
||
/>
|
||
</a-form-item>
|
||
<a-form-item label="单位" name="unit">
|
||
<a-input v-model:value="formState.unit" placeholder="如:个、件、套" />
|
||
</a-form-item>
|
||
<a-form-item label="当前库存" name="currentStock">
|
||
<a-input-number
|
||
v-model:value="formState.currentStock"
|
||
:min="0"
|
||
style="width: 100%"
|
||
/>
|
||
</a-form-item>
|
||
<a-form-item label="安全库存" name="safeStock">
|
||
<a-input-number
|
||
v-model:value="formState.safeStock"
|
||
:min="0"
|
||
style="width: 100%"
|
||
/>
|
||
</a-form-item>
|
||
<a-form-item label="备注" name="description">
|
||
<a-textarea v-model:value="formState.description" placeholder="请输入备注" :rows="3" />
|
||
</a-form-item>
|
||
</a-form>
|
||
</a-modal>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.page-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.page-title {
|
||
margin: 0;
|
||
font-size: 20px;
|
||
font-weight: 500;
|
||
color: var(--color-text, #262626);
|
||
}
|
||
|
||
.filter-bar {
|
||
margin-bottom: 24px;
|
||
padding: 16px;
|
||
background: var(--color-bg-filter, #fafafa);
|
||
border-radius: var(--radius-md, 8px);
|
||
}
|
||
|
||
.table-card {
|
||
background: var(--color-bg-card, #fff);
|
||
border-radius: var(--radius-md, 8px);
|
||
padding: 16px;
|
||
}
|
||
</style>
|