| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520 |
- <template>
- <view
- class="order-detail-view"
- @touchstart="onTouchStart"
- @touchmove="onTouchMove"
- >
- <!-- 单 scroll-view:先滚完当前页再进入下一页,避免误触下拉刷新 -->
- <scroll-view
- scroll-y
- class="page-scroll-view"
- :scroll-top="snapScrollTop >= 0 ? snapScrollTop : undefined"
- :scroll-into-view="scrollIntoView"
- :scroll-with-animation="true"
- :show-scrollbar="false"
- @scroll="onScroll"
- >
- <view id="page0" class="page-section" :style="{ minHeight: sectionMinHeight }">
- <view class="page-item">
- <PageOne :order-detail="orderDetail" :order-id="orderId" :current-receipt="currentReceipt" @next="handleNext" />
- </view>
- </view>
- <view id="page1" class="page-section" :style="{ minHeight: sectionMinHeight }">
- <view class="page-item">
- <PageTwo
- :order-detail="orderDetail"
- :order-id="orderId"
- :current-receipt="currentReceipt"
- :follow-up-list="followUpList"
- @next="handleNext"
- @update-file-ids="handleUpdateFileIds"
- @price-updated="$emit('price-updated')"
- @follow-saved="loadFollowUpList"
- />
- </view>
- </view>
- <view id="page2" class="page-section" :style="{ minHeight: sectionMinHeight }">
- <view class="page-item">
- <PageThree
- ref="pageThreeRef"
- :order-detail="orderDetail"
- :order-id="orderId"
- :current-receipt="currentReceipt"
- @next="handleNext"
- @save="handleNeedSave"
- @confirm-pay="handleConfirmPay"
- @update-file-ids="handleUpdateFileIds"
- @price-updated="$emit('price-updated')"
- />
- </view>
- </view>
- <view id="page3" class="page-section" :style="{ minHeight: sectionMinHeight }">
- <view class="page-item">
- <PageFour
- :order-detail="orderDetail"
- :current-receipt="currentReceipt"
- @next="handleNext"
- @confirm-warehouse="handleConfirmWarehouse"
- @update-order-detail="$emit('update-order-detail', $event)"
- />
- </view>
- </view>
- </scroll-view>
- <!-- 页面导航(点击仍可切换) -->
- <ul class="page-navigation">
- <li
- v-for="(tab, index) in tabs"
- :key="index"
- :class="{ active: activeIndex === index }"
- @click="handleTabClick(index)"
- >
- {{ tab }}
- </li>
- </ul>
- </view>
- </template>
- <script>
- import PageOne from './PageOne.vue'
- import PageTwo from './PageTwo.vue'
- import PageThree from './PageThree.vue'
- import PageFour from './PageFour.vue'
- export default {
- name: 'OrderDetailView',
- components: {
- PageOne,
- PageTwo,
- PageThree,
- PageFour
- },
- props: {
- orderDetail: {
- type: Object,
- default: () => ({})
- },
- topInfo: {
- type: Object,
- default: () => ({})
- },
- orderId: {
- type: String,
- default: ''
- },
- currentReceipt: {
- type: Object,
- default: () => ({})
- }
- },
- data() {
- const sys = typeof uni !== 'undefined' ? uni.getSystemInfoSync() : { windowHeight: 600 }
- const sectionMinHeight = (sys.windowHeight || 600) - 100 + 'px' // 一屏高度,留出导航等
- return {
- activeIndex: 0,
- tabs: ['一', '二', '三', '四'],
- scrollTop: 0,
- scrollIntoView: '',
- sectionMinHeight,
- sectionTops: [0],
- scrollTopLock: null,
- // 吸住效果:仅在有意义的数值时控制 scroll-top,-1 表示不控制
- snapScrollTop: -1,
- scrollEndTimer: null,
- snapInProgress: false,
- // 阻止在非顶部时触发页面下拉刷新(由 touch 捕获)
- touchStartY: 0,
- // 表单数据
- formData: {
- formOne: {},
- formTwo: {},
- formThree: {},
- formFour: {}
- },
- pageThreeForm: {},
- fileIds: '',
- // 跟进记录
- followUpList: [],
- _measureTopsTimer: null
- }
- },
- mounted() {
- this.$nextTick(() => this.measureSectionTops())
- },
- watch: {
- orderDetail: {
- handler(newVal) {
- if (newVal && newVal.clueId) {
- this.loadFollowUpList()
- }
- },
- deep: true,
- immediate: true
- },
- // 仅切换订单/收单时重新测量高度,避免 updated 每次渲染都测导致 sectionTops 被错误覆盖
- orderId() {
- this.$nextTick(() => this.measureSectionTopsDelayed())
- },
- currentReceipt: {
- handler() {
- this.$nextTick(() => this.measureSectionTopsDelayed())
- },
- deep: true
- }
- },
- methods: {
- /**
- * 加载跟进记录
- */
- async loadFollowUpList() {
- try {
- const res = await uni.$u.api.getDuplicateOrderFollowListByClueId({
- clueId: this.orderDetail.clueId
- })
- const data = res.data || {}
- const followUpList = []
- // 按日期键升序合并,保证“最后一条”=“最新一条”,回显才能拿到最新
- const dates = Object.keys(data).filter(Boolean).sort()
- for (const key of dates) {
- const list = data[key] || []
- followUpList.push(...list)
- }
- // 再按 createTime 排一次,同一天内多条时也保证时间顺序
- followUpList.sort((a, b) => {
- const t1 = a.createTime || ''
- const t2 = b.createTime || ''
- return t1.localeCompare(t2)
- })
- this.followUpList = followUpList
- } catch (error) {
- console.error('获取跟进记录失败:', error)
- uni.$u.toast('获取跟进记录失败')
- }
- },
- /**
- * 延迟测量,避免子组件未渲染完时测到全 0
- */
- measureSectionTopsDelayed() {
- if (this._measureTopsTimer) clearTimeout(this._measureTopsTimer)
- this._measureTopsTimer = setTimeout(() => {
- this._measureTopsTimer = null
- this.measureSectionTops()
- }, 300)
- },
- /**
- * 测量各段顶部位置,用于根据 scrollTop 计算当前页
- */
- measureSectionTops() {
- const query = uni.createSelectorQuery().in(this)
- query
- .selectAll('.page-section')
- .fields({ size: true }, (res) => {
- if (!res || res.length < 2) return
- const tops = [0]
- for (let i = 0; i < res.length - 1; i++) {
- tops.push(tops[i] + (res[i].height || 0))
- }
- // 只有测量结果合理(递增)时才覆盖,避免把正确值覆盖成 [0,0,0,0]
- const valid = tops.length >= 2 && tops.every((t, i) => i === 0 || t > tops[i - 1])
- if (valid) this.sectionTops = tops
- })
- .exec()
- },
- /**
- * 滚动时更新当前页索引
- */
- onScroll(e) {
- const scrollTop = e.detail?.scrollTop ?? 0
- if (this.scrollTopLock !== null) return
- this.scrollTop = scrollTop
- // 吸附过程中不更新页索引、不启动“滚动结束”定时器,避免释放控制后误触二次吸附到第一页
- if (this.snapInProgress) return
- const tops = this.sectionTops
- if (tops.length) {
- let idx = 0
- for (let i = tops.length - 1; i >= 0; i--) {
- if (scrollTop >= tops[i] - 10) {
- idx = i
- break
- }
- }
- if (idx !== this.activeIndex) {
- this.activeIndex = idx
- this.tryRefreshPageThree(idx)
- }
- }
- if (this.scrollEndTimer) clearTimeout(this.scrollEndTimer)
- this.scrollEndTimer = setTimeout(() => this.doSnap(), 220)
- },
- /**
- * 滚动停止后吸附到当前所在页的起始位置(轮播吸住感)
- */
- doSnap() {
- this.scrollEndTimer = null
- const tops = this.sectionTops
- // 测量结果无效(非递增或只有一项)时不吸附,避免误吸回第一页
- if (!tops.length || tops.length < 2) return
- const increasing = tops.every((t, i) => i === 0 || t > tops[i - 1])
- if (!increasing) return
- const scrollTop = this.scrollTop
- let nearest = tops.length - 1
- for (let i = tops.length - 1; i >= 0; i--) {
- if (scrollTop >= tops[i] - 2) {
- nearest = i
- break
- }
- }
- const targetTop = tops[nearest]
- if (Math.abs(scrollTop - targetTop) < 2) {
- this.snapInProgress = false
- return
- }
- this.snapInProgress = true
- this.snapScrollTop = targetTop
- this.activeIndex = nearest
- this.tryRefreshPageThree(nearest)
- setTimeout(() => {
- this.snapScrollTop = -1
- // 释放后仍保持 snapInProgress 一段时间,避免“释放”触发的 scroll 再次触发 doSnap 吸到第一页
- setTimeout(() => {
- this.snapInProgress = false
- }, 280)
- }, 350)
- },
- tryRefreshPageThree(index) {
- if (index === 2 && this.$refs.pageThreeRef) {
- const ref = Array.isArray(this.$refs.pageThreeRef) ? this.$refs.pageThreeRef[0] : this.$refs.pageThreeRef
- if (ref && ref.refreshImageList) ref.refreshImageList()
- }
- },
- /**
- * 触摸开始:记录 Y,用于避免误触页面下拉刷新
- */
- onTouchStart(e) {
- this.touchStartY = e.touches && e.touches[0] ? e.touches[0].clientY : 0
- },
- /**
- * 触摸移动:在非全局顶部时用 catch 阻止事件冒泡到页面,减少触发下拉刷新
- */
- onTouchMove(e) {
- if (this.scrollTop > 20 && e.cancelable) {
- e.stopPropagation()
- }
- },
- /**
- * 处理下一步:滚动到下一页
- */
- handleNext({ nowPage, form }) {
- if (nowPage) {
- this.formData[nowPage] = form
- }
- const nextIndex = Math.min(this.activeIndex + 1, this.tabs.length - 1)
- this.scrollToSection(nextIndex)
- this.activeIndex = nextIndex
- if (nextIndex === 2 && this.$refs.pageThreeRef) {
- const ref = Array.isArray(this.$refs.pageThreeRef) ? this.$refs.pageThreeRef[0] : this.$refs.pageThreeRef
- if (ref && ref.refreshImageList) ref.refreshImageList()
- }
- },
- /**
- * 处理保存
- */
- handleNeedSave({ nowPage, form, fileIds }) {
- this.pageThreeForm = form
- this.fileIds = fileIds
- },
- /**
- * 处理确认支付
- */
- async handleConfirmPay() {
- try {
- const response = await uni.$u.api.saveOrderFileAndTransfer({
- id: this.orderId,
- clueId: this.orderDetail.clueId
- })
- uni.$u.toast(response.msg || '支付成功')
- } catch (error) {
- console.error('支付失败:', error)
- uni.$u.toast(`支付失败:${error}`)
- //支付失败回滚支付信息
- await uni.$u.api.updateClueOrderForm({
- id: this.orderDetail.id,
- paymentMethod: ''
- })
- }
- },
- /**
- * 处理确认入库
- */
- async handleConfirmWarehouse({ warehouseInfo }) {
- try {
- const params = {
- searchValue: this.orderDetail.searchValue,
- createBy: this.orderDetail.createBy,
- createTime: this.orderDetail.createTime,
- updateBy: this.orderDetail.updateBy,
- updateTime: this.orderDetail.updateTime,
- params: this.orderDetail.params,
- id: this.currentReceipt.id,
- sendFormId: this.orderId,
- clueId: this.orderDetail.clueId,
- item: warehouseInfo.item || '',
- code: warehouseInfo.codeStorage || '',
- phone: this.orderDetail.phone,
- tableFee: warehouseInfo.watchPrice || '',
- benefitFee: warehouseInfo.benefitFee || '',
- freight: warehouseInfo.freight || '',
- checkCodeFee: warehouseInfo.checkCodeFee || '',
- receiptRemark: `${warehouseInfo.remarks || ''};${warehouseInfo.uploadedImage || ''}`,
- repairAmount: warehouseInfo.repairAmount || '',
- grossPerformance: warehouseInfo.grossPerformance || '',
- expressOrderNo: warehouseInfo.expressOrderNo || '',
- fileIds: this.fileIds,
- customerServiceName: warehouseInfo.customerServiceName || '1',
- deptId: this.orderDetail.deptId,
- category: warehouseInfo.category || this.orderDetail.category,
- delFlag: this.orderDetail.delFlag,
- idCard: this.pageThreeForm.idNumber || '',
- paymentMethod: '小葫芦线上支付',
- bankCardNumber: this.pageThreeForm.bankAccount || '',
- bankName: this.pageThreeForm.bankName || '',
- customName: this.pageThreeForm.customName || ''
- }
- if (this.currentReceipt.id) {
- await uni.$u.api.updateReceiptForm(params)
- } else {
- await uni.$u.api.addReceiptForm(params)
- }
- uni.$u.toast('入库成功')
- } catch (error) {
- console.error('入库失败:', error)
- uni.$u.toast('入库失败')
- }
- },
- /**
- * 滚动到指定页(用于点击导航、下一步)
- */
- scrollToSection(index) {
- if (this.scrollEndTimer) clearTimeout(this.scrollEndTimer)
- this.scrollEndTimer = null
- this.snapInProgress = true
- this.scrollIntoView = 'page' + index
- this.$nextTick(() => {
- setTimeout(() => {
- this.scrollIntoView = ''
- setTimeout(() => {
- this.snapInProgress = false
- }, 100)
- }, 300)
- })
- this.activeIndex = index
- this.tryRefreshPageThree(index)
- },
- /**
- * 处理标签点击
- */
- handleTabClick(index) {
- this.scrollToSection(index)
- },
- /**
- * 更新 fileIds
- */
- handleUpdateFileIds(fileIds) {
- if (this.currentReceipt) {
- this.$set(this.currentReceipt, 'fileIds', fileIds)
- this.fileIds = fileIds
- }
- }
- }
- }
- </script>
- <style scoped lang="scss">
- .order-detail-view {
- position: relative;
- padding: 20rpx;
- height: calc(100vh - 200rpx);
- min-height: calc(100vh - 200rpx);
- }
- .page-scroll-view {
- width: 100%;
- height: 100%;
- scroll-snap-type: y mandatory;
- -webkit-overflow-scrolling: touch;
- }
- .page-section {
- width: 100%;
- box-sizing: border-box;
- scroll-snap-align: start;
- scroll-snap-stop: always;
- }
- .page-item {
- width: 100%;
- min-height: 100%;
- box-sizing: border-box;
- }
- .page-navigation {
- position: fixed;
- right: 20rpx;
- top: 40%;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- list-style: none;
- color: #000;
- font-size: 20rpx;
- font-weight: 800;
- z-index: 100;
- li {
- opacity: 0.7;
- display: flex;
- align-items: center;
- justify-content: center;
- background-color: #fff;
- border-radius: 50%;
- width: 70rpx;
- height: 70rpx;
- line-height: 70rpx;
- text-align: center;
- margin-bottom: 20rpx;
- transition: all 0.3s ease-in-out;
- font-weight: 800;
- box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
- cursor: pointer;
- &.active {
- color: #fff;
- opacity: 1;
- background-color: rgb(37 99 235 / 1);
- }
- &:hover {
- opacity: 0.9;
- transform: scale(1.05);
- }
- }
- }
- </style>
|