From 7d5f9791941d0ef9cd6d2daae80b5ae391e18b43 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期三, 01 七月 2026 16:10:10 +0800
Subject: [PATCH] 银川 1.联调产品维护页面 2.添加采购申请页面、联调

---
 src/preferences.ts                                              |    4 
 src/packages/icons/src/offline-icons.ts                         |   15 
 src/views/erp/purchase/request/modules/process-select-modal.vue |   78 ++++
 src/views/erp/purchase/request/modules/item-form.vue            |   62 ++-
 src/views/system/menu/data.ts                                   |    2 
 pnpm-lock.yaml                                                  |    3 
 src/packages/icons/package.json                                 |    1 
 /dev/null                                                       |  523 -----------------------------
 src/views/erp/purchase/request/index.vue                        |  100 ++---
 src/views/erp/purchase/request/data.ts                          |  102 ++++-
 src/views/erp/purchase/request/modules/form.vue                 |   13 
 src/views/erp/purchase/supplier/modules/form.vue                |    2 
 src/views/basicData/mdm/index.vue                               |   24 
 src/views/erp/purchase/supplier/data.ts                         |   11 
 src/api/erp/purchase/request/index.ts                           |   51 +-
 src/views/erp/purchase/request/modules/order-form.vue           |   71 +++
 16 files changed, 400 insertions(+), 662 deletions(-)

diff --git a/mdm_frontend_change.md b/mdm_frontend_change.md
deleted file mode 100644
index 3e9c175..0000000
--- a/mdm_frontend_change.md
+++ /dev/null
@@ -1,523 +0,0 @@
-# 鐗╂枡涓绘暟鎹ā鍧� (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/pnpm-lock.yaml b/pnpm-lock.yaml
index 54fadff..97d3ddd 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1620,6 +1620,9 @@
 
   src/packages/icons:
     dependencies:
+      '@iconify/json':
+        specifier: ^2.2.483
+        version: 2.2.489
       '@vben-core/icons':
         specifier: workspace:*
         version: link:../@core/base/icons
diff --git a/src/api/erp/purchase/request/index.ts b/src/api/erp/purchase/request/index.ts
index c35f89c..a7702d1 100644
--- a/src/api/erp/purchase/request/index.ts
+++ b/src/api/erp/purchase/request/index.ts
@@ -7,15 +7,23 @@
   export interface PurchaseRequest {
     id?: number; // 缂栧彿
     no?: string; // 鐢宠缂栧彿
+    status?: number; // 鐘舵�侊細0鏈彁浜� 10瀹℃壒涓� 20瀹℃牳閫氳繃 30瀹℃牳涓嶉�氳繃 40宸插彇娑�
+    requestUserId?: number; // 鐢宠浜虹紪鍙�
+    requestUserName?: string; // 鐢宠浜哄悕绉�
+    requestDeptId?: number; // 鐢宠閮ㄩ棬缂栧彿
+    requestDeptName?: string; // 鐢宠閮ㄩ棬鍚嶇О
+    requestTime: string; // 鐢宠鏃堕棿
+    requestReason?: string; // 鐢宠鐞嗙敱
     supplierId?: number; // 渚涘簲鍟嗙紪鍙�
     supplierName?: string; // 渚涘簲鍟嗗悕绉�
-    requestTime: string; // 鐢宠鏃堕棿
-    requesterId?: number; // 鐢宠浜虹紪鍙�
-    requesterName?: string; // 鐢宠浜哄悕绉�
-    reason?: string; // 鐢宠鐞嗙敱
-    estimatedAmount?: number; // 棰勪及閲戦
-    status?: number; // 鐘舵��
+    discountPercent?: number; // 浼樻儬鐜�
+    fileUrl?: string; // 闄勪欢鍦板潃
     remark?: string; // 澶囨敞
+    totalCount?: number; // 鎬绘暟閲�
+    totalPrice?: number; // 鎬婚噾棰�
+    productNames?: string; // 浜у搧鍚嶇О鍒楄〃
+    orderId?: number; // 鐢熸垚鐨勯噰璐鍗曠紪鍙�
+    orderNo?: string; // 鐢熸垚鐨勯噰璐鍗曞彿
     items?: PurchaseRequestItem[]; // 鐢宠鏄庣粏
     createTime?: string; // 鍒涘缓鏃堕棿
   }
@@ -25,11 +33,13 @@
     id?: number; // 缂栧彿
     productId: number; // 浜у搧缂栧彿
     productName?: string; // 浜у搧鍚嶇О
-    productUnitName?: string; // 浜у搧鍗曚綅
+    productUnitId?: number; // 浜у搧鍗曚綅缂栧彿
+    productUnitName?: string; // 浜у搧鍗曚綅鍚嶇О
     productBarCode?: string; // 浜у搧鏉$爜
