feat: 优化首页服务入口与租住业务流程
补齐首页待办跳转、押金支付引导和多类租住服务页面能力,统一租客端关键业务流程体验。
This commit is contained in:
@@ -48,7 +48,7 @@
|
||||
<view v-else-if="displayList.length" class="list-wrapper">
|
||||
<view
|
||||
v-for="(item, index) in displayList"
|
||||
:key="item.no || item.contractNo || item.orderNo || index"
|
||||
:key="getContractKey(item, index)"
|
||||
class="contract-card"
|
||||
:style="{ animationDelay: `${index * 0.08}s` }"
|
||||
>
|
||||
@@ -58,8 +58,8 @@
|
||||
<text class="tn-icon-edit-write-fill"></text>
|
||||
</view>
|
||||
<view class="card-title-group">
|
||||
<view class="card-title">{{ $tr('住宿合同') }}</view>
|
||||
<view class="card-code">{{ item.no || item.contractNo || '--' }}</view>
|
||||
<view class="card-title">{{ item.contractDisplayName || $tr('住宿合同') }}</view>
|
||||
<view class="card-code">{{ $tr('合同编号:') }}{{ item.no || item.contractNo || '--' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view :class="['status-badge', `status-${resolveStatusType(item.approvalStatus)}`]">
|
||||
@@ -88,10 +88,10 @@
|
||||
<text class="info-label">{{ $tr('记录说明') }}</text>
|
||||
<text class="info-value">{{ getStatusTip(item) }}</text>
|
||||
</view>
|
||||
<view v-if="item.signImageUrl" class="sign-row">
|
||||
<text class="info-label">{{ $tr('我的签名') }}</text>
|
||||
<view v-if="item.contentImageUrl" class="sign-row">
|
||||
<text class="info-label">{{ $tr('合同图片') }}</text>
|
||||
<view class="sign-preview">
|
||||
<image :src="item.signImageUrl" mode="aspectFit"></image>
|
||||
<image :src="item.contentImageUrl" mode="aspectFit" @click="previewContractImage(index)"></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -100,7 +100,7 @@
|
||||
<view
|
||||
class="action-btn"
|
||||
:class="{ 'action-btn--secondary': !shouldShowSignAction(item) }"
|
||||
@click="handleContractAction(item)"
|
||||
@click="handleContractAction(index)"
|
||||
>
|
||||
{{ resolveActionText(item) }}
|
||||
</view>
|
||||
@@ -121,8 +121,42 @@
|
||||
|
||||
<script>
|
||||
import { addFilePrefix } from '@/utils/common.js'
|
||||
import { getMyEffectiveContracts, getMyPendingSignContracts } from '@/api/index.js'
|
||||
import { getSessionSnapshot } from '../../utils/app-state'
|
||||
import { syncLoginStayState } from '../../utils/session-sync'
|
||||
import { enrichContractDisplayNames } from '../../utils/contract-display'
|
||||
|
||||
function pickFirstValue(...values) {
|
||||
for (let index = 0; index < values.length; index += 1) {
|
||||
const value = values[index]
|
||||
if (value === undefined || value === null) {
|
||||
continue
|
||||
}
|
||||
const text = String(value).trim()
|
||||
if (text) {
|
||||
return text
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function parseContractList(response) {
|
||||
const payload = response && response.data ? response.data : response
|
||||
|
||||
if (Array.isArray(payload)) {
|
||||
return payload
|
||||
}
|
||||
|
||||
if (payload && Array.isArray(payload.data)) {
|
||||
return payload.data
|
||||
}
|
||||
|
||||
if (payload && payload.data && Array.isArray(payload.data.data)) {
|
||||
return payload.data.data
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
function normalizeContract(item = {}) {
|
||||
return {
|
||||
@@ -190,6 +224,67 @@ export default {
|
||||
const list = this.$store && this.$store.state && this.$store.state.vuex_effective_contracts
|
||||
return Array.isArray(list) ? list : []
|
||||
},
|
||||
resolveContractItem(source) {
|
||||
if (typeof source === 'number') {
|
||||
return this.displayList[source] || {}
|
||||
}
|
||||
if (source && typeof source === 'object') {
|
||||
return source
|
||||
}
|
||||
return {}
|
||||
},
|
||||
resolveCurrentOrderNo(currentOrder = null) {
|
||||
const storeCurrentOrder = this.$store && this.$store.state && this.$store.state.vuex_current_order
|
||||
const session = getSessionSnapshot()
|
||||
const sessionCurrentOrder = session && session.resolvedUser && session.resolvedUser.currentOrder
|
||||
return pickFirstValue(
|
||||
currentOrder && (currentOrder.orderNo || currentOrder.no || currentOrder.code || currentOrder.OrderNo),
|
||||
storeCurrentOrder && (storeCurrentOrder.orderNo || storeCurrentOrder.no || storeCurrentOrder.code || storeCurrentOrder.OrderNo),
|
||||
sessionCurrentOrder && (sessionCurrentOrder.orderNo || sessionCurrentOrder.no || sessionCurrentOrder.code || sessionCurrentOrder.OrderNo)
|
||||
)
|
||||
},
|
||||
fetchLatestContractsByOrderNo(orderNo = '') {
|
||||
const normalizedOrderNo = String(orderNo || '').trim()
|
||||
if (!normalizedOrderNo) {
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
|
||||
const query = {
|
||||
OrderNo: normalizedOrderNo,
|
||||
ProviderType: 'WechatMiniProgram'
|
||||
}
|
||||
|
||||
return Promise.all([
|
||||
getMyPendingSignContracts(query, this.language).catch(() => []),
|
||||
getMyEffectiveContracts(query, this.language).catch(() => [])
|
||||
]).then(([pendingContractsResponse, effectiveContractsResponse]) => {
|
||||
return {
|
||||
pendingContracts: parseContractList(pendingContractsResponse),
|
||||
effectiveContracts: parseContractList(effectiveContractsResponse)
|
||||
}
|
||||
})
|
||||
},
|
||||
updateContractImageUrl(targetNo = '', imageUrl = '') {
|
||||
const normalizedNo = String(targetNo || '').trim()
|
||||
const normalizedImageUrl = addFilePrefix(String(imageUrl || '').trim())
|
||||
if (!normalizedNo || !normalizedImageUrl) {
|
||||
return
|
||||
}
|
||||
|
||||
const patchList = (list = []) => list.map((contract) => {
|
||||
const contractNo = String((contract && (contract.no || contract.contractNo)) || '').trim()
|
||||
if (contractNo !== normalizedNo) {
|
||||
return contract
|
||||
}
|
||||
return {
|
||||
...contract,
|
||||
contentImageUrl: normalizedImageUrl
|
||||
}
|
||||
})
|
||||
|
||||
this.pendingContracts = patchList(this.pendingContracts)
|
||||
this.effectiveContracts = patchList(this.effectiveContracts)
|
||||
},
|
||||
isCurrentLogin() {
|
||||
const storeUser = this.getStoreUser()
|
||||
if (storeUser && Object.keys(storeUser).length) {
|
||||
@@ -201,12 +296,23 @@ export default {
|
||||
pendingContracts = [],
|
||||
effectiveContracts = []
|
||||
} = {}) {
|
||||
this.pendingContracts = Array.isArray(pendingContracts)
|
||||
const normalizedPendingContracts = Array.isArray(pendingContracts)
|
||||
? pendingContracts.map(normalizeContract)
|
||||
: []
|
||||
this.effectiveContracts = Array.isArray(effectiveContracts)
|
||||
const normalizedEffectiveContracts = Array.isArray(effectiveContracts)
|
||||
? effectiveContracts.map(normalizeContract)
|
||||
: []
|
||||
|
||||
this.pendingContracts = normalizedPendingContracts
|
||||
this.effectiveContracts = normalizedEffectiveContracts
|
||||
|
||||
Promise.all([
|
||||
enrichContractDisplayNames(normalizedPendingContracts, this.language),
|
||||
enrichContractDisplayNames(normalizedEffectiveContracts, this.language)
|
||||
]).then(([nextPendingContracts, nextEffectiveContracts]) => {
|
||||
this.pendingContracts = nextPendingContracts
|
||||
this.effectiveContracts = nextEffectiveContracts
|
||||
})
|
||||
},
|
||||
refreshContracts() {
|
||||
if (!this.isCurrentLogin()) {
|
||||
@@ -218,20 +324,32 @@ export default {
|
||||
this.loading = true
|
||||
return syncLoginStayState({ force: true })
|
||||
.then((result = {}) => {
|
||||
this.applyContractState({
|
||||
const fallbackState = {
|
||||
pendingContracts: result.pendingContracts,
|
||||
effectiveContracts: result.effectiveContracts
|
||||
})
|
||||
}
|
||||
const currentOrderNo = this.resolveCurrentOrderNo(result.currentOrder)
|
||||
|
||||
return this.fetchLatestContractsByOrderNo(currentOrderNo)
|
||||
.then((latestState) => {
|
||||
this.applyContractState(latestState || fallbackState)
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('refresh contracts failed', error)
|
||||
const storePendingContracts = this.getStorePendingContracts()
|
||||
const storeEffectiveContracts = this.getStoreEffectiveContracts()
|
||||
const session = getSessionSnapshot()
|
||||
this.applyContractState({
|
||||
const fallbackState = {
|
||||
pendingContracts: storePendingContracts.length ? storePendingContracts : session.pendingContracts,
|
||||
effectiveContracts: storeEffectiveContracts.length ? storeEffectiveContracts : session.effectiveContracts
|
||||
})
|
||||
}
|
||||
const currentOrderNo = this.resolveCurrentOrderNo()
|
||||
|
||||
return this.fetchLatestContractsByOrderNo(currentOrderNo)
|
||||
.then((latestState) => {
|
||||
this.applyContractState(latestState || fallbackState)
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
@@ -265,6 +383,9 @@ export default {
|
||||
formatDate(value) {
|
||||
return value ? String(value).slice(0, 10) : '--'
|
||||
},
|
||||
getContractKey(item = {}, index = 0) {
|
||||
return item.no || item.contractNo || item.orderNo || `contract-${index}`
|
||||
},
|
||||
formatDateRange(start, end) {
|
||||
if (!start && !end) {
|
||||
return '--'
|
||||
@@ -290,15 +411,84 @@ export default {
|
||||
if (this.shouldShowSignAction(item)) {
|
||||
return this.$tr('去签约')
|
||||
}
|
||||
return this.$tr('查看详情')
|
||||
return this.$tr('查看合同')
|
||||
},
|
||||
shouldShowSignAction(item = {}) {
|
||||
return Number(item.approvalStatus) === 0 && !item.signImageUrl
|
||||
},
|
||||
handleContractAction(item = {}) {
|
||||
previewContractImage(source = {}) {
|
||||
const item = this.resolveContractItem(source)
|
||||
const directImageUrl = addFilePrefix(String(item.contentImageUrl || '').trim())
|
||||
if (directImageUrl) {
|
||||
uni.previewImage({
|
||||
urls: [directImageUrl],
|
||||
current: directImageUrl
|
||||
})
|
||||
return Promise.resolve(directImageUrl)
|
||||
}
|
||||
|
||||
const orderNo = String(item.orderNo || '').trim()
|
||||
const contractNo = String(item.no || item.contractNo || '').trim()
|
||||
if (!orderNo) {
|
||||
uni.showToast({
|
||||
title: this.$tr('暂无合同图片'),
|
||||
icon: 'none'
|
||||
})
|
||||
return Promise.resolve('')
|
||||
}
|
||||
|
||||
uni.showLoading({
|
||||
title: this.$tr('加载中')
|
||||
})
|
||||
|
||||
return this.fetchLatestContractsByOrderNo(orderNo)
|
||||
.then((latestState) => {
|
||||
const effectiveContracts = latestState && Array.isArray(latestState.effectiveContracts)
|
||||
? latestState.effectiveContracts
|
||||
: []
|
||||
const matchedContract = effectiveContracts.find((contract) => {
|
||||
const currentNo = String((contract && (contract.no || contract.contractNo)) || '').trim()
|
||||
const currentOrderNo = String((contract && contract.orderNo) || '').trim()
|
||||
if (contractNo && currentNo) {
|
||||
return currentNo === contractNo
|
||||
}
|
||||
return currentOrderNo === orderNo
|
||||
}) || effectiveContracts[0] || null
|
||||
const imageUrl = addFilePrefix(String(matchedContract && matchedContract.contentImageUrl || '').trim())
|
||||
|
||||
if (!imageUrl) {
|
||||
uni.showToast({
|
||||
title: this.$tr('暂无合同图片'),
|
||||
icon: 'none'
|
||||
})
|
||||
return ''
|
||||
}
|
||||
|
||||
if (contractNo) {
|
||||
this.updateContractImageUrl(contractNo, imageUrl)
|
||||
}
|
||||
uni.previewImage({
|
||||
urls: [imageUrl],
|
||||
current: imageUrl
|
||||
})
|
||||
return imageUrl
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('preview contract image failed', error)
|
||||
uni.showToast({
|
||||
title: this.$tr('合同图片加载失败'),
|
||||
icon: 'none'
|
||||
})
|
||||
return ''
|
||||
})
|
||||
.finally(() => {
|
||||
uni.hideLoading()
|
||||
})
|
||||
},
|
||||
handleContractAction(source = {}) {
|
||||
const item = this.resolveContractItem(source)
|
||||
const contractNo = encodeURIComponent(item.no || item.contractNo || '')
|
||||
const orderNo = encodeURIComponent(item.orderNo || '')
|
||||
const contentImageUrl = encodeURIComponent(item.contentImageUrl || '')
|
||||
|
||||
if (Number(item.approvalStatus) === 0 && !item.signImageUrl) {
|
||||
uni.navigateTo({
|
||||
@@ -307,9 +497,7 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
uni.navigateTo({
|
||||
url: `/pkgService/contract_detail/contract_detail?no=${contractNo}&orderNo=${orderNo}&contentImageUrl=${contentImageUrl}`
|
||||
})
|
||||
this.previewContractImage(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user