PageThree.vue 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198
  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 v-model="paymentInfo.customName" placeholder="请输入开户人姓名" class="info-input"
  10. @blur="handleCustomNameInput" />
  11. </u-col>
  12. <u-col span="5.8">
  13. <view class="info-label">银行名称</view>
  14. <u-input v-model="paymentInfo.bankName" placeholder="请输入银行名称" class="info-input"
  15. :disabled="isAlipayPayment" @blur="handleBankNameInput" />
  16. </u-col>
  17. </u-row>
  18. <u-row class="info-row">
  19. <u-col span="12">
  20. <view class="info-label">{{ bankAccountLabel }}</view>
  21. <u-input v-model="paymentInfo.bankAccount" :placeholder="bankAccountPlaceholder" class="info-input"
  22. :type="isAlipayPayment ? 'text' : 'number'" @input="handleBankAccountInput" @blur="handleBankAccountBlur" />
  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 v-model="paymentInfo.idNumber" placeholder="请输入身份证号" class="info-input"
  29. @blur="handleIdNumberInput" />
  30. </u-col>
  31. </u-row>
  32. <u-row class="info-row">
  33. <u-col span="12">
  34. <view class="info-label">支付方式选择</view>
  35. <u-radio-group iconPlacement="left" v-model="paymentMethodRadio" placement="column"
  36. @change="handlePaymentMethodRadioChange">
  37. <u-radio shape="circle" label="小葫芦线上支付" name="online" :customStyle="{marginBottom: '16rpx'}"/>
  38. <u-radio shape="circle" label="小葫芦线上支付宝" name="online_alipay" :customStyle="{marginBottom: '16rpx'}" />
  39. <u-radio shape="circle" label="线下支付" name="offline" :customStyle="{marginBottom: '16rpx'}"/>
  40. </u-radio-group>
  41. </u-col>
  42. </u-row>
  43. <u-row class="info-row">
  44. <u-col span="12">
  45. <view class="info-label">支付方式</view>
  46. <u-input :disabled="paymentMethodRadio === 'online'||paymentMethodRadio === 'online_alipay'" v-model="paymentMethod" placeholder="请输入支付方式"
  47. class="info-input" />
  48. </u-col>
  49. </u-row>
  50. </view>
  51. <!-- 核价信息卡片 -->
  52. <view v-if="hasInquiryInfo" class="info-card">
  53. <view class="info-card-title">核价信息</view>
  54. <u-row class="info-row">
  55. <u-col span="6">
  56. <view class="info-label">核价次数</view>
  57. <view class="info-value">{{ currentReceipt.inquiryCount || 0 }}</view>
  58. </u-col>
  59. <u-col span="6">
  60. <view class="info-label">核价价格</view>
  61. <view class="info-value">¥{{ currentReceipt.inquiryPrice || 0 }}</view>
  62. </u-col>
  63. </u-row>
  64. </view>
  65. <!-- 高清实物图卡片 -->
  66. <view class="card-wrap">
  67. <view class="detail-image-section">
  68. <view class="detail-image-header">
  69. <text class="detail-image-title">高清实物图(拖拽排序)</text>
  70. <u-button type="primary" shape="circle" plain text="核价" size="small" class="pricing-btn" @click="handlePricing"></u-button>
  71. </view>
  72. <view class="detail-image-content">
  73. <view class="detail-image-list">
  74. <view v-for="(item, index) in displayImages" :key="item.id || `detail-${index}`" class="detail-image-item"
  75. :class="{
  76. 'dragging': draggingIndex === index,
  77. 'can-drop': canDropIndex === index && draggingIndex !== index
  78. }" :style="draggingIndex === index ? draggingStyle : ''" @touchstart.stop="onTouchStart($event, index)"
  79. @touchmove.stop="onTouchMove($event, index)" @touchend.stop="onTouchEnd">
  80. <PicComp :src="item.fileUrl" @needPreviewPic="previewImageDetail" />
  81. <view class="image-type-tag">{{ getImageType(index) }}</view>
  82. <view class="detail-delete-btn" @click.stop="handleHideImage(item, index)">
  83. ×
  84. </view>
  85. </view>
  86. <view class="detail-upload-btn" @click="handleUploadImage">
  87. <u-icon name="plus" size="40rpx" color="#999" />
  88. </view>
  89. </view>
  90. </view>
  91. </view>
  92. </view>
  93. <!-- 支付总额卡片 -->
  94. <view class="card-wrap payment-card">
  95. <view class="payment-section">
  96. <view class="payment-total-container">
  97. <text class="payment-label">支付总额</text>
  98. <view class="payment-amount-wrap">
  99. <u-input v-model="paymentAmount" class="payment-amount" type="number" decimal="2" prefix="¥" />
  100. </view>
  101. <view class="pay-now-btn-wrap">
  102. <u-button type="primary" shape="circle" plain text="立即支付" size="small" @click="handlePayNowClick"></u-button>
  103. </view>
  104. </view>
  105. <view class="payment-buttons-row">
  106. <view class="payment-button" @click="handleUnpaidClick">
  107. <u-icon name="star" size="40rpx" color="#ff9500" />
  108. <text class="button-text">未收</text>
  109. </view>
  110. <view class="payment-button" @click="handleFollowUpClick">
  111. <u-icon name="chat" size="40rpx" color="#108cff" />
  112. <text class="button-text">待跟进</text>
  113. </view>
  114. </view>
  115. </view>
  116. </view>
  117. <!-- 下一步按钮 -->
  118. <!-- <u-button class="next-btn" @click="handleNext" type="primary" size="middle">
  119. 下一步
  120. </u-button> -->
  121. <!-- 未收评级模态窗 -->
  122. <u-modal showCancelButton showConfirmButton @confirm="confirmUnpaid" @cancel="onUnpaidModalCancel"
  123. :show="unpaidModalVisible" title="未收评级">
  124. <view class="modal-content">
  125. <u-rate v-model="unpaidRating" :count="5" :size="50" active-color="#ff9500" />
  126. <view class="unpaid-benefit-fee-wrap">
  127. <view class="unpaid-benefit-fee-label">好处费</view>
  128. <u-input v-model="unpaidBenefitFee" class="unpaid-benefit-fee-input" type="number" :decimal="2"
  129. placeholder="请输入好处费(金额)" @input="handleUnpaidBenefitFeeInput" />
  130. </view>
  131. </view>
  132. </u-modal>
  133. <!-- 待跟进模态窗 -->
  134. <u-modal showCancelButton showConfirmButton @confirm="confirmFollowUp" @cancel="followUpModalVisible = false"
  135. :show="followUpModalVisible" title="填写跟进细节">
  136. <view class="modal-content">
  137. <u--textarea v-model="followUpNotes" placeholder="请输入情况" confirm-type="done"
  138. style="width: 100%; margin-bottom: 30rpx;" />
  139. </view>
  140. </u-modal>
  141. <!-- 确认支付模态窗 -->
  142. <u-modal showCancelButton showConfirmButton @confirm="confirmRiskWarning" @cancel="riskWarningModalVisible = false"
  143. confirmText="确认并继续付款" :show="riskWarningModalVisible" title="付款警示" :content="'湖南耒阳地区身份证号请确认!'">
  144. </u-modal>
  145. <!-- 核价对话框 -->
  146. <add-inquiry-dialog ref="pricingDialog" :clueId="pricingClueId" :editOrAdd="pricingEditOrAdd"
  147. :editInfo="pricingEditInfo" :type="2" />
  148. <u-modal :show="payNowModalVisible" title="确认支付信息" :showConfirmButton="false">
  149. <view class="modal-content">
  150. <view class="payment-amount-display">¥{{ paymentAmount }}</view>
  151. <view class="payment-info-section">
  152. <view class="info-item">
  153. <text class="info-label">收款姓名:</text>
  154. <text class="info-value">{{ paymentInfo.customName || '未填写' }}</text>
  155. </view>
  156. <view class="info-item">
  157. <text class="info-label">开户银行:</text>
  158. <text class="info-value">{{ paymentInfo.bankName || '未填写' }}</text>
  159. </view>
  160. <view class="info-item">
  161. <text class="info-label">银行卡号:</text>
  162. <text class="info-value">{{ paymentInfo.bankAccount || '未填写' }}</text>
  163. </view>
  164. <view class="info-item">
  165. <text class="info-label">支付方式:</text>
  166. <text class="info-value">{{ paymentMethod || '未填写' }}</text>
  167. </view>
  168. </view>
  169. <u-button type="primary" size="large" @click="confirmTransfer" style="margin-top: 40rpx;">
  170. 确认转账
  171. </u-button>
  172. <u-button type="primary" :plain="true" @click="payNowModalVisible = false" size="large"
  173. style="margin-top: 20rpx;">
  174. 取消
  175. </u-button>
  176. </view>
  177. </u-modal>
  178. </view>
  179. </template>
  180. <script>
  181. import PicComp from './PicComp.vue'
  182. import imageUpload from '../utils/imageUpload.js'
  183. import addInquiryDialog from '@/components/add-inquiry-dialog/index.vue'
  184. export default {
  185. name: 'PageThree',
  186. components: {
  187. PicComp,
  188. addInquiryDialog
  189. },
  190. props: {
  191. orderDetail: {
  192. type: Object,
  193. default: () => ({})
  194. },
  195. orderId: {
  196. type: String,
  197. default: ''
  198. },
  199. currentReceipt: {
  200. type: Object,
  201. default: () => ({})
  202. }
  203. },
  204. data() {
  205. return {
  206. paymentInfo: {
  207. customName: '',
  208. bankName: '',
  209. bankAccount: '',
  210. idNumber: ''
  211. },
  212. paymentMethodRadio: 'offline',
  213. paymentMethod: '',
  214. paymentAmount: '0.00',
  215. detailImages: [],
  216. // 拖拽相关
  217. draggingIndex: -1,
  218. canDropIndex: -1,
  219. startX: 0,
  220. startY: 0,
  221. currentX: 0,
  222. currentY: 0,
  223. // 模态窗
  224. unpaidModalVisible: false,
  225. followUpModalVisible: false,
  226. payNowModalVisible: false,
  227. riskWarningModalVisible: false,
  228. unpaidRating: 0,
  229. unpaidBenefitFee: '',
  230. followUpNotes: '',
  231. // 核价相关
  232. pricingClueId: '',
  233. pricingEditOrAdd: 'receptFormAdd',
  234. pricingEditInfo: {}
  235. }
  236. },
  237. computed: {
  238. // 显示前6个图片用于拖拽
  239. displayImages() {
  240. return this.detailImages.slice(0, 6)
  241. },
  242. // 拖拽时的样式
  243. draggingStyle() {
  244. if (this.draggingIndex === -1) return ''
  245. return {
  246. transform: `translate(${this.currentX - this.startX}px, ${this.currentY - this.startY}px)`,
  247. zIndex: 1000
  248. }
  249. },
  250. // 是否为支付宝支付
  251. isAlipayPayment() {
  252. return this.paymentMethodRadio === 'online_alipay'
  253. },
  254. // 银行账号标签
  255. bankAccountLabel() {
  256. return this.isAlipayPayment ? '支付宝账号' : '银行账号'
  257. },
  258. // 银行账号占位符
  259. bankAccountPlaceholder() {
  260. return this.isAlipayPayment ? '请输入支付宝账号' : '请输入银行账号'
  261. },
  262. // 是否有核价信息
  263. hasInquiryInfo() {
  264. return this.currentReceipt && (this.currentReceipt.inquiryCount || this.currentReceipt.inquiryPrice)
  265. }
  266. },
  267. watch: {
  268. // 支付信息已保存到 receiptForm,不再从 orderDetail(sendForm) 读取
  269. currentReceipt: {
  270. handler(newVal, oldVal) {
  271. if (newVal) {
  272. this.paymentAmount = newVal.tableFee || '0.00'
  273. // 从回单表 receiptForm 回填支付信息(与 PC 端一致)
  274. this.paymentInfo.customName = newVal.customName || ''
  275. this.paymentInfo.bankName = newVal.bankName || ''
  276. this.paymentInfo.bankAccount = newVal.bankCardNumber || ''
  277. this.paymentInfo.idNumber = newVal.idCard || ''
  278. this.initPaymentMethod(newVal.paymentMethod)
  279. // 只有当 receipt id 变化时才重新加载图片,避免 fileIds 更新时重新加载
  280. if (!oldVal || oldVal.id !== newVal.id) {
  281. this.loadDetailImages()
  282. }
  283. }
  284. },
  285. deep: true,
  286. immediate: true
  287. }
  288. },
  289. methods: {
  290. /**
  291. * 刷新图片列表(供父组件调用)
  292. */
  293. async refreshImageList() {
  294. await this.loadDetailImages()
  295. },
  296. /**
  297. * 加载细节图
  298. */
  299. async loadDetailImages() {
  300. if (!this.currentReceipt.id || !this.orderDetail.itemBrand) return
  301. try {
  302. const list = await imageUpload.getFileList(
  303. '2',
  304. '3',
  305. this.currentReceipt.id,
  306. this.orderDetail.itemBrand,
  307. this.currentReceipt.clueId
  308. )
  309. // 按照 fileIds 排序
  310. if (this.currentReceipt.fileIds && list && list.length > 0) {
  311. const sortedIds = this.currentReceipt.fileIds.split(',')
  312. list.sort((a, b) => {
  313. const indexA = sortedIds.indexOf(a.id)
  314. const indexB = sortedIds.indexOf(b.id)
  315. // 如果都不在列表中,保持原顺序
  316. if (indexA === -1 && indexB === -1) return 0
  317. // 如果 a 不在列表中,放到后面
  318. if (indexA === -1) return 1
  319. // 如果 b 不在列表中,放到后面
  320. if (indexB === -1) return -1
  321. // 都在列表中,按 index 排序
  322. return indexA - indexB
  323. })
  324. }
  325. this.detailImages = list || []
  326. } catch (error) {
  327. console.error('加载细节图失败:', error)
  328. }
  329. },
  330. /**
  331. * 初始化支付方式
  332. */
  333. initPaymentMethod(value) {
  334. if (value === '小葫芦线上支付') {
  335. this.paymentMethod = '小葫芦线上支付'
  336. this.paymentMethodRadio = 'online'
  337. } else if (value === '小葫芦线上支付宝') {
  338. this.paymentMethod = '小葫芦线上支付宝'
  339. this.paymentMethodRadio = 'online_alipay'
  340. this.paymentInfo.bankName = '支付宝'
  341. } else {
  342. this.paymentMethod = value || ''
  343. this.paymentMethodRadio = 'offline'
  344. }
  345. },
  346. /**
  347. * 支付方式选择改变
  348. */
  349. handlePaymentMethodRadioChange(value) {
  350. if (value === 'online') {
  351. this.paymentMethod = '小葫芦线上支付'
  352. // 恢复银行名称输入框
  353. if (this.paymentInfo.bankName === '支付宝') {
  354. this.paymentInfo.bankName = ''
  355. }
  356. } else if (value === 'online_alipay') {
  357. this.paymentMethod = '小葫芦线上支付宝'
  358. // 自动设置银行名称为支付宝
  359. this.paymentInfo.bankName = '支付宝'
  360. } else {
  361. this.paymentMethod = ''
  362. // 恢复银行名称输入框
  363. if (this.paymentInfo.bankName === '支付宝') {
  364. this.paymentInfo.bankName = ''
  365. }
  366. }
  367. },
  368. /**
  369. * 失焦后保存支付信息(开户人/银行名称/账号/身份证)
  370. */
  371. async savePaymentInfoOnBlur() {
  372. if (!this.currentReceipt || !this.currentReceipt.id) return
  373. try {
  374. await uni.$u.api.updateReceiptForm({
  375. id: this.currentReceipt.id,
  376. sendFormId: this.currentReceipt.sendFormId || this.orderDetail.id,
  377. customName: this.paymentInfo.customName || '',
  378. bankName: this.paymentInfo.bankName || '',
  379. bankCardNumber: this.paymentInfo.bankAccount || '',
  380. idCard: this.paymentInfo.idNumber || ''
  381. })
  382. } catch (e) {
  383. console.error('保存支付信息失败:', e)
  384. }
  385. },
  386. /**
  387. * 开户人输入处理 - 只允许中文,失焦保存
  388. */
  389. handleCustomNameInput(value) {
  390. const chineseReg = /[^\u4e00-\u9fa5]/g
  391. this.paymentInfo.customName = String(value ?? this.paymentInfo.customName ?? '').replace(chineseReg, '')
  392. this.savePaymentInfoOnBlur()
  393. },
  394. /**
  395. * 银行名称输入处理 - 只允许中文,失焦保存
  396. */
  397. handleBankNameInput(value) {
  398. const chineseReg = /[^\u4e00-\u9fa5]/g
  399. this.paymentInfo.bankName = String(value ?? this.paymentInfo.bankName ?? '').replace(chineseReg, '')
  400. this.savePaymentInfoOnBlur()
  401. },
  402. /**
  403. * 银行账号输入处理 - 只允许数字
  404. */
  405. handleBankAccountInput(value) {
  406. const numberReg = /[^\d]/g
  407. this.paymentInfo.bankAccount = String(value || '').replace(numberReg, '')
  408. },
  409. /**
  410. * 银行账号失焦保存
  411. */
  412. handleBankAccountBlur() {
  413. this.savePaymentInfoOnBlur()
  414. },
  415. /**
  416. * 身份证号输入处理 - 允许数字和X,失焦保存
  417. */
  418. handleIdNumberInput(value) {
  419. const reg = /[^\dxX]/g
  420. this.paymentInfo.idNumber = String(value ?? this.paymentInfo.idNumber ?? '').replace(reg, '')
  421. this.savePaymentInfoOnBlur()
  422. },
  423. /**
  424. * 获取图片类型
  425. */
  426. getImageType(index) {
  427. const types = ['正面', '反面', '侧面', '扣子', '编号']
  428. return types[index] || `细节${index - 4}`
  429. },
  430. /**
  431. * 触摸开始
  432. */
  433. onTouchStart(event, index) {
  434. if (this.draggingIndex !== -1) return
  435. this.draggingIndex = index
  436. const touch = event.touches[0]
  437. this.startX = touch.clientX
  438. this.startY = touch.clientY
  439. this.currentX = touch.clientX
  440. this.currentY = touch.clientY
  441. },
  442. /**
  443. * 触摸移动
  444. */
  445. onTouchMove(event, index) {
  446. if (this.draggingIndex === -1 || this.draggingIndex !== index) return
  447. const touch = event.touches[0]
  448. this.currentX = touch.clientX
  449. this.currentY = touch.clientY
  450. this.findTargetIndex(touch.clientX, touch.clientY)
  451. },
  452. /**
  453. * 触摸结束
  454. */
  455. async onTouchEnd() {
  456. if (this.draggingIndex === -1) return
  457. if (this.canDropIndex !== -1 && this.canDropIndex !== this.draggingIndex) {
  458. // 交换位置
  459. const images = [...this.detailImages]
  460. const temp = images[this.draggingIndex]
  461. images[this.draggingIndex] = images[this.canDropIndex]
  462. images[this.canDropIndex] = temp
  463. this.detailImages = images
  464. }
  465. this.resetDragState()
  466. //每次拖拽结束后把新的顺序传送给接口
  467. const fileIds = this.detailImages.map(item => item.id).join(',')
  468. await uni.$u.api.updateReceiptForm({
  469. id: this.currentReceipt.id,
  470. fileIds: fileIds
  471. })
  472. this.$emit('update-file-ids', fileIds)
  473. },
  474. /**
  475. * 查找目标索引
  476. */
  477. findTargetIndex(x, y) {
  478. const query = uni.createSelectorQuery().in(this)
  479. query.selectAll('.detail-image-item').boundingClientRect((rects) => {
  480. if (!rects || rects.length === 0) return
  481. let targetIndex = -1
  482. for (let i = 0; i < rects.length; i++) {
  483. if (i === this.draggingIndex) continue
  484. const rect = rects[i]
  485. if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
  486. targetIndex = i
  487. break
  488. }
  489. }
  490. this.canDropIndex = targetIndex
  491. }).exec()
  492. },
  493. /**
  494. * 重置拖拽状态
  495. */
  496. resetDragState() {
  497. this.draggingIndex = -1
  498. this.canDropIndex = -1
  499. this.startX = 0
  500. this.startY = 0
  501. this.currentX = 0
  502. this.currentY = 0
  503. },
  504. /**
  505. * 上传图片
  506. */
  507. async handleUploadImage() {
  508. try {
  509. const filePaths = await imageUpload.chooseImage(9)
  510. const uploadResults = await imageUpload.uploadFiles(filePaths)
  511. await imageUpload.bindOrderFile(
  512. this.currentReceipt.clueId,
  513. this.currentReceipt.id,
  514. '3',
  515. uploadResults
  516. )
  517. await this.loadDetailImages()
  518. //上传新图片完成之后也要把新数据给接口
  519. const fileIds = this.detailImages.map(item => item.id).join(',')
  520. await uni.$u.api.updateReceiptForm({
  521. id: this.currentReceipt.id,
  522. fileIds: fileIds
  523. })
  524. this.$emit('update-file-ids', fileIds)
  525. } catch (error) {
  526. console.error('上传失败:', error)
  527. }
  528. },
  529. /**
  530. * 隐藏图片
  531. */
  532. async handleHideImage(item, index) {
  533. const itemIndex = this.detailImages.findIndex(img => img.id === item.id || img.fileUrl === item.fileUrl)
  534. if (itemIndex !== -1) {
  535. this.detailImages.splice(itemIndex, 1)
  536. // 更新 fileIds
  537. const fileIds = this.detailImages.map(item => item.id).join(',')
  538. try {
  539. await uni.$u.api.updateReceiptForm({
  540. id: this.currentReceipt.id,
  541. fileIds: fileIds
  542. })
  543. this.$emit('update-file-ids', fileIds)
  544. uni.$u.toast('图片已隐藏')
  545. } catch (error) {
  546. console.error('更新失败:', error)
  547. }
  548. }
  549. },
  550. /**
  551. * 预览图片
  552. */
  553. previewImageDetail(src) {
  554. const urlList = this.detailImages.map(item => item.fileUrl)
  555. uni.previewImage({
  556. urls: urlList,
  557. current: src
  558. })
  559. },
  560. /**
  561. * 未收点击
  562. */
  563. handleUnpaidClick() {
  564. this.unpaidBenefitFee = ''
  565. this.unpaidModalVisible = true
  566. },
  567. /**
  568. * 未收弹窗取消 - 重置好处费
  569. */
  570. onUnpaidModalCancel() {
  571. this.unpaidModalVisible = false
  572. this.unpaidBenefitFee = ''
  573. },
  574. /**
  575. * 好处费输入 - 限制仅可输入金额(数字、最多两位小数)
  576. */
  577. handleUnpaidBenefitFeeInput(e) {
  578. const value = (e && e.detail && e.detail.value !== undefined) ? e.detail.value : e
  579. let val = String(value ?? '').trim()
  580. if (!val) {
  581. this.unpaidBenefitFee = ''
  582. return
  583. }
  584. // 只保留数字和第一个小数点,且小数点后最多两位
  585. const parts = val.replace(/[^\d.]/g, '').split('.')
  586. if (parts.length > 2) {
  587. val = parts[0] + '.' + parts.slice(1).join('')
  588. }
  589. if (parts.length === 2 && parts[1].length > 2) {
  590. val = parts[0] + '.' + parts[1].slice(0, 2)
  591. }
  592. this.unpaidBenefitFee = val
  593. },
  594. /**
  595. * 待跟进点击
  596. */
  597. handleFollowUpClick() {
  598. this.followUpModalVisible = true
  599. },
  600. /**
  601. * 立即支付点击
  602. */
  603. async handlePayNowClick() {
  604. // 与 PC 端一致:先做必填校验
  605. if (this.paymentAmount <= 0) {
  606. uni.$u.toast('请填写正确的支付总额')
  607. return
  608. }
  609. // 支付方式必填(与 PC 一致)
  610. if (!this.paymentMethod) {
  611. uni.$u.toast('请填写支付方式')
  612. return
  613. }
  614. // 仅线上支付时校验开户人/银行/身份证;线下支付不校验(与 PC 一致)
  615. if (this.paymentMethodRadio === 'online' || this.paymentMethodRadio === 'online_alipay') {
  616. if (!this.paymentInfo.customName || !this.paymentInfo.bankName ||
  617. !this.paymentInfo.bankAccount || !this.paymentInfo.idNumber) {
  618. uni.$u.toast('请填写完整的开户人信息、身份证号、银行名称、银行卡号')
  619. return
  620. }
  621. }
  622. // 保存支付信息到回单表(后端需 sendFormId 才能更新发单表的开户人/银行/身份证等)
  623. await uni.$u.api.updateReceiptForm({
  624. id: this.currentReceipt.id,
  625. sendFormId: this.currentReceipt.sendFormId || this.orderDetail.id,
  626. tableFee: this.paymentAmount,
  627. fileIds: this.detailImages.map(item => item.id).join(','),
  628. customName: this.paymentInfo.customName || '',
  629. bankName: this.paymentInfo.bankName || '',
  630. bankCardNumber: this.paymentInfo.bankAccount || '',
  631. idCard: this.paymentInfo.idNumber || ''
  632. })
  633. // 湖南耒阳身份证号确认(仅当已填写身份证时校验,与 PC 一致)
  634. if (this.paymentInfo.idNumber && String(this.paymentInfo.idNumber).startsWith('430481')) {
  635. this.riskWarningModalVisible = true
  636. return
  637. }
  638. // 线下支付:不弹确认框,直接更新支付方式并进入下一步(后端需 sendFormId 更新发单表支付方式)
  639. if (this.paymentMethodRadio === 'offline') {
  640. await uni.$u.api.updateReceiptForm({
  641. id: this.currentReceipt.id,
  642. sendFormId: this.currentReceipt.sendFormId || this.orderDetail.id,
  643. paymentMethod: this.paymentMethod || ''
  644. })
  645. this.handleNext()
  646. return
  647. }
  648. this.payNowModalVisible = true
  649. },
  650. /**
  651. * 确认风险警示
  652. */
  653. async confirmRiskWarning() {
  654. this.riskWarningModalVisible = false
  655. // 线下支付:不弹确认框,直接更新支付方式并进入下一步(后端需 sendFormId 更新发单表支付方式)
  656. if (this.paymentMethodRadio === 'offline') {
  657. await uni.$u.api.updateReceiptForm({
  658. id: this.currentReceipt.id,
  659. sendFormId: this.currentReceipt.sendFormId || this.orderDetail.id,
  660. paymentMethod: this.paymentMethod || ''
  661. })
  662. this.handleNext()
  663. return
  664. }
  665. this.payNowModalVisible = true
  666. },
  667. /**
  668. * 确认转账
  669. */
  670. async confirmTransfer() {
  671. this.payNowModalVisible = false
  672. // 支付方式保存(后端需 sendFormId 更新发单表)
  673. await uni.$u.api.updateReceiptForm({
  674. id: this.currentReceipt.id,
  675. sendFormId: this.currentReceipt.sendFormId || this.orderDetail.id,
  676. paymentMethod: this.paymentMethod || ''
  677. })
  678. // 根据 radio 选择判断:线上支付(online / online_alipay)需调用支付接口,线下则直接下一步
  679. if (this.paymentMethodRadio === 'online' || this.paymentMethodRadio === 'online_alipay') {
  680. this.$emit('confirm-pay')
  681. } else {
  682. this.handleNext()
  683. }
  684. },
  685. /**
  686. * 确认未收
  687. */
  688. async confirmUnpaid() {
  689. try {
  690. // 若填写了好处费,先更新回单表
  691. if (this.currentReceipt?.id && (this.unpaidBenefitFee !== '' && this.unpaidBenefitFee != null)) {
  692. await uni.$u.api.updateReceiptForm({
  693. id: this.currentReceipt.id,
  694. benefitFee: this.unpaidBenefitFee
  695. })
  696. }
  697. await uni.$u.api.addOrderFollow({
  698. orderId: this.orderId,
  699. content: `未收评分_${this.unpaidRating}`
  700. })
  701. await uni.$u.api.oderForm({
  702. status: '4',
  703. id: this.orderId
  704. })
  705. uni.$u.toast('提交未收评级成功')
  706. this.unpaidModalVisible = false
  707. this.unpaidBenefitFee = ''
  708. // 刷新当前收单数据,以便第四页好处费等字段同步
  709. this.$emit('price-updated')
  710. } catch (error) {
  711. console.error('提交失败:', error)
  712. uni.$u.toast('提交失败')
  713. }
  714. },
  715. /**
  716. * 确认跟进
  717. */
  718. async confirmFollowUp() {
  719. try {
  720. await uni.$u.api.addOrderFollow({
  721. orderId: this.orderId,
  722. content: `待跟进_${this.followUpNotes}`
  723. })
  724. uni.$u.toast('提交待跟进记录成功')
  725. this.followUpModalVisible = false
  726. this.followUpNotes = ''
  727. } catch (error) {
  728. console.error('提交失败:', error)
  729. uni.$u.toast('提交失败')
  730. }
  731. },
  732. /**
  733. * 下一步
  734. */
  735. async handleNext() {
  736. // 与 PC 端一致:支付信息通过 updateReceiptForm 保存;后端需 sendFormId 才能更新发单表的开户人/银行/身份证等
  737. await uni.$u.api.updateReceiptForm({
  738. id: this.currentReceipt.id,
  739. sendFormId: this.currentReceipt.sendFormId || this.orderDetail.id,
  740. tableFee: this.paymentAmount,
  741. fileIds: this.detailImages.map(item => item.id).join(','),
  742. customName: this.paymentInfo.customName || '',
  743. bankName: this.paymentInfo.bankName || '',
  744. bankCardNumber: this.paymentInfo.bankAccount || '',
  745. idCard: this.paymentInfo.idNumber || '',
  746. // paymentMethod: this.paymentMethod || ''
  747. })
  748. this.$emit('save', {
  749. nowPage: 'formThree',
  750. form: {
  751. ...this.paymentInfo
  752. },
  753. fileIds: this.detailImages.map(item => item.id).join(',')
  754. })
  755. this.$emit('next', {
  756. nowPage: 'formThree',
  757. form: {
  758. ...this.paymentInfo
  759. }
  760. })
  761. },
  762. /**
  763. * 核价
  764. */
  765. async handlePricing() {
  766. const brandList = await this.$getDicts("crm_form_brand");
  767. if (!this.currentReceipt || !this.currentReceipt.clueId) {
  768. uni.$u.toast('缺少线索ID')
  769. return
  770. }
  771. if (!this.currentReceipt.brand) {
  772. uni.$u.toast('缺少品牌信息')
  773. return
  774. }
  775. if (!this.displayImages || this.displayImages.length === 0) {
  776. uni.$u.toast('请先上传图片')
  777. return
  778. }
  779. //我这里询价要用receiptid来询价
  780. this.pricingClueId = this.currentReceipt.id
  781. // brand 是品牌的 id(dictValue),itemBrand 是品牌的 label(dictLabel)
  782. const brandDictLabel = this.currentReceipt.brand
  783. const brandDictValue = brandList.find(item => item.dictLabel === this.currentReceipt.brand).dictValue;
  784. // 将图片转换为 imgsUrl 格式
  785. const imgsUrl = this.displayImages.map(item => item.fileUrl).filter(url => url)
  786. // 检查是否已有核价记录
  787. try {
  788. const data = {
  789. clueId: this.currentReceipt.clueId,
  790. type: 2
  791. }
  792. const res = await uni.$u.api.inquiryDetail(data)
  793. if (res.code === 200 && res.data) {
  794. // 编辑模式:保留原有数据,但更新品牌和图片
  795. this.pricingEditOrAdd = 'editForm'
  796. this.pricingEditInfo = {
  797. ...res.data,
  798. dictLabel: brandDictLabel || res.data.dictLabel,
  799. dictValue: brandDictValue || res.data.dictValue,
  800. imgsUrl: imgsUrl.length > 0 ? imgsUrl : (res.data.imgsUrl || [])
  801. }
  802. this.$nextTick(() => {
  803. this.$refs.pricingDialog.showDialog()
  804. })
  805. } else {
  806. // 新增模式:设置品牌和图片
  807. this.pricingEditOrAdd = 'editForm'
  808. this.pricingEditInfo = {
  809. dictLabel: brandDictLabel,
  810. dictValue: brandDictValue,
  811. imgsUrl: imgsUrl,
  812. model: this.currentReceipt.model,
  813. code: '',
  814. price: ''
  815. }
  816. this.$nextTick(() => {
  817. this.$refs.pricingDialog.showDialog()
  818. })
  819. }
  820. } catch (error) {
  821. // 如果没有记录,则新增
  822. this.pricingEditOrAdd = 'receptFormAdd'
  823. this.pricingEditInfo = {
  824. dictLabel: brandDictLabel,
  825. dictValue: brandDictValue,
  826. imgsUrl: imgsUrl,
  827. model: this.currentReceipt.model,
  828. code: '',
  829. price: ''
  830. }
  831. this.$nextTick(() => {
  832. this.$refs.pricingDialog.showDialog()
  833. })
  834. }
  835. },
  836. }
  837. }
  838. </script>
  839. <style scoped lang="scss">
  840. @import '../styles/common.scss';
  841. .page-three-container {
  842. @extend .page-container;
  843. padding-bottom: 100rpx;
  844. }
  845. .info-card {
  846. @extend .card-wrap;
  847. padding: 20rpx;
  848. margin-top: 20rpx;
  849. box-sizing: border-box;
  850. width: 100%;
  851. max-width: 100%;
  852. }
  853. .info-card-title {
  854. @include font-styles($size: title, $weight: bold, $color: primary);
  855. margin-bottom: 25rpx;
  856. padding-bottom: 15rpx;
  857. border-bottom: 1rpx solid map-get($colors, border);
  858. }
  859. .info-row {
  860. margin-bottom: 20rpx;
  861. }
  862. .info-label {
  863. @include font-styles($size: tiny, $weight: regular, $color: tertiary);
  864. margin-bottom: 8rpx;
  865. display: block;
  866. }
  867. .info-input {
  868. border-radius: 8rpx;
  869. border: 1rpx solid #e5e7eb;
  870. padding: 12rpx 16rpx;
  871. width: 100%;
  872. box-sizing: border-box;
  873. }
  874. .detail-image-section {
  875. padding: 20rpx;
  876. }
  877. .detail-image-header {
  878. display: grid;
  879. grid-template-columns: 1fr auto;
  880. margin-bottom: 20rpx;
  881. padding-bottom: 20rpx;
  882. padding-right: 100rpx; /* 右侧留空,避免被固定页面导航遮挡,使整行(含核价按钮)左移 */
  883. border-bottom: 1rpx solid map-get($colors, border);
  884. }
  885. .detail-image-title {
  886. @include font-styles($size: content, $weight: bold, $color: primary);
  887. }
  888. .detail-image-list {
  889. display: flex;
  890. flex-wrap: wrap;
  891. gap: 20rpx;
  892. position: relative;
  893. }
  894. .detail-image-item {
  895. position: relative;
  896. width: 200rpx;
  897. height: 200rpx;
  898. touch-action: none;
  899. transition: transform 0.2s ease, opacity 0.2s ease;
  900. z-index: 1;
  901. box-sizing: border-box;
  902. &.dragging {
  903. opacity: 0.6;
  904. transform: scale(1.05);
  905. z-index: 999;
  906. box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.2);
  907. }
  908. &.can-drop {
  909. outline: 2rpx dashed #108cff;
  910. outline-offset: -2rpx;
  911. background-color: rgba(16, 140, 255, 0.1);
  912. border-radius: 8rpx;
  913. }
  914. }
  915. .image-type-tag {
  916. position: absolute;
  917. top: 10rpx;
  918. left: 10rpx;
  919. background-color: rgba(0, 0, 0, 0.6);
  920. color: white;
  921. padding: 5rpx 10rpx;
  922. border-radius: 12rpx;
  923. font-size: 22rpx;
  924. z-index: 1;
  925. }
  926. .detail-delete-btn {
  927. position: absolute;
  928. top: -10rpx;
  929. right: -10rpx;
  930. width: 40rpx;
  931. height: 40rpx;
  932. background-color: #ff4d4f;
  933. color: #fff;
  934. border-radius: 50%;
  935. display: flex;
  936. align-items: center;
  937. justify-content: center;
  938. font-weight: bold;
  939. z-index: 10;
  940. cursor: pointer;
  941. }
  942. .detail-upload-btn {
  943. width: 200rpx;
  944. height: 200rpx;
  945. border: 8rpx dashed #ddd;
  946. border-radius: 30rpx;
  947. display: flex;
  948. align-items: center;
  949. justify-content: center;
  950. background-color: #f9f9f9;
  951. cursor: pointer;
  952. }
  953. .pricing-button {
  954. margin-top: 0;
  955. }
  956. .payment-card {
  957. margin-top: 20rpx;
  958. }
  959. .payment-section {
  960. padding: 20rpx;
  961. }
  962. .payment-total-container {
  963. background-color: #f5f7fa;
  964. border: 2rpx solid #e5e7eb;
  965. border-radius: 12rpx;
  966. padding: 0rpx 30rpx;
  967. display: flex;
  968. align-items: center;
  969. justify-content: space-between;
  970. margin-bottom: 24rpx;
  971. }
  972. .payment-label {
  973. font-size: 36rpx;
  974. font-weight: 700;
  975. min-width: 140rpx;
  976. flex-shrink: 0;
  977. }
  978. .payment-amount-wrap {
  979. flex: 1;
  980. display: flex;
  981. justify-content: center;
  982. align-items: center;
  983. min-width: 0;
  984. }
  985. .payment-amount-wrap .payment-amount {
  986. font-size: 48rpx;
  987. font-weight: 600;
  988. color: #f53f3f;
  989. width: 100%;
  990. max-width: 240rpx;
  991. border: none;
  992. text-align: center;
  993. }
  994. /* 让 u-input 内部输入框文字居中 */
  995. .payment-amount-wrap :deep(.u-input__content__field-wrapper__field),
  996. .payment-amount-wrap :deep(input) {
  997. text-align: center !important;
  998. }
  999. .pay-now-btn-wrap {
  1000. flex-shrink: 0;
  1001. display: inline-flex;
  1002. }
  1003. .payment-total-container .pay-now-btn-wrap .u-button,
  1004. .payment-total-container .pay-now-btn-wrap .u-btn {
  1005. width: auto !important;
  1006. min-width: 0 !important;
  1007. flex: none !important;
  1008. }
  1009. .payment-buttons-row {
  1010. display: flex;
  1011. gap: 20rpx;
  1012. margin-bottom: 24rpx;
  1013. }
  1014. .payment-button {
  1015. flex: 1;
  1016. border-radius: 12rpx;
  1017. padding: 5rpx 0;
  1018. display: flex;
  1019. flex-direction: column;
  1020. align-items: center;
  1021. justify-content: center;
  1022. background-color: #f5f7fa;
  1023. border: 2rpx solid #e5e7eb;
  1024. cursor: pointer;
  1025. gap: 12rpx;
  1026. }
  1027. .button-text {
  1028. font-size: 28rpx;
  1029. font-weight: 500;
  1030. color: inherit;
  1031. }
  1032. .modal-content {
  1033. width: 100%;
  1034. padding: 40rpx 20rpx;
  1035. display: flex;
  1036. flex-direction: column;
  1037. align-items: center;
  1038. }
  1039. .unpaid-benefit-fee-wrap {
  1040. width: 100%;
  1041. margin-top: 32rpx;
  1042. display: flex;
  1043. flex-direction: column;
  1044. align-items: flex-start;
  1045. }
  1046. .unpaid-benefit-fee-label {
  1047. font-size: 28rpx;
  1048. color: #333;
  1049. margin-bottom: 16rpx;
  1050. }
  1051. .unpaid-benefit-fee-input {
  1052. width: 100%;
  1053. border: 2rpx solid #e5e7eb;
  1054. border-radius: 12rpx;
  1055. padding: 20rpx 24rpx;
  1056. box-sizing: border-box;
  1057. }
  1058. .payment-amount-display {
  1059. font-size: 64rpx;
  1060. font-weight: bold;
  1061. color: #108cff;
  1062. text-align: center;
  1063. margin-bottom: 40rpx;
  1064. width: 100%;
  1065. }
  1066. .payment-info-section {
  1067. width: 100%;
  1068. background-color: #f5f7fa;
  1069. border: 2rpx solid #e5e7eb;
  1070. border-radius: 12rpx;
  1071. padding: 30rpx 20rpx;
  1072. margin-bottom: 40rpx;
  1073. }
  1074. .info-item {
  1075. display: flex;
  1076. margin-bottom: 20rpx;
  1077. align-items: center;
  1078. justify-content: space-between;
  1079. &:last-child {
  1080. margin-bottom: 0;
  1081. }
  1082. }
  1083. .next-btn {
  1084. position: fixed;
  1085. bottom: 10rpx;
  1086. left: 2.5%;
  1087. width: 95%;
  1088. height: 80rpx;
  1089. line-height: 80rpx;
  1090. text-align: center;
  1091. border-radius: 20rpx;
  1092. z-index: 1000;
  1093. }
  1094. </style>