|
@@ -0,0 +1,389 @@
|
|
|
|
+<template>
|
|
|
|
+ <div class="min-h-screen bg-gray-100 dark:bg-gray-900">
|
|
|
|
+ <!-- 顶部导航 -->
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 shadow">
|
|
|
|
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
|
|
+ <div class="flex justify-between h-16">
|
|
|
|
+ <div class="flex">
|
|
|
|
+ <div class="flex-shrink-0 flex items-center">
|
|
|
|
+ <h1 class="text-xl font-bold text-gray-900 dark:text-white">原料品类管理</h1>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="flex items-center space-x-4">
|
|
|
|
+ <button @click="showAddModal = true" class="btn-primary">
|
|
|
|
+ 新增
|
|
|
|
+ </button>
|
|
|
|
+ <button @click="editSelectedItems" class="btn-secondary" :disabled="selectedItems.length === 0">
|
|
|
|
+ 修改
|
|
|
|
+ </button>
|
|
|
|
+ <button @click="deleteSelectedItems" class="btn-danger" :disabled="selectedItems.length === 0">
|
|
|
|
+ 删除
|
|
|
|
+ </button>
|
|
|
|
+ <button @click="exportData" class="btn-secondary">
|
|
|
|
+ 导出
|
|
|
|
+ </button>
|
|
|
|
+ <button @click="importData" class="btn-secondary">
|
|
|
|
+ 导入
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 主要内容 -->
|
|
|
|
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
|
|
+ <!-- 搜索和筛选 -->
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow mb-8">
|
|
|
|
+ <div class="p-6">
|
|
|
|
+ <div class="grid grid-cols-1 md:grid-cols-5 gap-4">
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">物料编码</label>
|
|
|
|
+ <input
|
|
|
|
+ v-model="filters.materialCode"
|
|
|
|
+ type="text"
|
|
|
|
+ placeholder="物料编码"
|
|
|
|
+ class="mt-1 form-input"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">物料组</label>
|
|
|
|
+ <input
|
|
|
|
+ v-model="filters.materialGroup"
|
|
|
|
+ type="text"
|
|
|
|
+ placeholder="物料组"
|
|
|
|
+ class="mt-1 form-input"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">类别一</label>
|
|
|
|
+ <input
|
|
|
|
+ v-model="filters.categoryOne"
|
|
|
|
+ type="text"
|
|
|
|
+ placeholder="类别一"
|
|
|
|
+ class="mt-1 form-input"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">类别二</label>
|
|
|
|
+ <input
|
|
|
|
+ v-model="filters.categoryTwo"
|
|
|
|
+ type="text"
|
|
|
|
+ placeholder="类别二"
|
|
|
|
+ class="mt-1 form-input"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">原料大类</label>
|
|
|
|
+ <input
|
|
|
|
+ v-model="filters.rawMaterialCategory"
|
|
|
|
+ type="text"
|
|
|
|
+ placeholder="原料大类"
|
|
|
|
+ class="mt-1 form-input"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="flex justify-end mt-4">
|
|
|
|
+ <button @click="resetFilters" class="btn-secondary mr-4">
|
|
|
|
+ 重置
|
|
|
|
+ </button>
|
|
|
|
+ <button @click="applyFilters" class="btn-primary">
|
|
|
|
+ 查询
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 原料品类列表 -->
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 rounded-lg shadow">
|
|
|
|
+ <div class="overflow-x-auto">
|
|
|
|
+ <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
|
|
+ <thead class="bg-gray-50 dark:bg-gray-700">
|
|
|
|
+ <tr>
|
|
|
|
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
|
|
|
+ <input
|
|
|
|
+ type="checkbox"
|
|
|
|
+ v-model="selectAll"
|
|
|
|
+ @change="toggleSelectAll"
|
|
|
|
+ />
|
|
|
|
+ </th>
|
|
|
|
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">序号</th>
|
|
|
|
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">物料编码</th>
|
|
|
|
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">物料组</th>
|
|
|
|
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">类别一</th>
|
|
|
|
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">类别二</th>
|
|
|
|
+ <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">原料大类</th>
|
|
|
|
+ </tr>
|
|
|
|
+ </thead>
|
|
|
|
+ <tbody class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
|
|
|
+ <tr v-for="(item, index) in filteredItems" :key="item.id">
|
|
|
|
+ <td class="px-6 py-4 whitespace-nowrap">
|
|
|
|
+ <input
|
|
|
|
+ type="checkbox"
|
|
|
|
+ v-model="selectedItems"
|
|
|
|
+ :value="item"
|
|
|
|
+ />
|
|
|
|
+ </td>
|
|
|
|
+ <td class="px-6 py-4 whitespace-nowrap">
|
|
|
|
+ <div class="text-sm font-medium text-gray-900 dark:text-white">{{ index + 1 }}</div>
|
|
|
|
+ </td>
|
|
|
|
+ <td class="px-6 py-4 whitespace-nowrap">
|
|
|
|
+ <div class="text-sm text-gray-900 dark:text-white">{{ item.materialCode }}</div>
|
|
|
|
+ </td>
|
|
|
|
+ <td class="px-6 py-4 whitespace-nowrap">
|
|
|
|
+ <div class="text-sm text-gray-900 dark:text-white">{{ item.materialGroup }}</div>
|
|
|
|
+ </td>
|
|
|
|
+ <td class="px-6 py-4 whitespace-nowrap">
|
|
|
|
+ <div class="text-sm text-gray-900 dark:text-white">{{ item.categoryOne }}</div>
|
|
|
|
+ </td>
|
|
|
|
+ <td class="px-6 py-4 whitespace-nowrap">
|
|
|
|
+ <div class="text-sm text-gray-900 dark:text-white">{{ item.categoryTwo }}</div>
|
|
|
|
+ </td>
|
|
|
|
+ <td class="px-6 py-4 whitespace-nowrap">
|
|
|
|
+ <div class="text-sm text-gray-900 dark:text-white">{{ item.rawMaterialCategory }}</div>
|
|
|
|
+ </td>
|
|
|
|
+ </tr>
|
|
|
|
+ </tbody>
|
|
|
|
+ </table>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 新增/编辑原料品类模态框 -->
|
|
|
|
+ <div v-if="showAddModal" class="fixed inset-0 z-10 overflow-y-auto">
|
|
|
|
+ <div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
|
|
|
+ <div class="fixed inset-0 transition-opacity" aria-hidden="true">
|
|
|
|
+ <div class="absolute inset-0 bg-gray-500 dark:bg-gray-900 opacity-75"></div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="inline-block align-bottom bg-white dark:bg-gray-800 rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
|
|
|
|
+ <div class="bg-white dark:bg-gray-800 px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
|
|
|
+ <h3 class="text-lg font-medium text-gray-900 dark:text-white mb-4">
|
|
|
|
+ {{ editingItem ? '编辑原料品类' : '新增原料品类' }}
|
|
|
|
+ </h3>
|
|
|
|
+ <form @submit.prevent="submitForm">
|
|
|
|
+ <div class="grid grid-cols-1 gap-4">
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">物料编码</label>
|
|
|
|
+ <input v-model="form.materialCode" type="text" class="mt-1 form-input" required />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">物料组</label>
|
|
|
|
+ <input v-model="form.materialGroup" type="text" class="mt-1 form-input" required />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">类别一</label>
|
|
|
|
+ <input v-model="form.categoryOne" type="text" class="mt-1 form-input" required />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">类别二</label>
|
|
|
|
+ <input v-model="form.categoryTwo" type="text" class="mt-1 form-input" required />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">原料大类</label>
|
|
|
|
+ <input v-model="form.rawMaterialCategory" type="text" class="mt-1 form-input" required />
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">备注</label>
|
|
|
|
+ <textarea v-model="form.remark" class="mt-1 form-textarea" rows="3"></textarea>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="mt-6 flex justify-end space-x-3">
|
|
|
|
+ <button type="button" @click="showAddModal = false" class="btn-secondary">
|
|
|
|
+ 取消
|
|
|
|
+ </button>
|
|
|
|
+ <button type="submit" class="btn-primary">
|
|
|
|
+ 保存
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </form>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<script setup>
|
|
|
|
+import { ref, computed, watch } from 'vue'
|
|
|
|
+
|
|
|
|
+// 数据定义
|
|
|
|
+const categories = ref([
|
|
|
|
+ {
|
|
|
|
+ id: 1,
|
|
|
|
+ materialCode: 'MC001',
|
|
|
|
+ materialGroup: 'MG001',
|
|
|
|
+ categoryOne: 'C1',
|
|
|
|
+ categoryTwo: 'C2',
|
|
|
|
+ rawMaterialCategory: 'RMC1',
|
|
|
|
+ remark: ''
|
|
|
|
+ },
|
|
|
|
+ // 更多示例数据...
|
|
|
|
+])
|
|
|
|
+
|
|
|
|
+const filters = ref({
|
|
|
|
+ materialCode: '',
|
|
|
|
+ materialGroup: '',
|
|
|
|
+ categoryOne: '',
|
|
|
|
+ categoryTwo: '',
|
|
|
|
+ rawMaterialCategory: ''
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+const selectedItems = ref([])
|
|
|
|
+const showAddModal = ref(false)
|
|
|
|
+const editingItem = ref(null)
|
|
|
|
+
|
|
|
|
+const form = ref({
|
|
|
|
+ materialCode: '',
|
|
|
|
+ materialGroup: '',
|
|
|
|
+ categoryOne: '',
|
|
|
|
+ categoryTwo: '',
|
|
|
|
+ rawMaterialCategory: '',
|
|
|
|
+ remark: ''
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 计算属性
|
|
|
|
+const filteredItems = computed(() => {
|
|
|
|
+ return categories.value.filter(item => {
|
|
|
|
+ const matchesMaterialCode = !filters.value.materialCode || item.materialCode.includes(filters.value.materialCode)
|
|
|
|
+ const matchesMaterialGroup = !filters.value.materialGroup || item.materialGroup.includes(filters.value.materialGroup)
|
|
|
|
+ const matchesCategoryOne = !filters.value.categoryOne || item.categoryOne.includes(filters.value.categoryOne)
|
|
|
|
+ const matchesCategoryTwo = !filters.value.categoryTwo || item.categoryTwo.includes(filters.value.categoryTwo)
|
|
|
|
+ const matchesRawMaterialCategory = !filters.value.rawMaterialCategory || item.rawMaterialCategory.includes(filters.value.rawMaterialCategory)
|
|
|
|
+ return matchesMaterialCode && matchesMaterialGroup && matchesCategoryOne && matchesCategoryTwo && matchesRawMaterialCategory
|
|
|
|
+ })
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+const selectAll = ref(false)
|
|
|
|
+
|
|
|
|
+// 监听 selectedItems 的变化来更新 selectAll 状态
|
|
|
|
+watch(selectedItems, (newVal) => {
|
|
|
|
+ selectAll.value = newVal.length === categories.value.length
|
|
|
|
+}, { deep: true })
|
|
|
|
+
|
|
|
|
+// 方法
|
|
|
|
+const toggleSelectAll = () => {
|
|
|
|
+ if (selectAll.value) {
|
|
|
|
+ selectedItems.value = [...categories.value]
|
|
|
|
+ } else {
|
|
|
|
+ selectedItems.value = []
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const resetFilters = () => {
|
|
|
|
+ filters.value = {
|
|
|
|
+ materialCode: '',
|
|
|
|
+ materialGroup: '',
|
|
|
|
+ categoryOne: '',
|
|
|
|
+ categoryTwo: '',
|
|
|
|
+ rawMaterialCategory: ''
|
|
|
|
+ }
|
|
|
|
+ applyFilters()
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const applyFilters = () => {
|
|
|
|
+ // 过滤逻辑已经在计算属性中实现
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const editSelectedItems = () => {
|
|
|
|
+ if (selectedItems.value.length === 1) {
|
|
|
|
+ editingItem.value = selectedItems.value[0]
|
|
|
|
+ form.value = { ...editingItem.value }
|
|
|
|
+ showAddModal.value = true
|
|
|
|
+ } else {
|
|
|
|
+ alert('请选择一个项目进行编辑。')
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const deleteSelectedItems = () => {
|
|
|
|
+ if (confirm('确定要删除这些原料品类吗?')) {
|
|
|
|
+ categories.value = categories.value.filter(item => !selectedItems.value.includes(item))
|
|
|
|
+ selectedItems.value = []
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const submitForm = () => {
|
|
|
|
+ if (editingItem.value) {
|
|
|
|
+ // 更新现有记录
|
|
|
|
+ const index = categories.value.findIndex(item => item.id === editingItem.value.id)
|
|
|
|
+ if (index !== -1) {
|
|
|
|
+ categories.value[index] = { ...form.value, id: editingItem.value.id }
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ // 添加新记录
|
|
|
|
+ const newItem = {
|
|
|
|
+ ...form.value,
|
|
|
|
+ id: categories.value.length + 1
|
|
|
|
+ }
|
|
|
|
+ categories.value.push(newItem)
|
|
|
|
+ }
|
|
|
|
+ showAddModal.value = false
|
|
|
|
+ resetForm()
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const resetForm = () => {
|
|
|
|
+ form.value = {
|
|
|
|
+ materialCode: '',
|
|
|
|
+ materialGroup: '',
|
|
|
|
+ categoryOne: '',
|
|
|
|
+ categoryTwo: '',
|
|
|
|
+ rawMaterialCategory: '',
|
|
|
|
+ remark: ''
|
|
|
|
+ }
|
|
|
|
+ editingItem.value = null
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const exportData = () => {
|
|
|
|
+ const data = JSON.stringify(categories.value, null, 2)
|
|
|
|
+ const blob = new Blob([data], { type: 'application/json' })
|
|
|
|
+ const url = URL.createObjectURL(blob)
|
|
|
|
+ const a = document.createElement('a')
|
|
|
|
+ a.href = url
|
|
|
|
+ a.download = 'raw_material_categories.json'
|
|
|
|
+ document.body.appendChild(a)
|
|
|
|
+ a.click()
|
|
|
|
+ document.body.removeChild(a)
|
|
|
|
+ URL.revokeObjectURL(url)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const importData = () => {
|
|
|
|
+ const input = document.createElement('input')
|
|
|
|
+ input.type = 'file'
|
|
|
|
+ input.accept = '.json'
|
|
|
|
+ input.onchange = (event) => {
|
|
|
|
+ const file = event.target.files[0]
|
|
|
|
+ if (file) {
|
|
|
|
+ const reader = new FileReader()
|
|
|
|
+ reader.onload = (e) => {
|
|
|
|
+ try {
|
|
|
|
+ const importedData = JSON.parse(e.target.result)
|
|
|
|
+ categories.value = importedData
|
|
|
|
+ } catch (error) {
|
|
|
|
+ alert('导入文件格式错误,请检查文件。')
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ reader.readAsText(file)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ input.click()
|
|
|
|
+}
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<style scoped>
|
|
|
|
+.btn-primary {
|
|
|
|
+ @apply bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600;
|
|
|
|
+}
|
|
|
|
+.btn-secondary {
|
|
|
|
+ @apply bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600;
|
|
|
|
+}
|
|
|
|
+.btn-danger {
|
|
|
|
+ @apply bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600;
|
|
|
|
+}
|
|
|
|
+.form-input {
|
|
|
|
+ @apply w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm;
|
|
|
|
+}
|
|
|
|
+.form-select {
|
|
|
|
+ @apply w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm;
|
|
|
|
+}
|
|
|
|
+.form-textarea {
|
|
|
|
+ @apply w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm;
|
|
|
|
+}
|
|
|
|
+</style>
|