From bb44f08f420fc6b1520c06f3698f3d4f52e4a06b Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期二, 03 三月 2026 16:08:25 +0800
Subject: [PATCH] 公司app 1.添加商机管理功能 2.app部署修改

---
 src/pages/index.vue                              |  496 ++-------------
 src/pages.json                                   |   14 
 src/pages/opportunityManagement/detail.vue       | 1035 +++++++++++++++++++++++++++++++
 src/pages/opportunityManagement/index.vue        |  237 +++++++
 src/api/salesManagement/opportunityManagement.js |   70 ++
 src/config.js                                    |    7 
 src/pages/opportunityManagement/fileList.vue     |   75 ++
 7 files changed, 1,499 insertions(+), 435 deletions(-)

diff --git a/src/api/salesManagement/opportunityManagement.js b/src/api/salesManagement/opportunityManagement.js
new file mode 100644
index 0000000..c67c2b5
--- /dev/null
+++ b/src/api/salesManagement/opportunityManagement.js
@@ -0,0 +1,70 @@
+// 鍟嗘満绠$悊鎺ュ彛
+import request from "@/utils/request";
+
+// 鍒嗛〉鏌ヨ鍟嗘満鍒楄〃
+export function opportunityListPage(query) {
+  return request({
+    url: "/businessOpportunity/listPage",
+    method: "get",
+    params: query,
+  });
+}
+
+// 鏂板鍟嗘満
+export function addOpportunity(data) {
+  return request({
+    url: "/businessOpportunity/add",
+    method: "post",
+    data: data,
+  });
+}
+
+// 淇敼鍟嗘満
+export function updateOpportunity(data) {
+  return request({
+    url: "/businessOpportunity/update",
+    method: "post",
+    data: data,
+  });
+}
+// 娣诲姞鍟嗘満
+export function addDescription(data) {
+  return request({
+    url: "/businessOpportunity/addDescription",
+    method: "post",
+    data: data,
+  });
+}
+
+// 鍒犻櫎鍟嗘満
+export function delOpportunity(ids) {
+  return request({
+    url: "/businessOpportunity/delete",
+    method: "delete",
+    data: ids,
+  });
+}
+// 鏌ヨ鐪�
+export function getProvinceList() {
+  return request({
+    url: "/businessOpportunity/getProvinceList",
+    method: "get",
+  });
+}
+// 鏌ヨ甯�
+export function getCityList(id) {
+  return request({
+    url: "/businessOpportunity/getCityList",
+    method: "get",
+    params: id,
+  });
+}
+
+// 鍒犻櫎闄勪欢锛堥�氱敤闄勪欢锛�
+export function delCommonFile(ids) {
+  return request({
+    url: "/commonFile/delCommonFile",
+    method: "delete",
+    data: ids,
+  });
+}
diff --git a/src/config.js b/src/config.js
index 028c4a0..6b76085 100644
--- a/src/config.js
+++ b/src/config.js
@@ -1,11 +1,6 @@
 // 搴旂敤鍏ㄥ眬閰嶇疆
 const config = {
-  //  baseUrl: 'https://vue.ruoyi.vip/prod-api',
-  // baseUrl: 'http://localhost/prod-api',
-  baseUrl: 'http://114.132.189.42:7003', // 瀹佸娑︽嘲
-  // baseUrl: 'http://192.168.1.147:9036',
-   //cloud鍚庡彴缃戝叧鍦板潃
-  //  baseUrl: 'http://192.168.10.3:8080',
+  baseUrl: 'http://114.132.189.42:7003', // 鍏徃
    // 搴旂敤淇℃伅
    appInfo: {
      // 搴旂敤鍚嶇О
diff --git a/src/pages.json b/src/pages.json
index 4151447..fdc11e7 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -149,6 +149,20 @@
       }
     },
     {
+      "path": "pages/opportunityManagement/index",
+      "style": {
+        "navigationBarTitleText": "鍟嗘満绠$悊",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/opportunityManagement/detail",
+      "style": {
+        "navigationBarTitleText": "鍟嗘満璇︽儏",
+        "navigationStyle": "custom"
+      }
+    },
+    {
       "path": "pages/procurementManagement/procurementLedger/index",
       "style": {
         "navigationBarTitleText": "閲囪喘鍙拌处",
diff --git a/src/pages/index.vue b/src/pages/index.vue
index 3ba08d3..63b3ecb 100644
--- a/src/pages/index.vue
+++ b/src/pages/index.vue
@@ -32,155 +32,20 @@
 		<!--			</view>-->
 		<!--		</view>-->
 		
-		<!-- 钀ラ攢绠$悊妯″潡 -->
-		<view class="common-module marketing-module">
-			<view class="module-header">
-				<view class="module-title-container">
-					<text class="module-title">钀ラ攢绠$悊</text>
+		<!-- 鍟嗘満绠$悊鍏ュ彛锛堝皬鎸夐挳锛� -->
+		<view class="opportunity-entry">
+			<view class="opportunity-item" @click="goOpportunity">
+				<view class="opportunity-icon-wrap">
+					<image
+						class="opportunity-icon"
+						src="/static/images/icon/xiaoshoutaizhang@2x.png"
+						mode="aspectFit"
+					/>
 				</view>
-			</view>
-			<view class="module-content">
-				<up-grid
-					:border="false"
-					col="4"
-				>
-					<up-grid-item
-						v-for="(item, index) in marketingItems"
-						:key="index"
-						@click="handleCommonItemClick(item)"
-					>
-						<view class="icon-container" :style="{ background: item.bgColor }">
-							<up-icon
-								:name="item.icon"
-								:size="58"
-								color="#ffffff"
-							></up-icon>
-						</view>
-						<text class="item-label">{{item.label}}</text>
-					</up-grid-item>
-				</up-grid>
+				<text class="opportunity-text small-title">鍟嗘満绠$悊</text>
 			</view>
 		</view>
 		
-		<!-- 閲囪喘绠$悊妯″潡 -->
-		<view class="common-module purchase-module">
-			<view class="module-header">
-				<view class="module-title-container">
-					<text class="module-title">閲囪喘绠$悊</text>
-				</view>
-			</view>
-			<view class="module-content">
-				<up-grid
-					:border="false"
-					col="4"
-				>
-					<up-grid-item
-						v-for="(item, index) in purchaseItems"
-						:key="index"
-						@click="handleCommonItemClick(item)"
-					>
-						<view class="icon-container" :style="{ background: item.bgColor }">
-							<up-icon
-								:name="item.icon"
-								:size="58"
-								color="#ffffff"
-							></up-icon>
-						</view>
-						<text class="item-label">{{item.label}}</text>
-					</up-grid-item>
-				</up-grid>
-			</view>
-		</view>
-		
-		<!-- 鍗忓悓鍔炲叕妯″潡 -->
-		<view class="common-module collaboration-module">
-			<view class="module-header">
-				<view class="module-title-container">
-					<text class="module-title">鍗忓悓鍔炲叕</text>
-				</view>
-			</view>
-			<view class="module-content">
-				<up-grid
-					:border="false"
-					col="4"
-				>
-					<up-grid-item
-						v-for="(item, index) in collaborationItems"
-						:key="index"
-						@click="handleCommonItemClick(item)"
-					>
-						<view class="icon-container" :style="{ background: item.bgColor }">
-							<up-icon
-								:name="item.icon"
-								:size="58"
-								color="#ffffff"
-							></up-icon>
-						</view>
-						<text class="item-label">{{item.label}}</text>
-					</up-grid-item>
-				</up-grid>
-			</view>
-		</view>
-		
-		<!-- 鐢熶骇绠℃帶妯″潡 -->
-<!--		<view class="common-module production-module">-->
-<!--			<view class="module-header">-->
-<!--				<view class="module-title-container">-->
-<!--					<text class="module-title">鐢熶骇绠℃帶</text>-->
-<!--				</view>-->
-<!--			</view>-->
-<!--			<view class="module-content">-->
-<!--				<up-grid-->
-<!--					:border="false"-->
-<!--					col="4"-->
-<!--				>-->
-<!--					<up-grid-item-->
-<!--						v-for="(item, index) in productionItems"-->
-<!--						:key="index"-->
-<!--						@click="handleCommonItemClick(item)"-->
-<!--					>-->
-<!--						<view class="icon-container" :style="{ background: item.bgColor }">-->
-<!--							<up-icon-->
-<!--								:name="item.icon"-->
-<!--								:size="58"-->
-<!--								color="#ffffff"-->
-<!--							></up-icon>-->
-<!--						</view>-->
-<!--						<text class="item-label">{{item.label}}</text>-->
-<!--					</up-grid-item>-->
-<!--				</up-grid>-->
-<!--			</view>-->
-<!--		</view>-->
-		
-		<!-- 璁惧绠$悊妯″潡 -->
-		<view class="common-module equipment-module">
-			<view class="module-header">
-				<view class="module-title-container">
-					<text class="module-title">璁惧绠$悊</text>
-				</view>
-			</view>
-			<view class="module-content">
-				<up-grid
-					:border="false"
-					col="4"
-				>
-					<up-grid-item
-						v-for="(item, index) in equipmentItems"
-						:key="index"
-						@click="handleCommonItemClick(item)"
-					>
-						<view class="icon-container" :style="{ background: item.bgColor }">
-							<up-icon
-								:name="item.icon"
-								:size="58"
-								color="#ffffff"
-							></up-icon>
-						</view>
-						<text class="item-label">{{item.label}}</text>
-					</up-grid-item>
-				</up-grid>
-			</view>
-		</view>
 	</view>
 </template>
 
@@ -213,290 +78,6 @@
 		currentStatus.value = statusList[statusIndex]
 	}, 3000)
 }
