ether-admin/src/views/sparepart/SparePartList.vue

446 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { ref, reactive, onMounted, computed } from 'vue'
import { useRouter } from 'vue-router'
import { Button, Select, Space, message, Tag, Modal, Form, Input, InputNumber, Popconfirm } 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'
import { TableActions } from '@/components'
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: '请输入备件名称' }],
code: [{ required: true, message: '请输入备件编码' }],
projectId: [{ required: true, message: '请选择项目' }]
}
// 获取项目列表
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) {
console.error('表单验证失败', 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}`)
}
// 库存状态显示
const getStockStatus = (record: SparePart) => {
if (record.lowStockWarning) {
return { color: 'error', text: '低库存' }
}
return { color: 'success', text: '正常' }
}
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: any) => 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: #262626;
}
.filter-bar {
margin-bottom: 24px;
padding: 16px;
background: #fafafa;
border-radius: 8px;
}
.table-card {
background: #fff;
border-radius: 8px;
padding: 16px;
}
</style>