PageThree.vue 36 KB

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