-
-// 钀ラ攢绠$悊鍔熻兘鏁版嵁
-const marketingItems = reactive([
-	{
-		icon: '/static/images/icon/xiaoshoutaizhang@2x.png',
-		label: '閿�鍞彴璐�',
-	},
-	{
-		icon: '/static/images/icon/kaipiaodengji@2x.png',
-		label: '寮�绁ㄧ櫥璁�',
-	},
-	{
-		icon: '/static/images/icon/kaipiaotaizhang@2x.png',
-		label: '寮�绁ㄥ彴璐�',
-	},
-	{
-		icon: '/static/images/icon/huikuandengji@2x.png',
-		label: '鍥炴鐧昏',
-	},
-	{
-		icon: '/static/images/icon/huikuanliushui@2x.png',
-		label: '鍥炴娴佹按',
-	},
-	{
-		icon: '/static/images/icon/kehuwanglai@2x.png',
-		label: '瀹㈡埛寰�鏉�',
-	}
-]);
-
-// 閲囪喘绠$悊鍔熻兘鏁版嵁
-const purchaseItems = reactive([
-	{
-		icon: '/static/images/icon/caigoutaizhang@2x.png',
-		label: '閲囪喘鍙拌处',
-	},
-	{
-		icon: '/static/images/icon/laipiaodengji@2x.png',
-		label: '鏉ョエ鐧昏',
-	},
-	{
-		icon: '/static/images/icon/laipiaotaizhang@2x.png',
-		label: '鏉ョエ鍙拌处',
-	},
-	{
-		icon: '/static/images/icon/fukuanjingji@2x.png',
-		label: '浠樻鐧昏',
-	},
-	{
-		icon: '/static/images/icon/fukuanliushui@2x.png',
-		label: '浠樻娴佹按',
-	},
-	{
-		icon: '/static/images/icon/gongyingshangwanglai@2x.png',
-		label: '渚涘簲鍟嗗線鏉�',
-	},
-]);
-
-// 鍗忓悓鍔炲叕鍔熻兘鏁版嵁
-const collaborationItems = reactive([
-	{
-		icon: '/static/images/icon/xietongshenpi@2x.png',
-		label: '鍗忓悓瀹℃壒',
-	},
-	{
-		icon: '/static/images/icon/kehubaifang@2x.png',
-		label: '瀹㈡埛鎷滆',
-	}
-]);
-
-// 鐢熶骇绠℃帶鍔熻兘鏁版嵁
-const productionItems = reactive([
-	{
-		icon: '/static/images/icon/shengchandingdan@2x.png',
-		label: '鐢熶骇璁㈠崟',
-		bgColor: '#FF9800'
-	},
-	{
-		icon: '/static/images/icon/shengchanpaigong@2x.png',
-		label: '鐢熶骇娲惧伐',
-		bgColor: '#FF6B35'
-	},
-	{
-		icon: '/static/images/icon/shengchanpaichan@2x.png',
-		label: '宸ュ簭鎺掍骇',
-		bgColor: '#E91E63'
-	},
-	{
-		icon: '/static/images/icon/shengchanbaogong@2x.png',
-		label: '鐢熶骇鎶ュ伐',
-		bgColor: '#673AB7'
-	},
-	{
-		icon: '/static/images/icon/shengchanhesuan@2x.png',
-		label: '鐢熶骇鏍哥畻',
-		bgColor: '#3F51B5'
-	}
-]);
-
-// 璁惧绠$悊鍔熻兘鏁版嵁
-const equipmentItems = reactive([
-	// {
-	// 	icon: '/static/images/icon/shebeitaizhang@2x.png',
-	// 	label: '璁惧鍙拌处',
-	// },
-	{
-		icon: '/static/images/icon/shbeibaoxiu@2x.png',
-		label: '璁惧鎶ヤ慨',
-	},
-	{
-		icon: '/static/images/icon/shbeibaoyang@2x.png',
-		label: '璁惧淇濆吇',
-	},
-	{
-		icon: '/static/images/icon/xunjianshangchuan@2x.png',
-		label: '宸℃涓婁紶',
-	},
-	{
-		icon: '/static/images/icon/guzhangfenxi@2x.png',
-		label: '鍒嗘瀽杩芥函',
-		bgColor: '#ff9800'
-	},
-	{
-		icon: '/static/images/icon/zhinengpaidan@2x.png',
-		label: '鏅鸿兘娲惧崟',
-		bgColor: '#ff6b35'
-	},
-	{
-		icon: '/static/images/icon/zuoyezhidao@2x.png',
-		label: '浣滀笟鎸囧',
-		bgColor: '#4caf50'
-	},
-	{
-		icon: '/static/images/icon/jieguoyanzheng@2x.png',
-		label: '缁撴灉楠岃瘉',
-		bgColor: '#9c27b0'
-	}
-]);
-
-// 澶勭悊甯哥敤鍔熻兘鐐瑰嚮
-const handleCommonItemClick = (item) => {
-	// 鏍规嵁涓嶅悓鐨勫姛鑳介」杩涜璺宠浆
-	switch (item.label) {
-		case '閿�鍞彴璐�':
-			uni.navigateTo({
-				url: '/pages/sales/salesAccount/index'
-			});
-			break;
-		case '寮�绁ㄧ櫥璁�':
-			uni.navigateTo({
-				url: '/pages/sales/invoicingRegistration/index'
-			});
-			break;
-		case '寮�绁ㄥ彴璐�':
-			uni.navigateTo({
-				url: '/pages/sales/invoiceLedger/index'
-			});
-			break;
-		case '鍥炴鐧昏':
-			uni.navigateTo({
-				url: '/pages/sales/receiptPayment/index'
-			});
-			break;
-		case '鍥炴娴佹按':
-			uni.navigateTo({
-				url: '/pages/sales/receiptPaymentHistory/index'
-			});
-			break;
-		case '瀹㈡埛寰�鏉�':
-			uni.navigateTo({
-				url: '/pages/sales/receiptPaymentLedger/index'
-			});
-			break;
-		case '閲囪喘鍙拌处':
-			uni.navigateTo({
-				url: '/pages/procurementManagement/procurementLedger/index'
-			});
-			break;
-		case '鏉ョエ鐧昏':
-			uni.navigateTo({
-				url: '/pages/procurementManagement/invoiceEntry/index'
-			});
-			break;
-		case '鏉ョエ鍙拌处':
-			uni.navigateTo({
-				url: '/pages/procurementManagement/procurementInvoiceLedger/index'
-			});
-			break;
-		case '浠樻鐧昏':
-			uni.navigateTo({
-				url: '/pages/procurementManagement/paymentEntry/index'
-			});
-			break;
-		case '浠樻娴佹按':
-			uni.navigateTo({
-				url: '/pages/procurementManagement/receiptPaymentHistory/index'
-			});
-			break;
-		case '渚涘簲鍟嗗線鏉�':
-			uni.navigateTo({
-				url: '/pages/procurementManagement/paymentLedger/index'
-			});
-			break;
-		case '鍗忓悓瀹℃壒':
-			uni.navigateTo({
-				url: '/pages/cooperativeOffice/collaborativeApproval/index'
-			});
-			break;
-		case '瀹㈡埛鎷滆':
-			uni.navigateTo({
-				url: '/pages/cooperativeOffice/clientVisit/index'
-			});
-			break;
-		case '鐢熶骇璁㈠崟':
-			uni.navigateTo({
-				url: '/pages/productionManagement/productionOrder/index'
-			});
-			break;
-		case '鐢熶骇娲惧伐':
-			uni.navigateTo({
-				url: '/pages/productionManagement/productionDispatching/index'
-			});
-			break;
-		case '宸ュ簭鎺掍骇':
-			uni.navigateTo({
-				url: '/pages/productionManagement/processScheduling/index'
-			});
-			break;
-		case '鐢熶骇鎶ュ伐':
-			uni.navigateTo({
-				url: '/pages/productionManagement/productionReport/index'
-			});
-			break;
-		case '鐢熶骇鏍哥畻':
-			uni.navigateTo({
-				url: '/pages/productionManagement/productionAccounting/index'
-			});
-			break;
-		case '璁惧鍙拌处':
-			uni.navigateTo({
-				url: '/pages/equipmentManagement/ledger/index'
-			});
-			break;
-		case '璁惧鎶ヤ慨':
-			uni.navigateTo({
-				url: '/pages/equipmentManagement/repair/index'
-			});
-			break;
-		case '璁惧淇濆吇':
-			uni.navigateTo({
-				url: '/pages/equipmentManagement/upkeep/index'
-			});
-			break;
-		case '宸℃涓婁紶':
-			uni.navigateTo({
-				url: '/pages/inspectionUpload/index'
-			});
-			break;
-		case '鍒嗘瀽杩芥函':
-			uni.navigateTo({
-				url: '/pages/equipmentManagement/faultAnalysis/index'
-			});
-			break;
-		case '鏅鸿兘娲惧崟':
-			uni.navigateTo({
-				url: '/pages/equipmentManagement/smartDispatch/index'
-			});
-			break;
-		case '浣滀笟鎸囧':
-			uni.navigateTo({
-				url: '/pages/equipmentManagement/sop/index'
-			});
-			break;
-		case '缁撴灉楠岃瘉':
-			uni.navigateTo({
-				url: '/pages/equipmentManagement/verification/index'
-			});
-			break;
-		default:
-			uni.showToast({
-				title: `鐐瑰嚮浜�${item.label}`,
-				icon: 'none'
-			});
-	}
-};
 
 // 鍒涘缓瀵瑰瓙缁勪欢鐨勫紩鐢�
 const uToastRef = ref(null);
