|
@@ -0,0 +1,382 @@
|
|
|
|
+<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">
|
|
|
|
+ 修改
|
|
|
|
+ </button>
|
|
|
|
+ <button @click="deleteSelectedItems" class="btn-danger">
|
|
|
|
+ 删除
|
|
|
|
+ </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="search.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="search.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="search.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="search.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="search.rawMaterialClass"
|
|
|
|
+ type="text"
|
|
|
|
+ placeholder="原料大类"
|
|
|
|
+ class="mt-1 form-input"
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ <div class="col-span-full flex justify-end mt-4">
|
|
|
|
+ <button @click="resetSearch" class="btn-secondary mr-2">
|
|
|
|
+ 重置
|
|
|
|
+ </button>
|
|
|
|
+ <button @click="performSearch" class="btn-primary">
|
|
|
|
+ 查询
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </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 filteredCategories" :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 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.rawMaterialClass }}</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.rawMaterialClass" type="text" class="mt-1 form-input" required />
|
|
|
|
+ </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 } from 'vue'
|
|
|
|
+
|
|
|
|
+// 数据定义
|
|
|
|
+const categories = ref([
|
|
|
|
+ {
|
|
|
|
+ id: 1,
|
|
|
|
+ materialCode: 'MC001',
|
|
|
|
+ materialGroup: 'MG001',
|
|
|
|
+ categoryOne: 'C1',
|
|
|
|
+ categoryTwo: 'C2',
|
|
|
|
+ rawMaterialClass: 'RMC001'
|
|
|
|
+ },
|
|
|
|
+ // 更多示例数据...
|
|
|
|
+])
|
|
|
|
+
|
|
|
|
+const search = ref({
|
|
|
|
+ materialCode: '',
|
|
|
|
+ materialGroup: '',
|
|
|
|
+ categoryOne: '',
|
|
|
|
+ categoryTwo: '',
|
|
|
|
+ rawMaterialClass: ''
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+const selectedItems = ref([])
|
|
|
|
+const showAddModal = ref(false)
|
|
|
|
+const editingItem = ref(null)
|
|
|
|
+
|
|
|
|
+const form = ref({
|
|
|
|
+ materialCode: '',
|
|
|
|
+ materialGroup: '',
|
|
|
|
+ categoryOne: '',
|
|
|
|
+ categoryTwo: '',
|
|
|
|
+ rawMaterialClass: ''
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 计算属性
|
|
|
|
+const selectAll = computed({
|
|
|
|
+ get() {
|
|
|
|
+ return selectedItems.value.length === categories.value.length && categories.value.length > 0
|
|
|
|
+ },
|
|
|
|
+ set(value) {
|
|
|
|
+ selectedItems.value = value ? categories.value : []
|
|
|
|
+ }
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+const filteredCategories = computed(() => {
|
|
|
|
+ return categories.value.filter(item => {
|
|
|
|
+ const matchesMaterialCode = !search.value.materialCode || item.materialCode.includes(search.value.materialCode)
|
|
|
|
+ const matchesMaterialGroup = !search.value.materialGroup || item.materialGroup.includes(search.value.materialGroup)
|
|
|
|
+ const matchesCategoryOne = !search.value.categoryOne || item.categoryOne.includes(search.value.categoryOne)
|
|
|
|
+ const matchesCategoryTwo = !search.value.categoryTwo || item.categoryTwo.includes(search.value.categoryTwo)
|
|
|
|
+ const matchesRawMaterialClass = !search.value.rawMaterialClass || item.rawMaterialClass.includes(search.value.rawMaterialClass)
|
|
|
|
+ return matchesMaterialCode && matchesMaterialGroup && matchesCategoryOne && matchesCategoryTwo && matchesRawMaterialClass
|
|
|
|
+ })
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// 方法
|
|
|
|
+const performSearch = () => {
|
|
|
|
+ // 搜索逻辑已经在 filteredCategories 中实现
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const resetSearch = () => {
|
|
|
|
+ search.value = {
|
|
|
|
+ materialCode: '',
|
|
|
|
+ materialGroup: '',
|
|
|
|
+ categoryOne: '',
|
|
|
|
+ categoryTwo: '',
|
|
|
|
+ rawMaterialClass: ''
|
|
|
|
+ }
|
|
|
|
+ selectedItems.value = []
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const toggleSelectAll = () => {
|
|
|
|
+ if (selectAll.value) {
|
|
|
|
+ selectedItems.value = categories.value
|
|
|
|
+ } else {
|
|
|
|
+ selectedItems.value = []
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const editSelectedItems = () => {
|
|
|
|
+ if (selectedItems.value.length === 1) {
|
|
|
|
+ editingItem.value = selectedItems.value[0]
|
|
|
|
+ form.value = { ...editingItem.value }
|
|
|
|
+ showAddModal.value = true
|
|
|
|
+ } else {
|
|
|
|
+ alert('请选择一个项目进行编辑')
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+const deleteSelectedItems = () => {
|
|
|
|
+ if (selectedItems.value.length > 0) {
|
|
|
|
+ if (confirm('确定要删除这些原料品类吗?')) {
|
|
|
|
+ categories.value = categories.value.filter(item => !selectedItems.value.includes(item))
|
|
|
|
+ selectedItems.value = []
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ alert('请选择要删除的项目')
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+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: '',
|
|
|
|
+ rawMaterialClass: ''
|
|
|
|
+ }
|
|
|
|
+ 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-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded;
|
|
|
|
+}
|
|
|
|
+.btn-secondary {
|
|
|
|
+ @apply bg-gray-600 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded;
|
|
|
|
+}
|
|
|
|
+.btn-danger {
|
|
|
|
+ @apply bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 rounded;
|
|
|
|
+}
|
|
|
|
+.form-input {
|
|
|
|
+ @apply appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm;
|
|
|
|
+}
|
|
|
|
+.form-select {
|
|
|
|
+ @apply appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm;
|
|
|
|
+}
|
|
|
|
+.form-textarea {
|
|
|
|
+ @apply appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm;
|
|
|
|
+}
|
|
|
|
+</style>
|