From d4bc592b5d0c42bc69abd5bd6334c191205dc244 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期六, 09 五月 2026 17:42:14 +0800
Subject: [PATCH] 浪潮 1.日志前端页面开发与联调 2.仓库盘点页面开发与联调

---
 src/api/inventoryManagement/stockProfitLoss.js                        |   95 +++
 src/views/customerService/feedbackRegistration/components/formDia.vue |    4 
 src/views/inventoryManagement/stockCheckPlan/index.vue                |  720 ++++++++++++++++++++++++++++
 src/api/inventoryManagement/stockCheck.js                             |  112 ++++
 src/views/inventoryManagement/stockProfitLoss/index.vue               |  565 ++++++++++++++++++++++
 5 files changed, 1,494 insertions(+), 2 deletions(-)

diff --git a/src/api/inventoryManagement/stockCheck.js b/src/api/inventoryManagement/stockCheck.js
new file mode 100644
index 0000000..3b52a2e
--- /dev/null
+++ b/src/api/inventoryManagement/stockCheck.js
@@ -0,0 +1,112 @@
+import request from '@/utils/request'
+
+// 鍒嗛〉鏌ヨ鐩樼偣璁″垝鍒楄〃
+export const getStockCheckPlanPage = (params) => {
+  return request({
+    url: '/stockInventoryCheckPlan/listPage',
+    method: 'get',
+    params,
+  })
+}
+
+// 鏂板鐩樼偣璁″垝
+export const addStockCheckPlan = (data) => {
+  return request({
+    url: '/stockInventoryCheckPlan/add',
+    method: 'post',
+    data,
+  })
+}
+
+// 淇敼鐩樼偣璁″垝
+export const updateStockCheckPlan = (data) => {
+  return request({
+    url: '/stockInventoryCheckPlan/update',
+    method: 'put',
+    data,
+  })
+}
+
+// 鍒犻櫎鐩樼偣璁″垝
+export const deleteStockCheckPlan = (ids) => {
+  return request({
+    url: '/stockInventoryCheckPlan/delete',
+    method: 'delete',
+    data: ids,
+  })
+}
+
+// 鎻愪氦瀹℃壒
+export const submitApproval = (id) => {
+  return request({
+    url: '/stockCheckPlan/submitApproval/' + id,
+    method: 'post',
+  })
+}
+
+// 瀹℃壒閫氳繃
+export const approvePlan = (id) => {
+  return request({
+    url: '/stockCheckPlan/approve/' + id,
+    method: 'post',
+  })
+}
+
+// 瀹℃壒鎷掔粷
+export const rejectPlan = (id, reason) => {
+  return request({
+    url: '/stockCheckPlan/reject/' + id,
+    method: 'post',
+    data: { reason },
+  })
+}
+
+// 寮�濮嬬洏鐐�
+export const startCheck = (id) => {
+  return request({
+    url: '/stockInventoryCheckPlan/start/' + id,
+    method: 'post',
+  })
+}
+
+// 缁撴潫鐩樼偣
+export const completeCheck = (data) => {
+  return request({
+    url: '/stockInventoryCheckPlan/end',
+    method: 'post',
+    data: data
+  })
+}
+
+// 鑾峰彇鐩樼偣鍟嗗搧鍒楄〃
+export const getCheckItems = (planId) => {
+  return request({
+    url: '/stockInventoryCheckPlan/detail/' + planId,
+    method: 'get',
+  })
+}
+
+// 淇濆瓨鐩樼偣鏁版嵁
+export const saveCheckData = (data) => {
+  return request({
+    url: '/stockInventoryCheckPlanItem/batchUpdate',
+    method: 'post',
+    data,
+  })
+}
+
+// 鑾峰彇宸紓姹囨��
+export const getDiffSummary = (planId) => {
+  return request({
+    url: '/stockCheckPlan/diffSummary/' + planId,
+    method: 'get',
+  })
+}
+
+// 鐢熸垚鐩堜簭澶勭悊鍗�
+export const generateProfitLoss = (planId) => {
+  return request({
+    url: '/stockCheckPlan/generateProfitLoss/' + planId,
+    method: 'post',
+  })
+}
\ No newline at end of file
diff --git a/src/api/inventoryManagement/stockProfitLoss.js b/src/api/inventoryManagement/stockProfitLoss.js
new file mode 100644
index 0000000..d818d7b
--- /dev/null
+++ b/src/api/inventoryManagement/stockProfitLoss.js
@@ -0,0 +1,95 @@
+import request from '@/utils/request'
+
+// 鍒嗛〉鏌ヨ鐩堜簭璁板綍鍒楄〃
+export const getStockProfitLossPage = (params) => {
+  return request({
+    url: '/stockProfitLoss/page',
+    method: 'get',
+    params,
+  })
+}
+
+// 鏌ヨ鐩堜簭璁板綍璇︽儏
+export const getStockProfitLossDetail = (id) => {
+  return request({
+    url: '/stockProfitLoss/' + id,
+    method: 'get',
+  })
+}
+
+// 鏂板鐩堜簭璁板綍锛堟墜鍔ㄥ垱寤猴級
+export const addStockProfitLoss = (data) => {
+  return request({
+    url: '/stockProfitLoss/add',
+    method: 'post',
+    data,
+  })
+}
+
+// 淇敼鐩堜簭璁板綍
+export const updateStockProfitLoss = (data) => {
+  return request({
+    url: '/stockProfitLoss/update',
+    method: 'put',
+    data,
+  })
+}
+
+// 鍒犻櫎鐩堜簭璁板綍
+export const deleteStockProfitLoss = (id) => {
+  return request({
+    url: '/stockProfitLoss/delete/' + id,
+    method: 'delete',
+  })
+}
+
+// 鎻愪氦瀹℃壒
+export const submitApproval = (id) => {
+  return request({
+    url: '/stockProfitLoss/submitApproval/' + id,
+    method: 'post',
+  })
+}
+
+// 瀹℃壒閫氳繃
+export const approveProfitLoss = (id) => {
+  return request({
+    url: '/stockProfitLoss/approve/' + id,
+    method: 'post',
+  })
+}
+
+// 瀹℃壒鎷掔粷
+export const rejectProfitLoss = (id, reason) => {
+  return request({
+    url: '/stockProfitLoss/reject/' + id,
+    method: 'post',
+    data: { reason },
+  })
+}
+
+// 鎵ц鐩堜簭澶勭悊锛堟洿鏂板簱瀛橈級
+export const executeProfitLoss = (id) => {
+  return request({
+    url: '/stockProfitLoss/execute/' + id,
+    method: 'post',
+  })
+}
+
+// 鏍规嵁鐩樼偣璁″垝ID鏌ヨ宸紓鍟嗗搧锛堢敤浜庣敓鎴愮泩浜忓崟锛�
+export const getDiffItemsByPlanId = (planId) => {
+  return request({
+    url: '/stockProfitLoss/diffItems/' + planId,
+    method: 'get',
+  })
+}
+
+// 瀵煎嚭鐩堜簭璁板綍
+export const exportStockProfitLoss = (params) => {
+  return request({
+    url: '/stockProfitLoss/export',
+    method: 'get',
+    params,
+    responseType: 'blob',
+  })
+}
\ No newline at end of file
diff --git a/src/views/customerService/feedbackRegistration/components/formDia.vue b/src/views/customerService/feedbackRegistration/components/formDia.vue
index 5c9e565..e5905ee 100644
--- a/src/views/customerService/feedbackRegistration/components/formDia.vue
+++ b/src/views/customerService/feedbackRegistration/components/formDia.vue
@@ -397,8 +397,8 @@
   size: 1000,
   total: 0,
   });