@@ -549,6 +130,13 @@
 	if (uToastRef.value) {
 		uToastRef.value.success(`鐐瑰嚮浜嗙${name + 1}涓猔); // 娉ㄦ剰锛氳繖閲屽姞1鏄洜涓洪�氬父鎴戜滑鏄粠绗�1涓紑濮嬭鏁扮殑
 	}
+};
+
+// 璺宠浆鍒板晢鏈虹鐞�
+const goOpportunity = () => {
+	uni.navigateTo({
+		url: '/pages/opportunityManagement/index'
+	});
 };
 
 onMounted(() => {
@@ -1003,6 +591,52 @@
 	/* #endif */
 }
 
+.opportunity-icon {
+	width: 2.4rem;
+	height: 2.4rem;
+}
+
+/* 鍟嗘満绠$悊灏忔寜閽牱寮� */
+.opportunity-entry {
+	margin-top: 0.5rem;
+	display: flex;
+	justify-content: flex-start;
+}
+
+.opportunity-item {
+	flex-direction: row;
+	align-items: center;
+	justify-content: flex-start;
+	background: #ffffff;
+	border-radius: 999px;
+	padding: 0.4rem 0.9rem 0.4rem 0.5rem;
+	box-shadow: 0 0.125rem 0.75rem rgba(15, 23, 42, 0.12);
+	display: inline-flex;
+}
+
+.opportunity-icon-wrap {
+	width: 2.4rem;
+	height: 2.4rem;
+	border-radius: 999px;
+	background: #e3f2ff;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	margin-right: 0.4rem;
+}
+
+.opportunity-text {
+	font-size: 0.9rem;
+	color: #1f2933;
+	font-weight: 500;
+}
+
+.small-title {
+	font-size: 0.78rem;
+	font-weight: 400;
+	color: #4b5563;
+}
+
 /* 鏆楄壊妯″紡閫傞厤 */
 @media (prefers-color-scheme: dark) {
 	.content {
@@ -1165,6 +799,10 @@
 
 .item-label { font-size: 0.8125rem; margin-top: 0.25rem; margin-bottom: 0.625rem; }
 .grid-text { font-size: 0.875rem; }
+.opportunity-icon {
+	width: 2.4rem;
+	height: 2.4rem;
+}
 
 @media (prefers-color-scheme: dark) {
 	.common-module { box-shadow: 0 0.375rem 1.5rem rgba(0,0,0,0.35); }
diff --git a/src/pages/opportunityManagement/detail.vue b/src/pages/opportunityManagement/detail.vue
new file mode 100644
index 0000000..14152bb
--- /dev/null
+++ b/src/pages/opportunityManagement/detail.vue
@@ -0,0 +1,1035 @@
+<template>
+	<view class="account-detail">
+		<PageHeader :title="pageTitle" @back="goBack" />
+		
+		<view class="detail-body">
+			<view class="detail-card">
+				<view class="section-header">
+					<text class="section-title">鍩烘湰淇℃伅</text>
+					<text class="section-subtitle">璇峰~鍐欏晢鏈虹殑鍩虹璧勬枡</text>
+				</view>
+				
+				<up-form
+					ref="formRef"
+					:rules="rules"
+					:model="form"
+					label-width="90"
+					@submit="onSubmit"
+				>
+			<!-- 鍩烘湰淇℃伅 -->
+			<up-form-item label="瀹㈡埛鍚嶇О" prop="customerName" :required="isAddOrEdit">
+				<up-input
+					v-model="form.customerName"
+					:disabled="isDetail || isAddOperation"
+					placeholder="璇疯緭鍏ュ鎴峰悕绉�"
+				/>
+			</up-form-item>
+			
+			<up-form-item label="鐪佷唤" prop="province">
+				<up-input
+					v-model="form.province"
+					readonly
+					:disabled="isDetail || isAddOperation"
+					placeholder="鐐瑰嚮閫夋嫨鐪佷唤"
+					@click="onProvinceClick"
+				/>
+				<template #right>
+					<up-icon
+						name="arrow-right"
+						@click="onProvinceClick"
+					></up-icon>
+				</template>
+			</up-form-item>
+			
+			<up-form-item label="鍩庡競" prop="city">
+				<up-input
+					v-model="form.city"
+					readonly
+					:disabled="isDetail || isAddOperation"
+					placeholder="鐐瑰嚮閫夋嫨鍩庡競"
+					@click="onCityClick"
+				/>
+				<template #right>
+					<up-icon
+						name="arrow-right"
+						@click="onCityClick"
+					></up-icon>
+				</template>
+			</up-form-item>
+			
+			<up-form-item label="鍟嗘満鏉ユ簮" prop="businessSource">
+				<up-input
+					v-model="form.businessSource"
+					:disabled="isDetail || isAddOperation"
+					placeholder="璇疯緭鍏ュ晢鏈烘潵婧�"
+				/>
+			</up-form-item>
+			
+			<up-form-item label="琛屼笟" prop="industry">
+				<up-input
+					v-model="form.industry"
+					:disabled="isDetail || isAddOperation"
+					placeholder="璇疯緭鍏ヨ涓�"
+				/>
+			</up-form-item>
+			
+			<up-form-item label="涓昏惀浜у搧" prop="mainProducts">
+				<up-input
+					v-model="form.mainProducts"
+					:disabled="isDetail || isAddOperation"
+					placeholder="璇疯緭鍏ヤ富钀ヤ骇鍝�"
+				/>
+			</up-form-item>
+			
+			<up-form-item label="涓昏惀涓氬姟鏀跺叆" prop="mainBusinessRevenue">
+				<up-input
+					v-model="form.mainBusinessRevenue"
+					:disabled="isDetail || isAddOperation"
+					placeholder="璇疯緭鍏ヤ富钀ヤ笟鍔℃敹鍏�"
+				/>
+			</up-form-item>
+			
+			<up-form-item label="瀹㈡埛瑙勬ā" prop="customerScale">
+				<up-input
+					v-model="form.customerScale"
+					:disabled="isDetail || isAddOperation"
+					placeholder="璇疯緭鍏ュ鎴疯妯�"
+				/>
+			</up-form-item>
+			
+			<up-form-item label="淇℃伅鍖栫幇鐘�" prop="informationState">
+				<up-textarea
+					v-model="form.informationState"
+					:disabled="isDetail || isAddOperation"
+					placeholder="璇疯緭鍏ヤ俊鎭寲鐜扮姸"
+					autoHeight
+					:maxlength="300"
+					count
+				/>
+			</up-form-item>
+			
+			<up-form-item label="鐘舵��" prop="status" required @click="onStatusClick">
+				<up-input
+					v-model="form.status"
+					readonly
+					:disabled="isDetail"
+					placeholder="鐐瑰嚮閫夋嫨鐘舵��"
+					@click="onStatusClick"
+				/>
+				<template #right>
+					<up-icon
+						name="arrow-right"
+						@click="onStatusClick"
+					></up-icon>
+				</template>
+			</up-form-item>
+			
+			<up-form-item label="鍚堝悓閲戦(鍏�)" prop="contractAmount" :required="isAddOrEdit">
+				<up-input
+					v-model="form.contractAmount"
+					:disabled="isDetail || isAddOperation"
+					type="number"
+					placeholder="璇疯緭鍏ュ悎鍚岄噾棰�"
+				/>
+			</up-form-item>
+			
+			<!-- 鎻忚堪淇℃伅 -->
+			<view class="section-header section-header-inner">
+				<text class="section-title">鎻忚堪淇℃伅</text>
+				<text class="section-subtitle">琛ュ厖鍟嗘満璇存槑锛屼究浜庡悗缁窡杩�</text>
+			</view>
+			<up-form-item label="鏀归�犲唴瀹�" prop="description" :required="isAddOrAddOperation">
+				<up-textarea
+					v-model="form.description"
+					:disabled="isDetail"
+					:placeholder="renovationPlaceholder"
+					autoHeight
+					count
+					maxlength="500"
+				/>
+			</up-form-item>
+			
+			<up-form-item label="浠樻鎻忚堪" prop="paymentDescription">
+				<up-textarea
+					v-model="form.paymentDescription"
+					:disabled="isDetail"
+					placeholder="鏄惁鍨祫锛熶紒涓氭槸鍚﹀紑绁紵浼佷笟鏄惁鍒嗚ˉ璐存垨棰濆鍑洪挶锛�"
+					autoHeight
+					count
+					maxlength="500"
+				/>
+			</up-form-item>
+			
+			<!-- 闄勪欢鏉愭枡 -->
+			<view class="section-header section-header-inner">
+				<text class="section-title">闄勪欢鏉愭枡</text>
+				<text class="section-subtitle" v-if="!isDetail">鏀寔澶氭枃浠朵笂浼�</text>
+			</view>
+			
+			<view v-if="!isDetail" class="upload-wrap">
+				<up-upload
+					:fileList="uploadFileList"
+					@afterRead="afterRead"
+					@delete="deleteUpload"
+					name="attachments"
+					multiple
+					:maxCount="10"
+					:previewImage="false"
+				>
+					<view class="upload-btn">
+						<up-icon name="plus" size="18" color="#667085"></up-icon>
+						<text class="upload-text">涓婁紶闄勪欢</text>
+					</view>
+				</up-upload>
+			</view>
+			
+			<view v-if="existingFiles.length" class="existing-files">
+				<view class="existing-title">宸蹭笂浼�</view>
+				<view v-for="f in existingFiles" :key="f.id || f.url" class="existing-item">
+					<text class="file-name">{{ f.name || getFileName(f.url) }}</text>
+					<view class="file-actions">
+						<u-button size="mini" type="primary" plain @click="downloadFile(f)">涓嬭浇</u-button>
+						<u-button
+							v-if="!isDetail"
+							size="mini"
+							type="error"
+							plain
+							@click="removeExistingFile(f)"
+						>
+							鍒犻櫎
+						</u-button>
+					</view>
+				</view>
+			</view>
+			
+			<!-- 褰曞叆淇℃伅 -->
+			<view class="section-header section-header-inner">
+				<text class="section-title">褰曞叆淇℃伅</text>
+			</view>
+			<up-form-item label="褰曞叆浜�" prop="entryPerson" required>
+				<up-input
+					v-model="form.entryPerson"
+					:disabled="true"
+				/>
+			</up-form-item>
+			
+			<up-form-item label="褰曞叆鏃ユ湡" prop="entryDate" required @click="onEntryDateClick">
+				<up-input
+					v-model="form.entryDate"
+					readonly
+					:disabled="isDetail"
+					placeholder="鐐瑰嚮閫夋嫨鏃ユ湡"
+					@click="onEntryDateClick"
+				/>
+				<template #right>
+					<up-icon
+						name="arrow-right"
+						@click="onEntryDateClick"
+					></up-icon>
+				</template>
+			</up-form-item>
+			
+			<!-- 鍘嗗彶璁板綍 -->
+			<view v-if="changeHistory.length" class="change-history-section">
+				<view class="history-title">鍙樻洿璁板綍</view>
+				<view v-for="item in changeHistory" :key="item.id" class="history-item">
+					<view class="history-header">
+						<text class="history-status">{{ getStatusText(item.status) }}</text>
+						<text class="history-operator">{{ item.operator }}</text>
+					</view>
+					<view class="history-time">{{ item.timestamp }}</view>
+					<view v-if="item.description" class="history-desc">
+						{{ item.description }}
+					</view>
+				</view>
+			</view>
+			
+			<!-- 搴曢儴鎸夐挳 -->
+			<view v-if="!isDetail" class="footer-btns">
+				<u-button class="cancel-btn" @click="goBack">鍙栨秷</u-button>
+				<u-button class="save-btn" type="primary" @click="onSubmit" :loading="loading">淇濆瓨</u-button>
+			</view>
+			</up-form>
+			</view>
+		</view>
+		
+		<!-- 鐪佷唤閫夋嫨 -->
+		<up-action-sheet
+			:show="showProvincePicker"
+			:actions="provinceActionList"
+			title="閫夋嫨鐪佷唤"
+			@select="onProvinceSelect"
+			@close="showProvincePicker = false"
+		/>
+		
+		<!-- 鍩庡競閫夋嫨 -->
+		<up-action-sheet
+			:show="showCityPicker"
+			:actions="cityActionList"
+			title="閫夋嫨鍩庡競"
+			@select="onCitySelect"
+			@close="showCityPicker = false"
+		/>
+		
+		<!-- 鐘舵�侀�夋嫨 -->
+		<up-action-sheet
+			:show="showStatusPicker"
+			:actions="statusActionList"
+			title="閫夋嫨鐘舵��"
+			@select="onStatusSelect"
+			@close="showStatusPicker = false"
+		/>
+		
+		<!-- 鏃ユ湡閫夋嫨 -->
+		<up-popup :show="showDatePicker" mode="bottom" @close="showDatePicker = false">
+			<up-datetime-picker
+				:show="true"
+				v-model="pickerDateValue"
+				mode="date"
+				@confirm="onDateConfirm"
+				@cancel="showDatePicker = false"
+			/>
+		</up-popup>
+	</view>
+</template>
+
+<script setup>
+import { ref, computed } from 'vue'
+import { onLoad } from '@dcloudio/uni-app'
+import dayjs from 'dayjs'
+import PageHeader from '@/components/PageHeader.vue'
+import useUserStore from '@/store/modules/user'
+import {
+	addOpportunity,
+	updateOpportunity,
+	addDescription,
+	getProvinceList,
+	getCityList,
+	delCommonFile
+} from '@/api/salesManagement/opportunityManagement.js'
+import { getToken } from '@/utils/auth'
+import config from '@/config.js'
+
+const userStore = useUserStore()
+
+const formRef = ref(null)
+const loading = ref(false)
+const operationType = ref('add')
+const renovationPlaceholder = '1.鏍囧噯鍖栵細\n2.瀹氬埗鍖栵細\n3.澶栭噰锛�'
+
+// 闄勪欢涓婁紶
+const uploadFileList = ref([]) // up-upload 缁戝畾鍒楄〃
+const tempFileIds = ref([]) // 鎻愪氦缁欏悗绔殑涓存椂鏂囦欢ID
+const existingFiles = ref([]) // 宸蹭笂浼犵殑闄勪欢锛堣鎯�/缂栬緫鍙嶆樉锛�
+
+const getFileName = (url) => {
+	try {
+		if (!url) return ''
+		return decodeURIComponent(url.split('/').pop())
+	} catch (e) {
+		return url || ''
+	}
+}
+
+const isImageUrl = (url) => /\.(png|jpe?g|gif|bmp|webp)$/i.test(url || '')
+
+const toAbsoluteUrl = (url) => {
+	if (!url) return ''
+	if (/^https?:\/\//i.test(url)) return url
+	return config.baseUrl.replace(/\/$/, '') + (url.startsWith('/') ? url : `/${url}`)
+}
+
+// 鍙笅杞藉埌鎵嬫満锛堜笉棰勮锛�
+const downloadFile = (file) => {
+	const url = toAbsoluteUrl(file?.url)
+	if (!url) return
+
+	// H5 鐩存帴鎵撳紑閾炬帴瑙﹀彂涓嬭浇
+	if (typeof window !== 'undefined' && window?.open) {
+		window.open(url, '_blank')
+		return
+	}
+
+	uni.showLoading({ title: '涓嬭浇涓�...' })
+	uni.downloadFile({
+		url,
+		success: (res) => {
+			if (res.statusCode !== 200) {
+				uni.hideLoading()
+				uni.showToast({ title: '涓嬭浇澶辫触', icon: 'none' })
+				return
+			}
+			uni.saveFile({
+				tempFilePath: res.tempFilePath,
+				success: () => {
+					uni.hideLoading()
+					uni.showToast({ title: '宸蹭笅杞藉埌鏈湴', icon: 'success' })
+				},
+				fail: () => {
+					uni.hideLoading()
+					uni.showToast({ title: '淇濆瓨澶辫触', icon: 'none' })
+				}
+			})
+		},
+		fail: () => {
+			uni.hideLoading()
+			uni.showToast({ title: '涓嬭浇澶辫触', icon: 'none' })
+		}
+	})
+}
+
+const uploadSingle = (fileObj) => {
+	return new Promise((resolve, reject) => {
+		const filePath = fileObj?.url || fileObj?.tempFilePath || fileObj?.path
+		if (!filePath) {
+			reject(new Error('鏈壘鍒板彲涓婁紶鐨勬枃浠�'))
+			return
+		}
+		uni.uploadFile({
+			url: config.baseUrl + '/file/upload',
+			filePath,
+			name: 'file',
+			formData: { type: 9 },
+			header: { Authorization: 'Bearer ' + getToken() },
+			success: (res) => {
+				try {
+					const data = JSON.parse(res.data || '{}')
+					if (data.code === 200) {
+						resolve(data.data)
+					} else {
+						reject(new Error(data.msg || '涓婁紶澶辫触'))
+					}
+				} catch (e) {
+					reject(e)
+				}
+			},
+			fail: (err) => reject(err)
+		})
+	})
+}
+
+const afterRead = async (event) => {
+	const files = Array.isArray(event.file) ? event.file : [event.file]
+	for (const f of files) {
+		const item = {
+			url: f.url,
+			name: f.name,
+			status: 'uploading',
+			message: '涓婁紶涓�...'
+		}
+		const idx = uploadFileList.value.length
+		uploadFileList.value.push(item)
+		try {
+			uni.showLoading({ title: '涓婁紶涓�...' })
+			const uploaded = await uploadSingle(f)
+			uni.hideLoading()
+			uploadFileList.value[idx] = {
+				...uploadFileList.value[idx],
+				status: 'success',
+				message: '',
+				url: uploaded?.url || uploadFileList.value[idx]?.url,
+				name: uploaded?.name || uploadFileList.value[idx]?.name,
+				tempId: uploaded?.tempId
+			}
+			if (uploaded?.tempId) {
+				tempFileIds.value.push(uploaded.tempId)
+			}
+		} catch (e) {
+			uni.hideLoading()
+			uploadFileList.value[idx] = {
+				...uploadFileList.value[idx],
+				status: 'failed',
+				message: '涓婁紶澶辫触'
+			}
+			uni.showToast({ title: '涓婁紶澶辫触', icon: 'none' })
+		}
+	}
+}
+
+const deleteUpload = (event) => {
+	const index = event?.index
+	if (index === undefined || index === null) return
+	const removed = uploadFileList.value[index]
+	uploadFileList.value.splice(index, 1)
+	if (removed?.tempId) {
+		const pos = tempFileIds.value.findIndex(id => String(id) === String(removed.tempId))
+		if (pos > -1) tempFileIds.value.splice(pos, 1)
+	}
+}
+
+const removeExistingFile = (file) => {
+	if (!file?.id) {
+		existingFiles.value = existingFiles.value.filter(f => f !== file)
+		return
+	}
+	uni.showModal({
+		title: '鎻愮ず',
+		content: '纭畾鍒犻櫎璇ラ檮浠跺悧锛�',
+		success: async (res) => {
+			if (!res.confirm) return
+			try {
+				const resp = await delCommonFile([file.id])
+				if (resp.code === 200) {
+					uni.showToast({ title: '鍒犻櫎鎴愬姛', icon: 'success' })
+					existingFiles.value = existingFiles.value.filter(f => f.id !== file.id)
+				} else {
+					uni.showToast({ title: resp.msg || '鍒犻櫎澶辫触', icon: 'none' })
+				}
+			} catch (e) {
+				uni.showToast({ title: '鍒犻櫎澶辫触', icon: 'none' })
+			}
+		}
+	})
+}
+
+const form = ref({
+	id: undefined,
+	status: '',
+	province: '',
+	city: '',
+	customerName: '',
+	industry: '',
+	informationState: '',
+	mainBusinessRevenue: '',
+	customerScale: '',
+	mainProducts: '',
+	businessSource: '',
+	contractAmount: '',
+	description: '',
+	paymentDescription: '',
+	entryPerson: userStore.nickName,
+	entryDate: dayjs().format('YYYY-MM-DD'),
+	businessDescription: [],
+	businessCommonFiles: []
+})
+
+const rules = {
+	customerName: [
+		{ required: true, message: '璇疯緭鍏ュ鎴峰悕绉�', trigger: ['blur', 'change'] }
+	],
+	status: [
+		{ required: true, message: '璇烽�夋嫨鐘舵��', trigger: ['blur', 'change'] }
+	],
+	contractAmount: [
+		{ required: true, message: '璇疯緭鍏ュ悎鍚岄噾棰�', trigger: ['blur', 'change'] }
+	],
+	description: [
+		{ required: true, message: '璇疯緭鍏ユ敼閫犲唴瀹�', trigger: ['blur', 'change'] }
+	],
+	entryPerson: [
+		{ required: true, message: '璇疯緭鍏ュ綍鍏ヤ汉', trigger: ['blur', 'change'] }
+	],
+	entryDate: [
+		{ required: true, message: '璇烽�夋嫨褰曞叆鏃ユ湡', trigger: ['blur', 'change'] }
+	]
+}
+
+// 鐘舵�� / 鐪佸競閫夐」
+const statusOptions = [
+	{ value: '鏂板缓', label: '鏂板缓' },
+	{ value: '椤圭洰璺熻釜', label: '椤圭洰璺熻釜' },
+	{ value: '鍚堝悓绛剧害', label: '鍚堝悓绛剧害' },
+	{ value: '澶囨鐢虫姤', label: '澶囨鐢虫姤' },
+	{ value: '椤圭洰浜や粯', label: '椤圭洰浜や粯' },
+	{ value: '椤圭洰楠屾敹', label: '椤圭洰楠屾敹' }
+]
+
+const provinceOptions = ref([])
+const cityOptions = ref([])
+const selectedProvinceId = ref(null)
+
+const statusActionList = computed(() =>
+	statusOptions.map(item => ({
+		name: item.label,
+		value: item.value
+	}))
+)
+
+const provinceActionList = computed(() =>
+	provinceOptions.value.map(item => ({
+		name: item.name,
+		value: item.id
+	}))
+)
+
+const cityActionList = computed(() =>
+	cityOptions.value.map(item => ({
+		name: item.name,
+		value: item.id
+	}))
+)
+
+const showProvincePicker = ref(false)
+const showCityPicker = ref(false)
+const showStatusPicker = ref(false)
+const showDatePicker = ref(false)
+const pickerDateValue = ref(Date.now())
+
+const changeHistory = ref([])
+
+const isDetail = computed(() => operationType.value === 'detail')
+const isAddOperation = computed(() => operationType.value === 'addOperation')
+const isAddOrEdit = computed(() => ['add', 'edit'].includes(operationType.value))
+const isAddOrAddOperation = computed(() => ['add', 'addOperation'].includes(operationType.value))
+
+const pageTitle = computed(() => {
+	switch (operationType.value) {
+		case 'add':
+			return '鏂板缓鍟嗘満'
+		case 'edit':
+			return '缂栬緫鍟嗘満'
+		case 'addOperation':
+			return '娣诲姞鎻忚堪'
+		case 'detail':
+		default:
+			return '鍟嗘満璇︽儏'
+	}
+})
+
+const goBack = () => {
+	uni.navigateBack()
+}
+
+const getStatusText = (status) => {
+	const map = statusOptions.reduce((acc, cur) => {
+		acc[cur.value] = cur.label
+		return acc
+	}, {})
+	return map[status] || status || '鏈煡'
+}
+
+// 鍔犺浇鐪佷唤
+const loadProvinces = async () => {
+	try {
+		const res = await getProvinceList()
+		provinceOptions.value = res.data || res.records || []
+	} catch (e) {
+		console.error('鑾峰彇鐪佷唤鍒楄〃澶辫触:', e)
+	}
+}
+
+// 鏍规嵁鐪佷唤鍔犺浇鍩庡競
+const loadCitiesByProvinceId = async (provinceId) => {
+	if (!provinceId) {
+		cityOptions.value = []
+		return
+	}
+	try {
+		const res = await getCityList({ provinceId })
+		cityOptions.value = res.data || res.records || []
+	} catch (e) {
+		console.error('鑾峰彇鍩庡競鍒楄〃澶辫触:', e)
+	}
+}
+
+// 鐪佷唤 / 鍩庡競閫夋嫨
+const onProvinceClick = () => {
+	if (isDetail.value || isAddOperation.value) return
+	showProvincePicker.value = true
+}
+
+const onProvinceSelect = async (e) => {
+	selectedProvinceId.value = e.value
+	const target = provinceOptions.value.find(p => p.id === e.value)
+	form.value.province = target ? target.name : e.name
+	// 閲嶇疆鍩庡競骞跺姞杞藉煄甯傚垪琛�
+	form.value.city = ''
+	await loadCitiesByProvinceId(e.value)
+	showProvincePicker.value = false
+}
+
+const onCityClick = () => {
+	if (isDetail.value || isAddOperation.value) return
+	if (!selectedProvinceId.value) {
+		uni.showToast({
+			title: '璇峰厛閫夋嫨鐪佷唤',
+			icon: 'none'
+		})
+		return
+	}
+	showCityPicker.value = true
+}
+
+const onCitySelect = (e) => {
+	const target = cityOptions.value.find(c => c.id === e.value)
+	form.value.city = target ? target.name : e.name
+	showCityPicker.value = false
+}
+
+// 鐘舵�侀�夋嫨
+const onStatusClick = () => {
+	if (isDetail.value) return
+	showStatusPicker.value = true
+}
+
+const onStatusSelect = (e) => {
+	form.value.status = e.value
+	showStatusPicker.value = false
+}
+
+// 褰曞叆鏃ユ湡閫夋嫨
+const onEntryDateClick = () => {
+	if (isDetail.value) return
+	showDatePicker.value = true
+}
+
+const onDateConfirm = (e) => {
+	const val = e.value || e
+	form.value.entryDate = dayjs(val).format('YYYY-MM-DD')
+	showDatePicker.value = false
+}
+
+// 鐢熸垚鍙樻洿璁板綍
+const generateChangeHistory = (row) => {
+	const history = []
+	if (row.businessDescription && Array.isArray(row.businessDescription)) {
+		row.businessDescription.forEach((item, index) => {
+			history.push({
+				id: item.id || index,
+				timestamp: item.entryDate || item.updateTime || item.createTime,
+				operator: item.entryPerson || '绯荤粺',
+				status: item.status,
+				description: item.description
+			})
+		})
+	}
+	changeHistory.value = history
+}
+
+// 鎻愪氦琛ㄥ崟
+const onSubmit = () => {
+	if (isDetail.value) return
+	if (!formRef.value) return
+	
+	formRef.value.validate().then(async () => {
+		loading.value = true
+		try {
+			let api
+			let params
+			
+			if (operationType.value === 'add') {
+				api = addOpportunity
+				params = {
+					...form.value,
+					type: 9,
+					tempFileIds: tempFileIds.value
+				}
+			} else if (operationType.value === 'edit') {
+				api = updateOpportunity
+				params = {
+					...form.value,
+					type: 9,
+					tempFileIds: tempFileIds.value
+				}
+			} else if (operationType.value === 'addOperation') {
+				api = addDescription
+				params = {
+					status: form.value.status,
+					description: form.value.description,
+					paymentDescription: form.value.paymentDescription,
+					entryPerson: form.value.entryPerson,
+					entryDate: form.value.entryDate,
+					type: 9,
+					businessOpportunityId: form.value.id,
+					tempFileIds: tempFileIds.value
+				}
+			}
+			
+			const res = await api(params)
+			if (res.code === 200) {
+				uni.showToast({
+					title: '鎿嶄綔鎴愬姛',
+					icon: 'success'
+				})
+				setTimeout(() => {
+					goBack()
+				}, 500)
+			} else {
+				uni.showToast({
+					title: res.msg || '鎿嶄綔澶辫触',
+					icon: 'none'
+				})
+			}
+		} catch (e) {
+			console.error('鍟嗘満鎿嶄綔澶辫触:', e)
+			uni.showToast({
+				title: '鎿嶄綔澶辫触锛岃閲嶈瘯',
+				icon: 'none'
+			})
+		} finally {
+			loading.value = false
+		}
+	}).catch(() => {})
+}
+
+onLoad(async () => {
+	// 璇诲彇鎿嶄綔绫诲瀷鍜屾暟鎹�
+	const type = uni.getStorageSync('opportunityOperationType') || 'add'
+	operationType.value = type
+	
+	// 鍔犺浇鐪佷唤鍒楄〃
+	await loadProvinces()
+	
+	const raw = uni.getStorageSync('opportunityData')
+	let row = null
+	
+	// 鍏煎澶氱瀛樺偍褰㈠紡锛氬瓧绗︿覆 / 瀵硅薄 / null
+	if (raw) {
+		try {
+			if (typeof raw === 'string') {
+				row = JSON.parse(raw)
+			} else if (typeof raw === 'object') {
+				row = raw
+			}
+		} catch (e) {
+			console.error('瑙f瀽鍟嗘満鏁版嵁澶辫触:', e)
+			row = null
+		}
+	}
+	
+	if (row && typeof row === 'object' && !Array.isArray(row)) {
+		try {
+			// 淇濈暀宸叉湁瀛楁锛岄伩鍏嶈鐩栨湭鍦ㄨ〃鍗曚腑缂栬緫鐨勫瓧娈�
+			form.value = Object.assign({}, form.value, row)
+
+			// 鍏煎鍚庣杩斿洖 null锛岄伩鍏嶇粍浠跺唴閮ㄨ length 鎶ラ敊
+			const nullToEmpty = (v) => (v === null || v === undefined ? '' : v)
+			form.value.status = nullToEmpty(form.value.status)
+			form.value.province = nullToEmpty(form.value.province)
+			form.value.city = nullToEmpty(form.value.city)
+			form.value.customerName = nullToEmpty(form.value.customerName)
+			form.value.businessSource = nullToEmpty(form.value.businessSource)
+			form.value.industry = nullToEmpty(form.value.industry)
+			form.value.mainProducts = nullToEmpty(form.value.mainProducts)
+			form.value.mainBusinessRevenue = nullToEmpty(form.value.mainBusinessRevenue)
+			form.value.customerScale = nullToEmpty(form.value.customerScale)
+			form.value.informationState = nullToEmpty(form.value.informationState)
+			form.value.contractAmount = nullToEmpty(form.value.contractAmount)
+			form.value.description = nullToEmpty(form.value.description)
+			form.value.paymentDescription = nullToEmpty(form.value.paymentDescription)
+			form.value.entryPerson = nullToEmpty(form.value.entryPerson)
+			form.value.entryDate = nullToEmpty(form.value.entryDate)
+			form.value.businessDescription = Array.isArray(form.value.businessDescription) ? form.value.businessDescription : []
+			form.value.businessCommonFiles = Array.isArray(form.value.businessCommonFiles) ? form.value.businessCommonFiles : []
+			
+			// 鍙嶆樉闄勪欢
+			existingFiles.value = form.value.businessCommonFiles
+			uploadFileList.value = []
+			tempFileIds.value = []
+			
+			// 鍙嶆樉鐪佷唤鍜屽煄甯�
+			if (row.province) {
+				const provinceMatch = provinceOptions.value.find(p => p.name === row.province || String(p.id) === String(row.province))
+				if (provinceMatch) {
+					selectedProvinceId.value = provinceMatch.id
+					form.value.province = provinceMatch.name
+					await loadCitiesByProvinceId(provinceMatch.id)
+					if (row.city) {
+						const cityMatch = cityOptions.value.find(c => c.name === row.city || String(c.id) === String(row.city))
+						form.value.city = cityMatch ? cityMatch.name : row.city
+					}
+				} else {
+					form.value.province = row.province
+					form.value.city = row.city || ''
+				}
+			} else {
+				form.value.province = ''
+				form.value.city = row.city || ''
+			}
+			
+			if (!form.value.entryPerson) {
+				form.value.entryPerson = userStore.nickName
+			}
+			if (!form.value.entryDate) {
+				form.value.entryDate = dayjs().format('YYYY-MM-DD')
+			}
+			generateChangeHistory(row)
+		} catch (e) {
+			console.error('澶勭悊鍟嗘満鏁版嵁澶辫触:', e)
+		}
+	} else {
+		// 鏂板缓妯″紡榛樿褰曞叆淇℃伅
+		form.value.entryPerson = userStore.nickName
+		form.value.entryDate = dayjs().format('YYYY-MM-DD')
+		existingFiles.value = []
+		uploadFileList.value = []
+		tempFileIds.value = []
+	}
+})
+</script>
+
+<style scoped lang="scss">
+@import '@/styles/sales-common.scss';
+
+.account-detail {
+	min-height: 100vh;
+	background: #f8f9fa;
+	padding-bottom: 80px;
+}
+
+.detail-body {
+	padding: 12px 12px 0;
+}
+
+.detail-card {
+	background: #ffffff;
+	border-radius: 16px;
+	box-shadow: 0 8px 24px rgba(15, 35, 52, 0.06);
+	padding: 8px 16px 16px;
+}
+
+.section-header {
+	margin: 8px 0 4px;
+}
+
+.section-header-inner {
+	margin-top: 18px;
+}
+
+.section-title {
+	font-size: 15px;
+	font-weight: 600;
+	color: #1f2933;
+}
+
+.section-subtitle {
+	margin-left: 8px;
+	font-size: 12px;
+	color: #9ca3af;
+}
+
+.footer-btns {
+	display: flex;
+	gap: 12px;
+	padding: 20px;
+}
+
+.cancel-btn {
+	flex: 1;
+}
+
+.save-btn {
+	flex: 1;
+}
+
+.change-history-section {
+	padding: 16px 20px 0 20px;
+}
+
+.history-title {
+	font-size: 14px;
+	font-weight: 600;
+	color: #333;
+	margin-bottom: 8px;
+}
+
+.history-item {
+	background: #ffffff;
+	border-radius: 8px;
+	padding: 12px;
+	margin-bottom: 8px;
+}
+
+.history-header {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	margin-bottom: 4px;
+}
+
+.history-status {
+	font-size: 13px;
+	color: #2979ff;
+	font-weight: 500;
+}
+
+.history-operator {
+	font-size: 12px;
+	color: #999;
+}
+
+.history-time {
+	font-size: 12px;
+	color: #999;
+	margin-bottom: 4px;
+}
+
+.history-desc {
+	font-size: 13px;
+	color: #333;
+	line-height: 1.5;
+}
+
+.upload-wrap {
+	padding: 6px 0 2px;
+}
+
+.upload-btn {
+	height: 40px;
+	padding: 0 12px;
+	border-radius: 10px;
+	background: #f2f4f7;
+	display: inline-flex;
+	align-items: center;
+	gap: 6px;
+}
+
+.upload-text {
+	font-size: 13px;
+	color: #475467;
+}
+
+.existing-files {
+	margin-top: 10px;
+}
+
+.existing-title {
+	font-size: 12px;
+	color: #98a2b3;
+	margin-bottom: 6px;
+}
+
+.existing-item {
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	padding: 10px 0;
+	border-top: 1px solid #f2f4f7;
+	gap: 10px;
+}
+
+.file-name {
+	flex: 1;
+	font-size: 13px;
+	color: #1f2933;
+	overflow: hidden;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+}
+
+.file-actions {
+	display: flex;
+	gap: 6px;
+}
+
+// 琛ㄥ崟缁嗚妭浼樺寲
+:deep(.u-form) {
+	.u-form-item__body {
+		padding: 6px 0;
+	}
+}
+
+:deep(.u-form-item__label) {
+	font-size: 13px;
+	color: #6b7280;
+}
+
+:deep(.u-input__content__field) {
+	font-size: 14px;
+}
+
+:deep(.u-textarea__field) {
+	font-size: 14px;
+}
+</style>
+
diff --git a/src/pages/opportunityManagement/fileList.vue b/src/pages/opportunityManagement/fileList.vue
new file mode 100644
index 0000000..51e3d15
--- /dev/null
+++ b/src/pages/opportunityManagement/fileList.vue
@@ -0,0 +1,75 @@
+<template>
+  <el-dialog v-model="dialogVisible" title="闄勪欢" width="40%" :before-close="handleClose">
+    <el-table :data="tableData" border height="40vh" stripe>
+      <el-table-column label="闄勪欢鍚嶇О" prop="name" min-width="400" show-overflow-tooltip />
+      <el-table-column fixed="right" label="鎿嶄綔" width="150" align="center">
+        <template #default="scope">
+          <el-button link type="primary" size="small" @click="downLoadFile(scope.row)">涓嬭浇</el-button>
+          <el-button link type="primary" size="small" @click="lookFile(scope.row)">棰勮</el-button>
+          <el-button link type="primary" size="small" @click="delFile(scope.row)">鍒犻櫎</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+  </el-dialog>
+  <filePreview ref="filePreviewRef" />
+</template>
+
+<script setup>
+import { ref, getCurrentInstance } from 'vue'
+import filePreview from '@/components/filePreview/index.vue'
+import { ElMessageBox } from 'element-plus'
+import {
+  delLedgerFile
+} from "@/api/salesManagement/salesLedger.js";
+
+const emit = defineEmits(['refresh'])
+const dialogVisible = ref(false)
+const tableData = ref([])
+const currentRowId = ref(null)
+const { proxy } = getCurrentInstance();
+const filePreviewRef = ref()
+const handleClose = () => {
+  dialogVisible.value = false
+}
+const open = (list, rowId = null) => {
+  dialogVisible.value = true
+  tableData.value = list
+  currentRowId.value = rowId
+}
+const downLoadFile = (row) => {
+  proxy.$download.name(row.url);
+
+}
+const lookFile = (row) => {
+  filePreviewRef.value.open(row.url)
+}
+const delFile = (row) => {
+  ElMessageBox.confirm('纭畾瑕佸垹闄よ闄勪欢鍚楋紵', '鍒犻櫎纭', {
+    confirmButtonText: '纭畾',
+    cancelButtonText: '鍙栨秷',
+    type: 'warning',
+  }).then(() => {
+    let ids = [];
+    ids.push(row.id);
+    delLedgerFile(ids).then((res) => {
+      if (res.code === 200) {
+        proxy.$modal.msgSuccess("鍒犻櫎鎴愬姛");
+        // 閫氱煡鐖剁粍浠跺埛鏂版暟鎹�
+        emit('refresh', currentRowId.value);
+      } else {
+        proxy.$modal.msgError(res.msg || "鍒犻櫎澶辫触");
+      }
+    }).catch((error) => {
+      console.error("鍒犻櫎闄勪欢澶辫触:", error);
+      proxy.$modal.msgError("鍒犻櫎澶辫触锛岃绋嶅悗閲嶈瘯");
+    });
+  }).catch(() => {
+    // 鐢ㄦ埛鍙栨秷鍒犻櫎
+  });
+}
+defineExpose({
+  open
+})
+</script>
+
+<style></style>
\ No newline at end of file
diff --git a/src/pages/opportunityManagement/index.vue b/src/pages/opportunityManagement/index.vue
new file mode 100644
index 0000000..5a39f58
--- /dev/null
+++ b/src/pages/opportunityManagement/index.vue
@@ -0,0 +1,237 @@
+<template>
+	<view class="sales-account">
+		<!-- 閫氱敤澶撮儴 -->
+		<PageHeader title="鍟嗘満绠$悊" @back="goBack" />
+		
+		<!-- 鎼滅储鍖哄煙 -->
+		<view class="search-section">
+			<view class="search-bar">
+				<view class="search-input">
+					<up-input
+						class="search-text"
+						placeholder="璇疯緭鍏ュ鎴峰悕绉版悳绱�"
+						v-model="customerName"
+						@change="getList"
+						clearable
+					/>
+				</view>
+				<view class="filter-button" @click="getList">
+					<up-icon name="search" size="24" color="#999"></up-icon>
+				</view>
+			</view>
+		</view>
+		
+		<!-- 鍟嗘満鍒楄〃 -->
+		<view class="ledger-list" v-if="opportunityList.length > 0">
+			<view v-for="(item, index) in opportunityList" :key="item.id || index">
+				<view class="ledger-item" @click="openOpportunity('detail', item)">
+					<view class="item-header">
+						<view class="item-left">
+							<view class="document-icon">
+								<up-icon name="file-text" size="16" color="#ffffff"></up-icon>
+							</view>
+							<text class="item-id">{{ item.customerName || '-' }}</text>
+						</view>
+						<view class="item-right">
+							<u-tag
+								size="mini"
+								:type="getStatusTagType(item.status)"
+							>
+								{{ getStatusText(item.status) }}
+							</u-tag>
+						</view>
+					</view>
+					<up-divider></up-divider>
+					
+					<view class="item-details">
+						<view class="detail-row">
+							<text class="detail-label">鍩庡競</text>
+							<text class="detail-value">{{ item.city || '-' }}</text>
+						</view>
+						<view class="detail-row">
+							<text class="detail-label">鍟嗘満鏉ユ簮</text>
+							<text class="detail-value">{{ item.businessSource || '-' }}</text>
+						</view>
+						<view class="detail-row">
+							<text class="detail-label">鍚堝悓閲戦(鍏�)</text>
+							<text class="detail-value highlight">{{ item.contractAmount || '-' }}</text>
+						</view>
+						<view class="detail-row">
+							<text class="detail-label">褰曞叆浜�</text>
+							<text class="detail-value">{{ item.entryPerson || '-' }}</text>
+						</view>
+						<view class="detail-row">
+							<text class="detail-label">鏇存柊鏃堕棿</text>
+							<text class="detail-value">
+								{{ formatDate(item.updateTime || item.entryDate) || '-' }}
+							</text>
+						</view>
+					</view>
+					
+					<!-- 鎿嶄綔鎸夐挳 -->
+					<view class="action-buttons">
+						<u-button
+							type="primary"
+							size="small"
+							class="action-btn"
+							@click.stop="openOpportunity('edit', item)"
+						>
+							缂栬緫
+						</u-button>
+						<u-button
+							type="warning"
+							size="small"
+							class="action-btn"
+							@click.stop="openOpportunity('addOperation', item)"
+						>
+							娣诲姞鏀归�犲唴瀹�
+						</u-button>
+						<u-button
+							type="default"
+							size="small"
+							class="action-btn"
+							@click.stop="openOpportunity('detail', item)"
+						>
+							璇︽儏
+						</u-button>
+					</view>
+				</view>
+			</view>
+		</view>
+		<view v-else class="no-data">
+			<text>鏆傛棤鍟嗘満鏁版嵁</text>
+		</view>
+		
+		<!-- 娴姩鏂板鎸夐挳 -->
+		<view class="fab-button" @click="addOpportunity">
+			<up-icon name="plus" size="24" color="#ffffff"></up-icon>
+		</view>
+	</view>
+</template>
+
+<script setup>
+import { ref } from 'vue'
+import { onShow } from '@dcloudio/uni-app'
+import dayjs from 'dayjs'
+import PageHeader from '@/components/PageHeader.vue'
+import { opportunityListPage } from '@/api/salesManagement/opportunityManagement.js'
+
+// 鎼滅储鏉′欢
+const customerName = ref('')
+
+// 鍒楄〃鏁版嵁
+const opportunityList = ref([])
+const total = ref(0)
+
+// 杩斿洖
+const goBack = () => {
+	uni.navigateBack()
+}
+
+// 鍔犺浇涓彁绀�
+const showLoadingToast = (message) => {
+	uni.showLoading({
+		title: message,
+		mask: true
+	})
+}
+
+const closeToast = () => {
+	uni.hideLoading()
+}
+
+// 鐘舵�佹爣绛剧被鍨�
+const getStatusTagType = (status) => {
+	const typeMap = {
+		'鏂板缓': 'info',
+		'椤圭洰璺熻釜': 'primary',
+		'鍚堝悓绛剧害': 'warning',
+		'澶囨鐢虫姤': 'primary',
+		'椤圭洰浜や粯': 'success',
+		'椤圭洰楠屾敹': 'success'
+	}
+	return typeMap[status] || 'default'
+}
+
+// 鐘舵�佹枃鏈�
+const getStatusText = (status) => {
+	const textMap = {
+		'鏂板缓': '鏂板缓',
+		'椤圭洰璺熻釜': '椤圭洰璺熻釜',
+		'鍚堝悓绛剧害': '鍚堝悓绛剧害',
+		'澶囨鐢虫姤': '澶囨鐢虫姤',
+		'椤圭洰浜や粯': '椤圭洰浜や粯',
+		'椤圭洰楠屾敹': '椤圭洰楠屾敹'
+	}
+	return textMap[status] || '鏈煡'
+}
+
+// 鏍煎紡鍖栨棩鏈�
+const formatDate = (date) => {
+	if (!date) return ''
+	return dayjs(date).format('YYYY-MM-DD')
+}
+
+// 鑾峰彇鍒楄〃
+const getList = () => {
+	showLoadingToast('鍔犺浇涓�...')
+	const params = {
+		current: -1,
+		size: -1,
+		customerName: customerName.value || undefined
+	}
+	
+	opportunityListPage(params)
+		.then((res) => {
+			const data = res.data || res
+			opportunityList.value = data.records || []
+			total.value = data.total || 0
+		})
+		.catch(() => {
+			uni.showToast({
+				title: '鑾峰彇鍟嗘満鏁版嵁澶辫触',
+				icon: 'none'
+			})
+			opportunityList.value = []
+			total.value = 0
+		})
+		.finally(() => {
+			closeToast()
+		})
+}
+
+// 鎵撳紑鍟嗘満鎿嶄綔椤甸潰锛堟柊澧炪�佺紪杈戙�佽鎯呫�佹坊鍔犳弿杩帮級
+const openOpportunity = (type, row) => {
+	try {
+		uni.setStorageSync('opportunityOperationType', type)
+		if (row) {
+			uni.setStorageSync('opportunityData', JSON.stringify(row))
+		} else {
+			uni.removeStorageSync('opportunityData')
+		}
+		uni.navigateTo({
+			url: '/pages/opportunityManagement/detail'
+		})
+	} catch (error) {
+		console.error('鎵撳紑鍟嗘満椤甸潰澶辫触:', error)
+		uni.showToast({
+			title: '鎿嶄綔澶辫触锛岃閲嶈瘯',
+			icon: 'none'
+		})
+	}
+}
+
+// 鏂板缓鍟嗘満
+const addOpportunity = () => {
+	openOpportunity('add')
+}
+
+onShow(() => {
+	getList()
+})
+</script>
+
+<style scoped lang="scss">
+@import '@/styles/sales-common.scss';
+</style>
+

--
Gitblit v1.9.3