Переглянути джерело

feat: 新增订单详情重构页面及相关组件

Yannay 2 місяців тому
батько
коміт
a1fc3f2541

+ 8 - 0
pages.json

@@ -258,6 +258,14 @@
258 258
 			}
259 259
 		},
260 260
 		{
261
+			"path": "pages/orderDetailRefactored/index",
262
+			"style": {
263
+				"navigationBarTitleText": "",
264
+				"enablePullDownRefresh": false,
265
+				"navigationStyle": "custom"
266
+			}
267
+		},
268
+		{
261 269
 			"path": "pages/receiptForm/index",
262 270
 			"style": {
263 271
 				"navigationBarTitleText": "",

+ 0 - 1
pages/orderDetailNew/components/common.scss

@@ -184,7 +184,6 @@ transition: box-shadow 0.3s ease;
184 184
 // 公共地址标题样式
185 185
 .address-header {
186 186
     display: flex;
187
-    justify-content: space-between;
188 187
     align-items: center;
189 188
     margin-bottom: map-get($sizes, margin-sm);
190 189
     padding-bottom: map-get($sizes, margin-sm);

+ 130 - 0
pages/orderDetailRefactored/components/CustomModal.vue

@@ -0,0 +1,130 @@
1
+<template>
2
+  <u-modal 
3
+    :show="visible" 
4
+    :show-cancel-button="false" 
5
+    :show-confirm-button="false"
6
+  >
7
+    <view class="modal-content">
8
+      <view class="modal-header">
9
+        <text class="modal-title">{{ title }}</text>
10
+      </view>
11
+      <view class="modal-body">
12
+        <u-input 
13
+          v-model="localValue" 
14
+          :placeholder="placeholder" 
15
+          class="modal-input"
16
+        />
17
+      </view>
18
+      <view class="modal-footer">
19
+        <text @click="handleCancel" class="btn cancel-btn">取消</text>
20
+        <text @click="handleConfirm" class="btn confirm-btn">确定</text>
21
+      </view>
22
+    </view>
23
+  </u-modal>
24
+</template>
25
+
26
+<script>
27
+export default {
28
+  name: 'CustomModal',
29
+  props: {
30
+    visible: {
31
+      type: Boolean,
32
+      default: false
33
+    },
34
+    title: {
35
+      type: String,
36
+      default: ''
37
+    },
38
+    value: {
39
+      type: String,
40
+      default: ''
41
+    },
42
+    placeholder: {
43
+      type: String,
44
+      default: ''
45
+    }
46
+  },
47
+  data() {
48
+    return {
49
+      localValue: this.value
50
+    }
51
+  },
52
+  watch: {
53
+    value(newVal) {
54
+      this.localValue = newVal
55
+    },
56
+    visible(newVal) {
57
+      if (newVal) {
58
+        this.localValue = this.value
59
+      }
60
+    }
61
+  },
62
+  methods: {
63
+    handleCancel() {
64
+      this.$emit('cancel')
65
+    },
66
+    handleConfirm() {
67
+      this.$emit('confirm', this.localValue)
68
+    }
69
+  }
70
+}
71
+</script>
72
+
73
+<style scoped lang="scss">
74
+.modal-content {
75
+  display: flex;
76
+  flex-direction: column;
77
+  gap: 30rpx;
78
+  padding: 20rpx;
79
+}
80
+
81
+.modal-header {
82
+  display: flex;
83
+  justify-content: center;
84
+  padding: 10rpx 0;
85
+}
86
+
87
+.modal-title {
88
+  font-size: 32rpx;
89
+  font-weight: bold;
90
+  color: #333;
91
+}
92
+
93
+.modal-body {
94
+  padding: 10rpx 0;
95
+}
96
+
97
+.modal-input {
98
+  border: 2rpx solid #e0e0e0;
99
+  border-radius: 10rpx;
100
+  padding: 20rpx;
101
+  font-size: 28rpx;
102
+}
103
+
104
+.modal-footer {
105
+  display: flex;
106
+  justify-content: space-between;
107
+  gap: 20rpx;
108
+  padding: 10rpx 0;
109
+}
110
+
111
+.btn {
112
+  flex: 1;
113
+  text-align: center;
114
+  padding: 20rpx;
115
+  border-radius: 10rpx;
116
+  font-size: 28rpx;
117
+  font-weight: 500;
118
+  cursor: pointer;
119
+}
120
+
121
+.cancel-btn {
122
+  background-color: #f5f5f5;
123
+  color: #666;
124
+}
125
+
126
+.confirm-btn {
127
+  background-color: blueviolet;
128
+  color: white;
129
+}
130
+</style>

+ 306 - 0
pages/orderDetailRefactored/components/OrderDetailView.vue

@@ -0,0 +1,306 @@
1
+<template>
2
+  <view class="order-detail-view">
3
+    <!-- 页面切换 -->
4
+    <view 
5
+      class="page-item" 
6
+      v-show="activeIndex === 0"
7
+    >
8
+      <PageOne 
9
+        :order-detail="orderDetail" 
10
+        :order-id="orderId"
11
+        :current-receipt="currentReceipt"
12
+        @next="handleNext"
13
+      />
14
+    </view>
15
+    
16
+    <view 
17
+      class="page-item" 
18
+      v-show="activeIndex === 1"
19
+    >
20
+      <PageTwo 
21
+        :order-detail="orderDetail" 
22
+        :order-id="orderId"
23
+        :current-receipt="currentReceipt"
24
+        :follow-up-list="followUpList"
25
+        @next="handleNext"
26
+      />
27
+    </view>
28
+    
29
+    <view 
30
+      class="page-item" 
31
+      v-show="activeIndex === 2"
32
+    >
33
+      <PageThree 
34
+        :order-detail="orderDetail" 
35
+        :order-id="orderId"
36
+        :current-receipt="currentReceipt"
37
+        @next="handleNext"
38
+        @save="handleSave"
39
+        @confirm-pay="handleConfirmPay"
40
+        ref="pageThreeRef"
41
+      />
42
+    </view>
43
+    
44
+    <view 
45
+      class="page-item" 
46
+      v-show="activeIndex === 3"
47
+    >
48
+      <PageFour 
49
+        :order-detail="orderDetail"
50
+        :current-receipt="currentReceipt"
51
+        @next="handleNext"
52
+        @confirm-warehouse="handleConfirmWarehouse"
53
+      />
54
+    </view>
55
+
56
+    <!-- 页面导航 -->
57
+    <ul class="page-navigation">
58
+      <li 
59
+        v-for="(tab, index) in tabs" 
60
+        :key="index"
61
+        :class="{ 'active': activeIndex === index }"
62
+        @click="handleTabClick(index)"
63
+      >
64
+        {{ tab }}
65
+      </li>
66
+    </ul>
67
+  </view>
68
+</template>
69
+
70
+<script>
71
+import PageOne from './PageOne.vue'
72
+import PageTwo from './PageTwo.vue'
73
+import PageThree from './PageThree.vue'
74
+import PageFour from './PageFour.vue'
75
+
76
+export default {
77
+  name: 'OrderDetailView',
78
+  components: {
79
+    PageOne,
80
+    PageTwo,
81
+    PageThree,
82
+    PageFour
83
+  },
84
+  props: {
85
+    orderDetail: {
86
+      type: Object,
87
+      default: () => ({})
88
+    },
89
+    topInfo: {
90
+      type: Object,
91
+      default: () => ({})
92
+    },
93
+    orderId: {
94
+      type: String,
95
+      default: ''
96
+    },
97
+    currentReceipt: {
98
+      type: Object,
99
+      default: () => ({})
100
+    }
101
+  },
102
+  data() {
103
+    return {
104
+      activeIndex: 0,
105
+      tabs: ['一', '二', '三', '四'],
106
+      // 表单数据
107
+      formData: {
108
+        formOne: {},
109
+        formTwo: {},
110
+        formThree: {},
111
+        formFour: {}
112
+      },
113
+      pageThreeForm: {},
114
+      fileIds: '',
115
+      // 跟进记录
116
+      followUpList: []
117
+    }
118
+  },
119
+  watch: {
120
+    orderDetail: {
121
+      handler(newVal) {
122
+        if (newVal && newVal.clueId) {
123
+          this.loadFollowUpList()
124
+        }
125
+      },
126
+      deep: true,
127
+      immediate: true
128
+    }
129
+  },
130
+  methods: {
131
+    /**
132
+     * 加载跟进记录
133
+     */
134
+    async loadFollowUpList() {
135
+      try {
136
+        const res = await uni.$u.api.getDuplicateOrderFollowListByClueId({
137
+          clueId: this.orderDetail.clueId
138
+        })
139
+        const data = res.data || {}
140
+        const followUpList = []
141
+        for (const key in data) {
142
+          followUpList.push(...(data[key] || []))
143
+        }
144
+        this.followUpList = followUpList
145
+      } catch (error) {
146
+        console.error('获取跟进记录失败:', error)
147
+        uni.$u.toast('获取跟进记录失败')
148
+      }
149
+    },
150
+
151
+    /**
152
+     * 处理下一步
153
+     */
154
+    handleNext({ nowPage, form }) {
155
+      this.activeIndex++
156
+      if (nowPage) {
157
+        this.formData[nowPage] = form
158
+      }
159
+      // 当切换到第三页时,更新第三页的图片列表
160
+      if (nowPage === 'formTwo' && this.$refs.pageThreeRef) {
161
+        this.$refs.pageThreeRef.refreshImageList()
162
+      }
163
+    },
164
+
165
+    /**
166
+     * 处理保存
167
+     */
168
+    handleNeedSave({ nowPage, form, fileIds }) {
169
+      this.pageThreeForm = form
170
+      this.fileIds = fileIds
171
+    },
172
+
173
+    /**
174
+     * 处理确认支付
175
+     */
176
+    async handleConfirmPay() {
177
+      try {
178
+        const response = await uni.$u.api.saveOrderFileAndTransfer({
179
+          id: this.orderId,
180
+          clueId: this.orderDetail.clueId
181
+        })
182
+        uni.$u.toast(response.msg || '支付成功')
183
+      } catch (error) {
184
+        console.error('支付失败:', error)
185
+        uni.$u.toast(`支付失败:${error}`)
186
+      }
187
+    },
188
+
189
+    /**
190
+     * 处理确认入库
191
+     */
192
+    async handleConfirmWarehouse({ warehouseInfo }) {
193
+      try {
194
+        const params = {
195
+          searchValue: this.orderDetail.searchValue,
196
+          createBy: this.orderDetail.createBy,
197
+          createTime: this.orderDetail.createTime,
198
+          updateBy: this.orderDetail.updateBy,
199
+          updateTime: this.orderDetail.updateTime,
200
+          params: this.orderDetail.params,
201
+          id: this.currentReceipt.id,
202
+          sendFormId: this.orderId,
203
+          clueId: this.orderDetail.clueId,
204
+          item: warehouseInfo.item || '',
205
+          code: warehouseInfo.codeStorage || '',
206
+          phone: this.orderDetail.phone,
207
+          tableFee: warehouseInfo.watchPrice || '',
208
+          benefitFee: warehouseInfo.benefitFee || '',
209
+          freight: warehouseInfo.freight || '',
210
+          checkCodeFee: warehouseInfo.checkCodeFee || '',
211
+          receiptRemark: `${warehouseInfo.remarks || ''};${warehouseInfo.uploadedImage || ''}`,
212
+          repairAmount: warehouseInfo.repairAmount || '',
213
+          grossPerformance: warehouseInfo.grossPerformance || '',
214
+          expressOrderNo: warehouseInfo.expressOrderNo || '',
215
+          fileIds: this.fileIds,
216
+          customerServiceName: warehouseInfo.customerServiceName || '1',
217
+          deptId: this.orderDetail.deptId,
218
+          category: warehouseInfo.category || this.orderDetail.category,
219
+          delFlag: this.orderDetail.delFlag,
220
+          idCard: this.pageThreeForm.idNumber || '',
221
+          paymentMethod: '小葫芦线上支付',
222
+          bankCardNumber: this.pageThreeForm.bankAccount || '',
223
+          bankName: this.pageThreeForm.bankName || '',
224
+          customName: this.pageThreeForm.customName || ''
225
+        }
226
+
227
+        if (this.currentReceipt.id) {
228
+          await uni.$u.api.updateReceiptForm(params)
229
+        } else {
230
+          await uni.$u.api.addReceiptForm(params)
231
+        }
232
+        uni.$u.toast('入库成功')
233
+      } catch (error) {
234
+        console.error('入库失败:', error)
235
+        uni.$u.toast('入库失败')
236
+      }
237
+    },
238
+
239
+    /**
240
+     * 处理标签点击
241
+     */
242
+    handleTabClick(index) {
243
+      this.activeIndex = index
244
+      // 切换到第三页时刷新图片列表
245
+      if (index === 2 && this.$refs.pageThreeRef) {
246
+        this.$refs.pageThreeRef.refreshImageList()
247
+      }
248
+    }
249
+  }
250
+}
251
+</script>
252
+
253
+<style scoped lang="scss">
254
+.order-detail-view {
255
+  padding: 20rpx;
256
+  min-height: calc(100vh - 200rpx);
257
+}
258
+
259
+.page-item {
260
+  width: 100%;
261
+}
262
+
263
+.page-navigation {
264
+  position: fixed;
265
+  right: 20rpx;
266
+  top: 40%;
267
+  display: flex;
268
+  flex-direction: column;
269
+  align-items: center;
270
+  justify-content: center;
271
+  list-style: none;
272
+  color: #000;
273
+  font-size: 20rpx;
274
+  font-weight: 800;
275
+  z-index: 100;
276
+
277
+  li {
278
+    opacity: 0.7;
279
+    display: flex;
280
+    align-items: center;
281
+    justify-content: center;
282
+    background-color: #fff;
283
+    border-radius: 50%;
284
+    width: 70rpx;
285
+    height: 70rpx;
286
+    line-height: 70rpx;
287
+    text-align: center;
288
+    margin-bottom: 20rpx;
289
+    transition: all 0.3s ease-in-out;
290
+    font-weight: 800;
291
+    box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
292
+    cursor: pointer;
293
+
294
+    &.active {
295
+      color: #fff;
296
+      opacity: 1;
297
+      background-color: rgb(37 99 235 / 1);
298
+    }
299
+
300
+    &:hover {
301
+      opacity: 0.9;
302
+      transform: scale(1.05);
303
+    }
304
+  }
305
+}
306
+</style>

Різницю між файлами не показано, бо вона завелика
+ 1065 - 0
pages/orderDetailRefactored/components/PageFour.vue


+ 601 - 0
pages/orderDetailRefactored/components/PageOne.vue

@@ -0,0 +1,601 @@
1
+<template>
2
+  <view class="page-one-container">
3
+    <!-- 图片资料标题 -->
4
+    <view class="page-header">
5
+      <text class="page-title">图片资料</text>
6
+      <u-button 
7
+        size="small" 
8
+        @click="handleSaveAllImages"
9
+        class="save-all-btn"
10
+      >
11
+        保存实物图
12
+      </u-button>
13
+    </view>
14
+
15
+    <!-- 聊天记录/通话记录卡片 -->
16
+    <view class="card-wrap">
17
+      <view class="card-title">
18
+        <text 
19
+          :class="{ 'active': recordType === 'chat' }"
20
+          @click="recordType = 'chat'"
21
+        >
22
+          聊天记录
23
+        </text>
24
+        <text class="divider">|</text>
25
+        <text 
26
+          :class="{ 'active': recordType === 'call' }"
27
+          @click="recordType = 'call'"
28
+        >
29
+          通话记录
30
+        </text>
31
+      </view>
32
+      
33
+      <!-- 聊天记录 -->
34
+      <view v-if="recordType === 'chat'" class="image-upload-container">
35
+        <view class="image-list">
36
+          <view 
37
+            v-for="(item, index) in chatRecordsList" 
38
+            :key="`chat-${index}`"
39
+            class="image-item"
40
+          >
41
+            <PicComp 
42
+              :src="item.fileUrl" 
43
+              @needPreviewPic="previewImage"
44
+            />
45
+            <view class="delete-btn" @click="handleDeleteImage(item)">×</view>
46
+          </view>
47
+          <view 
48
+            class="upload-btn" 
49
+            @click="handleUploadImage('chatRecords')"
50
+          >
51
+            <u-icon name="plus" size="40" color="#999" />
52
+          </view>
53
+        </view>
54
+      </view>
55
+
56
+      <!-- 通话录音 -->
57
+      <view v-if="recordType === 'call'" class="call-records-container">
58
+        <sound-recorder 
59
+          v-for="item in soundRecordList" 
60
+          :key="item.fileName"
61
+          :data="item"
62
+          @handleDelectThisSoundRecord="handleDeleteSoundRecord"
63
+        />
64
+      </view>
65
+    </view>
66
+
67
+    <!-- 实物图卡片 -->
68
+    <view class="card-wrap">
69
+      <view class="card-title">实物图</view>
70
+      <view class="image-upload-container">
71
+        <view class="image-list">
72
+          <view 
73
+            v-for="(item, index) in truePicList" 
74
+            :key="`truePic-${index}`"
75
+            class="image-item"
76
+          >
77
+            <PicComp 
78
+              :src="item.fileUrl" 
79
+              @needPreviewPic="previewTrueImage"
80
+            />
81
+            <view class="delete-btn" @click="handleDeleteImage(item)">×</view>
82
+          </view>
83
+          <view 
84
+            class="upload-btn" 
85
+            @click="handleUploadImage('truePic')"
86
+          >
87
+            <u-icon name="plus" size="40" color="#999" />
88
+          </view>
89
+        </view>
90
+      </view>
91
+    </view>
92
+
93
+    <!-- 基本信息卡片 -->
94
+    <view class="info-card">
95
+      <view class="info-card-title">基本信息</view>
96
+      <u-row class="info-row">
97
+        <u-col span="6">
98
+          <view class="info-label">发单人</view>
99
+          <view class="info-value">{{ orderDetail.createNickName || '未填写' }}</view>
100
+        </u-col>
101
+        <u-col span="6">
102
+          <view class="info-label">型号</view>
103
+          <view class="info-value">{{ orderDetail.model || '未填写' }}</view>
104
+        </u-col>
105
+      </u-row>
106
+      <u-row class="info-row">
107
+        <u-col span="6">
108
+          <view class="info-label">上门时间</view>
109
+          <view class="info-value">{{ orderDetail.visitTime || '未填写' }}</view>
110
+        </u-col>
111
+        <u-col span="6">
112
+          <view class="info-label">地址</view>
113
+          <view class="info-value">{{ orderDetail.address || '未填写' }}</view>
114
+        </u-col>
115
+      </u-row>
116
+    </view>
117
+
118
+    <!-- 联系方式卡片 -->
119
+    <view class="contact-card">
120
+      <view 
121
+        class="contact-item phone-card" 
122
+        @click="handlePhoneClick"
123
+      >
124
+        <u-icon name="phone" size="40" color="#07C160" />
125
+        <view class="contact-title">电话</view>
126
+        <view v-if="orderDetail.phone" class="red-dot"></view>
127
+      </view>
128
+      <view 
129
+        class="contact-item wechat-card" 
130
+        @click="handleWechatClick"
131
+      >
132
+        <u-icon name="chat" size="40" color="#07C160" />
133
+        <view class="contact-title">微信</view>
134
+        <view v-if="orderDetail.wechat" class="red-dot"></view>
135
+      </view>
136
+    </view>
137
+
138
+    <!-- 下一步按钮 -->
139
+    <view class="space-block"></view>
140
+    <u-button 
141
+      class="next-btn" 
142
+      @click="handleNext" 
143
+      type="primary" 
144
+      size="middle"
145
+    >
146
+      下一步
147
+    </u-button>
148
+  </view>
149
+</template>
150
+
151
+<script>
152
+import PicComp from './PicComp.vue'
153
+import soundRecorder from '@/components/soundRecorder/soundRecorder.vue'
154
+import imageUpload from '../utils/imageUpload.js'
155
+
156
+export default {
157
+  name: 'PageOne',
158
+  components: {
159
+    PicComp,
160
+    soundRecorder
161
+  },
162
+  props: {
163
+    orderDetail: {
164
+      type: Object,
165
+      default: () => ({})
166
+    },
167
+    orderId: {
168
+      type: String,
169
+      default: ''
170
+    },
171
+    currentReceipt: {
172
+      type: Object,
173
+      default: () => ({})
174
+    }
175
+  },
176
+  data() {
177
+    return {
178
+      recordType: 'chat', // 'chat' | 'call'
179
+      chatRecordsList: [],
180
+      truePicList: [],
181
+      soundRecordList: []
182
+    }
183
+  },
184
+  watch: {
185
+    currentReceipt: {
186
+      handler(newVal) {
187
+        if (newVal && newVal.id) {
188
+          this.loadImageList()
189
+          this.loadCallRecords()
190
+        }
191
+      },
192
+      immediate: true,
193
+      deep: true
194
+    }
195
+  },
196
+  methods: {
197
+    /**
198
+     * 加载图片列表
199
+     */
200
+    async loadImageList() {
201
+      if (!this.currentReceipt.id || !this.orderDetail.itemBrand) return
202
+      
203
+      try {
204
+        // 加载聊天记录
205
+        const chatList = await imageUpload.getFileList(
206
+          '2', 
207
+          '1', 
208
+          this.currentReceipt.id, 
209
+          this.orderDetail.itemBrand,
210
+          this.currentReceipt.clueId
211
+        )
212
+        this.chatRecordsList = chatList || []
213
+        
214
+        // 加载实物图
215
+        const truePicList = await imageUpload.getFileList(
216
+          '2', 
217
+          '2', 
218
+          this.currentReceipt.id, 
219
+          this.orderDetail.itemBrand,
220
+          this.currentReceipt.clueId
221
+        )
222
+        this.truePicList = truePicList || []
223
+      } catch (error) {
224
+        console.error('加载图片列表失败:', error)
225
+      }
226
+    },
227
+
228
+    /**
229
+     * 加载通话记录
230
+     */
231
+    async loadCallRecords() {
232
+      if (!this.currentReceipt.clueId) return
233
+      
234
+      try {
235
+        const { data } = await uni.$u.api.getCallClueFileByClueId({
236
+          clueId: this.currentReceipt.clueId
237
+        })
238
+        this.soundRecordList = data || []
239
+      } catch (error) {
240
+        console.error('加载通话记录失败:', error)
241
+      }
242
+    },
243
+
244
+    /**
245
+     * 上传图片
246
+     */
247
+    async handleUploadImage(type) {
248
+      try {
249
+        const filePaths = await imageUpload.chooseImage(9)
250
+        const uploadResults = await imageUpload.uploadFiles(filePaths)
251
+        
252
+        // 绑定订单文件
253
+        const orderFileType = type === 'truePic' ? '2' : '1'
254
+        await imageUpload.bindOrderFile(
255
+          this.currentReceipt.clueId,
256
+          this.currentReceipt.id,
257
+          orderFileType,
258
+          uploadResults
259
+        )
260
+        
261
+        // 刷新列表
262
+        this.loadImageList()
263
+      } catch (error) {
264
+        console.error('上传图片失败:', error)
265
+      }
266
+    },
267
+
268
+    /**
269
+     * 删除图片
270
+     */
271
+    async handleDeleteImage(item) {
272
+      uni.showModal({
273
+        title: '提示',
274
+        content: '确定要删除这张图片吗?',
275
+        success: async (res) => {
276
+          if (res.confirm) {
277
+            try {
278
+              await imageUpload.deleteFile(item.id)
279
+              this.loadImageList()
280
+            } catch (error) {
281
+              console.error('删除图片失败:', error)
282
+            }
283
+          }
284
+        }
285
+      })
286
+    },
287
+
288
+    /**
289
+     * 删除录音
290
+     */
291
+    async handleDeleteSoundRecord({ id }) {
292
+      uni.showModal({
293
+        title: '提示',
294
+        content: '是否确定删除?',
295
+        success: async (res) => {
296
+          if (res.confirm) {
297
+            try {
298
+              await uni.$u.api.deleteClueFile([id])
299
+              uni.showToast({
300
+                title: '删除成功',
301
+                icon: 'success'
302
+              })
303
+              this.loadCallRecords()
304
+            } catch (error) {
305
+              uni.showToast({
306
+                title: '删除失败',
307
+                icon: 'error'
308
+              })
309
+            }
310
+          }
311
+        }
312
+      })
313
+    },
314
+
315
+    /**
316
+     * 预览图片
317
+     */
318
+    previewImage(src) {
319
+      const urlList = this.chatRecordsList.map(item => item.fileUrl)
320
+      uni.previewImage({
321
+        urls: urlList,
322
+        current: src
323
+      })
324
+    },
325
+
326
+    /**
327
+     * 预览实物图
328
+     */
329
+    previewTrueImage(src) {
330
+      const urlList = this.truePicList.map(item => item.fileUrl)
331
+      uni.previewImage({
332
+        urls: urlList,
333
+        current: src
334
+      })
335
+    },
336
+
337
+    /**
338
+     * 保存全部图片
339
+     */
340
+    async handleSaveAllImages() {
341
+      const allUrls = this.truePicList.map(item => item.fileUrl)
342
+      if (allUrls.length === 0) {
343
+        uni.showToast({
344
+          title: '没有图片可保存',
345
+          icon: 'none'
346
+        })
347
+        return
348
+      }
349
+      
350
+      uni.showModal({
351
+        title: '保存图片',
352
+        content: `是否将 ${allUrls.length} 张图片保存到本地相册?`,
353
+        confirmText: '保存',
354
+        success: (res) => {
355
+          if (res.confirm) {
356
+            imageUpload.saveImagesToLocal(allUrls)
357
+          }
358
+        }
359
+      })
360
+    },
361
+
362
+    /**
363
+     * 电话点击
364
+     */
365
+    handlePhoneClick() {
366
+      if (!this.orderDetail.phone) {
367
+        uni.showToast({
368
+          title: '该订单暂时没有电话号码',
369
+          icon: 'none'
370
+        })
371
+        return
372
+      }
373
+      
374
+      uni.makePhoneCall({
375
+        phoneNumber: this.orderDetail.phone,
376
+        success: () => {
377
+          this.$store.commit('call/SET_FORM', {
378
+            clueId: this.orderDetail.clueId,
379
+            type: '3',
380
+            callee: this.orderDetail.phone
381
+          })
382
+        }
383
+      })
384
+    },
385
+
386
+    /**
387
+     * 微信点击
388
+     */
389
+    handleWechatClick() {
390
+      if (!this.orderDetail.wechat) {
391
+        uni.showToast({
392
+          title: '该订单暂时没有微信号',
393
+          icon: 'none'
394
+        })
395
+        return
396
+      }
397
+      
398
+      uni.setClipboardData({
399
+        data: this.orderDetail.wechat,
400
+        success: () => {
401
+          uni.showToast({
402
+            title: '微信号已复制',
403
+            icon: 'none'
404
+          })
405
+        }
406
+      })
407
+    },
408
+
409
+    /**
410
+     * 下一步
411
+     */
412
+    handleNext() {
413
+      this.$emit('next', {
414
+        nowPage: 'formOne',
415
+        form: {}
416
+      })
417
+    }
418
+  }
419
+}
420
+</script>
421
+
422
+<style scoped lang="scss">
423
+@import '../styles/common.scss';
424
+
425
+.page-one-container {
426
+  @extend .page-container;
427
+  padding-bottom: 100rpx;
428
+}
429
+
430
+.page-header {
431
+  display: flex;
432
+  justify-content: space-between;
433
+  align-items: center;
434
+  margin-bottom: 20rpx;
435
+}
436
+
437
+.page-title {
438
+  @include font-styles($size: title, $weight: bold, $color: primary);
439
+}
440
+
441
+.save-all-btn {
442
+  border-radius: 20rpx;
443
+  border-color: #007AFF;
444
+  color: #007AFF;
445
+}
446
+
447
+.card-wrap {
448
+  @extend .card-wrap;
449
+  margin-top: 20rpx;
450
+}
451
+
452
+.card-title {
453
+  padding: 20rpx;
454
+  border-bottom: 1rpx solid map-get($colors, border);
455
+  display: flex;
456
+  align-items: center;
457
+  
458
+  text {
459
+    padding: 0 10rpx;
460
+    cursor: pointer;
461
+    
462
+    &.active {
463
+      color: map-get($colors, primary);
464
+      font-weight: bold;
465
+    }
466
+  }
467
+  
468
+  .divider {
469
+    margin: 0 10rpx;
470
+    color: #ddd;
471
+  }
472
+}
473
+
474
+.image-upload-container {
475
+  padding: 20rpx;
476
+}
477
+
478
+.image-list {
479
+  display: flex;
480
+  flex-wrap: wrap;
481
+  gap: 20rpx;
482
+}
483
+
484
+.image-item {
485
+  position: relative;
486
+  width: 200rpx;
487
+  height: 200rpx;
488
+  box-sizing: border-box;
489
+}
490
+
491
+.delete-btn {
492
+  position: absolute;
493
+  top: -10rpx;
494
+  right: -10rpx;
495
+  width: 40rpx;
496
+  height: 40rpx;
497
+  background-color: #ff4d4f;
498
+  color: #fff;
499
+  border-radius: 50%;
500
+  display: flex;
501
+  align-items: center;
502
+  justify-content: center;
503
+  font-size: 30rpx;
504
+  font-weight: bold;
505
+  z-index: 10;
506
+  cursor: pointer;
507
+}
508
+
509
+.upload-btn {
510
+  width: 200rpx;
511
+  height: 200rpx;
512
+  border: 8rpx dashed #ddd;
513
+  border-radius: 30rpx;
514
+  display: flex;
515
+  align-items: center;
516
+  justify-content: center;
517
+  background-color: #f9f9f9;
518
+  box-sizing: border-box;
519
+  cursor: pointer;
520
+}
521
+
522
+.call-records-container {
523
+  padding: 20rpx;
524
+}
525
+
526
+.info-card {
527
+  @extend .card-wrap;
528
+  padding: 20rpx;
529
+  margin-top: 20rpx;
530
+}
531
+
532
+.info-card-title {
533
+  @include font-styles($size: title, $weight: bold, $color: primary);
534
+  margin-bottom: 25rpx;
535
+  padding-bottom: 15rpx;
536
+  border-bottom: 1rpx solid map-get($colors, border);
537
+}
538
+
539
+.info-row {
540
+  margin-bottom: 20rpx;
541
+}
542
+
543
+.info-label {
544
+  @include font-styles($size: tiny, $weight: regular, $color: tertiary);
545
+  margin-bottom: 8rpx;
546
+}
547
+
548
+.info-value {
549
+  @include font-styles($size: small, $weight: regular, $color: secondary);
550
+  word-break: break-all;
551
+}
552
+
553
+.contact-card {
554
+  display: flex;
555
+  justify-content: space-between;
556
+  margin: 20rpx 0;
557
+  gap: 20rpx;
558
+}
559
+
560
+.contact-item {
561
+  flex: 1;
562
+  @extend .card-wrap;
563
+  padding: 20rpx;
564
+  display: flex;
565
+  flex-direction: column;
566
+  align-items: center;
567
+  position: relative;
568
+  cursor: pointer;
569
+}
570
+
571
+.contact-title {
572
+  @include font-styles($size: tiny, $weight: regular, $color: tertiary);
573
+  margin-top: 10rpx;
574
+}
575
+
576
+.red-dot {
577
+  position: absolute;
578
+  top: 15rpx;
579
+  right: 15rpx;
580
+  width: 25rpx;
581
+  height: 25rpx;
582
+  background-color: #ff4d4f;
583
+  border-radius: 50%;
584
+  box-shadow: 0 0 4rpx rgba(255, 77, 79, 0.3);
585
+}
586
+
587
+.space-block {
588
+  height: 100rpx;
589
+}
590
+
591
+.next-btn {
592
+  position: fixed;
593
+  bottom: 10rpx;
594
+  left: 2.5%;
595
+  width: 95%;
596
+  height: 80rpx;
597
+  line-height: 80rpx;
598
+  text-align: center;
599
+  border-radius: 20rpx;
600
+}
601
+</style>

+ 865 - 0
pages/orderDetailRefactored/components/PageThree.vue

@@ -0,0 +1,865 @@
1
+<template>
2
+  <view class="page-three-container">
3
+    <!-- 支付信息卡片 -->
4
+    <view class="info-card">
5
+      <view class="info-card-title">支付信息</view>
6
+      <u-row class="info-row" justify="space-between">
7
+        <u-col span="5.8">
8
+          <view class="info-label">开户人</view>
9
+          <u-input 
10
+            v-model="paymentInfo.customName" 
11
+            placeholder="请输入开户人姓名" 
12
+            class="info-input" 
13
+          />
14
+        </u-col>
15
+        <u-col span="5.8">
16
+          <view class="info-label">银行名称</view>
17
+          <u-input 
18
+            v-model="paymentInfo.bankName" 
19
+            placeholder="请输入银行名称" 
20
+            class="info-input"
21
+            @blur="handleBankNameInput" 
22
+          />
23
+        </u-col>
24
+      </u-row>
25
+      <u-row class="info-row">
26
+        <u-col span="12">
27
+          <view class="info-label">银行账号</view>
28
+          <u-input 
29
+            v-model="paymentInfo.bankAccount" 
30
+            placeholder="请输入银行账号" 
31
+            class="info-input" 
32
+          />
33
+        </u-col>
34
+      </u-row>
35
+      <u-row class="info-row">
36
+        <u-col span="12">
37
+          <view class="info-label">身份证号</view>
38
+          <u-input 
39
+            v-model="paymentInfo.idNumber" 
40
+            placeholder="请输入身份证号" 
41
+            class="info-input" 
42
+          />
43
+        </u-col>
44
+      </u-row>
45
+      <u-row class="info-row">
46
+        <u-col span="12">
47
+          <view class="info-label">支付方式选择</view>
48
+          <u-radio-group 
49
+            iconPlacement="left" 
50
+            v-model="paymentMethodRadio" 
51
+            placement="row"
52
+            @change="handlePaymentMethodRadioChange"
53
+          >
54
+            <u-radio shape="circle" label="小葫芦线上支付" name="online" />
55
+            <u-radio shape="circle" label="线下支付" name="offline" />
56
+          </u-radio-group>
57
+        </u-col>
58
+      </u-row>
59
+      <u-row class="info-row">
60
+        <u-col span="12">
61
+          <view class="info-label">支付方式</view>
62
+          <u-input 
63
+            :disabled="paymentMethodRadio === 'online'" 
64
+            v-model="paymentMethod" 
65
+            placeholder="请输入支付方式"
66
+            class="info-input" 
67
+          />
68
+        </u-col>
69
+      </u-row>
70
+    </view>
71
+
72
+    <!-- 高清实物图卡片 -->
73
+    <view class="card-wrap">
74
+      <view class="detail-image-section">
75
+        <view class="detail-image-header">
76
+          <text class="detail-image-title">高清实物图(拖拽排序)</text>
77
+        </view>
78
+        <view class="detail-image-content">
79
+          <view class="detail-image-list">
80
+            <view 
81
+              v-for="(item, index) in detailImages.slice(0, 6)" 
82
+              :key="`detail-${index}`"
83
+              class="detail-image-item"
84
+              :class="{
85
+                'dragging': draggingIndex === index,
86
+                'can-drop': canDropIndex === index
87
+              }"
88
+              @touchstart.stop="onTouchStart($event, index)"
89
+              @touchmove.stop="onTouchMove($event)"
90
+              @touchend.stop="onTouchEnd"
91
+            >
92
+              <PicComp 
93
+                :src="item.fileUrl" 
94
+                @needPreviewPic="previewImageDetail"
95
+              />
96
+              <view class="image-type-tag">{{ getImageType(index) }}</view>
97
+              <view 
98
+                class="detail-delete-btn" 
99
+                @click.stop="handleHideImage(item, index)"
100
+              >
101
+                ×
102
+              </view>
103
+            </view>
104
+            <view 
105
+              class="detail-upload-btn" 
106
+              @click="handleUploadImage"
107
+            >
108
+              <u-icon name="plus" size="40rpx" color="#999" />
109
+            </view>
110
+          </view>
111
+        </view>
112
+      </view>
113
+    </view>
114
+
115
+    <!-- 支付总额卡片 -->
116
+    <view class="card-wrap payment-card">
117
+      <view class="payment-section">
118
+        <view class="payment-total-container">
119
+          <text class="payment-label">支付总额</text>
120
+          <u-input 
121
+            v-model="paymentAmount" 
122
+            class="payment-amount" 
123
+            type="number" 
124
+            decimal="2" 
125
+            prefix="¥" 
126
+          />
127
+        </view>
128
+        <view class="payment-buttons-row">
129
+          <view class="payment-button" @click="handleUnpaidClick">
130
+            <u-icon name="star" size="40rpx" color="#ff9500" />
131
+            <text class="button-text">未收</text>
132
+          </view>
133
+          <view class="payment-button" @click="handleFollowUpClick">
134
+            <u-icon name="chat" size="40rpx" color="#108cff" />
135
+            <text class="button-text">待跟进</text>
136
+          </view>
137
+        </view>
138
+        <view class="pay-now-button" @click="handlePayNowClick">
139
+          <text class="button-text">立即支付</text>
140
+        </view>
141
+      </view>
142
+    </view>
143
+
144
+    <!-- 下一步按钮 -->
145
+    <u-button 
146
+      class="next-btn" 
147
+      @click="handleNext" 
148
+      type="primary" 
149
+      size="middle"
150
+    >
151
+      下一步
152
+    </u-button>
153
+
154
+    <!-- 未收评级模态窗 -->
155
+    <u-modal 
156
+      showCancelButton 
157
+      showConfirmButton 
158
+      @confirm="confirmUnpaid" 
159
+      @cancel="unpaidModalVisible = false"
160
+      :show="unpaidModalVisible" 
161
+      title="未收评级"
162
+    >
163
+      <view class="modal-content">
164
+        <u-rate 
165
+          v-model="unpaidRating" 
166
+          :count="5" 
167
+          :size="50" 
168
+          active-color="#ff9500"
169
+        />
170
+      </view>
171
+    </u-modal>
172
+
173
+    <!-- 待跟进模态窗 -->
174
+    <u-modal 
175
+      showCancelButton 
176
+      showConfirmButton 
177
+      @confirm="confirmFollowUp" 
178
+      @cancel="followUpModalVisible = false"
179
+      :show="followUpModalVisible" 
180
+      title="填写跟进细节"
181
+    >
182
+      <view class="modal-content">
183
+        <u--textarea 
184
+          v-model="followUpNotes" 
185
+          placeholder="请输入情况" 
186
+          confirm-type="done"
187
+          style="width: 100%; margin-bottom: 30rpx;"
188
+        />
189
+      </view>
190
+    </u-modal>
191
+
192
+    <!-- 确认支付模态窗 -->
193
+    <u-modal 
194
+      :show="payNowModalVisible" 
195
+      title="确认支付信息" 
196
+      :showConfirmButton="false"
197
+    >
198
+      <view class="modal-content">
199
+        <view class="payment-amount-display">¥{{ paymentAmount }}</view>
200
+        <view class="payment-info-section">
201
+          <view class="info-item">
202
+            <text class="info-label">收款姓名:</text>
203
+            <text class="info-value">{{ paymentInfo.customName || '未填写' }}</text>
204
+          </view>
205
+          <view class="info-item">
206
+            <text class="info-label">开户银行:</text>
207
+            <text class="info-value">{{ paymentInfo.bankName || '未填写' }}</text>
208
+          </view>
209
+          <view class="info-item">
210
+            <text class="info-label">银行卡号:</text>
211
+            <text class="info-value">{{ paymentInfo.bankAccount || '未填写' }}</text>
212
+          </view>
213
+          <view class="info-item">
214
+            <text class="info-label">支付方式:</text>
215
+            <text class="info-value">{{ paymentMethod || '未填写' }}</text>
216
+          </view>
217
+        </view>
218
+        <u-button 
219
+          type="primary" 
220
+          size="large" 
221
+          @click="confirmTransfer"
222
+          style="margin-top: 40rpx;"
223
+        >
224
+          确认转账
225
+        </u-button>
226
+        <u-button 
227
+          type="primary" 
228
+          :plain="true" 
229
+          @click="payNowModalVisible = false" 
230
+          size="large"
231
+          style="margin-top: 20rpx;"
232
+        >
233
+          取消
234
+        </u-button>
235
+      </view>
236
+    </u-modal>
237
+  </view>
238
+</template>
239
+
240
+<script>
241
+import PicComp from './PicComp.vue'
242
+import imageUpload from '../utils/imageUpload.js'
243
+
244
+export default {
245
+  name: 'PageThree',
246
+  components: {
247
+    PicComp
248
+  },
249
+  props: {
250
+    orderDetail: {
251
+      type: Object,
252
+      default: () => ({})
253
+    },
254
+    orderId: {
255
+      type: String,
256
+      default: ''
257
+    },
258
+    currentReceipt: {
259
+      type: Object,
260
+      default: () => ({})
261
+    }
262
+  },
263
+  data() {
264
+    return {
265
+      paymentInfo: {
266
+        customName: '',
267
+        bankName: '',
268
+        bankAccount: '',
269
+        idNumber: ''
270
+      },
271
+      paymentMethodRadio: 'offline',
272
+      paymentMethod: '',
273
+      paymentAmount: '0.00',
274
+      detailImages: [],
275
+      // 拖拽相关
276
+      draggingIndex: -1,
277
+      canDropIndex: -1,
278
+      startX: 0,
279
+      startY: 0,
280
+      // 模态窗
281
+      unpaidModalVisible: false,
282
+      followUpModalVisible: false,
283
+      payNowModalVisible: false,
284
+      unpaidRating: 0,
285
+      followUpNotes: ''
286
+    }
287
+  },
288
+  watch: {
289
+    orderDetail: {
290
+      handler(newVal) {
291
+        if (newVal) {
292
+          this.paymentInfo.customName = newVal.customName || ''
293
+          this.paymentInfo.bankName = newVal.bankName || ''
294
+          this.paymentInfo.bankAccount = newVal.bankCardNumber || ''
295
+          this.paymentInfo.idNumber = newVal.idCard || ''
296
+          this.initPaymentMethod(newVal.paymentMethod)
297
+        }
298
+      },
299
+      deep: true,
300
+      immediate: true
301
+    },
302
+    currentReceipt: {
303
+      handler(newVal) {
304
+        if (newVal) {
305
+          this.paymentAmount = newVal.tableFee || '0.00'
306
+          this.loadDetailImages()
307
+        }
308
+      },
309
+      deep: true,
310
+      immediate: true
311
+    }
312
+  },
313
+  methods: {
314
+    /**
315
+     * 刷新图片列表(供父组件调用)
316
+     */
317
+    async refreshImageList() {
318
+      await this.loadDetailImages()
319
+    },
320
+
321
+    /**
322
+     * 加载细节图
323
+     */
324
+    async loadDetailImages() {
325
+      if (!this.currentReceipt.id || !this.orderDetail.itemBrand) return
326
+      
327
+      try {
328
+        const list = await imageUpload.getFileList(
329
+          '2',
330
+          '3',
331
+          this.currentReceipt.id,
332
+          this.orderDetail.itemBrand,
333
+          this.currentReceipt.clueId
334
+        )
335
+        this.detailImages = list || []
336
+      } catch (error) {
337
+        console.error('加载细节图失败:', error)
338
+      }
339
+    },
340
+
341
+    /**
342
+     * 初始化支付方式
343
+     */
344
+    initPaymentMethod(value) {
345
+      if (value === '小葫芦线上支付') {
346
+        this.paymentMethod = '小葫芦线上支付'
347
+        this.paymentMethodRadio = 'online'
348
+      } else {
349
+        this.paymentMethod = value || ''
350
+        this.paymentMethodRadio = 'offline'
351
+      }
352
+    },
353
+
354
+    /**
355
+     * 支付方式选择改变
356
+     */
357
+    handlePaymentMethodRadioChange(value) {
358
+      if (value === 'online') {
359
+        this.paymentMethod = '小葫芦线上支付'
360
+      } else {
361
+        this.paymentMethod = ''
362
+      }
363
+    },
364
+
365
+    /**
366
+     * 银行名称输入处理
367
+     */
368
+    handleBankNameInput() {
369
+      const strValue = String(this.paymentInfo.bankName || '')
370
+      const reg = /[0-9]/g
371
+      this.paymentInfo.bankName = strValue.replace(reg, '')
372
+    },
373
+
374
+    /**
375
+     * 获取图片类型
376
+     */
377
+    getImageType(index) {
378
+      const types = ['正面', '反面', '侧面', '扣子', '编号']
379
+      return types[index] || `细节${index - 4}`
380
+    },
381
+
382
+    /**
383
+     * 触摸开始
384
+     */
385
+    onTouchStart(event, index) {
386
+      if (this.draggingIndex !== -1) return
387
+      this.draggingIndex = index
388
+      this.startX = event.touches[0].clientX
389
+      this.startY = event.touches[0].clientY
390
+    },
391
+
392
+    /**
393
+     * 触摸移动
394
+     */
395
+    onTouchMove(event) {
396
+      if (this.draggingIndex === -1) return
397
+      const moveX = event.touches[0].clientX
398
+      const moveY = event.touches[0].clientY
399
+      this.findTargetIndex(moveX, moveY)
400
+    },
401
+
402
+    /**
403
+     * 触摸结束
404
+     */
405
+    onTouchEnd() {
406
+      if (this.draggingIndex === -1) return
407
+      
408
+      if (this.canDropIndex !== -1 && this.canDropIndex !== this.draggingIndex) {
409
+        const temp = this.detailImages[this.draggingIndex]
410
+        this.detailImages[this.draggingIndex] = this.detailImages[this.canDropIndex]
411
+        this.detailImages[this.canDropIndex] = temp
412
+      }
413
+      
414
+      this.resetDragState()
415
+    },
416
+
417
+    /**
418
+     * 查找目标索引
419
+     */
420
+    findTargetIndex(x, y) {
421
+      const query = uni.createSelectorQuery().in(this)
422
+      query.selectAll('.detail-image-item').boundingClientRect(rects => {
423
+        let targetIndex = -1
424
+        for (let i = 0; i < rects.length; i++) {
425
+          if (i === this.draggingIndex) continue
426
+          const rect = rects[i]
427
+          if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
428
+            targetIndex = i
429
+            break
430
+          }
431
+        }
432
+        this.canDropIndex = targetIndex
433
+      }).exec()
434
+    },
435
+
436
+    /**
437
+     * 重置拖拽状态
438
+     */
439
+    resetDragState() {
440
+      this.draggingIndex = -1
441
+      this.canDropIndex = -1
442
+      this.startX = 0
443
+      this.startY = 0
444
+    },
445
+
446
+    /**
447
+     * 上传图片
448
+     */
449
+    async handleUploadImage() {
450
+      try {
451
+        const filePaths = await imageUpload.chooseImage(9)
452
+        const uploadResults = await imageUpload.uploadFiles(filePaths)
453
+        await imageUpload.bindOrderFile(
454
+          this.currentReceipt.clueId,
455
+          this.currentReceipt.id,
456
+          '3',
457
+          uploadResults
458
+        )
459
+        this.loadDetailImages()
460
+      } catch (error) {
461
+        console.error('上传失败:', error)
462
+      }
463
+    },
464
+
465
+    /**
466
+     * 隐藏图片
467
+     */
468
+    handleHideImage(item, index) {
469
+      const itemIndex = this.detailImages.findIndex(img => img.fileUrl === item.fileUrl)
470
+      if (itemIndex !== -1) {
471
+        this.detailImages.splice(itemIndex, 1)
472
+        uni.$u.toast('图片已隐藏')
473
+      }
474
+    },
475
+
476
+    /**
477
+     * 预览图片
478
+     */
479
+    previewImageDetail(src) {
480
+      const urlList = this.detailImages.map(item => item.fileUrl)
481
+      uni.previewImage({
482
+        urls: urlList,
483
+        current: src
484
+      })
485
+    },
486
+
487
+    /**
488
+     * 未收点击
489
+     */
490
+    handleUnpaidClick() {
491
+      this.unpaidModalVisible = true
492
+    },
493
+
494
+    /**
495
+     * 待跟进点击
496
+     */
497
+    handleFollowUpClick() {
498
+      this.followUpModalVisible = true
499
+    },
500
+
501
+    /**
502
+     * 立即支付点击
503
+     */
504
+    async handlePayNowClick() {
505
+      // 保存支付信息
506
+      await uni.$u.api.updateReceiptForm({
507
+        id: this.currentReceipt.id,
508
+        tableFee: this.paymentAmount,
509
+        fileIds: this.detailImages.map(item => item.id).join(',')
510
+      })
511
+
512
+      await uni.$u.api.updateClueOrderForm({
513
+        id: this.orderDetail.id,
514
+        customName: this.paymentInfo.customName || '',
515
+        bankName: this.paymentInfo.bankName || '',
516
+        bankCardNumber: this.paymentInfo.bankAccount || '',
517
+        idCard: this.paymentInfo.idNumber || '',
518
+        paymentMethod: this.paymentMethod || ''
519
+      })
520
+
521
+      if (!this.paymentInfo.customName || !this.paymentInfo.bankName || 
522
+          !this.paymentInfo.bankAccount || !this.paymentInfo.idNumber || !this.paymentMethod) {
523
+        uni.$u.toast('请填写完整的支付信息')
524
+        return
525
+      }
526
+      if (this.paymentAmount <= 0) {
527
+        uni.$u.toast('请填写正确的支付总额')
528
+        return
529
+      }
530
+
531
+      this.payNowModalVisible = true
532
+    },
533
+
534
+    /**
535
+     * 确认转账
536
+     */
537
+    confirmTransfer() {
538
+      this.payNowModalVisible = false
539
+      this.$emit('confirm-pay')
540
+    },
541
+
542
+    /**
543
+     * 确认未收
544
+     */
545
+    async confirmUnpaid() {
546
+      try {
547
+        await uni.$u.api.addOrderFollow({
548
+          orderId: this.orderId,
549
+          content: `未收评分_${this.unpaidRating}`
550
+        })
551
+        await uni.$u.api.oderForm({
552
+          status: '4',
553
+          id: this.orderId
554
+        })
555
+        uni.$u.toast('提交未收评级成功')
556
+        this.unpaidModalVisible = false
557
+      } catch (error) {
558
+        console.error('提交失败:', error)
559
+        uni.$u.toast('提交失败')
560
+      }
561
+    },
562
+
563
+    /**
564
+     * 确认跟进
565
+     */
566
+    async confirmFollowUp() {
567
+      try {
568
+        await uni.$u.api.addOrderFollow({
569
+          orderId: this.orderId,
570
+          content: `待跟进_${this.followUpNotes}`
571
+        })
572
+        uni.$u.toast('提交待跟进记录成功')
573
+        this.followUpModalVisible = false
574
+        this.followUpNotes = ''
575
+      } catch (error) {
576
+        console.error('提交失败:', error)
577
+        uni.$u.toast('提交失败')
578
+      }
579
+    },
580
+
581
+    /**
582
+     * 下一步
583
+     */
584
+    async handleNext() {
585
+      await uni.$u.api.updateReceiptForm({
586
+        id: this.currentReceipt.id,
587
+        tableFee: this.paymentAmount,
588
+        fileIds: this.detailImages.map(item => item.id).join(',')
589
+      })
590
+
591
+      await uni.$u.api.updateClueOrderForm({
592
+        id: this.orderDetail.id,
593
+        customName: this.paymentInfo.customName || '',
594
+        bankName: this.paymentInfo.bankName || '',
595
+        bankCardNumber: this.paymentInfo.bankAccount || '',
596
+        idCard: this.paymentInfo.idNumber || ''
597
+      })
598
+
599
+      this.$emit('save', {
600
+        nowPage: 'formThree',
601
+        form: {
602
+          ...this.paymentInfo
603
+        },
604
+        fileIds: this.detailImages.map(item => item.id).join(',')
605
+      })
606
+
607
+      this.$emit('next', {
608
+        nowPage: 'formThree',
609
+        form: {
610
+          ...this.paymentInfo
611
+        }
612
+      })
613
+    }
614
+  }
615
+}
616
+</script>
617
+
618
+<style scoped lang="scss">
619
+@import '../styles/common.scss';
620
+
621
+.page-three-container {
622
+  @extend .page-container;
623
+  padding-bottom: 100rpx;
624
+}
625
+
626
+.info-card {
627
+  @extend .card-wrap;
628
+  padding: 20rpx;
629
+  margin-top: 20rpx;
630
+}
631
+
632
+.info-card-title {
633
+  @include font-styles($size: title, $weight: bold, $color: primary);
634
+  margin-bottom: 25rpx;
635
+  padding-bottom: 15rpx;
636
+  border-bottom: 1rpx solid map-get($colors, border);
637
+}
638
+
639
+.info-row {
640
+  margin-bottom: 20rpx;
641
+}
642
+
643
+.info-label {
644
+  @include font-styles($size: tiny, $weight: regular, $color: tertiary);
645
+  margin-bottom: 8rpx;
646
+  display: block;
647
+}
648
+
649
+.info-input {
650
+  border-radius: 8rpx;
651
+  border: 1rpx solid #e5e7eb;
652
+  padding: 12rpx 16rpx;
653
+  width: 100%;
654
+  box-sizing: border-box;
655
+}
656
+
657
+.detail-image-section {
658
+  padding: 20rpx;
659
+}
660
+
661
+.detail-image-header {
662
+  display: flex;
663
+  justify-content: space-between;
664
+  align-items: center;
665
+  margin-bottom: 20rpx;
666
+  padding-bottom: 20rpx;
667
+  border-bottom: 1rpx solid map-get($colors, border);
668
+}
669
+
670
+.detail-image-title {
671
+  @include font-styles($size: content, $weight: bold, $color: primary);
672
+}
673
+
674
+.detail-image-list {
675
+  display: flex;
676
+  flex-wrap: wrap;
677
+  gap: 20rpx;
678
+  position: relative;
679
+}
680
+
681
+.detail-image-item {
682
+  position: relative;
683
+  width: 200rpx;
684
+  height: 200rpx;
685
+  touch-action: none;
686
+  transition: all 0.2s ease;
687
+  z-index: 1;
688
+  cursor: move;
689
+  
690
+  &.dragging {
691
+    opacity: 0.5;
692
+    transform: scale(1.05);
693
+    z-index: 100;
694
+  }
695
+  
696
+  &.can-drop {
697
+    border: 2rpx dashed #108cff;
698
+    background-color: rgba(16, 140, 255, 0.1);
699
+  }
700
+}
701
+
702
+.image-type-tag {
703
+  position: absolute;
704
+  top: 10rpx;
705
+  left: 10rpx;
706
+  background-color: rgba(0, 0, 0, 0.6);
707
+  color: white;
708
+  padding: 5rpx 10rpx;
709
+  border-radius: 12rpx;
710
+  font-size: 22rpx;
711
+  z-index: 1;
712
+}
713
+
714
+.detail-delete-btn {
715
+  position: absolute;
716
+  top: -10rpx;
717
+  right: -10rpx;
718
+  width: 40rpx;
719
+  height: 40rpx;
720
+  background-color: #ff4d4f;
721
+  color: #fff;
722
+  border-radius: 50%;
723
+  display: flex;
724
+  align-items: center;
725
+  justify-content: center;
726
+  font-weight: bold;
727
+  z-index: 10;
728
+  cursor: pointer;
729
+}
730
+
731
+.detail-upload-btn {
732
+  width: 200rpx;
733
+  height: 200rpx;
734
+  border: 8rpx dashed #ddd;
735
+  border-radius: 30rpx;
736
+  display: flex;
737
+  align-items: center;
738
+  justify-content: center;
739
+  background-color: #f9f9f9;
740
+  cursor: pointer;
741
+}
742
+
743
+.payment-card {
744
+  margin-top: 20rpx;
745
+}
746
+
747
+.payment-section {
748
+  padding: 20rpx;
749
+}
750
+
751
+.payment-total-container {
752
+  background-color: #f5f7fa;
753
+  border: 2rpx solid #e5e7eb;
754
+  border-radius: 12rpx;
755
+  padding: 0rpx 30rpx;
756
+  display: flex;
757
+  align-items: center;
758
+  justify-content: space-between;
759
+  margin-bottom: 24rpx;
760
+}
761
+
762
+.payment-label {
763
+  font-size: 36rpx;
764
+  font-weight: 700;
765
+  min-width: 140rpx;
766
+}
767
+
768
+.payment-amount {
769
+  font-size: 48rpx;
770
+  font-weight: 600;
771
+  color: #f53f3f;
772
+  width: auto;
773
+  min-width: 150rpx;
774
+  border: none;
775
+  text-align: right !important;
776
+}
777
+
778
+.payment-buttons-row {
779
+  display: flex;
780
+  gap: 20rpx;
781
+  margin-bottom: 24rpx;
782
+}
783
+
784
+.payment-button {
785
+  flex: 1;
786
+  border-radius: 12rpx;
787
+  padding: 5rpx 0;
788
+  display: flex;
789
+  flex-direction: column;
790
+  align-items: center;
791
+  justify-content: center;
792
+  background-color: #f5f7fa;
793
+  border: 2rpx solid #e5e7eb;
794
+  cursor: pointer;
795
+  gap: 12rpx;
796
+}
797
+
798
+.pay-now-button {
799
+  width: 100%;
800
+  border-radius: 12rpx;
801
+  padding: 32rpx 0;
802
+  display: flex;
803
+  flex-direction: column;
804
+  align-items: center;
805
+  justify-content: center;
806
+  background-color: #108cff;
807
+  color: #fff;
808
+  cursor: pointer;
809
+  box-shadow: 0 4rpx 12rpx rgba(16, 140, 255, 0.2);
810
+}
811
+
812
+.button-text {
813
+  font-size: 28rpx;
814
+  font-weight: 500;
815
+  color: inherit;
816
+}
817
+
818
+.modal-content {
819
+  width: 100%;
820
+  padding: 40rpx 20rpx;
821
+  display: flex;
822
+  flex-direction: column;
823
+  align-items: center;
824
+}
825
+
826
+.payment-amount-display {
827
+  font-size: 64rpx;
828
+  font-weight: bold;
829
+  color: #108cff;
830
+  text-align: center;
831
+  margin-bottom: 40rpx;
832
+  width: 100%;
833
+}
834
+
835
+.payment-info-section {
836
+  width: 100%;
837
+  background-color: #f5f7fa;
838
+  border: 2rpx solid #e5e7eb;
839
+  border-radius: 12rpx;
840
+  padding: 30rpx 20rpx;
841
+  margin-bottom: 40rpx;
842
+}
843
+
844
+.info-item {
845
+  display: flex;
846
+  margin-bottom: 20rpx;
847
+  align-items: center;
848
+  justify-content: space-between;
849
+  
850
+  &:last-child {
851
+    margin-bottom: 0;
852
+  }
853
+}
854
+
855
+.next-btn {
856
+  position: fixed;
857
+  bottom: 10rpx;
858
+  left: 2.5%;
859
+  width: 95%;
860
+  height: 80rpx;
861
+  line-height: 80rpx;
862
+  text-align: center;
863
+  border-radius: 20rpx;
864
+}
865
+</style>

+ 702 - 0
pages/orderDetailRefactored/components/PageTwo.vue

@@ -0,0 +1,702 @@
1
+<template>
2
+  <view class="page-two-container">
3
+    <!-- 上门地址卡片 -->
4
+    <view class="card-wrap">
5
+      <view class="address-section">
6
+        <view class="address-header">
7
+          <u-icon name="map" size="36rpx" color="#108cff" class="location-icon" />
8
+          <text class="address-title">上门地址</text>
9
+        </view>
10
+        <view class="address-content">
11
+          <text class="address-text">{{ orderDetail.address || '未填写' }}</text>
12
+        </view>
13
+      </view>
14
+    </view>
15
+
16
+    <!-- 跟进清单卡片 -->
17
+    <view class="card-wrap checklist-card">
18
+      <view class="checklist-section">
19
+        <u-checkbox-group v-model="selectedCheckbox" placement="column">
20
+          <!-- 联系师傅 -->
21
+          <view class="checklist-item">
22
+            <view class="checkbox-text-container">
23
+              <u-checkbox name="contactMaster" size="40rpx" color="#108cff" />
24
+              <text class="checklist-text">联系师傅</text>
25
+            </view>
26
+            <u-input 
27
+              v-if="selectedCheckbox.includes('contactMaster')" 
28
+              v-model="formData.contactPhone"
29
+              placeholder="请输入师傅手机号" 
30
+              class="checklist-input" 
31
+            />
32
+          </view>
33
+
34
+          <!-- 师傅拍图技巧 -->
35
+          <view class="checklist-item">
36
+            <view class="checkbox-text-container">
37
+              <u-checkbox name="photoTips" size="40rpx" color="#108cff" />
38
+              <text class="checklist-text">师傅拍图技巧</text>
39
+            </view>
40
+            <u-input 
41
+              v-if="selectedCheckbox.includes('photoTips')" 
42
+              v-model="formData.photoTips"
43
+              type="textarea" 
44
+              placeholder="请输入拍图技巧" 
45
+              rows="3" 
46
+              class="checklist-textarea" 
47
+            />
48
+            <view v-if="selectedCheckbox.includes('photoTips')" class="upload-btn-container">
49
+              <view class="upload-btn" @click="handleUpload('photoTips')">
50
+                <u-icon name="camera" size="32rpx" color="#108cff" />
51
+                <text class="upload-btn-text">上传图片</text>
52
+              </view>
53
+            </view>
54
+            <view 
55
+              v-if="selectedCheckbox.includes('photoTips') && photoTipsImages.length > 0"
56
+              class="image-list"
57
+            >
58
+              <view 
59
+                v-for="(image, index) in photoTipsImages" 
60
+                :key="index" 
61
+                class="image-item"
62
+              >
63
+                <image :src="image" mode="aspectFill" class="image-thumb" />
64
+              </view>
65
+            </view>
66
+          </view>
67
+
68
+          <!-- 到达客户面对面 -->
69
+          <view class="checklist-item">
70
+            <view class="checkbox-text-container">
71
+              <u-checkbox name="faceToFace" size="40rpx" color="#108cff" />
72
+              <text class="checklist-text">到达客户面对面</text>
73
+            </view>
74
+            <u-input 
75
+              v-if="selectedCheckbox.includes('faceToFace')" 
76
+              v-model="formData.faceToFaceNotes"
77
+              type="textarea" 
78
+              placeholder="请输入备注信息" 
79
+              rows="3" 
80
+              class="checklist-textarea" 
81
+            />
82
+            <view v-if="selectedCheckbox.includes('faceToFace')" class="upload-btn-container">
83
+              <view class="upload-btn" @click="handleUpload('faceToFace')">
84
+                <u-icon name="camera" size="32rpx" color="#108cff" />
85
+                <text class="upload-btn-text">上传图片</text>
86
+              </view>
87
+            </view>
88
+            <view 
89
+              v-if="selectedCheckbox.includes('faceToFace') && faceToFaceImages.length > 0"
90
+              class="image-list"
91
+            >
92
+              <view 
93
+                v-for="(image, index) in faceToFaceImages" 
94
+                :key="index" 
95
+                class="image-item"
96
+              >
97
+                <image :src="image" mode="aspectFill" class="image-thumb" />
98
+              </view>
99
+            </view>
100
+          </view>
101
+        </u-checkbox-group>
102
+      </view>
103
+    </view>
104
+
105
+    <!-- 核准价卡片 -->
106
+    <view class="card-wrap price-card">
107
+      <view class="price-section">
108
+        <view class="price-picker-container">
109
+          <view class="quick-actions top-actions">
110
+            <view class="quick-btn increase" @click="quickChangePrice(100)">+100</view>
111
+            <view class="quick-btn increase" @click="quickChangePrice(1000)">+1000</view>
112
+          </view>
113
+          <view class="number-box-container">
114
+            <view class="price-input-box">
115
+              <text class="price-label">核准价¥</text>
116
+              <input 
117
+                type="number" 
118
+                v-model="approvedPrice" 
119
+                class="price-input" 
120
+                placeholder="0" 
121
+                min="0"
122
+                @input="onPriceInput" 
123
+              />
124
+            </view>
125
+          </view>
126
+          <view class="quick-actions bottom-actions">
127
+            <view class="quick-btn decrease" @click="quickChangePrice(-100)">-100</view>
128
+            <view class="quick-btn decrease" @click="quickChangePrice(-1000)">-1000</view>
129
+          </view>
130
+        </view>
131
+      </view>
132
+    </view>
133
+
134
+    <!-- 高清细节图卡片 -->
135
+    <view class="card-wrap detail-image-card">
136
+      <view class="detail-image-section">
137
+        <view class="detail-image-header">
138
+          <text class="detail-image-title">上传高清细节图(支持多选)</text>
139
+          <view class="copy-btn" @click="copyAllDetailImages">
140
+            <text>一键复制</text>
141
+          </view>
142
+        </view>
143
+        <view class="detail-image-upload-container">
144
+          <view class="detail-image-list">
145
+            <view 
146
+              v-for="(item, index) in detailImages" 
147
+              :key="`detail-${index}`"
148
+              class="detail-image-item"
149
+            >
150
+              <PicComp 
151
+                :src="item.fileUrl" 
152
+                @needPreviewPic="previewImageDetail"
153
+              />
154
+              <view class="detail-delete-btn" @click="handleDeleteImage(item)">×</view>
155
+            </view>
156
+            <view 
157
+              class="detail-upload-btn" 
158
+              @click="handleUploadImage('detailImages')"
159
+            >
160
+              <u-icon name="plus" size="40rpx" color="#999" />
161
+            </view>
162
+          </view>
163
+        </view>
164
+      </view>
165
+    </view>
166
+
167
+    <!-- 下一步按钮 -->
168
+    <u-button 
169
+      class="next-btn" 
170
+      @click="handleNext" 
171
+      type="primary" 
172
+      size="middle"
173
+    >
174
+      下一步
175
+    </u-button>
176
+  </view>
177
+</template>
178
+
179
+<script>
180
+import PicComp from './PicComp.vue'
181
+import imageUpload from '../utils/imageUpload.js'
182
+
183
+export default {
184
+  name: 'PageTwo',
185
+  components: {
186
+    PicComp
187
+  },
188
+  props: {
189
+    orderDetail: {
190
+      type: Object,
191
+      default: () => ({})
192
+    },
193
+    orderId: {
194
+      type: String,
195
+      default: ''
196
+    },
197
+    currentReceipt: {
198
+      type: Object,
199
+      default: () => ({})
200
+    },
201
+    followUpList: {
202
+      type: Array,
203
+      default: () => []
204
+    }
205
+  },
206
+  data() {
207
+    return {
208
+      selectedCheckbox: [],
209
+      formData: {
210
+        contactPhone: '',
211
+        photoTips: '',
212
+        faceToFaceNotes: ''
213
+      },
214
+      photoTipsImages: [],
215
+      faceToFaceImages: [],
216
+      approvedPrice: 0,
217
+      detailImages: []
218
+    }
219
+  },
220
+  watch: {
221
+    currentReceipt: {
222
+      handler(newVal) {
223
+        if (newVal && newVal.id) {
224
+          this.approvedPrice = Number(newVal.sellingPrice) || 0
225
+          this.loadDetailImages()
226
+        }
227
+      },
228
+      immediate: true,
229
+      deep: true
230
+    },
231
+    followUpList: {
232
+      handler(newVal) {
233
+        if (newVal && newVal.length > 0) {
234
+          this.checkFollowUpContent(newVal)
235
+        }
236
+      },
237
+      deep: true
238
+    }
239
+  },
240
+  methods: {
241
+    /**
242
+     * 加载细节图
243
+     */
244
+    async loadDetailImages() {
245
+      if (!this.currentReceipt.id || !this.orderDetail.itemBrand) return
246
+      
247
+      try {
248
+        const list = await imageUpload.getFileList(
249
+          '2',
250
+          '3',
251
+          this.currentReceipt.id,
252
+          this.orderDetail.itemBrand,
253
+          this.currentReceipt.clueId
254
+        )
255
+        this.detailImages = list || []
256
+      } catch (error) {
257
+        console.error('加载细节图失败:', error)
258
+      }
259
+    },
260
+
261
+    /**
262
+     * 检查跟进内容
263
+     */
264
+    checkFollowUpContent(followUpList) {
265
+      const contentList = followUpList.map(item => item.content || '')
266
+      
267
+      contentList.forEach(item => {
268
+        if (item.includes('联系师傅') && !this.selectedCheckbox.includes('contactMaster')) {
269
+          this.selectedCheckbox.push('contactMaster')
270
+          const phone = item.split(';')[1] || ''
271
+          this.formData.contactPhone = phone
272
+        }
273
+        if (item.includes('师傅拍图技巧') && !this.selectedCheckbox.includes('photoTips')) {
274
+          this.selectedCheckbox.push('photoTips')
275
+          const tips = item.split(';')[1] || ''
276
+          this.formData.photoTips = tips
277
+          const urls = item.split(';')[2] || ''
278
+          this.photoTipsImages = urls.split(',').filter(url => url.trim())
279
+        }
280
+        if (item.includes('到达客户面对面') && !this.selectedCheckbox.includes('faceToFace')) {
281
+          this.selectedCheckbox.push('faceToFace')
282
+          const notes = item.split(';')[1] || ''
283
+          this.formData.faceToFaceNotes = notes
284
+          const urls = item.split(';')[2] || ''
285
+          this.faceToFaceImages = urls.split(',').filter(url => url.trim())
286
+        }
287
+      })
288
+    },
289
+
290
+    /**
291
+     * 价格输入处理
292
+     */
293
+    onPriceInput(e) {
294
+      let value = Number(e.detail.value)
295
+      if (isNaN(value)) value = 0
296
+      value = Math.max(0, value)
297
+      this.approvedPrice = value
298
+    },
299
+
300
+    /**
301
+     * 快速调整价格
302
+     */
303
+    quickChangePrice(amount) {
304
+      let newPrice = this.approvedPrice + amount
305
+      newPrice = Math.max(0, newPrice)
306
+      this.approvedPrice = newPrice
307
+    },
308
+
309
+    /**
310
+     * 上传图片(跟进清单)
311
+     */
312
+    async handleUpload(field) {
313
+      try {
314
+        const filePaths = await imageUpload.chooseImage(9)
315
+        const uploadResults = await imageUpload.uploadFiles(filePaths)
316
+        const urls = uploadResults.map(item => item.fileUrl)
317
+        
318
+        if (field === 'photoTips') {
319
+          this.photoTipsImages = [...this.photoTipsImages, ...urls]
320
+        } else if (field === 'faceToFace') {
321
+          this.faceToFaceImages = [...this.faceToFaceImages, ...urls]
322
+        }
323
+      } catch (error) {
324
+        console.error('上传失败:', error)
325
+        uni.$u.toast('上传失败')
326
+      }
327
+    },
328
+
329
+    /**
330
+     * 上传细节图
331
+     */
332
+    async handleUploadImage() {
333
+      try {
334
+        const filePaths = await imageUpload.chooseImage(9)
335
+        const uploadResults = await imageUpload.uploadFiles(filePaths)
336
+        await imageUpload.bindOrderFile(
337
+          this.currentReceipt.clueId,
338
+          this.currentReceipt.id,
339
+          '3',
340
+          uploadResults
341
+        )
342
+        this.loadDetailImages()
343
+      } catch (error) {
344
+        console.error('上传失败:', error)
345
+      }
346
+    },
347
+
348
+    /**
349
+     * 删除图片
350
+     */
351
+    async handleDeleteImage(item) {
352
+      uni.showModal({
353
+        title: '提示',
354
+        content: '确定要删除这张图片吗?',
355
+        success: async (res) => {
356
+          if (res.confirm) {
357
+            try {
358
+              await imageUpload.deleteFile(item.id)
359
+              this.loadDetailImages()
360
+            } catch (error) {
361
+              console.error('删除失败:', error)
362
+            }
363
+          }
364
+        }
365
+      })
366
+    },
367
+
368
+    /**
369
+     * 复制所有细节图
370
+     */
371
+    async copyAllDetailImages() {
372
+      const allUrls = this.detailImages.map(item => item.fileUrl)
373
+      if (allUrls.length === 0) {
374
+        uni.showToast({
375
+          title: '没有图片可复制',
376
+          icon: 'none'
377
+        })
378
+        return
379
+      }
380
+      
381
+      uni.showModal({
382
+        title: '保存图片',
383
+        content: `是否将 ${allUrls.length} 张图片保存到本地相册?`,
384
+        confirmText: '保存',
385
+        success: (res) => {
386
+          if (res.confirm) {
387
+            imageUpload.saveImagesToLocal(allUrls)
388
+          }
389
+        }
390
+      })
391
+    },
392
+
393
+    /**
394
+     * 预览细节图
395
+     */
396
+    previewImageDetail(src) {
397
+      const urlList = this.detailImages.map(item => item.fileUrl)
398
+      uni.previewImage({
399
+        urls: urlList,
400
+        current: src
401
+      })
402
+    },
403
+
404
+    /**
405
+     * 下一步
406
+     */
407
+    async handleNext() {
408
+      // 保存核准价
409
+      await uni.$u.api.updateReceiptForm({
410
+        id: this.currentReceipt.id,
411
+        sellingPrice: this.approvedPrice
412
+      })
413
+
414
+      // 保存跟进记录
415
+      if (this.selectedCheckbox.includes('contactMaster')) {
416
+        await uni.$u.api.addOrderFollow({
417
+          orderId: this.orderId,
418
+          content: `联系师傅;${this.formData.contactPhone}`
419
+        })
420
+      }
421
+      if (this.selectedCheckbox.includes('photoTips')) {
422
+        const urls = this.photoTipsImages.join(',')
423
+        await uni.$u.api.addOrderFollow({
424
+          orderId: this.orderId,
425
+          content: `师傅拍图技巧;${this.formData.photoTips};${urls}`
426
+        })
427
+      }
428
+      if (this.selectedCheckbox.includes('faceToFace')) {
429
+        const urls = this.faceToFaceImages.join(',')
430
+        await uni.$u.api.addOrderFollow({
431
+          orderId: this.orderId,
432
+          content: `到达客户面对面;${this.formData.faceToFaceNotes};${urls}`
433
+        })
434
+      }
435
+
436
+      this.$emit('next', {
437
+        nowPage: 'formTwo',
438
+        form: {
439
+          approvedPrice: this.approvedPrice,
440
+          detailImages: this.detailImages
441
+        }
442
+      })
443
+    }
444
+  }
445
+}
446
+</script>
447
+
448
+<style scoped lang="scss">
449
+@import '../styles/common.scss';
450
+
451
+.page-two-container {
452
+  @extend .page-container;
453
+  padding-bottom: 100rpx;
454
+}
455
+
456
+.address-section {
457
+  padding: 20rpx;
458
+}
459
+
460
+.checklist-card {
461
+  margin-top: 20rpx;
462
+}
463
+
464
+.checklist-section {
465
+  padding: 20rpx;
466
+}
467
+
468
+.checklist-item {
469
+  padding: 16rpx 0;
470
+  border-bottom: 1rpx solid map-get($colors, border);
471
+  
472
+  &:last-child {
473
+    border-bottom: none;
474
+  }
475
+}
476
+
477
+.checkbox-text-container {
478
+  @include flex-center;
479
+  margin-bottom: 12rpx;
480
+}
481
+
482
+.checklist-text {
483
+  @include font-styles;
484
+  margin-left: 16rpx;
485
+}
486
+
487
+.checklist-input,
488
+.checklist-textarea {
489
+  margin-top: 12rpx;
490
+  margin-left: 56rpx;
491
+  width: calc(100% - 72rpx);
492
+  border-radius: 8rpx;
493
+  border: 1rpx solid #e5e7eb;
494
+  padding: 12rpx 16rpx;
495
+}
496
+
497
+.upload-btn-container {
498
+  margin-top: 16rpx;
499
+  margin-left: 56rpx;
500
+}
501
+
502
+.upload-btn {
503
+  display: inline-flex;
504
+  align-items: center;
505
+  gap: 12rpx;
506
+  padding: 20rpx 40rpx;
507
+  border-radius: 8rpx;
508
+  background-color: map-get($colors, bg);
509
+  border: 2rpx dashed map-get($colors, primary);
510
+  color: map-get($colors, primary);
511
+  cursor: pointer;
512
+}
513
+
514
+.upload-btn-text {
515
+  @include font-styles($size: small, $weight: medium);
516
+}
517
+
518
+.image-list {
519
+  margin-top: 16rpx;
520
+  margin-left: 56rpx;
521
+  display: flex;
522
+  flex-wrap: wrap;
523
+  gap: 16rpx;
524
+}
525
+
526
+.image-item {
527
+  width: 120rpx;
528
+  height: 120rpx;
529
+  border-radius: 8rpx;
530
+  overflow: hidden;
531
+}
532
+
533
+.image-thumb {
534
+  width: 100%;
535
+  height: 100%;
536
+  object-fit: cover;
537
+}
538
+
539
+.price-card {
540
+  margin-top: 20rpx;
541
+}
542
+
543
+.price-section {
544
+  padding: 20rpx;
545
+}
546
+
547
+.price-picker-container {
548
+  display: flex;
549
+  flex-direction: column;
550
+  align-items: center;
551
+  padding: 20rpx 0;
552
+}
553
+
554
+.quick-actions {
555
+  display: flex;
556
+  justify-content: center;
557
+  gap: 16rpx;
558
+  margin: 5rpx 0;
559
+  width: 100%;
560
+}
561
+
562
+.quick-btn {
563
+  flex: 1;
564
+  border-radius: 12rpx;
565
+  font-size: 36rpx;
566
+  padding: 10rpx 0;
567
+  text-align: center;
568
+  font-weight: 600;
569
+  cursor: pointer;
570
+  
571
+  &.increase {
572
+    background-color: #e6f7ed;
573
+    color: #00b42a;
574
+  }
575
+  
576
+  &.decrease {
577
+    background-color: #fff1f0;
578
+    color: #f53f3f;
579
+  }
580
+}
581
+
582
+.number-box-container {
583
+  display: flex;
584
+  align-items: center;
585
+  margin: 20rpx 0;
586
+  width: 100%;
587
+  justify-content: center;
588
+}
589
+
590
+.price-input-box {
591
+  flex: 1;
592
+  max-width: 800rpx;
593
+  background-color: #f5f7fa;
594
+  border: 2rpx solid #e5e7eb;
595
+  border-radius: 12rpx;
596
+  padding: 0rpx 24rpx;
597
+  display: flex;
598
+  align-items: center;
599
+  justify-content: space-between;
600
+}
601
+
602
+.price-label {
603
+  font-size: 30rpx;
604
+  font-weight: 700;
605
+  min-width: 120rpx;
606
+}
607
+
608
+.price-input {
609
+  width: 100%;
610
+  border: none;
611
+  outline: none;
612
+  background-color: transparent;
613
+  text-align: right;
614
+  font-size: 48rpx;
615
+  font-weight: 600;
616
+  padding: 0 10rpx;
617
+}
618
+
619
+.detail-image-card {
620
+  margin-top: 20rpx;
621
+}
622
+
623
+.detail-image-section {
624
+  padding: 20rpx;
625
+}
626
+
627
+.detail-image-header {
628
+  display: flex;
629
+  justify-content: space-between;
630
+  align-items: center;
631
+  margin-bottom: 20rpx;
632
+  padding-bottom: 20rpx;
633
+  border-bottom: 1rpx solid map-get($colors, border);
634
+}
635
+
636
+.detail-image-title {
637
+  @include font-styles($size: content, $weight: bold, $color: primary);
638
+}
639
+
640
+.copy-btn {
641
+  border-radius: 20rpx;
642
+  border: 1rpx solid #007AFF;
643
+  background-color: transparent;
644
+  color: #007AFF;
645
+  padding: 0 24rpx;
646
+  height: 64rpx;
647
+  line-height: 64rpx;
648
+  cursor: pointer;
649
+}
650
+
651
+.detail-image-list {
652
+  display: flex;
653
+  flex-wrap: wrap;
654
+  gap: 20rpx;
655
+}
656
+
657
+.detail-image-item {
658
+  position: relative;
659
+  width: 200rpx;
660
+  height: 200rpx;
661
+}
662
+
663
+.detail-delete-btn {
664
+  position: absolute;
665
+  top: -10rpx;
666
+  right: -10rpx;
667
+  width: 40rpx;
668
+  height: 40rpx;
669
+  background-color: #ff4d4f;
670
+  color: #fff;
671
+  border-radius: 50%;
672
+  display: flex;
673
+  align-items: center;
674
+  justify-content: center;
675
+  font-weight: bold;
676
+  z-index: 10;
677
+  cursor: pointer;
678
+}
679
+
680
+.detail-upload-btn {
681
+  width: 200rpx;
682
+  height: 200rpx;
683
+  border: 8rpx dashed #ddd;
684
+  border-radius: 30rpx;
685
+  display: flex;
686
+  align-items: center;
687
+  justify-content: center;
688
+  background-color: #f9f9f9;
689
+  cursor: pointer;
690
+}
691
+
692
+.next-btn {
693
+  position: fixed;
694
+  bottom: 10rpx;
695
+  left: 2.5%;
696
+  width: 95%;
697
+  height: 80rpx;
698
+  line-height: 80rpx;
699
+  text-align: center;
700
+  border-radius: 20rpx;
701
+}
702
+</style>

+ 43 - 0
pages/orderDetailRefactored/components/PicComp.vue

@@ -0,0 +1,43 @@
1
+<template>
2
+  <view class="pic-comp-container">
3
+    <image 
4
+      class="pic-comp-image" 
5
+      :src="src" 
6
+      mode="aspectFill" 
7
+      @click="handleClick"
8
+    />
9
+  </view>
10
+</template>
11
+
12
+<script>
13
+export default {
14
+  name: 'PicComp',
15
+  props: {
16
+    src: {
17
+      type: String,
18
+      default: ''
19
+    }
20
+  },
21
+  methods: {
22
+    handleClick() {
23
+      this.$emit('needPreviewPic', this.src)
24
+    }
25
+  }
26
+}
27
+</script>
28
+
29
+<style scoped lang="scss">
30
+.pic-comp-container {
31
+  width: 100%;
32
+  height: 100%;
33
+  box-sizing: border-box;
34
+  overflow: hidden;
35
+  border-radius: 30rpx;
36
+}
37
+
38
+.pic-comp-image {
39
+  width: 100% !important;
40
+  height: 100% !important;
41
+  object-fit: cover;
42
+}
43
+</style>

+ 403 - 0
pages/orderDetailRefactored/index.vue

@@ -0,0 +1,403 @@
1
+<template>
2
+  <view class="order-detail-container">
3
+    <!-- 顶部导航栏 -->
4
+    <u-navbar :autoBack="true" :placeholder="true" v-hideNav>
5
+      <template slot="center">
6
+        <view class="navbar-center">
7
+          <text class="navbar-item" @click="handleBrandClick">{{ topInfo.brand }}</text>
8
+          <text class="navbar-divider">|</text>
9
+          <text class="navbar-item" @click="handleModelClick">{{ topInfo.model }}</text>
10
+          <text class="navbar-divider">|</text>
11
+          <text class="navbar-item price" @click="handlePriceClick">¥{{ topInfo.price }}</text>
12
+        </view>
13
+      </template>
14
+      <template slot="right">
15
+        <view class="navbar-right" @click="handleAddClick">
16
+          <image src="/static/icons/plus.png" mode="scaleToFill" class="add-icon" />
17
+          <text class="add-text">加一单</text>
18
+        </view>
19
+      </template>
20
+    </u-navbar>
21
+
22
+    <!-- 收单列表切换 -->
23
+    <u-tabs 
24
+      keyName="brand" 
25
+      :list="receiptList" 
26
+      @click="handleReceiptClick"
27
+      class="receipt-tabs"
28
+    />
29
+
30
+    <!-- 订单详情视图 -->
31
+    <OrderDetailView 
32
+      :order-detail="orderDetail" 
33
+      :top-info="topInfo" 
34
+      :order-id="orderId"
35
+      :current-receipt="currentReceipt" 
36
+    />
37
+
38
+    <!-- 加一单模态窗 -->
39
+    <u-modal 
40
+      :show="addOneModalVisible" 
41
+      title="加一单" 
42
+      showCancelButton 
43
+      @cancel="handleAddOneCancel"
44
+      @confirm="handleAddOneConfirm"
45
+    >
46
+      <view class="add-one-modal-content">
47
+        <u-button 
48
+          type="primary" 
49
+          plain 
50
+          @click="showBrandSelector = true"
51
+          class="brand-select-btn"
52
+        >
53
+          {{ currentAddBrand.dictLabel || '点击请选择品牌' }}
54
+        </u-button>
55
+      </view>
56
+    </u-modal>
57
+
58
+    <!-- 品牌选择器 -->
59
+    <u-picker 
60
+      :show="showBrandSelector" 
61
+      :columns="brandColumns" 
62
+      keyName="dictLabel"
63
+      @confirm="handleBrandConfirm" 
64
+      @cancel="showBrandSelector = false" 
65
+    />
66
+
67
+    <!-- 编辑品牌选择器 -->
68
+    <u-picker 
69
+      :show="editBrandSelectorVisible" 
70
+      :columns="brandColumns" 
71
+      keyName="dictLabel"
72
+      @confirm="handleEditBrandConfirm" 
73
+      @cancel="editBrandSelectorVisible = false" 
74
+    />
75
+
76
+    <!-- 修改型号/价格弹窗 -->
77
+    <CustomModal 
78
+      :visible="modalVisible" 
79
+      :title="modalConfig.title" 
80
+      :value="modalConfig.value"
81
+      :placeholder="modalConfig.placeholder" 
82
+      @cancel="handleModalCancel" 
83
+      @confirm="handleModalConfirm" 
84
+    />
85
+  </view>
86
+</template>
87
+
88
+<script>
89
+import OrderDetailView from './components/OrderDetailView.vue'
90
+import CustomModal from './components/CustomModal.vue'
91
+
92
+export default {
93
+  name: 'OrderDetailIndex',
94
+  components: {
95
+    OrderDetailView,
96
+    CustomModal
97
+  },
98
+  data() {
99
+    return {
100
+      // 顶部信息
101
+      topInfo: {
102
+        brand: '',
103
+        model: '',
104
+        price: ''
105
+      },
106
+      // 路由参数
107
+      orderId: '',
108
+      clueId: '',
109
+      item: '',
110
+      type: '',
111
+      // 订单详情
112
+      orderDetail: {},
113
+      // 收单列表
114
+      receiptList: [],
115
+      // 当前选中的收单
116
+      currentReceipt: {},
117
+      // 模态窗状态
118
+      addOneModalVisible: false,
119
+      showBrandSelector: false,
120
+      editBrandSelectorVisible: false,
121
+      modalVisible: false,
122
+      // 品牌选择相关
123
+      brandColumns: [[]],
124
+      currentAddBrand: {},
125
+      // 模态窗配置
126
+      modalConfig: {
127
+        title: '',
128
+        value: '',
129
+        placeholder: ''
130
+      },
131
+      currentEditField: ''
132
+    }
133
+  },
134
+  onLoad(options) {
135
+    this.initParams(options)
136
+    this.loadOrderDetail()
137
+    this.loadReceiptList()
138
+  },
139
+  methods: {
140
+    /**
141
+     * 初始化参数
142
+     */
143
+    initParams(options) {
144
+      const { item, orderId, type, clueId } = options
145
+      this.item = item || ''
146
+      this.orderId = orderId || ''
147
+      this.type = type || ''
148
+      this.clueId = clueId || ''
149
+    },
150
+
151
+    /**
152
+     * 加载订单详情
153
+     */
154
+    async loadOrderDetail() {
155
+      try {
156
+        const res = await uni.$u.api.getClueSendFormVoByOrderId({
157
+          id: this.orderId
158
+        })
159
+        if (res.code === 200) {
160
+          this.orderDetail = res.data || {}
161
+        }
162
+      } catch (error) {
163
+        console.error('加载订单详情失败:', error)
164
+        uni.$u.toast('加载订单详情失败')
165
+      }
166
+    },
167
+
168
+    /**
169
+     * 加载收单列表
170
+     */
171
+    async loadReceiptList() {
172
+      try {
173
+        const res = await uni.$u.api.clueReceiptFormListByOrderId(this.orderId)
174
+        if (res.code === 200) {
175
+          this.receiptList = res.data || []
176
+          // 默认选择第一个收单
177
+          if (this.receiptList.length > 0) {
178
+            this.handleReceiptClick(this.receiptList[0])
179
+          }
180
+        }
181
+      } catch (error) {
182
+        console.error('加载收单列表失败:', error)
183
+        uni.$u.toast('加载收单列表失败')
184
+      }
185
+    },
186
+
187
+    /**
188
+     * 点击收单项
189
+     */
190
+    handleReceiptClick(item) {
191
+      this.currentReceipt = item
192
+      this.topInfo.brand = item.brand || '暂无'
193
+      this.topInfo.model = item.model || '暂无'
194
+      this.topInfo.price = item.sellingPrice || '暂无'
195
+    },
196
+
197
+    /**
198
+     * 点击品牌
199
+     */
200
+    handleBrandClick() {
201
+      this.editBrandSelectorVisible = true
202
+      this.loadBrandList()
203
+    },
204
+
205
+    /**
206
+     * 点击型号
207
+     */
208
+    handleModelClick() {
209
+      this.modalConfig = {
210
+        title: '修改型号',
211
+        value: this.currentReceipt.model || '',
212
+        placeholder: '请输入型号'
213
+      }
214
+      this.currentEditField = 'model'
215
+      this.modalVisible = true
216
+    },
217
+
218
+    /**
219
+     * 点击价格
220
+     */
221
+    handlePriceClick() {
222
+      this.modalConfig = {
223
+        title: '修改价格',
224
+        value: this.currentReceipt.sellingPrice?.toString() || '',
225
+        placeholder: '请输入价格'
226
+      }
227
+      this.currentEditField = 'price'
228
+      this.modalVisible = true
229
+    },
230
+
231
+    /**
232
+     * 点击加一单
233
+     */
234
+    handleAddClick() {
235
+      this.addOneModalVisible = true
236
+      this.loadBrandList()
237
+    },
238
+
239
+    /**
240
+     * 加载品牌列表
241
+     */
242
+    async loadBrandList() {
243
+      try {
244
+        const res = await this.$getDicts('crm_form_brand')
245
+        this.brandColumns = [res]
246
+      } catch (error) {
247
+        console.error('加载品牌列表失败:', error)
248
+      }
249
+    },
250
+
251
+    /**
252
+     * 确认修改模态窗
253
+     */
254
+    async handleModalConfirm(value) {
255
+      try {
256
+        if (this.currentEditField === 'model') {
257
+          await uni.$u.api.updateReceiptForm({
258
+            model: value,
259
+            id: this.currentReceipt.id
260
+          })
261
+        } else if (this.currentEditField === 'price') {
262
+          await uni.$u.api.updateReceiptForm({
263
+            sellingPrice: value,
264
+            id: this.currentReceipt.id
265
+          })
266
+        }
267
+        uni.$u.toast('修改成功')
268
+        this.loadReceiptList()
269
+      } catch (error) {
270
+        console.error('修改失败:', error)
271
+        uni.$u.toast('修改失败')
272
+      } finally {
273
+        this.modalVisible = false
274
+      }
275
+    },
276
+
277
+    /**
278
+     * 取消修改模态窗
279
+     */
280
+    handleModalCancel() {
281
+      this.modalVisible = false
282
+    },
283
+
284
+    /**
285
+     * 确认编辑品牌
286
+     */
287
+    async handleEditBrandConfirm(data) {
288
+      try {
289
+        await uni.$u.api.updateReceiptForm({
290
+          brand: data.value[0].dictValue,
291
+          id: this.currentReceipt.id
292
+        })
293
+        uni.$u.toast('修改成功')
294
+        this.loadReceiptList()
295
+      } catch (error) {
296
+        console.error('修改品牌失败:', error)
297
+        uni.$u.toast('修改失败')
298
+      } finally {
299
+        this.editBrandSelectorVisible = false
300
+      }
301
+    },
302
+
303
+    /**
304
+     * 确认选择品牌(加一单)
305
+     */
306
+    handleBrandConfirm(data) {
307
+      this.currentAddBrand = data.value[0]
308
+      this.showBrandSelector = false
309
+    },
310
+
311
+    /**
312
+     * 确认加一单
313
+     */
314
+    async handleAddOneConfirm() {
315
+      try {
316
+        await uni.$u.api.addReceiptForm({
317
+          brand: this.currentAddBrand.dictValue,
318
+          sendFormId: this.orderId,
319
+          clueId: this.clueId
320
+        })
321
+        uni.$u.toast('添加成功')
322
+        this.loadReceiptList()
323
+      } catch (error) {
324
+        console.error('添加失败:', error)
325
+        uni.$u.toast('添加失败')
326
+      } finally {
327
+        this.addOneModalVisible = false
328
+        this.currentAddBrand = {}
329
+      }
330
+    },
331
+
332
+    /**
333
+     * 取消加一单
334
+     */
335
+    handleAddOneCancel() {
336
+      this.addOneModalVisible = false
337
+      this.currentAddBrand = {}
338
+    }
339
+  }
340
+}
341
+</script>
342
+
343
+<style scoped lang="scss">
344
+.order-detail-container {
345
+  min-height: 100vh;
346
+  background-color: #f9fafb;
347
+}
348
+
349
+.navbar-center {
350
+  display: flex;
351
+  align-items: center;
352
+  background-color: #fff;
353
+  border-radius: 40rpx;
354
+  padding: 10rpx 20rpx;
355
+  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
356
+}
357
+
358
+.navbar-item {
359
+  font-weight: bold;
360
+  color: #333;
361
+  font-size: 28rpx;
362
+  
363
+  &.price {
364
+    color: blueviolet;
365
+  }
366
+}
367
+
368
+.navbar-divider {
369
+  margin: 0 15rpx;
370
+  color: #ddd;
371
+  font-size: 28rpx;
372
+}
373
+
374
+.navbar-right {
375
+  display: flex;
376
+  flex-direction: column;
377
+  align-items: center;
378
+  justify-content: center;
379
+  font-size: 20rpx;
380
+  color: blueviolet;
381
+}
382
+
383
+.add-icon {
384
+  width: 30rpx;
385
+  height: 30rpx;
386
+}
387
+
388
+.add-text {
389
+  margin-top: 4rpx;
390
+}
391
+
392
+.receipt-tabs {
393
+  background-color: #fff;
394
+}
395
+
396
+.add-one-modal-content {
397
+  padding: 20rpx 0;
398
+}
399
+
400
+.brand-select-btn {
401
+  width: 100%;
402
+}
403
+</style>

+ 152 - 0
pages/orderDetailRefactored/styles/common.scss

@@ -0,0 +1,152 @@
1
+// 公共SCSS变量定义
2
+$colors: (
3
+  primary: #108cff,
4
+  primary-light: rgba(16, 140, 255, 0.08),
5
+  bg: #f9fafb,
6
+  card: #ffffff,
7
+  border: #f5f5f5,
8
+  text-primary: #1f2937,
9
+  text-secondary: #374151,
10
+  shadow: rgba(0, 0, 0, 0.05)
11
+);
12
+
13
+$sizes: (
14
+  radius: 16rpx,
15
+  padding: 32rpx,
16
+  padding-sm: 28rpx,
17
+  margin-sm: 18rpx,
18
+  margin-xs: 16rpx,
19
+  icon-padding: 14rpx,
20
+  font-title: 34rpx,
21
+  font-content: 32rpx,
22
+  line-height: 56rpx,
23
+  checkbox-size: 32rpx
24
+);
25
+
26
+// 字体优化
27
+$font: (
28
+  family: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif',
29
+  smoothing: antialiased
30
+);
31
+
32
+// 字体大小变量
33
+$font-sizes: (
34
+  title: 38rpx,
35
+  content: 34rpx,
36
+  sub-content: 30rpx,
37
+  small: 26rpx,
38
+  tiny: 24rpx
39
+);
40
+
41
+// 字体权重变量
42
+$font-weights: (
43
+  bold: 700,
44
+  semi-bold: 600,
45
+  medium: 500,
46
+  regular: 450,
47
+  light: 400
48
+);
49
+
50
+// 行高变量
51
+$line-heights: (
52
+  large: 56rpx,
53
+  medium: 48rpx,
54
+  small: 40rpx
55
+);
56
+
57
+// 字间距变量
58
+$letter-spacings: (
59
+  large: 1rpx,
60
+  medium: 0.8rpx,
61
+  small: 0.5rpx
62
+);
63
+
64
+// 文本颜色变量
65
+$text-colors: (
66
+  primary: map-get($colors, text-primary),
67
+  secondary: map-get($colors, text-secondary),
68
+  tertiary: #666666,
69
+  placeholder: #999999
70
+);
71
+
72
+// 公共混合宏
73
+@mixin flex-center {
74
+  display: flex;
75
+  align-items: center;
76
+}
77
+
78
+@mixin shadow($level: 1) {
79
+  $shadow-levels: (
80
+    1: 0 2rpx 12rpx map-get($colors, shadow),
81
+    2: 0 4rpx 20rpx map-get($colors, shadow),
82
+    3: 0 8rpx 30rpx map-get($colors, shadow)
83
+  );
84
+  box-shadow: map-get($shadow-levels, $level);
85
+  transition: box-shadow 0.3s ease;
86
+}
87
+
88
+@mixin card {
89
+  width: 100%;
90
+  margin: 0;
91
+  background-color: map-get($colors, card);
92
+  border-radius: map-get($sizes, radius);
93
+  @include shadow;
94
+}
95
+
96
+// 字体样式混合宏
97
+@mixin font-styles(
98
+  $size: content,
99
+  $weight: regular,
100
+  $color: primary,
101
+  $line-height: medium,
102
+  $letter-spacing: medium
103
+) {
104
+  font-size: map-get($font-sizes, $size);
105
+  font-weight: map-get($font-weights, $weight);
106
+  color: map-get($text-colors, $color);
107
+  line-height: map-get($line-heights, $line-height);
108
+  letter-spacing: map-get($letter-spacings, $letter-spacing);
109
+  font-family: map-get($font, family);
110
+}
111
+
112
+// 公共页面容器样式
113
+.page-container {
114
+  box-sizing: border-box;
115
+  padding: 0;
116
+  background-color: map-get($colors, bg);
117
+  font-family: map-get($font, family);
118
+  -webkit-font-smoothing: map-get($font, smoothing);
119
+  font-smoothing: map-get($font, smoothing);
120
+}
121
+
122
+// 公共卡片样式
123
+.card-wrap {
124
+  @include card;
125
+  margin-bottom: 20rpx;
126
+
127
+  &:hover {
128
+    @include shadow(2);
129
+  }
130
+}
131
+
132
+// 公共地址标题样式
133
+.address-header {
134
+  display: flex;
135
+  align-items: center;
136
+  margin-bottom: map-get($sizes, margin-sm);
137
+  padding-bottom: map-get($sizes, margin-sm);
138
+  border-bottom: 1rpx solid map-get($colors, border);
139
+
140
+  .location-icon {
141
+    margin-right: map-get($sizes, margin-xs);
142
+    background-color: map-get($colors, primary-light);
143
+    padding: map-get($sizes, icon-padding);
144
+    border-radius: 50%;
145
+    flex-shrink: 0;
146
+  }
147
+
148
+  .address-title {
149
+    margin-left: 16rpx;
150
+    @include font-styles($size: title, $weight: bold, $color: primary);
151
+  }
152
+}

+ 270 - 0
pages/orderDetailRefactored/utils/imageUpload.js

@@ -0,0 +1,270 @@
1
+/**
2
+ * 图片上传和下载工具类
3
+ */
4
+export default {
5
+  /**
6
+   * 获取文件列表
7
+   * @param {String} type - 类型
8
+   * @param {String} orderFileType - 订单文件类型 (1:聊天记录, 2:实物图, 3:细节图)
9
+   * @param {String} receiptId - 收单ID
10
+   * @param {String} itemBrand - 物品品牌
11
+   * @param {String} clueId - 线索ID
12
+   */
13
+  async getFileList(type, orderFileType, receiptId, itemBrand, clueId) {
14
+    try {
15
+      const params = {
16
+        clueId,
17
+        sourceId: receiptId,
18
+        type,
19
+        orderFileType,
20
+        isDuplicate: '1',
21
+        pageNum: 1,
22
+        pageSize: 1000
23
+      }
24
+      const response = await uni.$u.api.selectClueFileByDto(params)
25
+      const rows = response.rows || []
26
+      
27
+      // 如果品牌包含逗号,说明是多个品牌,需要过滤
28
+      if (itemBrand && itemBrand.indexOf(',') !== -1) {
29
+        return rows.filter(item => item.sourceId === receiptId)
30
+      }
31
+      return rows
32
+    } catch (error) {
33
+      console.error('获取文件列表失败:', error)
34
+      uni.$u.toast('获取文件列表失败')
35
+      return []
36
+    }
37
+  },
38
+
39
+  /**
40
+   * 选择图片
41
+   * @param {Number} count - 最多选择数量
42
+   * @returns {Promise<Array>} 图片路径数组
43
+   */
44
+  chooseImage(count = 9) {
45
+    return new Promise((resolve, reject) => {
46
+      uni.chooseImage({
47
+        count,
48
+        sizeType: ['compressed'],
49
+        sourceType: ['album', 'camera'],
50
+        success: (res) => {
51
+          resolve(res.tempFilePaths)
52
+        },
53
+        fail: (err) => {
54
+          console.error('选择图片失败:', err)
55
+          reject(err)
56
+        }
57
+      })
58
+    })
59
+  },
60
+
61
+  /**
62
+   * 上传文件
63
+   * @param {String} filePath - 文件路径
64
+   * @returns {Promise<Object>} 上传结果
65
+   */
66
+  async uploadFile(filePath) {
67
+    try {
68
+      uni.showLoading({
69
+        title: '上传中...',
70
+        mask: true
71
+      })
72
+      const { data } = await uni.$u.api.uploadFile(filePath)
73
+      return {
74
+        fileSize: data.fileSize,
75
+        fileSuffix: data.fileSuffix,
76
+        fileName: data.name,
77
+        fileUrl: data.url
78
+      }
79
+    } catch (error) {
80
+      console.error('文件上传失败:', error)
81
+      uni.$u.toast('上传失败,请重试')
82
+      throw error
83
+    } finally {
84
+      uni.hideLoading()
85
+    }
86
+  },
87
+
88
+  /**
89
+   * 批量上传文件
90
+   * @param {Array<String>} filePaths - 文件路径数组
91
+   * @returns {Promise<Array>} 上传结果数组
92
+   */
93
+  async uploadFiles(filePaths) {
94
+    try {
95
+      const uploadPromises = filePaths.map(filePath => this.uploadFile(filePath))
96
+      return await Promise.all(uploadPromises)
97
+    } catch (error) {
98
+      console.error('批量上传失败:', error)
99
+      throw error
100
+    }
101
+  },
102
+
103
+  /**
104
+   * 绑定订单文件
105
+   * @param {String} clueId - 线索ID
106
+   * @param {String} receiptId - 收单ID
107
+   * @param {String} orderFileType - 订单文件类型
108
+   * @param {Array} fileList - 文件列表
109
+   */
110
+  async bindOrderFile(clueId, receiptId, orderFileType, fileList) {
111
+    try {
112
+      const list = fileList.map(file => ({
113
+        fileSize: file.fileSize,
114
+        fileSuffix: file.fileSuffix,
115
+        fileName: file.fileName,
116
+        fileUrl: file.fileUrl,
117
+        orderFileType
118
+      }))
119
+      
120
+      await uni.$u.api.saveClueFile({
121
+        clueId,
122
+        list,
123
+        sourceId: receiptId,
124
+        type: '2',
125
+        orderFileType
126
+      })
127
+      uni.$u.toast('上传成功')
128
+    } catch (error) {
129
+      console.error('绑定订单文件失败:', error)
130
+      uni.$u.toast('上传失败')
131
+      throw error
132
+    }
133
+  },
134
+
135
+  /**
136
+   * 删除文件
137
+   * @param {String|Array} fileIds - 文件ID或ID数组
138
+   */
139
+  async deleteFile(fileIds) {
140
+    try {
141
+      const ids = Array.isArray(fileIds) ? fileIds : [fileIds]
142
+      await uni.$u.api.deleteClueFile(ids)
143
+      uni.showToast({
144
+        title: '删除成功',
145
+        icon: 'success',
146
+        duration: 2000
147
+      })
148
+    } catch (error) {
149
+      console.error('删除文件失败:', error)
150
+      uni.showToast({
151
+        title: '删除失败',
152
+        icon: 'error',
153
+        duration: 2000
154
+      })
155
+      throw error
156
+    }
157
+  },
158
+
159
+  /**
160
+   * 保存图片到本地相册
161
+   * @param {String} url - 图片URL
162
+   * @returns {Promise}
163
+   */
164
+  saveImageToLocal(url) {
165
+    return new Promise((resolve, reject) => {
166
+      uni.downloadFile({
167
+        url,
168
+        success: (res) => {
169
+          if (res.statusCode === 200) {
170
+            uni.saveImageToPhotosAlbum({
171
+              filePath: res.tempFilePath,
172
+              success: () => {
173
+                resolve()
174
+              },
175
+              fail: (err) => {
176
+                console.error('保存到相册失败:', err)
177
+                if (err.errMsg.includes('auth denied')) {
178
+                  uni.showModal({
179
+                    title: '权限不足',
180
+                    content: '需要访问相册权限来保存图片,是否去设置?',
181
+                    success: (modalRes) => {
182
+                      if (modalRes.confirm) {
183
+                        uni.openSetting()
184
+                      }
185
+                    }
186
+                  })
187
+                }
188
+                reject(err)
189
+              }
190
+            })
191
+          } else {
192
+            reject(new Error('下载失败'))
193
+          }
194
+        },
195
+        fail: (err) => {
196
+          console.error('下载图片失败:', err)
197
+          reject(err)
198
+        }
199
+      })
200
+    })
201
+  },
202
+
203
+  /**
204
+   * 批量保存图片到本地
205
+   * @param {Array<String>} urls - 图片URL数组
206
+   */
207
+  async saveImagesToLocal(urls) {
208
+    try {
209
+      uni.showLoading({
210
+        title: '正在保存图片...',
211
+        mask: true
212
+      })
213
+
214
+      const savedImages = []
215
+      const failedImages = []
216
+
217
+      for (let i = 0; i < urls.length; i++) {
218
+        const url = urls[i]
219
+        try {
220
+          await this.saveImageToLocal(url)
221
+          savedImages.push(url)
222
+        } catch (error) {
223
+          console.error(`保存图片失败: ${url}`, error)
224
+          failedImages.push(url)
225
+        }
226
+
227
+        uni.showLoading({
228
+          title: `正在保存图片... (${i + 1}/${urls.length})`,
229
+          mask: true
230
+        })
231
+      }
232
+
233
+      uni.hideLoading()
234
+
235
+      let message = `成功保存 ${savedImages.length} 张图片`
236
+      if (failedImages.length > 0) {
237
+        message += `,${failedImages.length} 张保存失败`
238
+      }
239
+
240
+      uni.showToast({
241
+        title: message,
242
+        icon: 'none',
243
+        duration: 3000
244
+      })
245
+    } catch (error) {
246
+      uni.hideLoading()
247
+      console.error('保存图片过程中发生错误:', error)
248
+      uni.showToast({
249
+        title: '保存图片失败',
250
+        icon: 'error'
251
+      })
252
+    }
253
+  },
254
+
255
+  /**
256
+   * 复制图片链接
257
+   * @param {Array<String>} urls - 图片URL数组
258
+   */
259
+  copyImageUrls(urls) {
260
+    uni.setClipboardData({
261
+      data: JSON.stringify(urls),
262
+      success: () => {
263
+        uni.showToast({
264
+          title: '图片链接已复制',
265
+          icon: 'none'
266
+        })
267
+      }
268
+    })
269
+  }
270
+}

+ 2 - 5
pages/pagereceivecenter/pagereceivecenter.vue

@@ -44,9 +44,6 @@ export default {
44 44
         this.getStatisticsSendStatus();
45 45
 
46 46
         this.countdownInterval()
47
-        // uni.navigateTo({
48
-        //     url: `/pages/orderDetailNew/index?orderId=5464&item=测试发单&type=undefined&clueId=1973381744953516033`,
49
-        // })
50 47
     },
51 48
     onUnload() {
52 49
         clearInterval(this.countdownInterval);
@@ -143,7 +140,7 @@ export default {
143 140
                                 id: order.id,
144 141
                             });
145 142
                             uni.navigateTo({
146
-                                url: `/pages/orderDetailNew/index?orderId=${order.id}&item=${order.item}&type=${this.type}&clueId=${order.clueId}`,
143
+                                url: `/pages/orderDetailRefactored/index?orderId=${order.id}&item=${order.item}&type=${this.type}&clueId=${order.clueId}`,
147 144
                             })
148 145
                         } else if (res.cancel) {
149 146
                             // 用户点击了取消,不执行任何操作
@@ -194,7 +191,7 @@ export default {
194 191
             //点卡片看详情
195 192
             // if (order.status == '1' || order.status == '2') {
196 193
             uni.navigateTo({
197
-                url: `/pages/orderDetailNew/index?orderId=${order.id}&item=${order.item}&type=${this.type}&clueId=${order.clueId}`,
194
+                url: `/pages/orderDetailRefactored/index?orderId=${order.id}&item=${order.item}&type=${this.type}&clueId=${order.clueId}`,
198 195
             })
199 196
             // } else {
200 197
             //     uni.$u.toast('当前订单无法查看详情');