feat: add spare part management frontend pages

This commit is contained in:
chiguyong 2026-03-24 00:48:16 +08:00
parent 5500238be3
commit 5c7728e3db
6 changed files with 1078 additions and 24 deletions

148
src/api/sparepart.ts Normal file
View File

@ -0,0 +1,148 @@
import request from '@/utils/request'
// ==================== 备件相关类型 ====================
// 备件分类
export interface SparePartCategory {
id: string
name: string
description?: string
createdAt?: string
updatedAt?: string
}
// 备件
export interface SparePart {
id: string
name: string
code: string
categoryId?: string
categoryName?: string
projectId: string
projectName?: string
unit?: string
currentStock?: number
safeStock?: number
lowStockWarning?: boolean
description?: string
createdAt?: string
updatedAt?: string
}
// 备件表单
export interface SparePartForm {
id?: string
name: string
code: string
categoryId?: string
projectId: string
unit?: string
currentStock?: number
safeStock?: number
description?: string
}
// 库存记录
export interface StockRecord {
id: string
sparePartId: string
sparePartName?: string
operationType: 'IN' | 'OUT'
quantity: number
beforeStock?: number
afterStock?: number
relatedOrderId?: string
relatedOrderNo?: string
operatorId?: string
operatorName?: string
remark?: string
createdAt?: string
}
// 入库请求
export interface InStockRequest {
sparePartId: string
quantity: number
remark?: string
}
// 出库请求
export interface OutStockRequest {
sparePartId: string
quantity: number
relatedOrderId?: string
relatedOrderNo?: string
remark?: string
}
// 分页响应
export interface PageResponse<T> {
content: T[]
totalElements: number
totalPages: number
size: number
number: number
}
// ==================== 备件分类 API ====================
// 获取分类列表
export function getSparePartCategories() {
return request.get<SparePartCategory[]>('/api/v1/ops/spare-parts/categories')
}
// 创建分类
export function createSparePartCategory(data: { name: string; description?: string }) {
return request.post('/api/v1/ops/spare-parts/categories', data)
}
// ==================== 备件 API ====================
// 获取备件列表
export function getSparePartList(projectId: string, categoryId?: string) {
return request.get<PageResponse<SparePart>>('/api/v1/ops/spare-parts', {
params: { projectId, categoryId }
})
}
// 获取备件详情
export function getSparePartDetail(id: string) {
return request.get<SparePart>(`/api/v1/ops/spare-parts/${id}`)
}
// 创建备件
export function createSparePart(data: SparePartForm) {
return request.post('/api/v1/ops/spare-parts', data)
}
// 更新备件
export function updateSparePart(id: string, data: SparePartForm) {
return request.put(`/api/v1/ops/spare-parts/${id}`, data)
}
// 删除备件
export function deleteSparePart(id: string) {
return request.delete(`/api/v1/ops/spare-parts/${id}`)
}
// 获取低库存备件
export function getLowStockSpareParts(projectId: string) {
return request.get<SparePart[]>('/api/v1/ops/spare-parts/low-stock', {
params: { projectId }
})
}
// 入库
export function inStock(data: InStockRequest) {
return request.post('/api/v1/ops/spare-parts/in-stock', data)
}
// 出库
export function outStock(data: OutStockRequest) {
return request.post('/api/v1/ops/spare-parts/out-stock', data)
}
// 获取备件记录
export function getSparePartRecords(id: string) {
return request.get<StockRecord[]>(`/api/v1/ops/spare-parts/${id}/records`)
}

View File