-    requestCount: number; // 鐢宠鏁伴噺
-    estimatedPrice?: number; // 棰勪及鍗曚环
-    requiredDate?: string; // 闇�姹傛棩鏈�
+    productPrice?: number; // 浜у搧鍗曚环锛堝弬鑰冧环锛�
+    count: number; // 鏁伴噺
+    taxPercent?: number; // 绋庣巼
+    demandTime?: string; // 闇�姹傛棩鏈�
     remark?: string; // 澶囨敞
   }
 }
@@ -44,18 +54,14 @@
   return requestClient.put('/erp/purchase-request/update', data);
 }
 
-/** 瀹℃壒閲囪喘鐢宠 */
-export function approvePurchaseRequest(params: {
-  id: number;
-  status: number;
-  remark?: string;
-}) {
-  return requestClient.put('/erp/purchase-request/approve', null, { params });
+/** 鎻愪氦閲囪喘鐢宠瀹℃壒 */
+export function submitPurchaseRequest(id: number, processDefinitionKey: string) {
+  return requestClient.put('/erp/purchase-request/submit', null, { params: { id, processDefinitionKey } });
 }
 
-/** 杞负閲囪喘璁㈠崟 */
-export function convertToPurchaseOrder(id: number) {
-  return requestClient.put('/erp/purchase-request/convert', null, { params: { id } });
+/** 鐢熸垚閲囪喘璁㈠崟 */
+export function generatePurchaseOrder(id: number, supplierId: number) {
+  return requestClient.post<number>('/erp/purchase-request/generate-order', null, { params: { id, supplierId } });
 }
 
 /** 鍒犻櫎閲囪喘鐢宠 */
@@ -82,3 +88,8 @@
 export function exportPurchaseRequest(params: any) {
   return requestClient.download('/erp/purchase-request/export-excel', { params });
 }
+
+/** 鑾峰彇瀹℃壒娴佺▼鍒楄〃 */
+export function getApproveProcessList(id: number) {
+  return requestClient.get(`/erp/purchase-request/approve-process-list`, { params: { id } });
+}
diff --git a/src/packages/icons/package.json b/src/packages/icons/package.json
index 1a5fc72..0f8273c 100644
--- a/src/packages/icons/package.json
+++ b/src/packages/icons/package.json
@@ -17,6 +17,7 @@
     }
   },
   "dependencies": {
+    "@iconify/json": "^2.2.483",
     "@vben-core/icons": "workspace:*"
   }
 }
diff --git a/src/packages/icons/src/offline-icons.ts b/src/packages/icons/src/offline-icons.ts
new file mode 100644
index 0000000..d873267
--- /dev/null
+++ b/src/packages/icons/src/offline-icons.ts
@@ -0,0 +1,15 @@
+/**
+ * 绂荤嚎鍥炬爣棰勫姞杞介厤缃�
+ * 鏆傛椂绂佺敤锛屼娇鐢ㄥ湪绾垮姞杞芥柟寮�
+ */
+
+import { addCollection } from '@iconify/vue';
+
+/**
+ * 鍒濆鍖栫绾垮浘鏍�
+ * 鐩墠浣跨敤鍦ㄧ嚎鍔犺浇锛屾鍑芥暟涓虹┖
+ */
+export function initOfflineIcons() {
+  // 鏆傛椂涓嶉鍔犺浇锛屼娇鐢� Iconify 鍦ㄧ嚎 API
+  // 濡傛灉闇�瑕佺绾垮姞杞斤紝闇�瑕佸皢 JSON 鏂囦欢澶嶅埗鍒伴」鐩腑
+}
diff --git a/src/preferences.ts b/src/preferences.ts
index 131be05..626b2ff 100644
--- a/src/preferences.ts
+++ b/src/preferences.ts
@@ -32,6 +32,10 @@
     companyName: import.meta.env.VITE_APP_TITLE,
     companySiteLink: 'https://gitee.com/yudaocode/yudao-ui-admin-vben',
   },
+  tabbar: {
+    /** 鍒锋柊娴忚鍣ㄥ悗鍙繚鐣欏綋鍓嶉〉闈紝涓嶆寔涔呭寲鎵�鏈夋爣绛鹃〉 */
+    persist: false,
+  },
 });
 
 export const preferencesExtension =
diff --git a/src/views/basicData/mdm/index.vue b/src/views/basicData/mdm/index.vue
index c10a75f..16b34d4 100644
--- a/src/views/basicData/mdm/index.vue
+++ b/src/views/basicData/mdm/index.vue
@@ -13,7 +13,7 @@
 import {
   deleteItem,
   deleteItemList,
-  exportItem,
+  // exportItem,
   getItemPage,
   updateItemStatus,
 } from '#/api/mdm/item';
@@ -35,10 +35,10 @@
 }
 
 /** 瀵煎嚭琛ㄦ牸 */
