From 3b207cb93a1bb14d9b84ee216532a0388c56f147 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期一, 29 六月 2026 14:45:05 +0800
Subject: [PATCH] 银川 1.添加产品维护页面

---
 mdm_frontend_change.md                   |  523 +++++++++++++++++++++++
 src/views/basicData/mdm/data.ts          |  351 +++++++++++++++
 src/views/basicData/mdm/index.vue        |  214 +++++++++
 src/views/basicData/mdm/modules/form.vue |   80 +++
 src/packages/constants/src/dict-enum.ts  |    7 
 src/api/mdm/item/index.ts                |   88 ++++
 6 files changed, 1,263 insertions(+), 0 deletions(-)

diff --git a/mdm_frontend_change.md b/mdm_frontend_change.md
new file mode 100644
index 0000000..3e9c175
--- /dev/null
+++ b/mdm_frontend_change.md
@@ -0,0 +1,523 @@
+# 鐗╂枡涓绘暟鎹ā鍧� (MDM) 鍓嶇鍙樻洿鏂囨。
+
+## 涓�銆佹柊澧為〉闈�
+
+### 1.1 鐗╂枡涓绘暟鎹鐞�
+
+#### 娑夊強椤甸潰
+
+- 鐗╂枡鍒楄〃椤� (`views/mdm/item/index.vue`)
+- 鐗╂枡鏂板/缂栬緫寮圭獥 (`views/mdm/item/ItemForm.vue`)
+- 鐗╂枡璇︽儏椤� (`views/mdm/item/detail.vue`)
+
+#### API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /mdm/item/page | 鐗╂枡鍒嗛〉鏌ヨ |
+| GET | /mdm/item/list | 鐗╂枡鍒楄〃鏌ヨ |
+| GET | /mdm/item/get | 鑾峰彇鐗╂枡璇︽儏 |
+| POST | /mdm/item/create | 鍒涘缓鐗╂枡 |
+| PUT | /mdm/item/update | 鏇存柊鐗╂枡 |
+| DELETE | /mdm/item/delete | 鍒犻櫎鐗╂枡 |
+| PUT | /mdm/item/update-status | 鏇存柊鐗╂枡鐘舵�� |
+
+**璇锋眰鍙傛暟锛堢墿鏂欐煡璇級锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| code | String | 鍚� | 鐗╂枡缂栫爜 |
+| name | String | 鍚� | 鐗╂枡鍚嶇О |
+| categoryId | Long | 鍚� | 鍒嗙被ID |
+| itemType | Integer | 鍚� | 鐗╂枡绫诲瀷(1:鍘熸枡,2:鍗婃垚鍝�,3:鎴愬搧,4:杈呮枡) |
+| status | Integer | 鍚� | 鐘舵�� |
+
+**鍝嶅簲锛�**
+
+```json
+{
+  "code": 200,
+  "data": {
+    "list": [
+      {
+        "id": 1,
+        "code": "M001",
+        "name": "鐗╂枡鍚嶇О",
+        "barCode": "鏉$爜",
+        "specification": "瑙勬牸鍨嬪彿",
+        "categoryId": 10,
+        "categoryName": "鍒嗙被鍚嶇О",
+        "unitMeasureId": 1,
+        "unitMeasureName": "鍗曚綅鍚嶇О",
+        "brandId": 1,
+        "brandName": "鍝佺墝鍚嶇О",
+        "itemType": 1,
+        "status": 0,
+        "purchasePrice": 100.00,
+        "salesPrice": 150.00,
+        "costPrice": 80.00,
+        "safetyStock": 50.00,
+        "minStock": 20.00,
+        "maxStock": 200.00,
+        "isBatchManaged": true,
+        "expiryDay": 365,
+        "remark": "澶囨敞"
+      }
+    ],
+    "total": 100
+  }
+}
+```
+
+#### 鍓嶇淇敼鐐�
+
+**1. 鐗╂枡鍒楄〃椤甸潰缁撴瀯锛�**
+
+```html
+<template>
+  <ContentWrap>
+    <!-- 鎼滅储鍖哄煙 -->
+    <el-form :model="queryParams" ref="queryFormRef" :inline="true">
+      <el-form-item label="鐗╂枡缂栫爜" prop="code">
+        <el-input v-model="queryParams.code" placeholder="璇疯緭鍏ョ墿鏂欑紪鐮�" />
+      </el-form-item>
+      <el-form-item label="鐗╂枡鍚嶇О" prop="name">
+        <el-input v-model="queryParams.name" placeholder="璇疯緭鍏ョ墿鏂欏悕绉�" />
+      </el-form-item>
+      <el-form-item label="鐗╂枡鍒嗙被" prop="categoryId">
+        <el-tree-select v-model="queryParams.categoryId" :data="categoryTree" />
+      </el-form-item>
+      <el-form-item label="鐗╂枡绫诲瀷" prop="itemType">
+        <el-select v-model="queryParams.itemType" placeholder="璇烽�夋嫨鐗╂枡绫诲瀷">
+          <el-option label="鍘熸枡" :value="1" />
+          <el-option label="鍗婃垚鍝�" :value="2" />
+          <el-option label="鎴愬搧" :value="3" />
+          <el-option label="杈呮枡" :value="4" />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" @click="handleQuery">鎼滅储</el-button>
+        <el-button @click="resetQuery">閲嶇疆</el-button>
+      </el-form-item>
+    </el-form>
+
+    <!-- 鎿嶄綔鎸夐挳 -->
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" plain @click="handleCreate">鏂板</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="success" plain @click="handleExport">瀵煎嚭</el-button>
+      </el-col>
+    </el-row>
+
+    <!-- 琛ㄦ牸 -->
+    <el-table v-loading="loading" :data="list">
+      <el-table-column label="鐗╂枡缂栫爜" prop="code" width="120" />
+      <el-table-column label="鐗╂枡鍚嶇О" prop="name" width="180" />
+      <el-table-column label="瑙勬牸鍨嬪彿" prop="specification" width="150" />
+      <el-table-column label="鍒嗙被" prop="categoryName" width="100" />
+      <el-table-column label="鍗曚綅" prop="unitMeasureName" width="80" />
+      <el-table-column label="鐗╂枡绫诲瀷" prop="itemType" width="80">
+        <template #default="{ row }">
+          <dict-tag :type="DICT_TYPE.MDM_ITEM_TYPE" :value="row.itemType" />
+        </template>
+      </el-table-column>
+      <el-table-column label="閲囪喘浠�" prop="purchasePrice" width="100" />
+      <el-table-column label="閿�鍞环" prop="salesPrice" width="100" />
+      <el-table-column label="鎵规绠$悊" prop="isBatchManaged" width="80">
+        <template #default="{ row }">
+          <el-tag v-if="row.isBatchManaged" type="success">鏄�</el-tag>
+          <el-tag v-else type="info">鍚�</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="鐘舵��" prop="status" width="80">
+        <template #default="{ row }">
+          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
+        </template>
+      </el-table-column>
+      <el-table-column label="鎿嶄綔" width="180" fixed="right">
+        <template #default="{ row }">
+          <el-button link type="primary" @click="handleUpdate(row)">缂栬緫</el-button>
+          <el-button link type="danger" @click="handleDelete(row)">鍒犻櫎</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 鍒嗛〉 -->
+    <Pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" />
+  </ContentWrap>
+</template>
+```
+
+**2. 鐗╂枡琛ㄥ崟寮圭獥锛�**
+
+```html
+<el-dialog v-model="dialogVisible" title="鐗╂枡淇℃伅" width="800px">
+  <el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px">
+    <el-row :gutter="20">
+      <el-col :span="12">
+        <el-form-item label="鐗╂枡缂栫爜" prop="code">
+          <el-input v-model="formData.code" placeholder="璇疯緭鍏ョ墿鏂欑紪鐮�" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="12">
+        <el-form-item label="鐗╂枡鍚嶇О" prop="name">
+          <el-input v-model="formData.name" placeholder="璇疯緭鍏ョ墿鏂欏悕绉�" />
+        </el-form-item>
+      </el-col>
+    </el-row>
+    <el-row :gutter="20">
+      <el-col :span="12">
+        <el-form-item label="鏉$爜" prop="barCode">
+          <el-input v-model="formData.barCode" placeholder="璇疯緭鍏ユ潯鐮�" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="12">
+        <el-form-item label="瑙勬牸鍨嬪彿" prop="specification">
+          <el-input v-model="formData.specification" placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�" />
+        </el-form-item>
+      </el-col>
+    </el-row>
+    <el-row :gutter="20">
+      <el-col :span="12">
+        <el-form-item label="鐗╂枡鍒嗙被" prop="categoryId">
+          <el-tree-select v-model="formData.categoryId" :data="categoryTree" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="12">
+        <el-form-item label="璁¢噺鍗曚綅" prop="unitMeasureId">
+          <el-select v-model="formData.unitMeasureId" placeholder="璇烽�夋嫨鍗曚綅">
+            <el-option v-for="unit in unitList" :key="unit.id" :label="unit.name" :value="unit.id" />
+          </el-select>
+        </el-form-item>
+      </el-col>
+    </el-row>
+    <el-row :gutter="20">
+      <el-col :span="12">
+        <el-form-item label="鍝佺墝" prop="brandId">
+          <el-select v-model="formData.brandId" placeholder="璇烽�夋嫨鍝佺墝">
+            <el-option v-for="brand in brandList" :key="brand.id" :label="brand.name" :value="brand.id" />
+          </el-select>
+        </el-form-item>
+      </el-col>
+      <el-col :span="12">
+        <el-form-item label="鐗╂枡绫诲瀷" prop="itemType">
+          <el-select v-model="formData.itemType" placeholder="璇烽�夋嫨鐗╂枡绫诲瀷">
+            <el-option label="鍘熸枡" :value="1" />
+            <el-option label="鍗婃垚鍝�" :value="2" />
+            <el-option label="鎴愬搧" :value="3" />
+            <el-option label="杈呮枡" :value="4" />
+          </el-select>
+        </el-form-item>
+      </el-col>
+    </el-row>
+    <!-- 浠锋牸淇℃伅 -->
+    <el-row :gutter="20">
+      <el-col :span="8">
+        <el-form-item label="閲囪喘浠�" prop="purchasePrice">
+          <el-input-number v-model="formData.purchasePrice" :precision="2" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="8">
+        <el-form-item label="閿�鍞环" prop="salesPrice">
+          <el-input-number v-model="formData.salesPrice" :precision="2" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="8">
+        <el-form-item label="鎴愭湰浠�" prop="costPrice">
+          <el-input-number v-model="formData.costPrice" :precision="2" />
+        </el-form-item>
+      </el-col>
+    </el-row>
+    <!-- 搴撳瓨绠$悊 -->
+    <el-row :gutter="20">
+      <el-col :span="12">
+        <el-form-item label="瀹夊叏搴撳瓨" prop="safetyStock">
+          <el-input-number v-model="formData.safetyStock" :precision="2" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="12">
+        <el-form-item label="鎵规绠$悊" prop="isBatchManaged">
+          <el-switch v-model="formData.isBatchManaged" />
+        </el-form-item>
+      </el-col>
+    </el-row>
+    <el-form-item label="澶囨敞" prop="remark">
+      <el-input v-model="formData.remark" type="textarea" :rows="3" />
+    </el-form-item>
+  </el-form>
+  <template #footer>
+    <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+    <el-button type="primary" @click="submitForm">纭畾</el-button>
+  </template>
+</el-dialog>
+```
+
+**3. 鏁版嵁瀹氫箟锛�**
+
+```js
+<script setup>
+import { ref, reactive, onMounted } from 'vue'
+import * as ItemApi from '@/api/mdm/item'
+
+const loading = ref(false)
+const list = ref([])
+const total = ref(0)
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  code: '',
+  name: '',
+  categoryId: null,
+  itemType: null,
+  status: null
+})
+
+const dialogVisible = ref(false)
+const formData = ref({
+  id: undefined,
+  code: '',
+  name: '',
+  barCode: '',
+  specification: '',
+  categoryId: null,
+  unitMeasureId: null,
+  brandId: null,
+  itemType: 1,
+  purchasePrice: null,
+  salesPrice: null,
+  costPrice: null,
+  safetyStock: null,
+  isBatchManaged: false,
+  remark: ''
+})
+
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await ItemApi.getItemPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+const handleCreate = () => {
+  formData.value = { ...initialFormData }
+  dialogVisible.value = true
+}
+
+const handleUpdate = (row) => {
+  formData.value = { ...row }
+  dialogVisible.value = true
+}
+
+const submitForm = async () => {
+  await formRef.value.validate()
+  if (formData.value.id) {
+    await ItemApi.updateItem(formData.value)
+  } else {
+    await ItemApi.createItem(formData.value)
+  }
+  dialogVisible.value = false
+  getList()
+}
+
+onMounted(() => {
+  getList()
+})
+</script>
+```
+
+---
+
+### 1.2 鐗╂枡鍒嗙被绠$悊
+
+#### 娑夊強椤甸潰
+
+- 鐗╂枡鍒嗙被绠$悊椤� (`views/mdm/category/index.vue`)
+
+#### API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /mdm/category/list | 鍒嗙被鍒楄〃鏌ヨ |
+| GET | /mdm/category/get | 鑾峰彇鍒嗙被璇︽儏 |
+| POST | /mdm/category/create | 鍒涘缓鍒嗙被 |
+| PUT | /mdm/category/update | 鏇存柊鍒嗙被 |
+| DELETE | /mdm/category/delete | 鍒犻櫎鍒嗙被 |
+
+---
+
+### 1.3 璁¢噺鍗曚綅绠$悊
+
+#### 娑夊強椤甸潰
+
+- 璁¢噺鍗曚綅绠$悊椤� (`views/mdm/unit/index.vue`)
+
+#### API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /mdm/unit/page | 鍗曚綅鍒嗛〉鏌ヨ |
+| GET | /mdm/unit/list | 鍗曚綅鍒楄〃鏌ヨ |
+| POST | /mdm/unit/create | 鍒涘缓鍗曚綅 |
+| PUT | /mdm/unit/update | 鏇存柊鍗曚綅 |
+| DELETE | /mdm/unit/delete | 鍒犻櫎鍗曚綅 |
+
+---
+
+### 1.4 鍝佺墝绠$悊
+
+#### 娑夊強椤甸潰
+
+- 鍝佺墝绠$悊椤� (`views/mdm/brand/index.vue`)
+
+#### API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /mdm/brand/page | 鍝佺墝鍒嗛〉鏌ヨ |
+| GET | /mdm/brand/list | 鍝佺墝鍒楄〃鏌ヨ |
+| POST | /mdm/brand/create | 鍒涘缓鍝佺墝 |
+| PUT | /mdm/brand/update | 鏇存柊鍝佺墝 |
+| DELETE | /mdm/brand/delete | 鍒犻櫎鍝佺墝 |
+
+---
+
+## 浜屻�佷慨鏀归〉闈�
+
+### 2.1 ERP 浜у搧绠$悊椤甸潰
+
+#### 淇敼鍐呭
+
+**鍘熼〉闈�**锛歚views/erp/product/index.vue`
+
+**淇敼鐐�**锛�
+1. API 鎺ュ彛浠� `/erp/product/*` 鏀逛负璋冪敤 `/mdm/item/*`
+2. 鏁版嵁瀛楁鏄犲皠璋冩暣锛坄productId` 鈫� `itemId`锛�
+3. 澧炲姞鐗╂枡绫诲瀷绛涢�夛紙鍘熸枡/鍗婃垚鍝�/鎴愬搧/杈呮枡锛�
+
+**娉ㄦ剰浜嬮」**锛�
+- ERP 浜у搧椤甸潰鏁版嵁鏉ユ簮浠� MDM 鐗╂枡鑾峰彇
+- 淇濈暀 ERP 鐗规湁瀛楁锛堥噰璐环銆侀攢鍞环銆佹渶浣庝环锛夌殑缂栬緫鑳藉姏
+
+---
+
+### 2.2 MES 鐗╂枡绠$悊椤甸潰
+
+#### 淇敼鍐呭
+
+**鍘熼〉闈�**锛歚views/mes/md/item/index.vue`
+
+**淇敼鐐�**锛�
+1. API 鎺ュ彛浠� `/mes/md-item/*` 鏀逛负璋冪敤 `/mdm/item/*`
+2. 鏁版嵁瀛楁鏄犲皠璋冩暣锛坄itemTypeId` 鈫� `categoryId`锛�
+3. 鎵规閰嶇疆浠庣嫭绔嬮〉闈㈡敼涓虹墿鏂欒鎯呴〉瀛愭ā鍧�
+
+**娉ㄦ剰浜嬮」**锛�
+- MES 鐗╂枡椤甸潰鏁版嵁鏉ユ簮浠� MDM 鐗╂枡鑾峰彇
+- 鎵规灞炴�ч厤缃繚鐣欏湪 MDM 妯″潡绠$悊
+
+---
+
+### 2.3 WMS 鍟嗗搧绠$悊椤甸潰
+
+#### 淇敼鍐呭
+
+**鍘熼〉闈�**锛歚views/wms/item/index.vue`
+
+**淇敼鐐�**锛�
+1. API 鎺ュ彛浠� `/wms/item/*` 鏀逛负璋冪敤 `/mdm/item/*`
+2. SKU 绠$悊鏀逛负鐗╂枡璇︽儏椤靛瓙妯″潡
+3. 搴撳瓨鏌ヨ鏀圭敤缁熶竴鎺ュ彛 `/wms/stock/*`
+
+**娉ㄦ剰浜嬮」**锛�
+- WMS 鍟嗗搧椤甸潰鏁版嵁鏉ユ簮浠� MDM 鐗╂枡鑾峰彇
+- SKU 浣滀负鐗╂枡鐨勮鏍煎睘鎬х鐞�
+
+---
+
+## 涓夈�佹柊澧炲瓧鍏哥被鍨�
+
+| 瀛楀吀绫诲瀷 | 瀛楀吀鍊� | 璇存槑 |
+|---------|-------|------|
+| MDM_ITEM_TYPE | 1 | 鍘熸枡 |
+| MDM_ITEM_TYPE | 2 | 鍗婃垚鍝� |
+| MDM_ITEM_TYPE | 3 | 鎴愬搧 |
+| MDM_ITEM_TYPE | 4 | 杈呮枡 |
+| STOCK_BIZ_TYPE | 10 | 閲囪喘鍏ュ簱 |
+| STOCK_BIZ_TYPE | 20 | 閿�鍞嚭搴� |
+| STOCK_BIZ_TYPE | 30 | 鐢熶骇鍏ュ簱 |
+| STOCK_BIZ_TYPE | 40 | 鐢熶骇棰嗘枡 |
+| STOCK_BIZ_TYPE | 50 | 鐢熶骇閫�鏂� |
+| STOCK_BIZ_TYPE | 60 | 閲囪喘閫�璐у嚭搴� |
+| STOCK_BIZ_TYPE | 70 | 閿�鍞��璐у叆搴� |
+| STOCK_BIZ_TYPE | 80 | 璋冩嫧鍏ュ簱 |
+| STOCK_BIZ_TYPE | 90 | 璋冩嫧鍑哄簱 |
+| STOCK_BIZ_TYPE | 100 | 鐩樼泩鍏ュ簱 |
+| STOCK_BIZ_TYPE | 110 | 鐩樹簭鍑哄簱 |
+
+---
+
+## 鍥涖�佽矾鐢遍厤缃�
+
+```js
+// 鏂板璺敱
+{
+  path: '/mdm',
+  component: Layout,
+  name: 'MDM',
+  meta: { title: '涓绘暟鎹鐞�', icon: 'ep:goods' },
+  children: [
+    {
+      path: 'item',
+      name: 'MdmItem',
+      component: () => import('@/views/mdm/item/index.vue'),
+      meta: { title: '鐗╂枡绠$悊' }
+    },
+    {
+      path: 'category',
+      name: 'MdmCategory',
+      component: () => import('@/views/mdm/category/index.vue'),
+      meta: { title: '鐗╂枡鍒嗙被' }
+    },
+    {
+      path: 'unit',
+      name: 'MdmUnit',
+      component: () => import('@/views/mdm/unit/index.vue'),
+      meta: { title: '璁¢噺鍗曚綅' }
+    },
+    {
+      path: 'brand',
+      name: 'MdmBrand',
+      component: () => import('@/views/mdm/brand/index.vue'),
+      meta: { title: '鍝佺墝绠$悊' }
+    },
+    {
+      path: 'warehouse',
+      name: 'MdmWarehouse',
+      component: () => import('@/views/mdm/warehouse/index.vue'),
+      meta: { title: '浠撳簱绠$悊' }
+    }
+  ]
+}
+```
+
+---
+
+## 浜斻�佹敞鎰忎簨椤�
+
+1. **鏁版嵁杩佺Щ**锛氭墽琛屾暟鎹縼绉� SQL 鍚庯紝鍘熸湁 ERP/MES/WMS 鐨勭墿鏂欓〉闈㈠皢灞曠ず杩佺Щ鍚庣殑 MDM 鏁版嵁
+2. **鏉冮檺閰嶇疆**锛氭柊澧� MDM 妯″潡鑿滃崟鍜屾潈闄愰厤缃�
+3. **API 瀵规帴**锛氬墠绔渶瑕佹柊澧� MDM 鐩稿叧 API 鏂囦欢 (`api/mdm/item.js`, `api/mdm/category.js` 绛�)
+4. **瀛楁鍏煎**锛氭棫椤甸潰鐨勫瓧娈靛悕绉伴渶瑕佹槧灏勫埌鏂板瓧娈碉紝濡� `productId` 鈫� `itemId`
+5. **鐘舵�佸悓姝�**锛氱墿鏂欑姸鎬佸彉鏇村悗锛岄渶瑕佸悓姝ユ洿鏂板悇妯″潡寮曠敤鐘舵��
+
+---
+
+**鏂囨。鐗堟湰**: v1.0
+**鍒涘缓鏃ユ湡**: 2026-06-29
+**浣滆��**: Claude Code
\ No newline at end of file
diff --git a/src/api/mdm/item/index.ts b/src/api/mdm/item/index.ts
new file mode 100644
index 0000000..6ceb79a
--- /dev/null
+++ b/src/api/mdm/item/index.ts
@@ -0,0 +1,88 @@
+import type { PageParam, PageResult } from '#/packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace MdmItemApi {
+  /** 鐗╂枡淇℃伅 */
+  export interface Item {
+    id?: number;
+    code: string;
+    name: string;
+    barCode?: string;
+    specification?: string;
+    categoryId?: number;
+    categoryName?: string;
+    unitMeasureId?: number;
+    unitMeasureName?: string;
+    brandId?: number;
+    brandName?: string;
+    itemType: number;
+    status: number;
+    purchasePrice?: number;
+    salesPrice?: number;
+    costPrice?: number;
+    safetyStock?: number;
+    minStock?: number;
+    maxStock?: number;
+    isBatchManaged: boolean;
+    expiryDay?: number;
+    remark?: string;
+    createTime?: string;
+  }
+
+  /** 鐗╂枡鍒嗛〉鏌ヨ鍙傛暟 */
+  export interface PageParams extends PageParam {
+    code?: string;
+    name?: string;
+    categoryId?: number;
+    itemType?: number;
+    status?: number;
+  }
+}
+
+/** 鏌ヨ鐗╂枡鍒嗛〉鍒楄〃 */
+export function getItemPage(params: MdmItemApi.PageParams) {
+  return requestClient.get<PageResult<MdmItemApi.Item>>('/mdm/item/page', {
+    params,
+  });
+}
+
+/** 鏌ヨ鐗╂枡鍒楄〃 */
+export function getItemList(params?: Partial<MdmItemApi.Item>) {
+  return requestClient.get<MdmItemApi.Item[]>('/mdm/item/list', { params });
+}
+
+/** 鏌ヨ鐗╂枡璇︽儏 */
+export function getItem(id: number) {
+  return requestClient.get<MdmItemApi.Item>(`/mdm/item/get?id=${id}`);
+}
+
+/** 鏂板鐗╂枡 */
+export function createItem(data: MdmItemApi.Item) {
+  return requestClient.post('/mdm/item/create', data);
+}
+
+/** 淇敼鐗╂枡 */
+export function updateItem(data: MdmItemApi.Item) {
+  return requestClient.put('/mdm/item/update', data);
+}
+
+/** 鍒犻櫎鐗╂枡 */
+export function deleteItem(id: number) {
+  return requestClient.delete(`/mdm/item/delete?id=${id}`);
+}
+
+/** 鎵归噺鍒犻櫎鐗╂枡 */
+export function deleteItemList(ids: number[]) {
+  return requestClient.delete(`/mdm/item/delete-list?ids=${ids.join(',')}`);
+}
+
+/** 鏇存柊鐗╂枡鐘舵�� */
+export function updateItemStatus(id: number, status: number) {
+  return requestClient.put('/mdm/item/update-status', { id, status });
+}
+
+/** 瀵煎嚭鐗╂枡 */
+export function exportItem(params: Partial<MdmItemApi.Item>) {
+  return requestClient.download('/mdm/item/export-excel', { params });
+}
diff --git a/src/packages/constants/src/dict-enum.ts b/src/packages/constants/src/dict-enum.ts
index 73e0095..af6fc57 100644
--- a/src/packages/constants/src/dict-enum.ts
+++ b/src/packages/constants/src/dict-enum.ts
@@ -279,6 +279,12 @@
   WMS_SHIPMENT_ORDER_TYPE: 'wms_shipment_order_type', // WMS 鍑哄簱鍗曠被鍨�
 } as const;
 
