PageThree.vue 37 KB

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