377 lines
9.6 KiB
Vue
377 lines
9.6 KiB
Vue
<template>
|
|
<view class="contract-detail-page tn-safe-area-inset-bottom">
|
|
<tn-nav-bar :isBack="true" :bottomShadow="false" backgroundColor="transparent">
|
|
<view class="tn-text-bold tn-text-xl tn-color-black">{{ t('navTitle') }}</view>
|
|
</tn-nav-bar>
|
|
|
|
<view class="page-main" :style="{ paddingTop: vuex_custom_bar_height + 'px' }">
|
|
<view class="overview-panel">
|
|
<view class="overview-header">
|
|
<view>
|
|
<view class="overview-title">{{ t('overviewTitle') }}</view>
|
|
<view class="overview-subtitle">{{ t('contractNoLabel') }}{{ contractNo || t('notAvailable') }}</view>
|
|
</view>
|
|
<view class="overview-badge">
|
|
<text class="tn-icon-read"></text>
|
|
<text>{{ Math.round(scale * 100) }}%</text>
|
|
</view>
|
|
</view>
|
|
<view class="overview-tips">
|
|
<view class="tip-chip">
|
|
<text class="tn-icon-safe-fill"></text>
|
|
<text>{{ t('signedChip') }}</text>
|
|
</view>
|
|
<view class="tip-chip">
|
|
<text class="tn-icon-charts-ring"></text>
|
|
<text>{{ t('zoomSupportedChip') }}</text>
|
|
</view>
|
|
<view class="tip-chip" v-if="imageUrl">
|
|
<text class="tn-icon-image-text-fill"></text>
|
|
<text>{{ t('imageModeChip') }}</text>
|
|
</view>
|
|
<view class="tip-chip" v-else-if="contractContent">
|
|
<text class="tn-icon-text-new"></text>
|
|
<text>{{ t('textModeChip') }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view v-if="loading" class="empty-panel">
|
|
<view class="empty-title">{{ t('loadingTitle') }}</view>
|
|
<view class="empty-desc">{{ t('loadingDesc') }}</view>
|
|
</view>
|
|
|
|
<view v-else-if="imageUrl || contractContent" class="preview-panel">
|
|
<scroll-view scroll-y scroll-x class="scroll-area" :enable-flex="true">
|
|
<view class="a4-wrapper" :style="{ transform: `scale(${scale})`, transformOrigin: 'top center' }">
|
|
<view class="a4-paper">
|
|
<image
|
|
v-if="imageUrl"
|
|
class="contract-image"
|
|
:src="imageUrl"
|
|
mode="widthFix"
|
|
@click="previewImage"
|
|
></image>
|
|
<rich-text v-else class="contract-rich-text" :nodes="contractContent"></rich-text>
|
|
</view>
|
|
</view>
|
|
</scroll-view>
|
|
|
|
<view class="zoom-controls">
|
|
<view class="zoom-btn" @click="zoomOut">
|
|
<text class="tn-icon-reduce-circle"></text>
|
|
</view>
|
|
<view class="zoom-text">{{ Math.round(scale * 100) }}%</view>
|
|
<view class="zoom-btn" @click="zoomIn">
|
|
<text class="tn-icon-add-circle"></text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view v-else class="empty-panel">
|
|
<view class="empty-icon">
|
|
<text class="tn-icon-folder"></text>
|
|
</view>
|
|
<view class="empty-title">{{ t('emptyTitle') }}</view>
|
|
<view class="empty-desc">{{ t('emptyDesc') }}</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import { addFilePrefix } from '@/utils/common.js'
|
|
import { contractPreview } from '@/api/index.js'
|
|
import { normalizeRichTextImages } from '../../utils/rich-text'
|
|
|
|
function resolvePreviewPayload(response) {
|
|
const payload = response && response.data ? response.data : response
|
|
return {
|
|
content: (payload && payload.content) || (payload && payload.data && payload.data.content) || '',
|
|
imageUrl: (payload && payload.contentImageUrl) || (payload && payload.data && payload.data.contentImageUrl) || ''
|
|
}
|
|
}
|
|
|
|
function normalizeRichTextContent(content = '') {
|
|
return normalizeRichTextImages(
|
|
content,
|
|
'display:block;width:100%;max-width:100%;height:auto;box-sizing:border-box;border-radius:12rpx;margin:16rpx 0;vertical-align:top;'
|
|
)
|
|
}
|
|
|
|
export default {
|
|
i18n: {
|
|
zh: {
|
|
navTitle: '合同详情',
|
|
overviewTitle: '合同影像',
|
|
contractNoLabel: '合同编号:',
|
|
notAvailable: '未获取',
|
|
signedChip: '已签合同',
|
|
zoomSupportedChip: '支持缩放',
|
|
imageModeChip: 'A4 影像模式',
|
|
textModeChip: '正文预览模式',
|
|
loadingTitle: '合同加载中',
|
|
loadingDesc: '正在获取合同影像与正文,请稍候。',
|
|
emptyTitle: '暂无合同内容',
|
|
emptyDesc: '页面未带入影像地址,且通过合同编号也未获取到可预览的合同内容。'
|
|
},
|
|
en: {
|
|
navTitle: 'Contract Detail',
|
|
overviewTitle: 'Contract Preview',
|
|
contractNoLabel: 'Contract No.: ',
|
|
notAvailable: 'N/A',
|
|
signedChip: 'Signed Contract',
|
|
zoomSupportedChip: 'Zoom Supported',
|
|
imageModeChip: 'A4 Image Mode',
|
|
textModeChip: 'Text Preview Mode',
|
|
loadingTitle: 'Loading contract',
|
|
loadingDesc: 'Fetching contract images and content. Please wait.',
|
|
emptyTitle: 'No contract content',
|
|
emptyDesc: 'No previewable contract content was provided by image URL or contract number.'
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
contractNo: '',
|
|
orderNo: '',
|
|
loading: false,
|
|
imageUrl: '',
|
|
contractContent: '',
|
|
scale: 1
|
|
}
|
|
},
|
|
onLoad(options = {}) {
|
|
this.contractNo = decodeURIComponent(options.no || '')
|
|
this.orderNo = decodeURIComponent(options.orderNo || '')
|
|
this.imageUrl = addFilePrefix(decodeURIComponent(options.contentImageUrl || ''))
|
|
this.fetchContractDetail()
|
|
},
|
|
methods: {
|
|
t(key, params) {
|
|
return this.$tm('pkgService.contract_detail.contract_detail', key, params)
|
|
},
|
|
fetchContractDetail() {
|
|
if (!this.contractNo || (this.imageUrl && this.contractContent)) {
|
|
return Promise.resolve()
|
|
}
|
|
|
|
this.loading = true
|
|
this.scale = 1
|
|
|
|
return contractPreview({ contractNo: this.contractNo })
|
|
.then((response) => {
|
|
const payload = resolvePreviewPayload(response)
|
|
this.contractContent = normalizeRichTextContent(payload.content || '')
|
|
if (!this.imageUrl) {
|
|
this.imageUrl = addFilePrefix(payload.imageUrl || '')
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
console.error('fetch contract detail failed', error)
|
|
this.contractContent = ''
|
|
})
|
|
.finally(() => {
|
|
this.loading = false
|
|
})
|
|
},
|
|
previewImage() {
|
|
if (!this.imageUrl) {
|
|
return
|
|
}
|
|
uni.previewImage({
|
|
urls: [this.imageUrl],
|
|
current: this.imageUrl
|
|
})
|
|
},
|
|
zoomIn() {
|
|
if (this.scale < 3) {
|
|
this.scale = Number((this.scale + 0.2).toFixed(1))
|
|
}
|
|
},
|
|
zoomOut() {
|
|
if (this.scale > 0.6) {
|
|
this.scale = Number((this.scale - 0.2).toFixed(1))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.contract-detail-page {
|
|
min-height: 100vh;
|
|
background: linear-gradient(180deg, #f0f6ff 0%, #f8faff 100%);
|
|
}
|
|
|
|
.page-main {
|
|
padding: 0 24rpx calc(40rpx + env(safe-area-inset-bottom));
|
|
}
|
|
|
|
.overview-panel,
|
|
.preview-panel,
|
|
.empty-panel {
|
|
background: rgba(255, 255, 255, 0.96);
|
|
border-radius: 28rpx;
|
|
border: 1px solid rgba($brand-primary, 0.08);
|
|
box-shadow: 0 18rpx 48rpx rgba(25, 60, 104, 0.1);
|
|
}
|
|
|
|
.overview-panel {
|
|
padding: 28rpx;
|
|
}
|
|
|
|
.overview-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
|
|
.overview-title {
|
|
font-size: 34rpx;
|
|
font-weight: 700;
|
|
color: #1f2d3d;
|
|
}
|
|
|
|
.overview-subtitle {
|
|
margin-top: 8rpx;
|
|
font-size: 22rpx;
|
|
color: #7c8da0;
|
|
}
|
|
|
|
.overview-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 10rpx;
|
|
padding: 10rpx 20rpx;
|
|
border-radius: 999rpx;
|
|
font-size: 24rpx;
|
|
font-weight: 700;
|
|
color: $brand-primary;
|
|
background: rgba($brand-primary, 0.1);
|
|
}
|
|
|
|
.overview-tips {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 14rpx;
|
|
}
|
|
|
|
.tip-chip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8rpx;
|
|
padding: 10rpx 18rpx;
|
|
border-radius: 999rpx;
|
|
font-size: 22rpx;
|
|
color: #61758a;
|
|
background: #f7fafd;
|
|
border: 1px solid rgba($brand-primary, 0.08);
|
|
}
|
|
|
|
.preview-panel {
|
|
position: relative;
|
|
overflow: hidden;
|
|
margin-top: 24rpx;
|
|
min-height: 980rpx;
|
|
background: linear-gradient(180deg, #e8eef5 0%, #e0e8f1 100%);
|
|
}
|
|
|
|
.scroll-area {
|
|
width: 100%;
|
|
height: 980rpx;
|
|
}
|
|
|
|
.a4-wrapper {
|
|
padding: 40rpx 24rpx 120rpx;
|
|
display: flex;
|
|
justify-content: center;
|
|
transition: transform 0.2s ease-out;
|
|
}
|
|
|
|
.a4-paper {
|
|
width: 650rpx;
|
|
min-height: 900rpx;
|
|
padding: 24rpx;
|
|
box-sizing: border-box;
|
|
background: #ffffff;
|
|
border-radius: 20rpx;
|
|
overflow: hidden;
|
|
box-shadow: 0 16rpx 42rpx rgba(28, 53, 90, 0.1);
|
|
}
|
|
|
|
.contract-image {
|
|
display: block;
|
|
width: 100%;
|
|
}
|
|
|
|
.contract-rich-text {
|
|
display: block;
|
|
width: 100%;
|
|
color: #334155;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.zoom-controls {
|
|
position: absolute;
|
|
right: 32rpx;
|
|
bottom: 32rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
background: rgba(255, 255, 255, 0.96);
|
|
border-radius: 999rpx;
|
|
padding: 10rpx 16rpx;
|
|
border: 1px solid rgba($brand-primary, 0.08);
|
|
}
|
|
|
|
.zoom-btn {
|
|
width: 60rpx;
|
|
height: 60rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 34rpx;
|
|
color: $brand-primary;
|
|
}
|
|
|
|
.zoom-text {
|
|
min-width: 72rpx;
|
|
text-align: center;
|
|
font-size: 24rpx;
|
|
font-weight: 700;
|
|
color: #334155;
|
|
}
|
|
|
|
.empty-panel {
|
|
margin-top: 24rpx;
|
|
padding: 54rpx 32rpx;
|
|
text-align: center;
|
|
}
|
|
|
|
.empty-icon {
|
|
width: 132rpx;
|
|
height: 132rpx;
|
|
margin: 0 auto 22rpx;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 70rpx;
|
|
color: $brand-primary;
|
|
background: rgba($brand-primary, 0.08);
|
|
}
|
|
|
|
.empty-title {
|
|
font-size: 30rpx;
|
|
font-weight: 700;
|
|
color: #233244;
|
|
}
|
|
|
|
.empty-desc {
|
|
margin-top: 12rpx;
|
|
font-size: 24rpx;
|
|
line-height: 1.7;
|
|
color: #8a99a8;
|
|
}
|
|
</style>
|