@ -110,6 +110,30 @@ const router = createRouter({
name: 'EnergyStatistics', name: 'EnergyStatistics',
component: () => import('@/views/energy/EnergyStatistics.vue'), component: () => import('@/views/energy/EnergyStatistics.vue'),
meta: { title: '能耗统计' } meta: { title: '能耗统计' }
},
{
path: 'sparepart/list',
name: 'SparePartList',
component: () => import('@/views/sparepart/SparePartList.vue'),
meta: { title: '备件管理' }
},
{
path: 'sparepart/detail/:id',
name: 'SparePartDetail',
component: () => import('@/views/sparepart/SparePartDetail.vue'),
meta: { title: '备件详情' }
},
{
path: 'sparepart/stock/in',
name: 'SparePartInStock',
component: () => import('@/views/sparepart/StockOperation.vue'),
meta: { title: '备件入库' }
},
{
path: 'sparepart/stock/out',
name: 'SparePartOutStock',
component: () => import('@/views/sparepart/StockOperation.vue'),
meta: { title: '备件出库' }
} }
] ]
} }

View File

@ -47,6 +47,11 @@ const menuItems: MenuProps['items'] = [
label: '运营管理', label: '运营管理',
type: 'group', type: 'group',
children: [ children: [
{
key: '/sparepart/list',
icon: () => h(ToolOutlined),
label: '备件管理'
},
{ {
key: '/maintenance/plans', key: '/maintenance/plans',
icon: () => h(ToolOutlined), icon: () => h(ToolOutlined),

View File

@ -0,0 +1,206 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { Descriptions, Button, Space, Card, Statistic, Row, Col, message } from 'ant-design-vue'
import type { ColumnsType } from 'ant-design-vue/es/table'
import {
ArrowLeftOutlined,
InboxOutlined,
ExportOutlined,
ReloadOutlined
} from '@ant-design/icons-vue'
import {
getSparePartDetail,
getSparePartRecords,
type SparePart,
type StockRecord
} from '@/api/sparepart'
const route = useRoute()
const router = useRouter()
const loading = ref(false)
const recordLoading = ref(false)
const sparePart = ref<SparePart | null>(null)
const records = ref<StockRecord[]>([])
const id = route.params.id as string
//
const recordColumns: ColumnsType = [
{ title: '操作类型', dataIndex: 'operationType', key: 'operationType', width: 100 },
{ title: '数量', dataIndex: 'quantity', key: 'quantity', width: 80 },
{ title: '操作前库存', dataIndex: 'beforeStock', key: 'beforeStock', width: 100 },
{ title: '操作后库存', dataIndex: 'afterStock', key: 'afterStock', width: 100 },
{ title: '关联工单', dataIndex: 'relatedOrderNo', key: 'relatedOrderNo', width: 140 },
{ title: '操作人', dataIndex: 'operatorName', key: 'operatorName', width: 100 },
{ title: '备注', dataIndex: 'remark', key: 'remark' },
{ title: '操作时间', dataIndex: 'createdAt', key: 'createdAt', width: 180 }
]
//
const fetchDetail = async () => {
loading.value = true
try {
const res = await getSparePartDetail(id)
sparePart.value = res.data.data
} catch {
message.error('获取备件详情失败')
} finally {
loading.value = false
}
}
//
const fetchRecords = async () => {
recordLoading.value = true
try {
const res = await getSparePartRecords(id)
records.value = res.data.data || []
} catch {
message.error('获取库存记录失败')
} finally {
recordLoading.value = false
}
}
//
const handleBack = () => {
router.push('/sparepart/list')
}
//
const handleInStock = () => {
router.push(`/sparepart/stock/in?sparePartId=${id}`)
}
//
const handleOutStock = () => {
router.push(`/sparepart/stock/out?sparePartId=${id}`)
}
//
const getOperationType = (type: string) => {
return type === 'IN' ? '入库' : '出库'
}
onMounted(() => {
fetchDetail()
fetchRecords()
})
</script>
<template>
<div class="page-container">
<!-- 页面标题 -->
<div class="page-header">
<Space>
<Button @click="handleBack">
<ArrowLeftOutlined /> 返回
</Button>
<h2 class="page-title">备件详情</h2>
</Space>
</div>
<!-- 备件基本信息 -->
<Card title="基本信息" style="margin-bottom: 16px">
<Descriptions :column="3" bordered>
<Descriptions.Item label="备件编码">{{ sparePart?.code || '-' }}</Descriptions.Item>
<Descriptions.Item label="备件名称">{{ sparePart?.name || '-' }}</Descriptions.Item>
<Descriptions.Item label="分类">{{ sparePart?.categoryName || '-' }}</Descriptions.Item>
<Descriptions.Item label="单位">{{ sparePart?.unit || '-' }}</Descriptions.Item>
<Descriptions.Item label="所属项目">{{ sparePart?.projectName || '-' }}</Descriptions.Item>
<Descriptions.Item label="描述">{{ sparePart?.description || '-' }}</Descriptions.Item>
</Descriptions>
</Card>
<!-- 库存信息 -->
<Card title="库存信息" style="margin-bottom: 16px">
<Row :gutter="24">
<Col :span="6">
<Statistic
title="当前库存"
:value="sparePart?.currentStock || 0"
:suffix="sparePart?.unit || '个'"
/>
</Col>
<Col :span="6">
<Statistic
title="安全库存"
:value="sparePart?.safeStock || 0"
:suffix="sparePart?.unit || '个'"
/>
</Col>
<Col :span="6">
<Statistic
title="库存状态"
:value="sparePart?.lowStockWarning ? '低库存' : '正常'"
:value-style="sparePart?.lowStockWarning ? { color: '#cf1322' } : { color: '#3f8600' }"
/>
</Col>
<Col :span="6">
<Space direction="vertical">
<Button type="primary" @click="handleInStock">
<InboxOutlined /> 入库
</Button>
<Button @click="handleOutStock">
<ExportOutlined /> 出库
</Button>
</Space>
</Col>
</Row>
</Card>
<!-- 库存记录 -->
<Card title="库存记录">
<template #extra>
<Button @click="fetchRecords" :loading="recordLoading">
<ReloadOutlined /> 刷新
</Button>
</template>
<a-table
:columns="recordColumns"
:data-source="records"
:loading="recordLoading"
:row-key="(record: StockRecord) => record.id"
:pagination="{
pageSize: 10,
showSizeChanger: true,
showTotal: (total: number) => `${total}`
}"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'operationType'">
<a-tag :color="record.operationType === 'IN' ? 'green' : 'blue'">
{{ getOperationType(record.operationType) }}
</a-tag>
</template>
<template v-else-if="column.key === 'quantity'">
<span :style="{ color: record.operationType === 'IN' ? '#3f8600' : '#cf1322' }">
{{ record.operationType === 'IN' ? '+' : '-' }}{{ record.quantity }}
</span>
</template>
<template v-else-if="column.key === 'createdAt'">
{{ record.createdAt ? new Date(record.createdAt).toLocaleString() : '-' }}
</template>
</template>
</a-table>
</Card>
</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;
}
</style>

View File

@ -0,0 +1,445 @@
<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: 180, 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>

View File

@ -0,0 +1,226 @@
<script setup lang="ts">
import { ref, reactive, onMounted, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { Card, Form, InputNumber, Input, Button, Space, message, Result } from 'ant-design-vue'
import {
ArrowLeftOutlined,
InboxOutlined,
ExportOutlined
} from '@ant-design/icons-vue'
import {
getSparePartDetail,
inStock,
outStock,
type SparePart,
type InStockRequest,
type OutStockRequest
} from '@/api/sparepart'
const route = useRoute()
const router = useRouter()
const loading = ref(false)
const submitLoading = ref(false)
const sparePart = ref<SparePart | null>(null)
const formRef = ref()
const formState = reactive({
quantity: 0,
relatedOrderNo: '',
remark: ''
})
const rules = {
quantity: [{ required: true, message: '请输入数量', type: 'number' }]
}
const isInStock = computed(() => route.path.includes('/stock/in'))
const operationType = computed(() => isInStock.value ? '入库' : '出库')
const operationIcon = computed(() => isInStock.value ? InboxOutlined : ExportOutlined)
const sparePartId = route.query.sparePartId as string
//
const fetchSparePart = async () => {
if (!sparePartId) {
message.error('缺少备件ID')
return
}
loading.value = true
try {
const res = await getSparePartDetail(sparePartId)
sparePart.value = res.data.data
} catch {
message.error('获取备件信息失败')
} finally {
loading.value = false
}
}
//
const handleBack = () => {
router.back()
}
//
const handleSubmit = async () => {
try {
await formRef.value.validate()
submitLoading.value = true
if (isInStock.value) {
const data: InStockRequest = {
sparePartId,
quantity: formState.quantity,
remark: formState.remark
}
await inStock(data)
message.success('入库成功')
} else {
const data: OutStockRequest = {
sparePartId,
quantity: formState.quantity,
relatedOrderNo: formState.relatedOrderNo,
remark: formState.remark
}
await outStock(data)
message.success('出库成功')
}
router.back()
} catch (error) {
console.error('操作失败', error)
} finally {
submitLoading.value = false
}
}
onMounted(() => {
fetchSparePart()
})
</script>
<template>
<div class="page-container">
<!-- 页面标题 -->
<div class="page-header">
<Space>
<Button @click="handleBack">
<ArrowLeftOutlined /> 返回
</Button>
<h2 class="page-title">{{ operationType }}</h2>
</Space>
</div>
<Card v-if="sparePart">
<!-- 备件信息 -->
<div class="sparepart-info">
<span class="info-label">备件信息</span>
<span class="info-value">
{{ sparePart.name }} ({{ sparePart.code }})
</span>
<span class="info-label" style="margin-left: 24px">当前库存</span>
<span class="info-value">
{{ sparePart.currentStock || 0 }} {{ sparePart.unit || '个' }}
</span>
</div>
</Card>
<!-- 操作表单 -->
<Card :title="operationType" style="margin-top: 16px">
<Form
ref="formRef"
:model="formState"
:rules="rules"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 16 }"
>
<Form.Item :label="operationType + '数量'" name="quantity">
<InputNumber
v-model:value="formState.quantity"
:min="1"
style="width: 200px"
:placeholder="'请输入' + operationType + '数量'"
/>
</Form.Item>
<Form.Item v-if="!isInStock" label="关联工单号" name="relatedOrderNo">
<Input
v-model:value="formState.relatedOrderNo"
placeholder="请输入关联工单号(可选)"
style="width: 300px"
/>
</Form.Item>
<Form.Item label="备注" name="remark">
<Input.TextArea
v-model:value="formState.remark"
placeholder="请输入备注(可选)"
:rows="3"
style="width: 400px"
/>
</Form.Item>
<Form.Item :wrapper-col="{ offset: 6, span: 16 }">
<Space>
<Button type="primary" :loading="submitLoading" @click="handleSubmit">
<component :is="operationIcon" /> {{ operationType }}
</Button>
<Button @click="handleBack">取消</Button>
</Space>
</Form.Item>
</Form>
</Card>
<!-- 加载状态 -->
<Card v-if="loading" style="margin-top: 16px">
<Result title="加载中..." />
</Card>
<!-- 缺少参数 -->
<Card v-if="!sparePartId && !loading" style="margin-top: 16px">
<Result
status="warning"
title="缺少参数"
sub-title="未指定备件ID无法进行库存操作"
>
<template #extra>
<Button type="primary" @click="handleBack">返回</Button>
</template>
</Result>
</Card>
</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;
}
.sparepart-info {
padding: 16px;
background: #fafafa;
border-radius: 4px;
}
.info-label {
color: #8c8c8c;
font-size: 14px;
}
.info-value {
color: #262626;
font-size: 14px;
font-weight: 500;
}
</style>