-async function handleExport() {
-  const data = await exportItem(await gridApi.formApi.getValues());
-  downloadFileFromBlobPart({ fileName: '鐗╂枡.xls', source: data });
-}
+// async function handleExport() {
+//   const data = await exportItem(await gridApi.formApi.getValues());
+//   downloadFileFromBlobPart({ fileName: '鐗╂枡.xls', source: data });
+// }
 
 /** 鍒涘缓鐗╂枡 */
 function handleCreate() {
@@ -156,13 +156,13 @@
               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.export'),
+            //   type: 'primary',
+            //   icon: ACTION_ICON.DOWNLOAD,
+            //   auth: ['mdm:item:export'],
+            //   onClick: handleExport,
+            // },
             {
               label: $t('ui.actionTitle.deleteBatch'),
               type: 'primary',
diff --git a/src/views/erp/purchase/request/data.ts b/src/views/erp/purchase/request/data.ts
index 34682d6..fb3c4ae 100644
--- a/src/views/erp/purchase/request/data.ts
+++ b/src/views/erp/purchase/request/data.ts
@@ -14,10 +14,11 @@
 
 /** 鐘舵�佸父閲� */
 export const PURCHASE_REQUEST_STATUS = {
-  PENDING: 0, // 寰呭鎵�
-  APPROVED: 1, // 宸插鎵�
-  REJECTED: 2, // 宸叉嫆缁�
-  CONVERTED: 3, // 宸茶浆璁㈠崟
+  DRAFT: 0, // 鏈彁浜わ紙鑽夌锛�
+  PROCESSING: 10, // 瀹℃壒涓�
+  APPROVED: 20, // 瀹℃牳閫氳繃
+  REJECTED: 30, // 瀹℃牳涓嶉�氳繃
+  CANCELLED: 40, // 宸插彇娑�
 };
 
 /** 琛ㄥ崟鐨勯厤缃」 */
@@ -54,7 +55,7 @@
       rules: 'required',
     },
     {
-      label: '渚涘簲鍟�',
+      label: '渚涘簲鍟嗭紙鍙�夛級',
       fieldName: 'supplierId',
       component: 'ApiSelect',
       componentProps: {
@@ -68,12 +69,37 @@
       },
     },
     {
-      fieldName: 'reason',
+      fieldName: 'discountPercent',
+      label: '浼樻儬鐜�(%)',
+      component: 'InputNumber',
+      componentProps: {
+        placeholder: '璇疯緭鍏ヤ紭鎯犵巼',
+        min: 0,
+        max: 100,
+        precision: 2,
+        disabled: formType === 'detail',
+      },
+    },
+    {
+      fieldName: 'requestReason',
       label: '鐢宠鐞嗙敱',
       component: 'Textarea',
       componentProps: {
         placeholder: '璇疯緭鍏ョ敵璇风悊鐢�',
         autoSize: { minRows: 1, maxRows: 1 },
+        disabled: formType === 'detail',
+      },
+      formItemClass: 'col-span-2',
+    },
+    {
+      fieldName: 'fileUrl',
+      label: '闄勪欢',
+      component: 'FileUpload',
+      componentProps: {
+        placeholder: '璇蜂笂浼犻檮浠�',
+        maxNumber: 5,
+        maxSize: 20,
+        accept: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'jpg', 'png'],
         disabled: formType === 'detail',
       },
       formItemClass: 'col-span-2',
@@ -121,18 +147,18 @@
       minWidth: 80,
     },
     {
-      field: 'requestCount',
-      title: '鐢宠鏁伴噺',
+      field: 'count',
+      title: '鏁伴噺',
       minWidth: 120,
       fixed: 'right',
-      slots: { default: 'requestCount' },
+      slots: { default: 'count' },
     },
     {
-      field: 'estimatedPrice',
-      title: '棰勪及鍗曚环',
+      field: 'productPrice',
+      title: '鍗曚环锛堝弬鑰冧环锛�',
       minWidth: 120,
       fixed: 'right',
-      slots: { default: 'estimatedPrice' },
+      slots: { default: 'productPrice' },
     },
     {
       field: 'totalPrice',
@@ -142,11 +168,18 @@
       formatter: 'formatAmount2',
     },
     {
-      field: 'requiredDate',
+      field: 'taxPercent',
+      title: '绋庣巼(%)',
+      minWidth: 100,
+      fixed: 'right',
+      slots: { default: 'taxPercent' },
+    },
+    {
+      field: 'demandTime',
       title: '闇�姹傛棩鏈�',
       minWidth: 140,
       fixed: 'right',
-      slots: { default: 'requiredDate' },
+      slots: { default: 'demandTime' },
     },
     {
       field: 'remark',
@@ -199,7 +232,7 @@
       },
     },
     {
-      fieldName: 'requesterId',
+      fieldName: 'requestUserId',
       label: '鐢宠浜�',
       component: 'ApiSelect',
       componentProps: {
@@ -217,10 +250,11 @@
       component: 'Select',
       componentProps: {
         options: [
-          { label: '寰呭鎵�', value: 0 },
-          { label: '宸插鎵�', value: 1 },
-          { label: '宸叉嫆缁�', value: 2 },
-          { label: '宸茶浆璁㈠崟', value: 3 },
+          { label: '鏈彁浜�', value: 0 },
+          { label: '瀹℃壒涓�', value: 10 },
+          { label: '瀹℃牳閫氳繃', value: 20 },
+          { label: '瀹℃牳涓嶉�氳繃', value: 30 },
+          { label: '宸插彇娑�', value: 40 },
         ],
         placeholder: '璇烽�夋嫨鐘舵��',
         allowClear: true,
@@ -255,15 +289,37 @@
       formatter: 'formatDate',
     },
     {
-      field: 'requesterName',
+      field: 'requestUserName',
       title: '鐢宠浜�',
       minWidth: 120,
     },
     {
-      field: 'estimatedAmount',
+      field: 'requestDeptName',
+      title: '鐢宠閮ㄩ棬',
+      minWidth: 120,
+    },
+    {
+      field: 'totalCount',
+      title: '鐢宠鏁伴噺',
+      minWidth: 120,
+      formatter: 'formatAmount3',
+    },
+    {
+      field: 'totalPrice',
       title: '棰勪及閲戦',
       formatter: 'formatAmount2',
       minWidth: 120,
+    },
+    {
+      field: 'productNames',
+      title: '鐢宠浜у搧',
+      minWidth: 150,
+      showOverflow: 'tooltip',
+    },
+    {
+      field: 'orderNo',
+      title: '閲囪喘璁㈠崟',
+      minWidth: 180,
     },
     {
       field: 'status',
@@ -272,7 +328,7 @@
       slots: { default: 'status' },
     },
     {
-      field: 'reason',
+      field: 'requestReason',
       title: '鐢宠鐞嗙敱',
       minWidth: 150,
       showOverflow: 'tooltip',
@@ -285,7 +341,7 @@
     },
     {
       title: '鎿嶄綔',
-      width: 200,
+      width: 240,
       fixed: 'right',
       slots: { default: 'actions' },
     },
diff --git a/src/views/erp/purchase/request/index.vue b/src/views/erp/purchase/request/index.vue
index a3cd39c..1eebbc5 100644
--- a/src/views/erp/purchase/request/index.vue
+++ b/src/views/erp/purchase/request/index.vue
@@ -4,29 +4,41 @@
 
 import { ref } from 'vue';
 
-import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
+import { confirm, Page, useVbenModal } from '../../../../packages/effects/common-ui/src';
 import { downloadFileFromBlobPart, isEmpty } from '../../../../packages/utils/src';
 
 import { message, Tag } from 'ant-design-vue';
 
 import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
 import {
-  approvePurchaseRequest,
-  convertToPurchaseOrder,
   deletePurchaseRequest,
   exportPurchaseRequest,
+  getApproveProcessList,
   getPurchaseRequestPage,
+  submitPurchaseRequest,
 } from '#/api/erp/purchase/request';
 import { $t } from '#/locales';
 
-import { useGridColumns, useGridFormSchema, PURCHASE_REQUEST_STATUS } from './data';
+import { useGridColumns, useGridFormSchema } from './data';
 import Form from './modules/form.vue';
+import OrderForm from './modules/order-form.vue';
+import ProcessSelectModal from './modules/process-select-modal.vue';
 
 /** ERP 閲囪喘鐢宠鍒楄〃 */
 defineOptions({ name: 'ErpPurchaseRequest' });
 
 const [FormModal, formModalApi] = useVbenModal({
   connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+const [OrderFormModal, orderFormModalApi] = useVbenModal({
+  connectedComponent: OrderForm,
+  destroyOnClose: true,
+});
+
+const [ProcessModal, processModalApi] = useVbenModal({
+  connectedComponent: ProcessSelectModal,
   destroyOnClose: true,
 });
 
@@ -66,34 +78,29 @@
   }
 }
 
-/** 瀹℃壒閲囪喘鐢宠 */
-async function handleApprove(row: ErpPurchaseRequestApi.PurchaseRequest, status: number) {
+/** 鎻愪氦瀹℃壒 - 鍏堣幏鍙栨祦绋嬪垪琛� */
+async function handleSubmit(row: ErpPurchaseRequestApi.PurchaseRequest) {
   const hideLoading = message.loading({
-    content: `纭畾${status === 1 ? '瀹℃壒閫氳繃' : '瀹℃壒鎷掔粷'}璇ョ敵璇峰悧锛焋,
+    content: '鑾峰彇瀹℃壒娴佺▼...',
     duration: 0,
   });
   try {
-    await approvePurchaseRequest({ id: row.id!, status, remark: '' });
-    message.success(`${status === 1 ? '瀹℃壒閫氳繃' : '瀹℃壒鎷掔粷'}鎴愬姛`);
-    handleRefresh();
-  } finally {
+    const processList = await getApproveProcessList(row.id!);
+    hideLoading();
+    if (!processList || processList.length === 0) {
+      message.warning('鏆傛棤鍙敤瀹℃壒娴佺▼锛岃鍏堥厤缃� BPM 娴佺▼');
+      return;
+    }
+    // 鎵撳紑娴佺▼閫夋嫨寮圭獥
+    processModalApi.setData({ id: row.id, processList }).open();
+  } catch {
     hideLoading();
   }
 }
 
-/** 杞负閲囪喘璁㈠崟 */
-async function handleConvert(row: ErpPurchaseRequestApi.PurchaseRequest) {
-  const hideLoading = message.loading({
-    content: '姝e湪杞负閲囪喘璁㈠崟...',
-    duration: 0,
-  });
-  try {
-    await convertToPurchaseOrder(row.id!);
-    message.success('宸叉垚鍔熻浆涓洪噰璐鍗�');
-    handleRefresh();
-  } finally {
-    hideLoading();
-  }
+/** 鐢熸垚閲囪喘璁㈠崟 */
+function handleGenerateOrder(row: ErpPurchaseRequestApi.PurchaseRequest) {
+  orderFormModalApi.setData({ id: row.id, supplierId: row.supplierId }).open();
 }
 
 const checkedIds = ref<number[]>([]);
@@ -148,6 +155,8 @@
 <template>
   <Page auto-content-height>
     <FormModal @success="handleRefresh" />
+    <OrderFormModal @success="handleRefresh" />
+    <ProcessModal @success="handleRefresh" />
     <Grid table-title="閲囪喘鐢宠鍒楄〃">
       <template #toolbar-tools>
         <TableAction
@@ -182,10 +191,11 @@
         />
       </template>
       <template #status="{ row }">
-        <Tag v-if="row.status === 0" color="warning">寰呭鎵�</Tag>
-        <Tag v-else-if="row.status === 1" color="success">宸插鎵�</Tag>
-        <Tag v-else-if="row.status === 2" color="error">宸叉嫆缁�</Tag>
-        <Tag v-else-if="row.status === 3" color="default">宸茶浆璁㈠崟</Tag>
+        <Tag v-if="row.status === 0" color="default">鏈彁浜�</Tag>
+        <Tag v-else-if="row.status === 10" color="processing">瀹℃壒涓�</Tag>
+        <Tag v-else-if="row.status === 20" color="success">瀹℃牳閫氳繃</Tag>
+        <Tag v-else-if="row.status === 30" color="error">瀹℃牳涓嶉�氳繃</Tag>
+        <Tag v-else-if="row.status === 40" color="warning">宸插彇娑�</Tag>
       </template>
       <template #actions="{ row }">
         <TableAction
@@ -206,35 +216,17 @@
               onClick: handleEdit.bind(null, row),
             },
             {
-              label: '瀹℃壒閫氳繃',
+              label: '鎻愪氦',
               type: 'link',
-              auth: ['erp:purchase-request:approve'],
-              ifShow: () => row.status === 0,
-              popConfirm: {
-                title: '纭瀹℃壒閫氳繃璇ョ敵璇峰悧锛�',
-                confirm: handleApprove.bind(null, row, 1),
-              },
+              ifShow: () => row.status === 0 || row.status === 30,
+              onClick: handleSubmit.bind(null, row),
             },
             {
-              label: '瀹℃壒鎷掔粷',
+              label: '鐢熸垚璁㈠崟',
               type: 'link',
-              danger: true,
-              auth: ['erp:purchase-request:approve'],
-              ifShow: () => row.status === 0,
-              popConfirm: {
-                title: '纭瀹℃壒鎷掔粷璇ョ敵璇峰悧锛�',
-                confirm: handleApprove.bind(null, row, 2),
-              },
-            },
-            {
-              label: '杞鍗�',
-              type: 'link',
-              auth: ['erp:purchase-request:convert'],
-              ifShow: () => row.status === 1,
-              popConfirm: {
-                title: '纭杞负閲囪喘璁㈠崟鍚楋紵',
-                confirm: handleConvert.bind(null, row),
-              },
+              auth: ['erp:purchase-request:generate-order'],
+              ifShow: () => row.status === 20 && !row.orderId,
+              onClick: handleGenerateOrder.bind(null, row),
             },
             {
               label: $t('common.delete'),
@@ -253,4 +245,4 @@
       </template>
     </Grid>
   </Page>
-</template>
\ No newline at end of file
+</template>
diff --git a/src/views/erp/purchase/request/modules/form.vue b/src/views/erp/purchase/request/modules/form.vue
index 4bf6954..ab656c1 100644
--- a/src/views/erp/purchase/request/modules/form.vue
+++ b/src/views/erp/purchase/request/modules/form.vue
@@ -7,7 +7,7 @@
 
 import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
 
-import { message, Tag } from 'ant-design-vue';
+import { message } from 'ant-design-vue';
 
 import { useVbenForm } from '#/adapter/form';
 import {
@@ -17,7 +17,7 @@
 } from '#/api/erp/purchase/request';
 import { $t } from '#/locales';
 
-import { useFormSchema, PURCHASE_REQUEST_STATUS } from '../data';
+import { useFormSchema } from '../data';
 import PurchaseRequestItemForm from './item-form.vue';
 
 const emit = defineEmits(['success']);
@@ -78,8 +78,13 @@
     const data =
       (await formApi.getValues()) as ErpPurchaseRequestApi.PurchaseRequest;
     data.items = formData.value?.items?.map((item) => ({
-      ...item,
-      id: undefined,
+      productId: item.productId,
+      productUnitId: item.productUnitId,
+      productPrice: item.productPrice,
+      count: item.count,
+      taxPercent: item.taxPercent,
+      demandTime: item.demandTime,
+      remark: item.remark,
     }));
     try {
       await (formType.value === 'create'
diff --git a/src/views/erp/purchase/request/modules/item-form.vue b/src/views/erp/purchase/request/modules/item-form.vue
index 14b2713..8cd2a2f 100644
--- a/src/views/erp/purchase/request/modules/item-form.vue
+++ b/src/views/erp/purchase/request/modules/item-form.vue
@@ -35,10 +35,10 @@
 /** 鑾峰彇琛ㄦ牸鍚堣鏁版嵁 */
 const summaries = computed(() => {
   return {
-    count: tableData.value.reduce((sum, item) => sum + (item.requestCount || 0), 0),
+    count: tableData.value.reduce((sum, item) => sum + (item.count || 0), 0),
     totalPrice: tableData.value.reduce(
       (sum, item) => {
-        const price = erpPriceMultiply(item.estimatedPrice || 0, item.requestCount || 0) ?? 0;
+        const price = erpPriceMultiply(item.productPrice || 0, item.count || 0) ?? 0;
         return sum + price;
       },
       0,
@@ -89,11 +89,13 @@
   const newRow = {
     id: undefined,
     productId: undefined,
+    productUnitId: undefined,
     productUnitName: undefined,
     productBarCode: undefined,
-    requestCount: 1,
-    estimatedPrice: undefined,
-    requiredDate: undefined,
+    count: 1,
+    productPrice: undefined,
+    taxPercent: undefined,
+    demandTime: undefined,
     remark: undefined,
     totalPrice: 0,
   };
@@ -118,10 +120,11 @@
   }
   row.productId = productId;
   row.productBarCode = product.barCode;
+  row.productUnitId = product.unitMeasureId;
   row.productUnitName = product.unitMeasureName;
   row.productName = product.name;
-  row.estimatedPrice = product.purchasePrice || 0;
-  row.requestCount = row.requestCount || 1;
+  row.productPrice = product.purchasePrice || 0;
+  row.count = row.count || 1;
   handleRowChange(row);
 }
 
@@ -134,16 +137,16 @@
     tableData.value[index] = row;
   }
   // 璁$畻棰勪及閲戦
-  if (row.estimatedPrice && row.requestCount) {
-    row.totalPrice = erpPriceMultiply(row.estimatedPrice, row.requestCount) ?? 0;
+  if (row.productPrice && row.count) {
+    row.totalPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
   }
   emit('update:items', [...tableData.value]);
 }
 
 /** 鍒濆鍖栬鏁版嵁 */
 function initRow(row: ErpPurchaseRequestApi.PurchaseRequestItem) {
-  if (row.estimatedPrice && row.requestCount) {
-    row.totalPrice = erpPriceMultiply(row.estimatedPrice, row.requestCount) ?? 0;
+  if (row.productPrice && row.count) {
+    row.totalPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
   }
 }
 
@@ -155,8 +158,8 @@
       if (!item.productId) {
         throw new Error(`绗� ${i + 1} 琛岋細浜у搧涓嶈兘涓虹┖`);
       }
-      if (!item.requestCount || item.requestCount <= 0) {
-        throw new Error(`绗� ${i + 1} 琛岋細鐢宠鏁伴噺涓嶈兘涓虹┖`);
+      if (!item.count || item.count <= 0) {
+        throw new Error(`绗� ${i + 1} 琛岋細鏁伴噺涓嶈兘涓虹┖`);
       }
     }
   }
@@ -191,35 +194,46 @@
       />
       <span v-else>{{ row.productName || '-' }}</span>
     </template>
-    <template #requestCount="{ row }">
+    <template #count="{ row }">
       <InputNumber
         v-if="!disabled"
-        v-model:value="row.requestCount"
+        v-model:value="row.count"
         :min="0"
         :precision="3"
         @change="handleRowChange(row)"
       />
-      <span v-else>{{ erpCountInputFormatter(row.requestCount) || '-' }}</span>
+      <span v-else>{{ erpCountInputFormatter(row.count) || '-' }}</span>
     </template>
-    <template #estimatedPrice="{ row }">
+    <template #productPrice="{ row }">
       <InputNumber
         v-if="!disabled"
-        v-model:value="row.estimatedPrice"
+        v-model:value="row.productPrice"
         :min="0"
         :precision="2"
         @change="handleRowChange(row)"
       />
-      <span v-else>{{ erpPriceInputFormatter(row.estimatedPrice) || '-' }}</span>
+      <span v-else>{{ erpPriceInputFormatter(row.productPrice) || '-' }}</span>
     </template>
-    <template #requiredDate="{ row }">
+    <template #taxPercent="{ row }">
+      <InputNumber
+        v-if="!disabled"
+        v-model:value="row.taxPercent"
+        :min="0"
+        :max="100"
+        :precision="2"
+        @change="handleRowChange(row)"
+      />
+      <span v-else>{{ row.taxPercent ? `${row.taxPercent}%` : '-' }}</span>
+    </template>
+    <template #demandTime="{ row }">
       <DatePicker
         v-if="!disabled"
-        v-model:value="row.requiredDate"
+        v-model:value="row.demandTime"
         valueFormat="YYYY-MM-DD"
         class="w-full"
         @change="handleRowChange(row)"
       />
-      <span v-else>{{ row.requiredDate || '-' }}</span>
+      <span v-else>{{ row.demandTime || '-' }}</span>
     </template>
     <template #remark="{ row }">
       <Input v-if="!disabled" v-model:value="row.remark" class="w-full" @change="handleRowChange(row)" />
@@ -246,7 +260,7 @@
         <div class="flex justify-between text-sm text-muted-foreground">
           <span class="font-medium text-foreground">鍚堣锛�</span>
           <div class="flex space-x-4">
-            <span>鐢宠鏁伴噺锛歿{ erpCountInputFormatter(summaries.count) }}</span>
+            <span>鏁伴噺锛歿{ erpCountInputFormatter(summaries.count) }}</span>
             <span>棰勪及閲戦锛歿{ erpPriceInputFormatter(summaries.totalPrice) }}</span>
           </div>
         </div>
@@ -264,4 +278,4 @@
       />
     </template>
   </Grid>
-</template>
\ No newline at end of file
+</template>
diff --git a/src/views/erp/purchase/request/modules/order-form.vue b/src/views/erp/purchase/request/modules/order-form.vue
new file mode 100644
index 0000000..8ea784d
--- /dev/null
+++ b/src/views/erp/purchase/request/modules/order-form.vue
@@ -0,0 +1,71 @@
+<script lang="ts" setup>
+import type { ErpPurchaseRequestApi } from '#/api/erp/purchase/request';
+
+import { ref, computed } from 'vue';
+
+import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
+
+import { message, Select } from 'ant-design-vue';
+
+import { generatePurchaseOrder } from '#/api/erp/purchase/request';
+import { getSupplierSimpleList } from '#/api/erp/purchase/supplier';
+
+const emit = defineEmits(['success']);
+
+const requestId = ref<number>();
+const supplierId = ref<number>();
+const supplierOptions = ref<{ id: number; name: string }[]>([]);
+
+const supplierSelectOptions = computed(() => {
+  return supplierOptions.value.map((s) => ({
+    label: s.name,
+    value: s.id,
+  }));
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    if (!supplierId.value) {
+      message.warning('璇烽�夋嫨渚涘簲鍟�');
+      return;
+    }
+    modalApi.lock();
+    try {
+      const orderId = await generatePurchaseOrder(requestId.value!, supplierId.value!);
+      await modalApi.close();
+      emit('success');
+      message.success(`鐢熸垚閲囪喘璁㈠崟鎴愬姛锛岃鍗曠紪鍙凤細${orderId}`);
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      requestId.value = undefined;
+      supplierId.value = undefined;
+      return;
+    }
+    const data = modalApi.getData<{ id: number; supplierId?: number }>();
+    requestId.value = data.id;
+    supplierId.value = data.supplierId;
+    // 鑾峰彇渚涘簲鍟嗗垪琛�
+    supplierOptions.value = await getSupplierSimpleList();
+  },
+});
+</script>
+
+<template>
+  <Modal title="鐢熸垚閲囪喘璁㈠崟" class="w-1/3">
+    <div class="mb-4">
+      <span class="mr-2">閫夋嫨渚涘簲鍟嗭細</span>
+      <Select
+        v-model:value="supplierId"
+        :options="supplierSelectOptions"
+        style="width: 200px"
+        placeholder="璇烽�夋嫨渚涘簲鍟�"
+        show-search
+        :filter-option="(input: string, option: any) => option.label.toLowerCase().includes(input.toLowerCase())"
+      />
+    </div>
+  </Modal>
+</template>
\ No newline at end of file
diff --git a/src/views/erp/purchase/request/modules/process-select-modal.vue b/src/views/erp/purchase/request/modules/process-select-modal.vue
new file mode 100644
index 0000000..f4b3d83
--- /dev/null
+++ b/src/views/erp/purchase/request/modules/process-select-modal.vue
@@ -0,0 +1,78 @@
+<script lang="ts" setup>
+import type { ErpPurchaseRequestApi } from '#/api/erp/purchase/request';
+
+import { ref, computed } from 'vue';
+
+import { useVbenModal } from '../../../../../packages/effects/common-ui/src';
+
+import { message, Select } from 'ant-design-vue';
+
+import { submitPurchaseRequest } from '#/api/erp/purchase/request';
+
+interface ProcessDefinition {
+  id: string;
+  name: string;
+  version: number;
+  key: string;
+}
+
+const emit = defineEmits(['success']);
+
+const requestId = ref<number>();
+const processList = ref<ProcessDefinition[]>([]);
+const selectedProcessKey = ref<string>('');
+
+const processOptions = computed(() => {
+  return processList.value.map((p) => ({
+    label: `${p.name} (V${p.version})`,
+    value: p.key,
+  }));
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    if (!selectedProcessKey.value) {
+      message.warning('璇烽�夋嫨瀹℃壒娴佺▼');
+      return;
+    }
+    modalApi.lock();
+    try {
+      await submitPurchaseRequest(requestId.value!, selectedProcessKey.value);
+      await modalApi.close();
+      emit('success');
+      message.success('鎻愪氦鎴愬姛');
+    } finally {
+      modalApi.unlock();
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      requestId.value = undefined;
+      selectedProcessKey.value = '';
+      processList.value = [];
+      return;
+    }
+    const data = modalApi.getData<{ id: number; processList: ProcessDefinition[] }>();
+    requestId.value = data.id;
+    processList.value = data.processList || [];
+    // 榛樿閫夋嫨绗竴涓祦绋�
+    if (processList.value.length > 0) {
+      selectedProcessKey.value = processList.value[0].key;
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal title="閫夋嫨瀹℃壒娴佺▼" class="w-1/3">
+    <div class="mb-4">
+      <span class="mr-2">瀹℃壒娴佺▼锛�</span>
+      <Select
+        v-model:value="selectedProcessKey"
+        :options="processOptions"
+        style="width: 200px"
+        placeholder="璇烽�夋嫨瀹℃壒娴佺▼"
+      />
+    </div>
+  </Modal>
+</template>
\ No newline at end of file
diff --git a/src/views/erp/purchase/supplier/data.ts b/src/views/erp/purchase/supplier/data.ts
index 2dc2f3f..1277a48 100644
--- a/src/views/erp/purchase/supplier/data.ts
+++ b/src/views/erp/purchase/supplier/data.ts
@@ -116,6 +116,17 @@
       rules: z.number().default(0),
     },
     {
+      fieldName: 'sort',
+      label: '鎺掑簭',
+      component: 'InputNumber',
+      componentProps: {
+        placeholder: '璇疯緭鍏ユ帓搴�',
+        min: 0,
+        class: '!w-full',
+      },
+      rules: z.number().default(0),
+    },
+    {
       fieldName: 'qualification',
       label: '璧勮川淇℃伅',
       component: 'Textarea',
diff --git a/src/views/erp/purchase/supplier/modules/form.vue b/src/views/erp/purchase/supplier/modules/form.vue
index 423b63f..4b0b761 100644
--- a/src/views/erp/purchase/supplier/modules/form.vue
+++ b/src/views/erp/purchase/supplier/modules/form.vue
@@ -30,7 +30,7 @@
     componentProps: {
       class: 'w-full',
     },
-    labelWidth: 100,
+    labelWidth: 120,
   },
   wrapperClass: 'grid-cols-2',
   layout: 'horizontal',
diff --git a/src/views/system/menu/data.ts b/src/views/system/menu/data.ts
index f20984c..9490a83 100644
--- a/src/views/system/menu/data.ts
+++ b/src/views/system/menu/data.ts
@@ -101,7 +101,7 @@
       component: 'IconPicker',
       componentProps: {
         placeholder: '璇烽�夋嫨鑿滃崟鍥炬爣',
-        prefix: 'carbon',
+        prefix: 'ep',
       },
       rules: 'required',
       dependencies: {

--
Gitblit v1.9.3