+/** ========== MDM - 涓绘暟鎹鐞嗘ā鍧� ========== */
+const MDM_DICT = {
+  MDM_ITEM_TYPE: 'mdm_item_type', // MDM 鐗╂枡绫诲瀷
+  STOCK_BIZ_TYPE: 'stock_biz_type', // 搴撳瓨涓氬姟绫诲瀷
+} as const;
+
 /** 瀛楀吀绫诲瀷鏋氫妇 - 缁熶竴瀵煎嚭 */
 const DICT_TYPE = {
   ...AI_DICT,
@@ -289,6 +295,7 @@
   ...IM_DICT,
   ...IOT_DICT,
   ...MES_DICT,
+  ...MDM_DICT,
   ...WMS_DICT,
   ...MEMBER_DICT,
   ...MP_DICT,
diff --git a/src/views/basicData/mdm/data.ts b/src/views/basicData/mdm/data.ts
new file mode 100644
index 0000000..2ae684a
--- /dev/null
+++ b/src/views/basicData/mdm/data.ts
@@ -0,0 +1,351 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+
+import { CommonStatusEnum, DICT_TYPE } from '#/packages/constants/src';
+import { getDictOptions } from '#/packages/effects/hooks/src';
+
+import { z } from '#/adapter/form';
+
+/** 鐗╂枡绫诲瀷閫夐」 */
+export const ITEM_TYPE_OPTIONS = [
+  { label: '鍘熸枡', value: 1 },
+  { label: '鍗婃垚鍝�', value: 2 },
+  { label: '鎴愬搧', value: 3 },
+  { label: '杈呮枡', value: 4 },
+];
+
+/** 鏂板/淇敼鐨勮〃鍗� */
+export function useFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      component: 'Input',
+      fieldName: 'id',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+    {
+      component: 'Input',
+      fieldName: 'code',
+      label: '鐗╂枡缂栫爜',
+      componentProps: {
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�',
+      },
+      rules: 'required',
+    },
+    {
+      component: 'Input',
+      fieldName: 'name',
+      label: '鐗╂枡鍚嶇О',
+      componentProps: {
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�',
+      },
+      rules: 'required',
+    },
+    {
+      component: 'Input',
+      fieldName: 'barCode',
+      label: '鏉$爜',
+      componentProps: {
+        placeholder: '璇疯緭鍏ユ潯鐮�',
+      },
+    },
+    {
+      component: 'Input',
+      fieldName: 'specification',
+      label: '瑙勬牸鍨嬪彿',
+      componentProps: {
+        placeholder: '璇疯緭鍏ヨ鏍煎瀷鍙�',
+      },
+    },
+    {
+      component: 'TreeSelect',
+      fieldName: 'categoryId',
+      label: '鐗╂枡鍒嗙被',
+      componentProps: {
+        placeholder: '璇烽�夋嫨鐗╂枡鍒嗙被',
+        allowClear: true,
+        treeData: [],
+        fieldNames: { label: 'name', value: 'id', children: 'children' },
+      },
+    },
+    {
+      component: 'Select',
+      fieldName: 'unitMeasureId',
+      label: '璁¢噺鍗曚綅',
+      componentProps: {
+        placeholder: '璇烽�夋嫨璁¢噺鍗曚綅',
+        allowClear: true,
+        options: [],
+        fieldNames: { label: 'name', value: 'id' },
+      },
+    },
+    {
+      component: 'Select',
+      fieldName: 'brandId',
+      label: '鍝佺墝',
+      componentProps: {
+        placeholder: '璇烽�夋嫨鍝佺墝',
+        allowClear: true,
+        options: [],
+        fieldNames: { label: 'name', value: 'id' },
+      },
+    },
+    {
+      component: 'Select',
+      fieldName: 'itemType',
+      label: '鐗╂枡绫诲瀷',
+      componentProps: {
+        placeholder: '璇烽�夋嫨鐗╂枡绫诲瀷',
+        options: ITEM_TYPE_OPTIONS,
+      },
+      rules: 'required',
+    },
+    {
+      component: 'InputNumber',
+      fieldName: 'purchasePrice',
+      label: '閲囪喘浠�',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        precision: 2,
+        placeholder: '璇疯緭鍏ラ噰璐环',
+      },
+    },
+    {
+      component: 'InputNumber',
+      fieldName: 'salesPrice',
+      label: '閿�鍞环',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        precision: 2,
+        placeholder: '璇疯緭鍏ラ攢鍞环',
+      },
+    },
+    {
+      component: 'InputNumber',
+      fieldName: 'costPrice',
+      label: '鎴愭湰浠�',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        precision: 2,
+        placeholder: '璇疯緭鍏ユ垚鏈环',
+      },
+    },
+    {
+      component: 'InputNumber',
+      fieldName: 'safetyStock',
+      label: '瀹夊叏搴撳瓨',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        precision: 2,
+        placeholder: '璇疯緭鍏ュ畨鍏ㄥ簱瀛�',
+      },
+    },
+    {
+      component: 'InputNumber',
+      fieldName: 'minStock',
+      label: '鏈�浣庡簱瀛�',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        precision: 2,
+        placeholder: '璇疯緭鍏ユ渶浣庡簱瀛�',
+      },
+    },
+    {
+      component: 'InputNumber',
+      fieldName: 'maxStock',
+      label: '鏈�楂樺簱瀛�',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        precision: 2,
+        placeholder: '璇疯緭鍏ユ渶楂樺簱瀛�',
+      },
+    },
+    {
+      component: 'Switch',
+      fieldName: 'isBatchManaged',
+      label: '鎵规绠$悊',
+      componentProps: {
+        checkedChildren: '鏄�',
+        unCheckedChildren: '鍚�',
+      },
+      rules: z.boolean().default(false),
+    },
+    {
+      component: 'InputNumber',
+      fieldName: 'expiryDay',
+      label: '鏈夋晥鏈�(澶�)',
+      componentProps: {
+        class: '!w-full',
+        min: 0,
+        placeholder: '璇疯緭鍏ユ湁鏁堟湡',
+      },
+    },
+    {
+      component: 'RadioGroup',
+      fieldName: 'status',
+      label: '鐘舵��',
+      componentProps: {
+        options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
+        buttonStyle: 'solid',
+        optionType: 'button',
+      },
+      rules: z.number().default(CommonStatusEnum.ENABLE),
+    },
+    {
+      component: 'Textarea',
+      fieldName: 'remark',
+      label: '澶囨敞',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ娉�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '鐗╂枡缂栫爜',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�',
+        allowClear: true,
+      },
+    },
+    {
+      fieldName: 'name',
+      label: '鐗╂枡鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�',
+        allowClear: true,
+      },
+    },
+    {
+      fieldName: 'categoryId',
+      label: '鐗╂枡鍒嗙被',
+      component: 'TreeSelect',
+      componentProps: {
+        placeholder: '璇烽�夋嫨鐗╂枡鍒嗙被',
+        allowClear: true,
+        treeData: [],
+        fieldNames: { label: 'name', value: 'id', children: 'children' },
+      },
+    },
+    {
+      fieldName: 'itemType',
+      label: '鐗╂枡绫诲瀷',
+      component: 'Select',
+      componentProps: {
+        placeholder: '璇烽�夋嫨鐗╂枡绫诲瀷',
+        allowClear: true,
+        options: ITEM_TYPE_OPTIONS,
+      },
+    },
+    {
+      fieldName: 'status',
+      label: '鐘舵��',
+      component: 'Select',
+      componentProps: {
+        placeholder: '璇烽�夋嫨鐘舵��',
+        allowClear: true,
+        options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
+      },
+    },
+  ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions['columns'] {
+  return [
+    { type: 'checkbox', width: 40 },
+    {
+      field: 'code',
+      title: '鐗╂枡缂栫爜',
+      minWidth: 120,
+    },
+    {
+      field: 'name',
+      title: '鐗╂枡鍚嶇О',
+      minWidth: 180,
+    },
+    {
+      field: 'specification',
+      title: '瑙勬牸鍨嬪彿',
+      minWidth: 150,
+    },
+    {
+      field: 'categoryName',
+      title: '鍒嗙被',
+      minWidth: 100,
+    },
+    {
+      field: 'unitMeasureName',
+      title: '鍗曚綅',
+      minWidth: 80,
+    },
+    {
+      field: 'itemType',
+      title: '鐗╂枡绫诲瀷',
+      minWidth: 100,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.MDM_ITEM_TYPE },
+      },
+    },
+    {
+      field: 'purchasePrice',
+      title: '閲囪喘浠�',
+      minWidth: 100,
+      formatter: ({ cellValue }) => {
+        return cellValue != null ? `楼${Number(cellValue).toFixed(2)}` : '';
+      },
+    },
+    {
+      field: 'salesPrice',
+      title: '閿�鍞环',
+      minWidth: 100,
+      formatter: ({ cellValue }) => {
+        return cellValue != null ? `楼${Number(cellValue).toFixed(2)}` : '';
+      },
+    },
+    {
+      field: 'isBatchManaged',
+      title: '鎵规绠$悊',
+      minWidth: 80,
+      slots: { default: 'isBatchManaged' },
+    },
+    {
+      field: 'status',
+      title: '鐘舵��',
+      minWidth: 80,
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.COMMON_STATUS },
+      },
+    },
+    {
+      field: 'createTime',
+      title: '鍒涘缓鏃堕棿',
+      minWidth: 180,
+      formatter: 'formatDateTime',
+    },
+    {
+      title: '鎿嶄綔',
+      width: 180,
+      fixed: 'right',
+      slots: { default: 'actions' },
+    },
+  ];
+}
diff --git a/src/views/basicData/mdm/index.vue b/src/views/basicData/mdm/index.vue
new file mode 100644
index 0000000..b5a84fa
--- /dev/null
+++ b/src/views/basicData/mdm/index.vue
@@ -0,0 +1,214 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MdmItemApi } from '#/api/mdm/item';
+
+import { ref } from 'vue';
+
+import { confirm, Page, useVbenModal } from '@vben/common-ui';
+import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
+
+import { message, Tag } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+  deleteItem,
+  deleteItemList,
+  exportItem,
+  getItemPage,
+  updateItemStatus,
+} from '#/api/mdm/item';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+defineOptions({ name: 'MdmItem' });
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+  gridApi.query();
+}
+
+/** 瀵煎嚭琛ㄦ牸 */
+async function handleExport() {
+  const data = await exportItem(await gridApi.formApi.getValues());
+  downloadFileFromBlobPart({ fileName: '鐗╂枡.xls', source: data });
+}
+
+/** 鍒涘缓鐗╂枡 */
+function handleCreate() {
+  formModalApi.setData(null).open();
+}
+
+/** 缂栬緫鐗╂枡 */
+function handleEdit(row: MdmItemApi.Item) {
+  formModalApi.setData(row).open();
+}
+
+/** 鍒犻櫎鐗╂枡 */
+async function handleDelete(row: MdmItemApi.Item) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.name]),
+    duration: 0,
+  });
+  try {
+    await deleteItem(row.id!);
+    message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 鎵归噺鍒犻櫎鐗╂枡 */
+async function handleDeleteBatch() {
+  await confirm($t('ui.actionMessage.deleteBatchConfirm'));
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deletingBatch'),
+    duration: 0,
+  });
+  try {
+    await deleteItemList(checkedIds.value);
+    checkedIds.value = [];
+    message.success($t('ui.actionMessage.deleteSuccess'));
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+/** 鏇存柊鐘舵�� */
+async function handleStatusChange(row: MdmItemApi.Item, status: boolean) {
+  const newStatus = status ? 0 : 1;
+  const hideLoading = message.loading({
+    content: '姝e湪鏇存柊鐘舵��...',
+    duration: 0,
+  });
+  try {
+    await updateItemStatus(row.id!, newStatus);
+    message.success('鐘舵�佹洿鏂版垚鍔�');
+    handleRefresh();
+  } finally {
+    hideLoading();
+  }
+}
+
+const checkedIds = ref<number[]>([]);
+function handleRowCheckboxChange({
+  records,
+}: {
+  records: MdmItemApi.Item[];
+}) {
+  checkedIds.value = records.map((item) => item.id!);
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getItemPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MdmItemApi.Item>,
+  gridEvents: {
+    checkboxAll: handleRowCheckboxChange,
+    checkboxChange: handleRowCheckboxChange,
+  },
+});
+</script>
+
+<template>
+  <Page auto-content-height>
+    <FormModal @success="handleRefresh" />
+    <Grid table-title="鐗╂枡鍒楄〃">
+      <template #toolbar-tools>
+        <TableAction
+          :actions="[
+            {
+              label: $t('ui.actionTitle.create', ['鐗╂枡']),
+              type: 'primary',
+              icon: ACTION_ICON.ADD,
+              auth: ['mdm:item:create'],
+              onClick: handleCreate,
+            },
+            {
+              label: $t('ui.actionTitle.export'),
+              type: 'primary',
+              icon: ACTION_ICON.DOWNLOAD,
+              auth: ['mdm:item:export'],
+              onClick: handleExport,
+            },
+            {
+              label: $t('ui.actionTitle.deleteBatch'),
+              type: 'primary',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mdm:item:delete'],
+              disabled: isEmpty(checkedIds),
+              onClick: handleDeleteBatch,
+            },
+          ]"
+        />
+      </template>
+      <template #isBatchManaged="{ row }">
+        <Tag v-if="row.isBatchManaged" color="success">鏄�</Tag>
+        <Tag v-else>鍚�</Tag>
+      </template>
+      <template #actions="{ row }">
+        <TableAction
+          :actions="[
+            {
+              label: $t('common.edit'),
+              type: 'link',
+              icon: ACTION_ICON.EDIT,
+              auth: ['mdm:item:update'],
+              onClick: handleEdit.bind(null, row),
+            },
+            {
+              label: row.status === 0 ? '鍋滅敤' : '鍚敤',
+              type: 'link',
+              auth: ['mdm:item:update'],
+              onClick: handleStatusChange.bind(null, row, row.status === 0),
+            },
+            {
+              label: $t('common.delete'),
+              type: 'link',
+              danger: true,
+              icon: ACTION_ICON.DELETE,
+              auth: ['mdm:item:delete'],
+              popConfirm: {
+                title: $t('ui.actionMessage.deleteConfirm', [row.name]),
+                confirm: handleDelete.bind(null, row),
+              },
+            },
+          ]"
+        />
+      </template>
+    </Grid>
+  </Page>
+</template>
\ No newline at end of file
diff --git a/src/views/basicData/mdm/modules/form.vue b/src/views/basicData/mdm/modules/form.vue
new file mode 100644
index 0000000..4d5be9a
--- /dev/null
+++ b/src/views/basicData/mdm/modules/form.vue
@@ -0,0 +1,80 @@
+<script lang="ts" setup>
+import type { MdmItemApi } from '#/api/mdm/item';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import { createItem, getItem, updateItem } from '#/api/mdm/item';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+
+defineOptions({ name: 'ItemForm' });
+
+const emit = defineEmits(['success']);
+const formData = ref<MdmItemApi.Item>();
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['鐗╂枡'])
+    : $t('ui.actionTitle.create', ['鐗╂枡']);
+});
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 100,
+  },
+  layout: 'horizontal',
+  schema: useFormSchema(),
+  showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    const data = (await formApi.getValues()) as MdmItemApi.Item;
+    try {
+      await (formData.value?.id ? updateItem(data) : createItem(data));
+      await modalApi.close();
+      emit('success');
+      message.success($t('ui.actionMessage.operationSuccess'));
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      formData.value = undefined;
+      return;
+    }
+    const data = modalApi.getData<MdmItemApi.Item>();
+    if (!data || !data.id) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      formData.value = await getItem(data.id);
+      await formApi.setValues(formData.value);
+    } finally {
+      modalApi.unlock();
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal class="w-[800px]" :title="getTitle">
+    <Form class="mx-4" />
+  </Modal>
+</template>
\ No newline at end of file

--
Gitblit v1.9.3