2026-06-22 23:06:04 +08:00
|
|
|
|
<script setup>
|
|
|
|
|
|
|
2026-06-24 16:42:13 +08:00
|
|
|
|
import { Search } from '@element-plus/icons-vue'
|
2026-06-25 21:49:55 +08:00
|
|
|
|
import { ref, onMounted, reactive, watch, computed } from 'vue'
|
2026-06-24 16:42:13 +08:00
|
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
|
|
import {
|
|
|
|
|
|
getProductsList,
|
|
|
|
|
|
createProducts,
|
|
|
|
|
|
updateProducts,
|
|
|
|
|
|
deleteProductsAPI
|
|
|
|
|
|
} from '../api/products'
|
2026-06-25 21:49:55 +08:00
|
|
|
|
import { getSupplierList } from '../api/suppliers'
|
|
|
|
|
|
import { hasPermission } from '../store/user'
|
2026-06-24 16:42:13 +08:00
|
|
|
|
|
|
|
|
|
|
const form = reactive({
|
|
|
|
|
|
name: '',
|
|
|
|
|
|
type: '',
|
|
|
|
|
|
quantity: 0,
|
|
|
|
|
|
price: 0,
|
|
|
|
|
|
unit: '件',
|
|
|
|
|
|
specification: '',
|
|
|
|
|
|
supplier: '',
|
|
|
|
|
|
remark: ''
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const search = ref('')
|
|
|
|
|
|
const select = ref('2')
|
|
|
|
|
|
const filterType = ref('')
|
|
|
|
|
|
const searchResults = ref([])
|
|
|
|
|
|
const showSearchResults = ref(false)
|
|
|
|
|
|
const showAddForm = ref(false)
|
|
|
|
|
|
const isEditMode = ref(false)
|
|
|
|
|
|
const currentProductId = ref(null)
|
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
|
const showDetailDialog = ref(false)
|
|
|
|
|
|
const currentDetail = ref(null)
|
2026-06-25 21:49:55 +08:00
|
|
|
|
const supplierList = ref([])
|
|
|
|
|
|
|
|
|
|
|
|
// 分页相关
|
|
|
|
|
|
const currentPage = ref(1)
|
|
|
|
|
|
const pageSize = ref(10)
|
2026-06-28 00:36:03 +08:00
|
|
|
|
const total = ref(0)
|
2026-06-24 16:42:13 +08:00
|
|
|
|
|
|
|
|
|
|
// 产品类型映射
|
|
|
|
|
|
const productTypeMap = {
|
2026-06-28 00:36:03 +08:00
|
|
|
|
'茶饮原料': '茶饮原料',
|
|
|
|
|
|
'包装物料': '包装物料',
|
|
|
|
|
|
'设备器具': '设备器具'
|
2026-06-24 16:42:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
fetchData()
|
2026-06-25 21:49:55 +08:00
|
|
|
|
fetchSuppliers()
|
2026-06-24 16:42:13 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
2026-06-25 21:49:55 +08:00
|
|
|
|
const fetchSuppliers = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await getSupplierList(null, { quiet: true })
|
|
|
|
|
|
supplierList.value = res.data?.list || res.data || []
|
|
|
|
|
|
} catch {}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-24 16:42:13 +08:00
|
|
|
|
// 监听筛选变化
|
|
|
|
|
|
watch(filterType, () => {
|
2026-06-25 21:49:55 +08:00
|
|
|
|
currentPage.value = 1
|
2026-06-24 16:42:13 +08:00
|
|
|
|
handleSearch()
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-06-28 00:36:03 +08:00
|
|
|
|
// 分页后的数据(服务端已分页,searchResults 即为当前页数据)
|
|
|
|
|
|
const paginatedData = computed(() => searchResults.value)
|
2026-06-25 21:49:55 +08:00
|
|
|
|
|
2026-06-28 00:36:03 +08:00
|
|
|
|
// 获取产品列表(服务端分页)
|
|
|
|
|
|
const fetchData = async (page) => {
|
2026-06-24 16:42:13 +08:00
|
|
|
|
loading.value = true
|
2026-06-28 00:36:03 +08:00
|
|
|
|
const params = {
|
|
|
|
|
|
page: page || currentPage.value,
|
|
|
|
|
|
pageSize: pageSize.value
|
|
|
|
|
|
}
|
|
|
|
|
|
const res = await getProductsList(params)
|
2026-06-25 21:49:55 +08:00
|
|
|
|
if (res.code === 0 || res.code === 200) {
|
2026-06-28 00:36:03 +08:00
|
|
|
|
if (res.data && Array.isArray(res.data.list)) {
|
|
|
|
|
|
searchResults.value = res.data.list
|
|
|
|
|
|
total.value = res.data.total
|
|
|
|
|
|
} else if (Array.isArray(res.data)) {
|
|
|
|
|
|
searchResults.value = res.data
|
|
|
|
|
|
total.value = res.data.length
|
|
|
|
|
|
}
|
2026-06-25 21:49:55 +08:00
|
|
|
|
}
|
2026-06-24 16:42:13 +08:00
|
|
|
|
showSearchResults.value = true
|
|
|
|
|
|
loading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 搜索
|
|
|
|
|
|
const handleSearch = async () => {
|
2026-06-25 21:49:55 +08:00
|
|
|
|
// 如果在表单页面,先关闭表单
|
|
|
|
|
|
if (showAddForm.value) {
|
|
|
|
|
|
cancelAdd()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-24 16:42:13 +08:00
|
|
|
|
loading.value = true
|
2026-06-25 21:49:55 +08:00
|
|
|
|
const params = {
|
2026-06-28 00:36:03 +08:00
|
|
|
|
page: currentPage.value,
|
|
|
|
|
|
pageSize: pageSize.value,
|
2026-06-25 21:49:55 +08:00
|
|
|
|
type: filterType.value || undefined
|
|
|
|
|
|
}
|
|
|
|
|
|
if (select.value === '1') {
|
|
|
|
|
|
params.id = Number(search.value)
|
|
|
|
|
|
} else if (select.value === '2') {
|
|
|
|
|
|
params.name = search.value
|
|
|
|
|
|
} else if (select.value === '3') {
|
|
|
|
|
|
params.supplier = search.value
|
|
|
|
|
|
}
|
|
|
|
|
|
const res = await getProductsList(params)
|
|
|
|
|
|
if (res.code === 0 || res.code === 200) {
|
2026-06-28 00:36:03 +08:00
|
|
|
|
if (res.data && Array.isArray(res.data.list)) {
|
|
|
|
|
|
searchResults.value = res.data.list
|
|
|
|
|
|
total.value = res.data.total
|
|
|
|
|
|
} else if (Array.isArray(res.data)) {
|
|
|
|
|
|
searchResults.value = res.data
|
|
|
|
|
|
total.value = res.data.length
|
|
|
|
|
|
}
|
2026-06-25 21:49:55 +08:00
|
|
|
|
}
|
2026-06-24 16:42:13 +08:00
|
|
|
|
showSearchResults.value = true
|
|
|
|
|
|
loading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 重置搜索
|
|
|
|
|
|
const resetSearch = () => {
|
|
|
|
|
|
search.value = ''
|
|
|
|
|
|
filterType.value = ''
|
|
|
|
|
|
showSearchResults.value = false
|
|
|
|
|
|
showAddForm.value = false
|
2026-06-25 21:49:55 +08:00
|
|
|
|
currentPage.value = 1
|
2026-06-24 16:42:13 +08:00
|
|
|
|
fetchData()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清空搜索
|
|
|
|
|
|
const clearSearch = () => {
|
|
|
|
|
|
resetSearch()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清空表单
|
|
|
|
|
|
const resetForm = () => {
|
|
|
|
|
|
form.name = ''
|
|
|
|
|
|
form.type = ''
|
|
|
|
|
|
form.quantity = 0
|
|
|
|
|
|
form.price = 0
|
|
|
|
|
|
form.unit = '件'
|
|
|
|
|
|
form.specification = ''
|
|
|
|
|
|
form.supplier = ''
|
|
|
|
|
|
form.remark = ''
|
|
|
|
|
|
isEditMode.value = false
|
|
|
|
|
|
currentProductId.value = null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示新增表单
|
|
|
|
|
|
const addProduct = () => {
|
|
|
|
|
|
resetForm()
|
|
|
|
|
|
showAddForm.value = true
|
|
|
|
|
|
showSearchResults.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 编辑
|
|
|
|
|
|
const editProduct = (row) => {
|
|
|
|
|
|
isEditMode.value = true
|
|
|
|
|
|
currentProductId.value = row.id
|
|
|
|
|
|
form.name = row.name
|
|
|
|
|
|
form.type = row.type
|
|
|
|
|
|
form.quantity = row.quantity
|
|
|
|
|
|
form.price = row.price
|
|
|
|
|
|
form.unit = row.unit
|
|
|
|
|
|
form.specification = row.specification
|
|
|
|
|
|
form.supplier = row.supplier
|
|
|
|
|
|
form.remark = row.remark
|
|
|
|
|
|
showAddForm.value = true
|
|
|
|
|
|
showSearchResults.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 保存产品
|
|
|
|
|
|
const saveProduct = async () => {
|
|
|
|
|
|
const formData = {
|
|
|
|
|
|
name: form.name,
|
|
|
|
|
|
type: form.type,
|
|
|
|
|
|
quantity: form.quantity,
|
|
|
|
|
|
price: form.price,
|
|
|
|
|
|
unit: form.unit,
|
|
|
|
|
|
specification: form.specification,
|
|
|
|
|
|
supplier: form.supplier,
|
|
|
|
|
|
remark: form.remark
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-25 21:49:55 +08:00
|
|
|
|
try {
|
|
|
|
|
|
if (isEditMode.value) {
|
|
|
|
|
|
await updateProducts(currentProductId.value, formData)
|
|
|
|
|
|
ElMessage.success('更新成功')
|
|
|
|
|
|
} else {
|
|
|
|
|
|
await createProducts(formData)
|
|
|
|
|
|
ElMessage.success('新增成功')
|
|
|
|
|
|
}
|
2026-06-24 16:42:13 +08:00
|
|
|
|
|
2026-06-25 21:49:55 +08:00
|
|
|
|
cancelAdd()
|
|
|
|
|
|
fetchData()
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
ElMessage.error(error.response?.data?.message || error.message || '操作失败')
|
|
|
|
|
|
}
|
2026-06-24 16:42:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 删除产品
|
|
|
|
|
|
const deleteProduct = async (id) => {
|
|
|
|
|
|
ElMessageBox.confirm('确定删除该产品吗?', '提示', {
|
2026-06-25 21:49:55 +08:00
|
|
|
|
confirmButtonText: '确认',
|
|
|
|
|
|
cancelButtonText: '取消',
|
2026-06-24 16:42:13 +08:00
|
|
|
|
type: 'warning'
|
|
|
|
|
|
}).then(async () => {
|
2026-06-25 21:49:55 +08:00
|
|
|
|
try {
|
|
|
|
|
|
await deleteProductsAPI(id)
|
|
|
|
|
|
ElMessage.success('删除成功')
|
|
|
|
|
|
fetchData()
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
ElMessage.error(error.response?.data?.message || error.message || '删除失败')
|
|
|
|
|
|
}
|
2026-06-24 16:42:13 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 取消
|
|
|
|
|
|
const cancelAdd = () => {
|
|
|
|
|
|
resetForm()
|
|
|
|
|
|
showAddForm.value = false
|
|
|
|
|
|
showSearchResults.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 双击行查看详情
|
|
|
|
|
|
const handleRowDblClick = (row) => {
|
|
|
|
|
|
currentDetail.value = { ...row }
|
|
|
|
|
|
showDetailDialog.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 关闭详情弹窗
|
|
|
|
|
|
const closeDetail = () => {
|
|
|
|
|
|
showDetailDialog.value = false
|
|
|
|
|
|
currentDetail.value = null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 产品类型标签样式
|
|
|
|
|
|
const getTypeTag = (type) => {
|
|
|
|
|
|
const map = {
|
2026-06-28 00:36:03 +08:00
|
|
|
|
'茶饮原料': '',
|
|
|
|
|
|
'包装物料': 'success',
|
|
|
|
|
|
'设备器具': 'warning'
|
2026-06-24 16:42:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
return map[type] || 'info'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-28 00:36:03 +08:00
|
|
|
|
// 页码变化(服务端分页:重新请求后端)
|
2026-06-25 21:49:55 +08:00
|
|
|
|
const handleCurrentChange = (val) => {
|
|
|
|
|
|
currentPage.value = val
|
2026-06-28 00:36:03 +08:00
|
|
|
|
fetchData(val)
|
2026-06-25 21:49:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查权限
|
|
|
|
|
|
const canCreate = computed(() => hasPermission('product', 'create'))
|
|
|
|
|
|
const canUpdate = computed(() => hasPermission('product', 'update'))
|
|
|
|
|
|
const canDelete = computed(() => hasPermission('product', 'delete'))
|
2026-06-26 01:25:15 +08:00
|
|
|
|
const canOperate = computed(() => canUpdate.value || canDelete.value)
|
2026-06-25 21:49:55 +08:00
|
|
|
|
|
2026-06-22 23:06:04 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
2026-06-25 21:49:55 +08:00
|
|
|
|
<div class="products-container">
|
2026-06-24 16:42:13 +08:00
|
|
|
|
<!-- 搜索栏区域 -->
|
|
|
|
|
|
<div class="products-search">
|
|
|
|
|
|
<div class="search-row">
|
|
|
|
|
|
<el-input v-model="search" style="max-width: 500px" placeholder="请输入搜索关键词"
|
|
|
|
|
|
class="products-search-with-select" @keyup.enter="handleSearch">
|
|
|
|
|
|
<template #prepend>
|
|
|
|
|
|
<el-select v-model="select" placeholder="请选择" style="width: 115px">
|
2026-06-28 00:36:03 +08:00
|
|
|
|
<el-option label="产品编号" value="1" />
|
|
|
|
|
|
<el-option label="产品名称" value="2" />
|
2026-06-24 16:42:13 +08:00
|
|
|
|
<el-option label="供应商" value="3" />
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template #append>
|
|
|
|
|
|
<el-button :icon="Search" @click="handleSearch" />
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-input>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="search-buttons">
|
2026-06-25 21:49:55 +08:00
|
|
|
|
<el-button type="success" @click="addProduct" v-if="canCreate">新增产品</el-button>
|
2026-06-24 16:42:13 +08:00
|
|
|
|
<el-button type="default" @click="resetSearch">重置</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-06-25 21:49:55 +08:00
|
|
|
|
<div class="filter-row">
|
2026-06-24 16:42:13 +08:00
|
|
|
|
<span class="filter-label">筛选条件:</span>
|
|
|
|
|
|
<el-select v-model="filterType" placeholder="产品类型" clearable style="width: 140px; margin-right: 12px;">
|
2026-06-28 00:36:03 +08:00
|
|
|
|
<el-option label="茶饮原料" value="茶饮原料" />
|
|
|
|
|
|
<el-option label="包装物料" value="包装物料" />
|
|
|
|
|
|
<el-option label="设备器具" value="设备器具" />
|
2026-06-24 16:42:13 +08:00
|
|
|
|
</el-select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-06-25 21:49:55 +08:00
|
|
|
|
<div v-if="showSearchResults" class="products-list">
|
2026-06-24 16:42:13 +08:00
|
|
|
|
<div v-if="loading" class="loading-container">
|
|
|
|
|
|
<el-skeleton :rows="5" animated />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div v-else-if="searchResults.length === 0" class="empty-container">
|
|
|
|
|
|
<el-empty description="暂无数据" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-06-25 21:49:55 +08:00
|
|
|
|
<div v-else>
|
|
|
|
|
|
<div class="list-header">
|
2026-06-28 00:36:03 +08:00
|
|
|
|
<span class="total-count">共找到 {{ total }} 条记录</span>
|
2026-06-25 21:49:55 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<el-table :data="paginatedData" v-loading="loading" border style="width: 100%"
|
|
|
|
|
|
@row-dblclick="handleRowDblClick" row-class-name="clickable-row">
|
2026-06-28 00:36:03 +08:00
|
|
|
|
<el-table-column prop="id" label="产品编号" width="90" />
|
2026-06-24 16:42:13 +08:00
|
|
|
|
<el-table-column prop="name" label="产品名称" min-width="120" />
|
|
|
|
|
|
<el-table-column prop="type" label="产品类型" width="110">
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
<el-tag :type="getTypeTag(row.type)">
|
|
|
|
|
|
{{ productTypeMap[row.type] || row.type }}
|
|
|
|
|
|
</el-tag>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="quantity" label="库存" width="80" />
|
|
|
|
|
|
<el-table-column prop="price" label="单价" width="100">
|
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
|
¥{{ row.price?.toFixed(2) }}
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="unit" label="单位" width="70" />
|
|
|
|
|
|
<el-table-column prop="specification" label="规格" min-width="140" show-overflow-tooltip />
|
|
|
|
|
|
<el-table-column prop="supplier" label="供应商" min-width="150" show-overflow-tooltip />
|
2026-06-26 01:25:15 +08:00
|
|
|
|
<el-table-column label="操作" width="150" fixed="right" v-if="canOperate">
|
2026-06-24 16:42:13 +08:00
|
|
|
|
<template #default="{ row }">
|
2026-06-25 21:49:55 +08:00
|
|
|
|
<el-button link type="primary" @click="editProduct(row)" v-if="canUpdate">编辑</el-button>
|
|
|
|
|
|
<el-button link type="danger" @click="deleteProduct(row.id)" v-if="canDelete">删除</el-button>
|
2026-06-24 16:42:13 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
2026-06-25 21:49:55 +08:00
|
|
|
|
<!-- 分页 -->
|
|
|
|
|
|
<div class="pagination-container">
|
|
|
|
|
|
<el-pagination
|
|
|
|
|
|
v-model:current-page="currentPage"
|
|
|
|
|
|
:page-size="pageSize"
|
2026-06-28 00:36:03 +08:00
|
|
|
|
:total="total"
|
2026-06-25 21:49:55 +08:00
|
|
|
|
layout="prev, pager, next"
|
|
|
|
|
|
prev-text="上一页"
|
|
|
|
|
|
next-text="下一页"
|
|
|
|
|
|
@current-change="handleCurrentChange"
|
|
|
|
|
|
/>
|
2026-06-24 16:42:13 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 新增/编辑产品表单 -->
|
|
|
|
|
|
<div v-if="showAddForm" class="products-info-label">
|
|
|
|
|
|
<el-divider content-position="left">{{ isEditMode ? '编辑产品信息' : '新增产品' }}</el-divider>
|
|
|
|
|
|
<el-form :model="form" label-width="auto" style="max-width: 600px" label-position="top">
|
|
|
|
|
|
<el-form-item label="产品名称" required>
|
|
|
|
|
|
<el-input v-model="form.name" placeholder="请输入产品名称" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
<el-form-item label="产品类型" required>
|
2026-06-25 21:49:55 +08:00
|
|
|
|
<el-select v-model="form.type" placeholder="请选择产品类型" style="width: 100%;">
|
2026-06-28 00:36:03 +08:00
|
|
|
|
<el-option label="茶饮原料" value="茶饮原料" />
|
|
|
|
|
|
<el-option label="包装物料" value="包装物料" />
|
|
|
|
|
|
<el-option label="设备器具" value="设备器具" />
|
2026-06-24 16:42:13 +08:00
|
|
|
|
</el-select>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
<el-form-item label="产品库存" required>
|
|
|
|
|
|
<el-input-number v-model="form.quantity" :min="0" :step="10" style="width: 100%;" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
<el-form-item label="产品单价" required>
|
2026-06-25 21:49:55 +08:00
|
|
|
|
<el-input-number v-model="form.price" :min="0" :step="0.01" :precision="2" style="width: 100%;" />
|
2026-06-24 16:42:13 +08:00
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
<el-form-item label="计量单位">
|
|
|
|
|
|
<el-input v-model="form.unit" placeholder="请输入计量单位" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
2026-06-25 21:49:55 +08:00
|
|
|
|
<el-form-item label="产品规格">
|
2026-06-24 16:42:13 +08:00
|
|
|
|
<el-input v-model="form.specification" placeholder="请输入产品规格/型号" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
<el-form-item label="供应商">
|
2026-06-25 21:49:55 +08:00
|
|
|
|
<el-select v-model="form.supplier" placeholder="请选择供应商" style="width: 100%;" filterable clearable>
|
|
|
|
|
|
<el-option v-for="s in supplierList" :key="s.id" :label="s.name" :value="s.name" />
|
|
|
|
|
|
</el-select>
|
2026-06-24 16:42:13 +08:00
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
2026-06-25 21:49:55 +08:00
|
|
|
|
<el-form-item label="备注">
|
2026-06-24 16:42:13 +08:00
|
|
|
|
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注信息" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
<el-form-item>
|
|
|
|
|
|
<el-button type="primary" @click="saveProduct">保存</el-button>
|
|
|
|
|
|
<el-button type="danger" @click="cancelAdd">取消</el-button>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 产品详情弹窗 -->
|
|
|
|
|
|
<el-dialog v-model="showDetailDialog" title="产品详情" width="650px" @close="closeDetail">
|
|
|
|
|
|
<div v-if="currentDetail" class="detail-content">
|
|
|
|
|
|
<el-descriptions :column="2" border>
|
|
|
|
|
|
<el-descriptions-item label="产品编号">{{ currentDetail.id }}</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="产品名称">{{ currentDetail.name }}</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="产品类型">
|
|
|
|
|
|
<el-tag :type="getTypeTag(currentDetail.type)">
|
|
|
|
|
|
{{ productTypeMap[currentDetail.type] || currentDetail.type }}
|
|
|
|
|
|
</el-tag>
|
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="库存数量">{{ currentDetail.quantity }}</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="产品单价">
|
|
|
|
|
|
<span class="amount-text">¥{{ currentDetail.price?.toFixed(2) }}</span>
|
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="计量单位">{{ currentDetail.unit }}</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="产品规格" :span="2">{{ currentDetail.specification || '无' }}</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="供应商" :span="2">{{ currentDetail.supplier || '无' }}</el-descriptions-item>
|
|
|
|
|
|
<el-descriptions-item label="备注" :span="2">
|
|
|
|
|
|
{{ currentDetail.remark || '无' }}
|
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
|
</el-descriptions>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<el-button @click="closeDetail">关闭</el-button>
|
2026-06-25 21:49:55 +08:00
|
|
|
|
<el-button type="primary" @click="(() => { const d = currentDetail; closeDetail(); editProduct(d) })()">编辑</el-button>
|
2026-06-24 16:42:13 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
2026-06-22 23:06:04 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
2026-06-25 21:49:55 +08:00
|
|
|
|
.products-container {
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.products-search {
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
padding: 15px;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.search-row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.search-buttons {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.filter-row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
margin-top: 15px;
|
|
|
|
|
|
padding-top: 15px;
|
|
|
|
|
|
border-top: 1px solid #eee;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.filter-label {
|
|
|
|
|
|
color: #606266;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
margin-right: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.list-header {
|
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.total-count {
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.loading-container,
|
|
|
|
|
|
.empty-container {
|
|
|
|
|
|
padding: 40px 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.clickable-row {
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pagination-container {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
margin-top: 15px;
|
|
|
|
|
|
padding-top: 15px;
|
|
|
|
|
|
border-top: 1px solid #eee;
|
|
|
|
|
|
}
|
2026-06-22 23:06:04 +08:00
|
|
|
|
|
2026-06-25 21:49:55 +08:00
|
|
|
|
.detail-content {
|
|
|
|
|
|
padding: 10px 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.amount-text {
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #e6a23c;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
}
|
2026-06-24 16:42:13 +08:00
|
|
|
|
</style>
|