-  if(res.records){
-    customerNameOptions.value = res.records.map(item => ({
+  if(res.data.records){
+    customerNameOptions.value = res.data.records.map(item => ({
       label: item.customerName,
       value: item.customerName,
       id: item.id
diff --git a/src/views/inventoryManagement/stockCheckPlan/index.vue b/src/views/inventoryManagement/stockCheckPlan/index.vue
new file mode 100644
index 0000000..a934bb3
--- /dev/null
+++ b/src/views/inventoryManagement/stockCheckPlan/index.vue
@@ -0,0 +1,720 @@
+<template>
+  <div class="app-container">
+    <div class="search_form" style="margin-bottom: 20px;">
+      <div>
+        <span class="search_title">鐩樼偣鍗曞彿锛�</span>
+        <el-input v-model="searchForm.planNo" placeholder="璇疯緭鍏�" clearable style="width: 240px; margin-right: 10px" @change="handleQuery" />
+        <span class="search_title">鐘舵�侊細</span>
+        <el-select v-model="searchForm.status" placeholder="璇烽�夋嫨" clearable style="width: 240px" @change="handleQuery">
+          <el-option label="寰呮墽琛�" value="0" />
+          <el-option label="鎵ц涓�" value="1" />
+          <el-option label="宸插畬鎴�" value="2" />
+          <el-option label="宸插彇娑�" value="3" />
+        </el-select>
+        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">鎼滅储</el-button>
+      </div>
+      <div>
+        <el-button type="primary" @click="openForm('add')">鏂板鐩樼偣鏂规</el-button>
+      </div>
+    </div>
+    <div class="table_list">
+      <PIMTable
+        rowKey="id"
+        :column="tableColumn"
+        :tableData="tableData"
+        :tableLoading="tableLoading"
+        :page="page"
+        :isShowPagination="true"
+        @pagination="paginationChange"
+      />
+    </div>
+
+    <!-- 鏂板/缂栬緫鐩樼偣璁″垝 -->
+    <FormDialog
+      v-model="dialogVisible"
+      :title="dialogTitle"
+      width="950px"
+      @close="resetForm"
+      @confirm="handleSubmit"
+    >
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
+        <el-divider content-position="left">鍩烘湰淇℃伅</el-divider>
+        <el-row>
+          <el-col :span="8">
+            <el-form-item label="鐩樼偣鍗曞彿">
+              <el-input v-model="form.planNo" disabled placeholder="绯荤粺鑷姩鐢熸垚" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="鍒跺崟浜�" prop="createBy">
+              <el-select v-model="form.createBy" placeholder="璇烽�夋嫨鍒跺崟浜�" style="width: 100%" filterable>
+                <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="鍒跺崟鏃ユ湡">
+              <el-date-picker
+                v-model="form.createTime"
+                type="date"
+                placeholder="璇烽�夋嫨"
+                value-format="YYYY-MM-DD"
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="8">
+            <el-form-item label="鐩樼偣浜�" prop="checkerId">
+              <el-select v-model="form.checkerId" placeholder="璇烽�夋嫨鐩樼偣浜�" style="width: 100%" filterable>
+                <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="璁″垝鏃ユ湡" prop="planDate">
+              <el-date-picker
+                v-model="form.planDate"
+                type="date"
+                placeholder="璇烽�夋嫨"
+                value-format="YYYY-MM-DD"
+                style="width: 100%"
+              />
+            </el-form-item>
+          </el-col>
+          <el-col :span="8">
+            <el-form-item label="澶囨敞">
+              <el-input v-model="form.remark" placeholder="璇疯緭鍏ュ娉�" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-divider content-position="left">鐩樼偣浜у搧</el-divider>
+        <div class="mb10">
+          <el-button type="primary" size="small" @click="openProductDialog">閫夋嫨浜у搧</el-button>
+          <el-button size="small" @click="clearProducts">娓呯┖</el-button>
+        </div>
+        <PIMTable
+          :column="productColumn"
+          :tableData="form.items"
+          :isShowPagination="false"
+          height="350px"
+        />
+      </el-form>
+    </FormDialog>
+
+    <!-- 閫夋嫨浜у搧寮规 -->
+    <ProductSelectDialog
+      v-model="productDialogVisible"
+      requestUrl="/stockInventory/pagestockInventoryNoQua"
+      @confirm="handleProductSelect"
+    />
+
+    <!-- 褰曞叆鐩樼偣鏁版嵁 -->
+    <FormDialog
+      v-model="checkDialogVisible"
+      title="褰曞叆鐩樼偣鏁版嵁"
+      width="1000px"
+      operationType="detail"
+      @close="checkDialogVisible = false"
+    >
+      <el-alert
+        title="鎻愮ず锛氬綍鍏ュ疄闄呯洏鐐规暟閲忥紝绯荤粺灏嗚嚜鍔ㄨ绠楀樊寮�"
+        type="info"
+        :closable="false"
+        style="margin-bottom: 15px"
+      />
+      <el-table
+        v-loading="checkLoading"
+        :data="checkItemList"
+        height="400"
+        border
+      >
+        <el-table-column type="index" label="搴忓彿" width="60" align="center" />
+        <el-table-column prop="productName" label="浜у搧鍚嶇О" min-width="150" />
+        <el-table-column prop="model" label="瑙勬牸鍨嬪彿" min-width="150" />
+        <el-table-column prop="unit" label="鍗曚綅" width="80" align="center" />
+        <el-table-column prop="batchNo" label="鎵瑰彿" width="120" align="center" />
+        <el-table-column prop="systemQuantity" label="绯荤粺搴撳瓨" width="100" align="center" />
+        <el-table-column label="鐩樼偣鏁伴噺" width="120" align="center">
+          <template #default="{ row }">
+            <el-input-number
+              v-model="row.actualQuantity"
+              :min="0"
+              :precision="2"
+              :controls="false"
+              style="width: 100%"
+            />
+          </template>
+        </el-table-column>
+        <el-table-column label="宸紓" width="100" align="center">
+          <template #default="{ row }">
+            <span :class="{ 'text-profit': (row.actualQuantity - row.systemQuantity) > 0, 'text-loss': (row.actualQuantity - row.systemQuantity) < 0 }">
+              {{ row.actualQuantity - row.systemQuantity }}
+            </span>
+          </template>
+        </el-table-column>
+        <el-table-column label="澶囨敞" min-width="150">
+          <template #default="{ row }">
+            <el-input v-model="row.remark" placeholder="璇疯緭鍏ュ娉�" />
+          </template>
+        </el-table-column>
+      </el-table>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="handleSaveCheckData" :loading="submitLoading">淇濆瓨</el-button>
+          <el-button @click="checkDialogVisible = false">鍙栨秷</el-button>
+        </div>
+      </template>
+    </FormDialog>
+
+    <!-- 鏌ョ湅宸紓 -->
+    <FormDialog
+      v-model="diffDialogVisible"
+      title="鐩樼偣宸紓姹囨��"
+      width="1000px"
+      operationType="detail"
+      @close="diffDialogVisible = false"
+    >
+      <div class="diff-summary">
+        <el-row :gutter="20">
+          <el-col :span="6">
+            <div class="stat-item">
+              <div class="stat-label">鐩樼偣浜у搧鎬绘暟</div>
+              <div class="stat-value">{{ diffSummary.totalCount }}</div>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="stat-item">
+              <div class="stat-label">鐩樼泩浜у搧</div>
+              <div class="stat-value text-profit">{{ diffSummary.profitCount }}</div>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="stat-item">
+              <div class="stat-label">鐩樹簭浜у搧</div>
+              <div class="stat-value text-loss">{{ diffSummary.lossCount }}</div>
+            </div>
+          </el-col>
+          <el-col :span="6">
+            <div class="stat-item">
+              <div class="stat-label">鏃犲樊寮�</div>
+              <div class="stat-value">{{ diffSummary.normalCount }}</div>
+            </div>
+          </el-col>
+        </el-row>
+      </div>
+      <PIMTable
+        :column="diffItemColumn"
+        :tableData="diffItemList"
+        :isShowPagination="false"
+        height="400px"
+      />
+      <template #footer>
+        <div class="dialog-footer">
+          <!-- <el-button type="success" @click="handleGenerateFromDiff">鐢熸垚鐩堜簭澶勭悊鍗�</el-button> -->
+          <el-button @click="diffDialogVisible = false">鍏抽棴</el-button>
+        </div>
+      </template>
+    </FormDialog>
+
+    <!-- 鏌ョ湅璇︽儏 -->
+    <FormDialog
+      v-model="viewDialogVisible"
+      title="鐩樼偣璁″垝璇︽儏"
+      width="900px"
+      operationType="detail"
+      @close="viewDialogVisible = false"
+    >
+      <el-descriptions :column="2" border>
+        <el-descriptions-item label="鐩樼偣鍗曞彿">{{ viewData.planNo }}</el-descriptions-item>
+        <el-descriptions-item label="鐩樼偣浜�">{{ viewData.checkerName }}</el-descriptions-item>
+        <el-descriptions-item label="鍒跺崟浜�">{{ viewData.createBy }}</el-descriptions-item>
+        <el-descriptions-item label="鍒跺崟鏃ユ湡">{{ viewData.createTime }}</el-descriptions-item>
+        <el-descriptions-item label="璁″垝鏃ユ湡">{{ viewData.planDate }}</el-descriptions-item>
+        <el-descriptions-item label="鐘舵��">
+          <el-tag :type="getStatusType(viewData.status)">{{ getStatusText(viewData.status) }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="澶囨敞" :span="2">{{ viewData.remark || '-' }}</el-descriptions-item>
+      </el-descriptions>
+      <div style="margin-top: 20px;">
+        <div class="section-title">鐩樼偣浜у搧娓呭崟</div>
+        <PIMTable
+          :column="viewProductColumn"
+          :tableData="viewData.items"
+          :isShowPagination="false"
+          height="300px"
+        />
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="viewDialogVisible = false">鍏抽棴</el-button>
+        </div>
+      </template>
+    </FormDialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import PIMTable from '@/components/PIMTable/PIMTable.vue'
+import FormDialog from '@/components/Dialog/FormDialog.vue'
+import ProductSelectDialog from '@/views/basicData/product/ProductSelectDialog.vue'
+import { userListNoPage } from '@/api/system/user.js'
+import {
+  getStockCheckPlanPage,
+  addStockCheckPlan,
+  updateStockCheckPlan,
+  deleteStockCheckPlan,
+  submitApproval,
+  startCheck,
+  completeCheck,
+  getCheckItems,
+  saveCheckData,
+  getDiffSummary,
+  generateProfitLoss,
+} from '@/api/inventoryManagement/stockCheck.js'
+
+
+
+const tableData = ref([])
+const tableLoading = ref(false)
+const page = reactive({ current: 1, size: 20, total: 0 })
+const searchForm = reactive({ planNo: '', status: '' })
+
+// 涓昏〃鏍煎垪閰嶇疆
+const tableColumn = ref([
+  { label: '鐩樼偣鍗曞彿', prop: 'planNo' },
+  { label: '鍒跺崟浜�', prop: 'createBy' },
+  { label: '鐩樼偣浜�', prop: 'checkerName' },
+  { label: '鍒跺崟鏃ユ湡', prop: 'createTime', align: 'center' },
+  { label: '璁″垝鏃ユ湡', prop: 'planDate', align: 'center' },
+  {
+    label: '鐘舵��',
+    prop: 'status',
+    align: 'center',
+    dataType: 'tag',
+    formatData: (v) => ({
+      '0': '寰呮墽琛�', '1': '鎵ц涓�', '2': '宸插畬鎴�', '3': '宸插彇娑�',
+    }[v] || v),
+    formatType: (v) => ({
+      '0': 'info', '1': 'primary', '2': 'success', '3': 'warning',
+    }[v] || 'info')
+  },
+  {
+    label: '鎿嶄綔',
+    dataType: 'action',
+    fixed: 'right',
+    align: 'center',
+    operation: [
+      { name: '缂栬緫', clickFun: (row) => handleEdit(row), showHide: (row) => row.status == '0' },
+      { name: '鏌ョ湅', clickFun: (row) => handleView(row) },
+      { name: '寮�濮嬬洏鐐�', clickFun: (row) => handleStart(row), showHide: (row) => row.status == '0' },
+      { name: '褰曞叆鐩樼偣鏁版嵁', clickFun: (row) => openCheckDialog(row), showHide: (row) => row.status == '1' },
+      { name: '鏌ョ湅宸紓', clickFun: (row) => openDiffDialog(row), showHide: (row) => row.status == '2' },
+      // { name: '鐢熸垚鐩堜簭鍗�', clickFun: (row) => handleGenerateProfitLoss(row), showHide: (row) => row.status == '2' },
+    ]
+  }
+])
+
+// 浜у搧閫夋嫨琛ㄦ牸鍒楋紙缂栬緫鐢級
+const productColumn = ref([
+  { label: '浜у搧鍚嶇О', prop: 'productName' },
+  { label: '瑙勬牸鍨嬪彿', prop: 'model' },
+  { label: '鍗曚綅', prop: 'unit', align: 'center' }
+])
+
+// 鐩樼偣鏁版嵁褰曞叆琛ㄦ牸鍒�
+const checkItemColumn = ref([
+  { label: '浜у搧鍚嶇О', prop: 'productName' },
+  { label: '瑙勬牸鍨嬪彿', prop: 'model' },
+  { label: '鍗曚綅', prop: 'unit', align: 'center' },
+  { label: '绯荤粺搴撳瓨', prop: 'systemQty', align: 'center' },
+  {
+    label: '鐩樼偣鏁伴噺',
+    prop: 'checkQty',
+    align: 'center',
+    dataType: 'slot',
+    slot: 'checkQty'
+  },
+  {
+    label: '宸紓',
+    align: 'center',
+    dataType: 'slot',
+    slot: 'diff'
+  }
+])
+
+// 宸紓姹囨�昏〃鏍煎垪
+const diffItemColumn = ref([
+  { label: '浜у搧鍚嶇О', prop: 'productName' },
+  { label: '瑙勬牸鍨嬪彿', prop: 'model' },
+  { label: '鍗曚綅', prop: 'unit', align: 'center' },
+  { label: '鎵瑰彿', prop: 'batchNo', align: 'center' },
+  { label: '绯荤粺搴撳瓨', prop: 'systemQuantity', align: 'center' },
+  { label: '鐩樼偣鏁伴噺', prop: 'actualQuantity', align: 'center' },
+  { label: '宸紓鏁伴噺', prop: 'differenceQuantity', align: 'center' },
+  { label: '澶囨敞', prop: 'remark' }
+])
+
+// 鏌ョ湅璇︽儏浜у搧琛ㄦ牸鍒�
+const viewProductColumn = ref([
+  { label: '浜у搧鍚嶇О', prop: 'productName' },
+  { label: '瑙勬牸鍨嬪彿', prop: 'model' },
+  { label: '鍗曚綅', prop: 'unit', align: 'center' },
+  { label: '绯荤粺搴撳瓨', prop: 'systemQuantity', align: 'center' }
+])
+
+const dialogVisible = ref(false)
+const dialogTitle = ref('')
+const dialogMode = ref('add')
+const submitLoading = ref(false)
+const formRef = ref(null)
+
+// 鑾峰彇褰撳墠鏃ユ湡
+const getCurrentDate = () => {
+  const now = new Date()
+  return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
+}
+
+const form = reactive({
+  id: null,
+  planNo: '',
+  checkerId: null,
+  checkerName: '',
+  createBy: null,
+  createByName: '',
+  createTime: getCurrentDate(),
+  planDate: '',
+  remark: '',
+  items: [],
+})
+
+const userList = ref([])
+
+const productDialogVisible = ref(false)
+
+const checkDialogVisible = ref(false)
+const checkLoading = ref(false)
+const checkItemList = ref([])
+const currentPlanId = ref(null)
+
+const diffDialogVisible = ref(false)
+const diffItemList = ref([])
+const diffSummary = reactive({ totalCount: 0, profitCount: 0, lossCount: 0, normalCount: 0 })
+
+const viewDialogVisible = ref(false)
+const viewData = reactive({
+  planNo: '', checkerName: '', createByName: '', createTime: '', planDate: '', status: '', remark: '', items: []
+})
+
+const rules = {
+  createBy: [{ required: true, message: '璇烽�夋嫨鍒跺崟浜�', trigger: 'change' }],
+  checkerId: [{ required: true, message: '璇烽�夋嫨鐩樼偣浜�', trigger: 'change' }],
+  planDate: [{ required: true, message: '璇烽�夋嫨璁″垝鏃ユ湡', trigger: 'change' }],
+}
+
+const getList = () => {
+  tableLoading.value = true
+  getStockCheckPlanPage({ current: page.current, size: page.size, ...searchForm })
+    .then(res => {
+      tableData.value = res.data?.records || []
+      page.total = res.data?.total || 0
+    })
+    .finally(() => { tableLoading.value = false })
+}
+
+const handleQuery = () => {
+  page.current = 1
+  getList()
+}
+
+const resetQuery = () => {
+  Object.assign(searchForm, { planNo: '', status: '' })
+  handleQuery()
+}
+
+const paginationChange = ({ page: current, limit }) => {
+  page.current = current
+  page.size = limit
+  getList()
+}
+
+const openForm = (mode) => {
+  dialogMode.value = mode
+  dialogTitle.value = mode === 'add' ? '鏂板鐩樼偣鏂规' : '缂栬緫鐩樼偣鏂规'
+  if (mode === 'add') {
+    Object.assign(form, {
+      id: null,
+      planNo: '',
+      checkerId: null,
+      checkerName: '',
+      createBy: null,
+      createByName: '',
+      createTime: getCurrentDate(),
+      planDate: '',
+      remark: '',
+      items: [],
+    })
+  }
+  dialogVisible.value = true
+}
+
+const handleEdit = (row) => {
+  Object.assign(form, {
+    id: row.id,
+    planNo: row.planNo,
+    checkerId: row.checkerId,
+    checkerName: row.checkerName,
+    createBy: row.createBy,
+    createByName: row.createByName,
+    createTime: row.createTime,
+    planDate: row.planDate,
+    remark: row.remark,
+    items: row.items || [],
+  })
+  dialogMode.value = 'edit'
+  dialogTitle.value = '缂栬緫鐩樼偣鏂规'
+  dialogVisible.value = true
+}
+
+const handleSubmit = () => {
+  formRef.value?.validate(valid => {
+    if (!valid) return
+    if (form.items.length === 0) {
+      ElMessage.warning('璇疯嚦灏戦�夋嫨涓�涓洏鐐逛骇鍝�')
+      return
+    }
+    submitLoading.value = true
+    const data = { ...form }
+    const action = dialogMode.value === 'add' ? addStockCheckPlan : updateStockCheckPlan
+    action(data).then(() => {
+      ElMessage.success('淇濆瓨鎴愬姛')
+      dialogVisible.value = false
+      getList()
+    }).finally(() => { submitLoading.value = false })
+  })
+}
+
+const resetForm = () => {
+  formRef.value?.resetFields()
+}
+
+const handleView = (row) => {
+  Object.assign(viewData, {
+    planNo: row.planNo,
+    checkerName: row.checkerName,
+    createBy: row.createBy,
+    createTime: row.createTime,
+    planDate: row.planDate,
+    status: row.status,
+    remark: row.remark,
+    items: row.items || [],
+  })
+  viewDialogVisible.value = true
+}
+
+const handleDelete = (row) => {
+  ElMessageBox.confirm('纭鍒犻櫎璇ョ洏鐐硅鍒掞紵', '鎻愮ず', { type: 'warning' })
+    .then(() => deleteStockCheckPlan([row.id]))
+    .then(() => {
+      ElMessage.success('鍒犻櫎鎴愬姛')
+      getList()
+    })
+}
+
+const handleSubmitApproval = (row) => {
+  ElMessageBox.confirm('纭鎻愪氦瀹℃壒锛�', '鎻愮ず', { type: 'warning' })
+    .then(() => submitApproval(row.id))
+    .then(() => {
+      ElMessage.success('鎻愪氦鎴愬姛')
+      getList()
+    })
+}
+
+const handleStart = (row) => {
+  ElMessageBox.confirm('纭寮�濮嬬洏鐐癸紵寮�濮嬪悗灏嗛攣瀹氱浉鍏冲簱瀛�', '鎻愮ず', { type: 'warning' })
+    .then(() => startCheck(row.id))
+    .then(() => {
+      ElMessage.success('鐩樼偣宸插紑濮�')
+      getList()
+    })
+}
+
+const openCheckDialog = (row) => {
+  currentPlanId.value = row.id
+  checkDialogVisible.value = true
+  checkLoading.value = true
+  getCheckItems(row.id).then(res => {
+    const data = res.data || {}
+    // 鐩存帴浣跨敤鎺ュ彛杩斿洖鐨� checkItems
+    checkItemList.value = (data.checkItems || []).map(item => ({
+      ...item,
+      actualQuantity: item.actualQuantity || item.systemQuantity,
+    }))
+  }).finally(() => { checkLoading.value = false })
+}
+
+const handleSaveCheckData = () => {
+  submitLoading.value = true
+  completeCheck({
+    id: currentPlanId.value,
+    checkItems: checkItemList.value.map(item => ({
+      id: item.id,
+      productModelId: item.productModelId,
+      productCode: item.productCode,
+      productName: item.productName,
+      specification: item.model,
+      unit: item.unit,
+      batchNo: item.batchNo,
+      systemQuantity: item.systemQuantity,
+      actualQuantity: item.actualQuantity,
+      differenceQuantity: item.actualQuantity - item.systemQuantity,
+      remark: item.remark,
+    })),
+  }).then(() => {
+    ElMessage.success('鐩樼偣鏁版嵁淇濆瓨鎴愬姛')
+    checkDialogVisible.value = false
+    getList()
+  }).finally(() => { submitLoading.value = false })
+}
+
+const handleComplete = (row) => {
+  ElMessageBox.confirm('纭瀹屾垚鐩樼偣锛熷畬鎴愬悗灏嗙敓鎴愬樊寮傛暟鎹�', '鎻愮ず', { type: 'warning' })
+    .then(() => completeCheck({ id: row.id }))
+    .then(() => {
+      ElMessage.success('鐩樼偣宸插畬鎴�')
+      getList()
+    })
+}
+
+const openDiffDialog = (row) => {
+  currentPlanId.value = row.id
+  diffDialogVisible.value = true
+  getCheckItems(row.id).then(res => {
+    const data = res.data || {}
+    // 鐩存帴浣跨敤鎺ュ彛杩斿洖鐨� checkItems
+    diffItemList.value = data.checkItems || []
+    // 璁$畻姹囨�绘暟鎹�
+    const items = diffItemList.value
+    const profitCount = items.filter(i => i.differenceQuantity > 0).length
+    const lossCount = items.filter(i => i.differenceQuantity < 0).length
+    const normalCount = items.filter(i => i.differenceQuantity === 0).length
+    Object.assign(diffSummary, {
+      totalCount: items.length,
+      profitCount,
+      lossCount,
+      normalCount,
+    })
+  })
+}
+
+const handleGenerateProfitLoss = (row) => {
+  ElMessageBox.confirm('纭鏍规嵁鐩樼偣宸紓鐢熸垚鐩堜簭澶勭悊鍗曪紵', '鎻愮ず', { type: 'warning' })
+    .then(() => generateProfitLoss(row.id))
+    .then(() => {
+      ElMessage.success('鐩堜簭澶勭悊鍗曞凡鐢熸垚')
+      getList()
+    })
+}
+
+const handleGenerateFromDiff = () => {
+  handleGenerateProfitLoss({ id: currentPlanId.value })
+  diffDialogVisible.value = false
+}
+
+// 浜у搧閫夋嫨鐩稿叧
+const openProductDialog = () => {
+  productDialogVisible.value = true
+}
+
+const handleProductSelect = (selectedRows) => {
+  // 鎺掗櫎宸插瓨鍦ㄧ殑浜у搧
+  const existingIds = form.items.map(item => item.productModelId)
+  const newItems = selectedRows
+    .filter(p => !existingIds.includes(p.productModelId || p.id))
+    .map(p => ({
+      productModelId: p.productModelId || p.id,
+      productName: p.productName,
+      model: p.model,
+      unit: p.unit,
+      systemQuantity: p.systemQuantity || p.systemQty || 0,
+    }))
+  if (newItems.length === 0) {
+    ElMessage.warning('鎵�閫変骇鍝佸凡瀛樺湪')
+    return
+  }
+  form.items.push(...newItems)
+  ElMessage.success(`宸叉坊鍔� ${newItems.length} 涓骇鍝乣)
+}
+
+const clearProducts = () => {
+  if (form.items.length === 0) return
+  ElMessageBox.confirm('纭娓呯┖鎵�鏈変骇鍝侊紵', '鎻愮ず', { type: 'warning' })
+    .then(() => {
+      form.items = []
+      ElMessage.success('宸叉竻绌�')
+    })
+}
+
+const removeProduct = (index) => {
+  form.items.splice(index, 1)
+}
+
+const getStatusText = (status) => ({
+  '0': '寰呮墽琛�', '1': '鎵ц涓�', '2': '宸插畬鎴�', '3': '宸插彇娑�',
+}[status] || status)
+
+const getStatusType = (status) => ({
+  '0': 'info', '1': 'primary', '2': 'success', '3': 'warning',
+}[status] || 'info')
+
+// 鍔犺浇鐢ㄦ埛鍒楄〃
+const loadUserList = async () => {
+  try {
+    const res = await userListNoPage()
+    userList.value = res?.data || []
+  } catch (err) {
+    console.error('鑾峰彇鐢ㄦ埛鍒楄〃澶辫触:', err)
+    userList.value = []
+  }
+}
+
+onMounted(() => {
+  getList()
+  loadUserList()
+})
+</script>
+
+<style scoped lang="scss">
+.diff-summary {
+  margin-bottom: 20px;
+  padding: 15px;
+  background: #f5f7fa;
+  border-radius: 4px;
+  .stat-item {
+    text-align: center;
+    .stat-label {
+      color: #606266;
+      font-size: 13px;
+      margin-bottom: 8px;
+    }
+    .stat-value {
+      font-size: 24px;
+      font-weight: bold;
+      color: #303133;
+    }
+  }
+}
+.text-profit { color: #67c23a; font-weight: bold; }
+.text-loss { color: #f56c6c; font-weight: bold; }
+.mb10 { margin-bottom: 10px; }
+.section-title {
+  font-weight: bold;
+  margin-bottom: 10px;
+  padding-left: 10px;
+  border-left: 4px solid #409eff;
+}
+</style>
diff --git a/src/views/inventoryManagement/stockProfitLoss/index.vue b/src/views/inventoryManagement/stockProfitLoss/index.vue
new file mode 100644
index 0000000..fdd21fb
--- /dev/null
+++ b/src/views/inventoryManagement/stockProfitLoss/index.vue
@@ -0,0 +1,565 @@
+<template>
+  <div class="app-container">
+    <div class="search_form" style="margin-bottom: 20px;">
+      <div>
+        <span class="search_title">鍗曞彿锛�</span>
+        <el-input v-model="searchForm.orderNo" placeholder="璇疯緭鍏�" clearable style="width: 240px; margin-right: 10px" @change="handleQuery" />
+        <span class="search_title">绫诲瀷锛�</span>
+        <el-select v-model="searchForm.type" placeholder="璇烽�夋嫨" clearable style="width: 240px" @change="handleQuery">
+          <el-option label="鐩樼泩" value="profit" />
+          <el-option label="鐩樹簭" value="loss" />
+        </el-select>
+        <span class="search_title" style="margin-left: 10px;">鐘舵�侊細</span>
+        <el-select v-model="searchForm.status" placeholder="璇烽�夋嫨" clearable style="width: 240px" @change="handleQuery">
+          <el-option label="鑽夌" value="draft" />
+          <el-option label="瀹℃壒涓�" value="approving" />
+          <el-option label="寰呮墽琛�" value="pending" />
+          <el-option label="宸叉墽琛�" value="executed" />
+        </el-select>
+        <span class="search_title" style="margin-left: 10px;">鏉ユ簮锛�</span>
+        <el-select v-model="searchForm.source" placeholder="璇烽�夋嫨" clearable style="width: 240px" @change="handleQuery">
+          <el-option label="鐩樼偣鐢熸垚" value="check" />
+          <el-option label="鎵嬪姩鍒涘缓" value="manual" />
+        </el-select>
+        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">鎼滅储</el-button>
+      </div>
+      <div>
+        <el-button type="primary" @click="openForm('add')">鏂板缓鐩堜簭鍗�</el-button>
+      </div>
+    </div>
+    <div class="table_list">
+      <PIMTable
+        rowKey="id"
+        :column="tableColumn"
+        :tableData="tableData"
+        :tableLoading="tableLoading"
+        :page="page"
+        :isShowPagination="true"
+        @pagination="paginationChange"
+      />
+    </div>
+
+    <!-- 鏂板/缂栬緫鐩堜簭鍗� -->
+    <FormDialog
+      v-model="dialogVisible"
+      :title="dialogTitle"
+      width="1000px"
+      @close="resetForm"
+      @confirm="handleSubmit"
+    >
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="鐩堜簭绫诲瀷" prop="type">
+              <el-radio-group v-model="form.type" :disabled="form.source === 'check'">
+                <el-radio-button label="profit">鐩樼泩</el-radio-button>
+                <el-radio-button label="loss">鐩樹簭</el-radio-button>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="浠撳簱" prop="warehouseId">
+              <el-select v-model="form.warehouseId" placeholder="璇烽�夋嫨浠撳簱" style="width: 100%" filterable :disabled="form.source === 'check'">
+                <el-option v-for="item in warehouseList" :key="item.id" :label="item.name" :value="item.id" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row v-if="form.source === 'check'">
+          <el-col :span="24">
+            <el-form-item label="鍏宠仈鐩樼偣璁″垝">
+              <el-input v-model="form.checkPlanNo" disabled />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="澶囨敞">
+              <el-input v-model="form.remark" type="textarea" :rows="2" />
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-divider content-position="left">鏄庣粏淇℃伅</el-divider>
+        <div class="mb10" v-if="form.source === 'manual'">
+          <el-button type="primary" size="small" @click="openSelectProduct">娣诲姞浜у搧</el-button>
+        </div>
+        <PIMTable
+          :column="itemColumn"
+          :tableData="form.items"
+          :isShowPagination="false"
+          height="300px"
+        />
+        <div class="total-row" style="margin-top: 10px; text-align: right;">
+          <span style="font-weight: bold;">鎬诲樊寮傞噾棰濓細</span>
+          <span :class="getDiffClass(totalDiffAmount)" style="font-size: 18px; font-weight: bold;">
+            {{ totalDiffAmount > 0 ? '+' : '' }}楼{{ totalDiffAmount.toFixed(2) }}
+          </span>
+        </div>
+      </el-form>
+    </FormDialog>
+
+    <!-- 鏌ョ湅璇︽儏 -->
+    <FormDialog
+      v-model="viewDialogVisible"
+      title="鐩堜簭鍗曡鎯�"
+      width="950px"
+      operationType="detail"
+      @close="viewDialogVisible = false"
+    >
+      <el-descriptions :column="2" border>
+        <el-descriptions-item label="鍗曞彿">{{ viewData.orderNo }}</el-descriptions-item>
+        <el-descriptions-item label="绫诲瀷">
+          <el-tag :type="viewData.type === 'profit' ? 'success' : 'danger'">
+            {{ viewData.type === 'profit' ? '鐩樼泩' : '鐩樹簭' }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="浠撳簱">{{ viewData.warehouseName }}</el-descriptions-item>
+        <el-descriptions-item label="鐘舵��">
+          <el-tag :type="getStatusType(viewData.status)">{{ getStatusText(viewData.status) }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="鏉ユ簮">
+          <el-tag :type="viewData.source === 'check' ? 'primary' : 'info'" size="small">
+            {{ viewData.source === 'check' ? '鐩樼偣鐢熸垚' : '鎵嬪姩鍒涘缓' }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="鍏宠仈鐩樼偣璁″垝">{{ viewData.checkPlanNo || '-' }}</el-descriptions-item>
+        <el-descriptions-item label="鍒涘缓浜�">{{ viewData.createByName }}</el-descriptions-item>
+        <el-descriptions-item label="鍒涘缓鏃堕棿">{{ viewData.createTime }}</el-descriptions-item>
+        <el-descriptions-item label="澶囨敞" :span="2">{{ viewData.remark || '-' }}</el-descriptions-item>
+      </el-descriptions>
+      <div style="margin-top: 20px;">
+        <div class="section-title">鏄庣粏淇℃伅</div>
+        <PIMTable
+          :column="viewItemColumn"
+          :tableData="viewData.items"
+          :isShowPagination="false"
+          height="300px"
+        />
+        <div class="total-row" style="margin-top: 10px; text-align: right;">
+          <span style="font-weight: bold;">鎬诲樊寮傞噾棰濓細</span>
+          <span :class="getDiffClass(viewData.totalAmount)" style="font-size: 18px; font-weight: bold;">
+            {{ viewData.totalAmount > 0 ? '+' : '' }}楼{{ viewData.totalAmount?.toFixed(2) }}
+          </span>
+        </div>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="viewDialogVisible = false">鍏抽棴</el-button>
+        </div>
+      </template>
+    </FormDialog>
+
+    <!-- 閫夋嫨鍟嗗搧寮规 -->
+    <FormDialog
+      v-model="productDialogVisible"
+      title="閫夋嫨浜у搧"
+      width="850px"
+      @close="productDialogVisible = false"
+      @confirm="confirmSelectProduct"
+    >
+      <el-form :model="productSearchForm" inline>
+        <el-form-item label="浜у搧鍚嶇О">
+          <el-input v-model="productSearchForm.productName" placeholder="璇疯緭鍏�" clearable style="width: 180px" />
+        </el-form-item>
+        <el-form-item label="瑙勬牸鍨嬪彿">
+          <el-input v-model="productSearchForm.model" placeholder="璇疯緭鍏�" clearable style="width: 150px" />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleProductSearch">鎼滅储</el-button>
+        </el-form-item>
+      </el-form>
+      <PIMTable
+        rowKey="id"
+        :column="productColumn"
+        :tableData="productList"
+        :isSelection="true"
+        :isShowPagination="false"
+        height="350px"
+        @selection-change="handleProductSelectionChange"
+      />
+    </FormDialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, computed } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import PIMTable from '@/components/PIMTable/PIMTable.vue'
+import FormDialog from '@/components/Dialog/FormDialog.vue'
+import {
+  getStockProfitLossPage,
+  getStockProfitLossDetail,
+  addStockProfitLoss,
+  updateStockProfitLoss,
+  deleteStockProfitLoss,
+  submitApproval,
+  executeProfitLoss,
+} from '@/api/inventoryManagement/stockProfitLoss.js'
+
+const tableData = ref([])
+const tableLoading = ref(false)
+const page = reactive({ current: 1, size: 20, total: 0 })
+const searchForm = reactive({ orderNo: '', type: '', status: '', source: '' })
+
+// 涓昏〃鏍煎垪閰嶇疆
+const tableColumn = ref([
+  { label: '鍗曞彿', prop: 'orderNo' },
+  {
+    label: '绫诲瀷',
+    prop: 'type',
+    align: 'center',
+    dataType: 'tag',
+    formatData: (v) => v === 'profit' ? '鐩樼泩' : '鐩樹簭',
+    formatType: (v) => v === 'profit' ? 'success' : 'danger'
+  },
+  { label: '浠撳簱', prop: 'warehouseName' },
+  { label: '鍟嗗搧鏁伴噺', prop: 'productCount', align: 'center' },
+  {
+    label: '閲戦',
+    prop: 'totalAmount',
+    align: 'right',
+    formatData: (v) => `楼${v?.toFixed(2) || '0.00'}`
+  },
+  {
+    label: '鏉ユ簮',
+    prop: 'source',
+    align: 'center',
+    dataType: 'tag',
+    formatData: (v) => v === 'check' ? '鐩樼偣鐢熸垚' : '鎵嬪姩鍒涘缓',
+    formatType: (v) => v === 'check' ? 'primary' : 'info'
+  },
+  {
+    label: '鐘舵��',
+    prop: 'status',
+    align: 'center',
+    dataType: 'tag',
+    formatData: (v) => ({
+      draft: '鑽夌', approving: '瀹℃壒涓�', pending: '寰呮墽琛�', executed: '宸叉墽琛�'
+    }[v] || v),
+    formatType: (v) => ({
+      draft: 'info', approving: 'warning', pending: 'primary', executed: 'success'
+    }[v] || '')
+  },
+  { label: '鍒涘缓鏃堕棿', prop: 'createTime', align: 'center' },
+  {
+    label: '鎿嶄綔',
+    dataType: 'action',
+    fixed: 'right',
+    align: 'center',
+    operation: [
+      { name: '鏌ョ湅', clickFun: (row) => handleView(row) },
+      { name: '缂栬緫', clickFun: (row) => handleEdit(row), showHide: (row) => row.status === 'draft' && row.source === 'manual' },
+      { name: '鎻愪氦瀹℃壒', clickFun: (row) => handleSubmitApproval(row), showHide: (row) => row.status === 'draft' && row.source === 'manual' },
+      { name: '鍒犻櫎', clickFun: (row) => handleDelete(row), showHide: (row) => row.status === 'draft' && row.source === 'manual' },
+      { name: '鎵ц', clickFun: (row) => handleExecute(row), showHide: (row) => row.status === 'pending' },
+    ]
+  }
+])
+
+// 鏄庣粏琛ㄦ牸鍒楅厤缃紙缂栬緫鐢級
+const itemColumn = ref([
+  { label: '浜у搧鍚嶇О', prop: 'productName' },
+  { label: '瑙勬牸鍨嬪彿', prop: 'model' },
+  { label: '鍗曚綅', prop: 'unit', align: 'center' },
+  { label: '绯荤粺搴撳瓨', prop: 'systemQty', align: 'center' },
+  {
+    label: '瀹為檯鏁伴噺',
+    align: 'center',
+    dataType: 'slot',
+    slot: 'actualQty'
+  },
+  {
+    label: '宸紓鏁伴噺',
+    align: 'center',
+    dataType: 'slot',
+    slot: 'diffQty'
+  },
+  {
+    label: '鎴愭湰鍗曚环',
+    align: 'center',
+    dataType: 'slot',
+    slot: 'costPrice'
+  },
+  {
+    label: '宸紓閲戦',
+    align: 'center',
+    dataType: 'slot',
+    slot: 'diffAmount'
+  },
+  {
+    label: '鎿嶄綔',
+    align: 'center',
+    dataType: 'slot',
+    slot: 'action'
+  }
+])
+
+// 鏌ョ湅鏄庣粏琛ㄦ牸鍒楅厤缃�
+const viewItemColumn = ref([
+  { label: '浜у搧鍚嶇О', prop: 'productName' },
+  { label: '瑙勬牸鍨嬪彿', prop: 'model' },
+  { label: '鍗曚綅', prop: 'unit', align: 'center' },
+  { label: '绯荤粺搴撳瓨', prop: 'systemQty', align: 'center' },
+  { label: '瀹為檯鏁伴噺', prop: 'actualQty', align: 'center' },
+  {
+    label: '宸紓鏁伴噺',
+    align: 'center',
+    dataType: 'slot',
+    slot: 'viewDiffQty'
+  },
+  {
+    label: '鎴愭湰鍗曚环',
+    align: 'right',
+    formatData: (v) => `楼${v?.toFixed(2) || '0.00'}`
+  },
+  {
+    label: '宸紓閲戦',
+    align: 'right',
+    dataType: 'slot',
+    slot: 'viewDiffAmount'
+  }
+])
+
+// 浜у搧閫夋嫨琛ㄦ牸鍒楅厤缃�
+const productColumn = ref([
+  { label: '浜у搧鍚嶇О', prop: 'productName' },
+  { label: '瑙勬牸鍨嬪彿', prop: 'model' },
+  { label: '鍗曚綅', prop: 'unit', align: 'center' },
+  { label: '褰撳墠搴撳瓨', prop: 'currentQty', align: 'center' }
+])
+
+const dialogVisible = ref(false)
+const dialogTitle = ref('')
+const dialogMode = ref('add')
+const submitLoading = ref(false)
+const formRef = ref(null)
+const form = reactive({
+  id: null,
+  type: 'profit',
+  warehouseId: null,
+  remark: '',
+  source: 'manual',
+  checkPlanNo: '',
+  items: [],
+})
+
+const warehouseList = ref([])
+const productList = ref([])
+const selectedProducts = ref([])
+const productDialogVisible = ref(false)
+const productSearchForm = reactive({ productName: '', model: '' })
+
+const viewDialogVisible = ref(false)
+const viewData = reactive({
+  orderNo: '', type: '', warehouseName: '', status: '', source: '',
+  checkPlanNo: '', createByName: '', createTime: '', remark: '',
+  items: [], totalAmount: 0,
+})
+
+const rules = {
+  type: [{ required: true, message: '璇烽�夋嫨鐩堜簭绫诲瀷', trigger: 'change' }],
+  warehouseId: [{ required: true, message: '璇烽�夋嫨浠撳簱', trigger: 'change' }],
+}
+
+const totalDiffAmount = computed(() => {
+  return form.items.reduce((sum, item) => sum + (item.actualQty - item.systemQty) * (item.costPrice || 0), 0)
+})
+
+const getList = () => {
+  tableLoading.value = true
+  getStockProfitLossPage({ current: page.current, size: page.size, ...searchForm })
+    .then(res => {
+      tableData.value = res.data?.records || []
+      page.total = res.data?.total || 0
+    })
+    .finally(() => { tableLoading.value = false })
+}
+
+const handleQuery = () => {
+  page.current = 1
+  getList()
+}
+
+const resetQuery = () => {
+  Object.assign(searchForm, { orderNo: '', type: '', status: '', source: '' })
+  handleQuery()
+}
+
+const paginationChange = ({ page: current, limit }) => {
+  page.current = current
+  page.size = limit
+  getList()
+}
+
+const openForm = (mode) => {
+  dialogMode.value = mode
+  dialogTitle.value = mode === 'add' ? '鏂板缓鐩堜簭鍗�' : '缂栬緫鐩堜簭鍗�'
+  if (mode === 'add') {
+    Object.assign(form, {
+      id: null, type: 'profit', warehouseId: null, remark: '',
+      source: 'manual', checkPlanNo: '', items: [],
+    })
+  }
+  dialogVisible.value = true
+}
+
+const handleEdit = (row) => {
+  getStockProfitLossDetail(row.id).then(res => {
+    const data = res.data
+    Object.assign(form, {
+      id: data.id,
+      type: data.type,
+      warehouseId: data.warehouseId,
+      remark: data.remark,
+      source: data.source,
+      checkPlanNo: data.checkPlanNo,
+      items: data.items || [],
+    })
+    dialogMode.value = 'edit'
+    dialogTitle.value = '缂栬緫鐩堜簭鍗�'
+    dialogVisible.value = true
+  })
+}
+
+const handleSubmit = () => {
+  formRef.value?.validate(valid => {
+    if (!valid) return
+    if (form.items.length === 0) {
+      ElMessage.warning('璇锋坊鍔犺嚦灏戜竴涓骇鍝�')
+      return
+    }
+    submitLoading.value = true
+    const data = { ...form }
+    const action = dialogMode.value === 'add' ? addStockProfitLoss : updateStockProfitLoss
+    action(data).then(() => {
+      ElMessage.success('淇濆瓨鎴愬姛')
+      dialogVisible.value = false
+      getList()
+    }).finally(() => { submitLoading.value = false })
+  })
+}
+
+const resetForm = () => {
+  formRef.value?.resetFields()
+}
+
+const handleView = (row) => {
+  getStockProfitLossDetail(row.id).then(res => {
+    const data = res.data || {}
+    Object.assign(viewData, {
+      orderNo: data.orderNo,
+      type: data.type,
+      warehouseName: data.warehouseName,
+      status: data.status,
+      source: data.source,
+      checkPlanNo: data.checkPlanNo,
+      createByName: data.createByName,
+      createTime: data.createTime,
+      remark: data.remark,
+      items: data.items || [],
+      totalAmount: data.totalAmount || 0,
+    })
+    viewDialogVisible.value = true
+  })
+}
+
+const handleDelete = (row) => {
+  ElMessageBox.confirm('纭鍒犻櫎璇ョ泩浜忓崟锛�', '鎻愮ず', { type: 'warning' })
+    .then(() => deleteStockProfitLoss(row.id))
+    .then(() => {
+      ElMessage.success('鍒犻櫎鎴愬姛')
+      getList()
+    })
+}
+
+const handleSubmitApproval = (row) => {
+  ElMessageBox.confirm('纭鎻愪氦瀹℃壒锛�', '鎻愮ず', { type: 'warning' })
+    .then(() => submitApproval(row.id))
+    .then(() => {
+      ElMessage.success('鎻愪氦鎴愬姛')
+      getList()
+    })
+}
+
+const handleExecute = (row) => {
+  const actionText = row.type === 'profit' ? '鐩樼泩鍏ュ簱' : '鐩樹簭鍑哄簱'
+  ElMessageBox.confirm(`纭鎵ц${actionText}锛熸墽琛屽悗灏嗘洿鏂板簱瀛樻暟閲廯, '鎻愮ず', { type: 'warning' })
+    .then(() => executeProfitLoss(row.id))
+    .then(() => {
+      ElMessage.success('鎵ц鎴愬姛')
+      getList()
+    })
+}
+
+const openSelectProduct = () => {
+  productDialogVisible.value = true
+  handleProductSearch()
+}
+
+const handleProductSearch = () => {
+  // 璋冪敤鎺ュ彛鏌ヨ鍟嗗搧搴撳瓨
+}
+
+const handleProductSelectionChange = (selection) => {
+  selectedProducts.value = selection
+}
+
+const confirmSelectProduct = () => {
+  if (selectedProducts.value.length === 0) {
+    ElMessage.warning('璇烽�夋嫨浜у搧')
+    return
+  }
+  const newItems = selectedProducts.value.map(p => ({
+    productId: p.id,
+    productName: p.productName,
+    model: p.model,
+    unit: p.unit,
+    batchNo: p.batchNo || '',
+    systemQty: p.currentQty || 0,
+    actualQty: p.currentQty || 0,
+    costPrice: p.costPrice || 0,
+  }))
+  form.items.push(...newItems)
+  productDialogVisible.value = false
+}
+
+const removeItem = (index) => {
+  form.items.splice(index, 1)
+}
+
+const getStatusText = (status) => ({
+  draft: '鑽夌', approving: '瀹℃壒涓�', pending: '寰呮墽琛�', executed: '宸叉墽琛�',
+}[status] || status)
+
+const getStatusType = (status) => ({
+  draft: 'info', approving: 'warning', pending: 'primary', executed: 'success',
+}[status] || '')
+
+const getDiffClass = (val) => val > 0 ? 'text-profit' : val < 0 ? 'text-loss' : ''
+
+onMounted(() => {
+  getList()
+})
+</script>
+
+<style scoped lang="scss">
+.search_form {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  margin-bottom: 16px;
+}
+.dialog-footer {
+  display: flex;
+  justify-content: center;
+  gap: 10px;
+}
+.section-title {
+  font-weight: bold;
+  margin-bottom: 10px;
+  padding-left: 10px;
+  border-left: 4px solid #409eff;
+}
+.text-profit { color: #67c23a; font-weight: bold; }
+.text-loss { color: #f56c6c; font-weight: bold; }
+</style>
\ No newline at end of file

--
Gitblit v1.9.3