871 lines
23 KiB
Vue
871 lines
23 KiB
Vue
<template>
|
|
<view class="my-contract-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">{{ $tr('我的合同') }}</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">{{ $tr('合同概览') }}</view>
|
|
<view class="overview-subtitle">{{ $tr('查看住宿合同、签约状态与有效期限') }}</view>
|
|
</view>
|
|
<view class="overview-badge">
|
|
<text class="tn-icon-edit-form"></text>
|
|
<text>{{ summaryStats.total }}</text>
|
|
</view>
|
|
</view>
|
|
<view class="overview-metrics">
|
|
<view class="metric-item">
|
|
<view class="metric-label">{{ $tr('待签约/审批') }}</view>
|
|
<view class="metric-value">{{ summaryStats.pending }}</view>
|
|
</view>
|
|
<view class="metric-item is-primary">
|
|
<view class="metric-label">{{ $tr('当前展示') }}</view>
|
|
<view class="metric-value">{{ displayList.length }}</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="filter-row">
|
|
<view
|
|
v-for="item in tabs"
|
|
:key="item.value"
|
|
class="filter-chip"
|
|
:class="{ 'filter-chip--active': currentTab === item.value }"
|
|
@click="changeTab(item.value)"
|
|
>
|
|
{{ item.label }}
|
|
</view>
|
|
</view>
|
|
|
|
<view v-if="loading" class="empty-panel">
|
|
<view class="empty-title">{{ $tr('合同记录加载中') }}</view>
|
|
<view class="empty-desc">{{ $tr('正在同步您的合同数据,请稍候。') }}</view>
|
|
</view>
|
|
|
|
<view v-else-if="displayList.length" class="list-wrapper">
|
|
<view
|
|
v-for="(item, index) in displayList"
|
|
:key="getContractKey(item, index)"
|
|
class="contract-card"
|
|
:style="{ animationDelay: `${index * 0.08}s` }"
|
|
>
|
|
<view class="card-header">
|
|
<view class="card-header-left">
|
|
<view class="card-icon">
|
|
<text class="tn-icon-edit-write-fill"></text>
|
|
</view>
|
|
<view class="card-title-group">
|
|
<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)}`]">
|
|
<view class="status-dot"></view>
|
|
<text>{{ getDisplayStatusText(item) }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="meta-strip">
|
|
<view class="meta-chip">
|
|
<view class="meta-chip-label">{{ $tr('关联订单') }}</view>
|
|
<view class="meta-chip-value">{{ item.orderNo || '--' }}</view>
|
|
</view>
|
|
<view class="meta-chip">
|
|
<view class="meta-chip-label">{{ $tr('签署状态') }}</view>
|
|
<view class="meta-chip-value">{{ item.signImageUrl ? $tr('已签名') : $tr('待签名') }}</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="detail-panel">
|
|
<view class="info-row">
|
|
<text class="info-label">{{ $tr('合同期限') }}</text>
|
|
<text class="info-value">{{ formatDateRange(item.start, item.end) }}</text>
|
|
</view>
|
|
<view class="info-row">
|
|
<text class="info-label">{{ $tr('记录说明') }}</text>
|
|
<text class="info-value">{{ getStatusTip(item) }}</text>
|
|
</view>
|
|
<view v-if="item.contentImageUrl" class="sign-row">
|
|
<text class="info-label">{{ $tr('合同图片') }}</text>
|
|
<view class="sign-preview">
|
|
<image :src="item.contentImageUrl" mode="aspectFit" @click="previewContractImage(index)"></image>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="card-footer">
|
|
<view
|
|
class="action-btn"
|
|
:class="{ 'action-btn--secondary': !shouldShowSignAction(item) }"
|
|
@click="handleContractAction(index)"
|
|
>
|
|
{{ resolveActionText(item) }}
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<view v-else class="empty-panel">
|
|
<view class="empty-icon">
|
|
<text class="tn-icon-folder"></text>
|
|
</view>
|
|
<view class="empty-title">{{ $tr('暂无对应的合同记录') }}</view>
|
|
<view class="empty-desc">{{ $tr('切换筛选条件,或等待系统生成新的合同数据。') }}</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<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 {
|
|
...item,
|
|
signImageUrl: item.signImageUrl ? addFilePrefix(item.signImageUrl) : '',
|
|
contentImageUrl: item.contentImageUrl ? addFilePrefix(item.contentImageUrl) : ''
|
|
}
|
|
}
|
|
|
|
export default {
|
|
data() {
|
|
return {
|
|
currentTab: 'all',
|
|
loading: false,
|
|
pendingContracts: [],
|
|
effectiveContracts: [],
|
|
tabs: []
|
|
}
|
|
},
|
|
computed: {
|
|
displayList() {
|
|
if (this.currentTab === 'pending') {
|
|
return this.pendingContracts
|
|
}
|
|
return [...this.pendingContracts, ...this.effectiveContracts]
|
|
},
|
|
summaryStats() {
|
|
return {
|
|
total: this.pendingContracts.length + this.effectiveContracts.length,
|
|
pending: this.pendingContracts.length,
|
|
effective: this.effectiveContracts.length
|
|
}
|
|
},
|
|
language() {
|
|
return this.$getLanguage()
|
|
}
|
|
},
|
|
created() {
|
|
this.syncTabs()
|
|
},
|
|
onShow() {
|
|
this.refreshContracts()
|
|
},
|
|
watch: {
|
|
language() {
|
|
this.syncTabs()
|
|
this.refreshContracts()
|
|
}
|
|
},
|
|
methods: {
|
|
syncTabs() {
|
|
this.tabs = [
|
|
{ label: this.$tr('全部'), value: 'all' },
|
|
{ label: this.$tr('待签约'), value: 'pending' }
|
|
]
|
|
},
|
|
getStoreUser() {
|
|
return (this.$store && this.$store.state && this.$store.state.vuex_user) || {}
|
|
},
|
|
getStorePendingContracts() {
|
|
const list = this.$store && this.$store.state && this.$store.state.vuex_pending_contracts
|
|
return Array.isArray(list) ? list : []
|
|
},
|
|
getStoreEffectiveContracts() {
|
|
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) {
|
|
return true
|
|
}
|
|
return !!getSessionSnapshot().isLogin
|
|
},
|
|
applyContractState({
|
|
pendingContracts = [],
|
|
effectiveContracts = []
|
|
} = {}) {
|
|
const normalizedPendingContracts = Array.isArray(pendingContracts)
|
|
? pendingContracts.map(normalizeContract)
|
|
: []
|
|
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()) {
|
|
this.applyContractState()
|
|
this.loading = false
|
|
return Promise.resolve()
|
|
}
|
|
|
|
this.loading = true
|
|
return syncLoginStayState({ force: true })
|
|
.then((result = {}) => {
|
|
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()
|
|
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
|
|
})
|
|
},
|
|
changeTab(value) {
|
|
if (this.currentTab === value) {
|
|
return
|
|
}
|
|
this.currentTab = value
|
|
},
|
|
getStatusText(status) {
|
|
const map = {
|
|
'-1': this.$tr('已拒绝'),
|
|
'0': this.$tr('待审批'),
|
|
'1': this.$tr('已通过')
|
|
}
|
|
return map[String(status)] || this.$tr('处理中')
|
|
},
|
|
getDisplayStatusText(item = {}) {
|
|
if (this.language !== 'en' && item.approvalStatusDesc) {
|
|
return item.approvalStatusDesc
|
|
}
|
|
return this.getStatusText(item.approvalStatus)
|
|
},
|
|
resolveStatusType(status) {
|
|
if (Number(status) === 1) return 'approved'
|
|
if (Number(status) === -1) return 'rejected'
|
|
return 'pending'
|
|
},
|
|
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 '--'
|
|
}
|
|
return `${this.formatDate(start)}${this.$tr(' 至 ')}${this.formatDate(end)}`
|
|
},
|
|
getStatusTip(item = {}) {
|
|
if (Number(item.approvalStatus) === 1) {
|
|
return this.$tr('合同已生效,可查看签约结果。')
|
|
}
|
|
if (Number(item.approvalStatus) === 0 && !item.signImageUrl) {
|
|
return this.$tr('待您完成签名后进入审批流程。')
|
|
}
|
|
if (Number(item.approvalStatus) === 0) {
|
|
return this.$tr('已提交签约信息,等待审批结果。')
|
|
}
|
|
if (Number(item.approvalStatus) === -1) {
|
|
return this.$tr('合同未通过审批,请联系管理员处理。')
|
|
}
|
|
return this.$tr('请关注合同最新状态。')
|
|
},
|
|
resolveActionText(item = {}) {
|
|
if (this.shouldShowSignAction(item)) {
|
|
return this.$tr('去签约')
|
|
}
|
|
return this.$tr('查看合同')
|
|
},
|
|
shouldShowSignAction(item = {}) {
|
|
return Number(item.approvalStatus) === 0 && !item.signImageUrl
|
|
},
|
|
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 || '')
|
|
|
|
if (Number(item.approvalStatus) === 0 && !item.signImageUrl) {
|
|
uni.navigateTo({
|
|
url: `/pkgService/contract_sign/contract_sign?no=${contractNo}&orderNo=${orderNo}`
|
|
})
|
|
return
|
|
}
|
|
|
|
this.previewContractImage(item)
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.my-contract-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,
|
|
.empty-panel,
|
|
.contract-card {
|
|
background: rgba(255, 255, 255, 0.96);
|
|
border: 1px solid rgba($brand-primary, 0.08);
|
|
box-shadow: 0 16rpx 42rpx rgba(28, 53, 90, 0.08);
|
|
}
|
|
|
|
.overview-panel {
|
|
padding: 28rpx;
|
|
border-radius: 28rpx;
|
|
}
|
|
|
|
.overview-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 22rpx;
|
|
}
|
|
|
|
.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: 8rpx;
|
|
padding: 10rpx 20rpx;
|
|
border-radius: 999rpx;
|
|
color: $brand-primary;
|
|
font-size: 24rpx;
|
|
font-weight: 700;
|
|
background: rgba($brand-primary, 0.1);
|
|
}
|
|
|
|
.overview-metrics {
|
|
display: flex;
|
|
gap: 18rpx;
|
|
}
|
|
|
|
.metric-item {
|
|
flex: 1;
|
|
padding: 20rpx 16rpx;
|
|
border-radius: 20rpx;
|
|
background: #f7fafd;
|
|
text-align: center;
|
|
}
|
|
|
|
.metric-item.is-primary {
|
|
background: linear-gradient(135deg, rgba($brand-primary, 0.14) 0%, rgba($brand-primary, 0.05) 100%);
|
|
}
|
|
|
|
.metric-label {
|
|
font-size: 22rpx;
|
|
color: #7c8da0;
|
|
}
|
|
|
|
.metric-value {
|
|
margin-top: 10rpx;
|
|
font-size: 40rpx;
|
|
line-height: 1;
|
|
font-weight: 700;
|
|
color: #203248;
|
|
}
|
|
|
|
.filter-row {
|
|
display: flex;
|
|
gap: 16rpx;
|
|
margin: 24rpx 0;
|
|
}
|
|
|
|
.filter-chip {
|
|
padding: 14rpx 28rpx;
|
|
border-radius: 999rpx;
|
|
background: #ffffff;
|
|
border: 1px solid rgba($brand-primary, 0.14);
|
|
color: #6a7a99;
|
|
font-size: 24rpx;
|
|
box-shadow: 0 6rpx 14rpx rgba($brand-primary, 0.04);
|
|
}
|
|
|
|
.filter-chip--active {
|
|
color: $brand-primary;
|
|
border-color: rgba($brand-primary, 0.24);
|
|
background: rgba($brand-primary, 0.08);
|
|
font-weight: 700;
|
|
}
|
|
|
|
.list-wrapper {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 24rpx;
|
|
}
|
|
|
|
.contract-card {
|
|
padding: 26rpx;
|
|
border-radius: 30rpx;
|
|
animation: slide-up 0.45s ease both;
|
|
}
|
|
|
|
.card-header {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
gap: 18rpx;
|
|
}
|
|
|
|
.card-header-left {
|
|
flex: 1;
|
|
min-width: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.card-icon {
|
|
width: 64rpx;
|
|
height: 64rpx;
|
|
margin-right: 16rpx;
|
|
border-radius: 18rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: $brand-primary;
|
|
font-size: 32rpx;
|
|
background: rgba($brand-primary, 0.1);
|
|
}
|
|
|
|
.card-title-group {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.card-title {
|
|
font-size: 30rpx;
|
|
font-weight: 700;
|
|
color: #1f2d3d;
|
|
}
|
|
|
|
.card-code {
|
|
margin-top: 8rpx;
|
|
font-size: 22rpx;
|
|
color: #8a99a8;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.status-badge {
|
|
flex-shrink: 0;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8rpx;
|
|
padding: 8rpx 18rpx;
|
|
border-radius: 999rpx;
|
|
font-size: 22rpx;
|
|
font-weight: 600;
|
|
border: 1px solid transparent;
|
|
}
|
|
|
|
.status-dot {
|
|
width: 12rpx;
|
|
height: 12rpx;
|
|
border-radius: 50%;
|
|
background: currentColor;
|
|
}
|
|
|
|
.status-pending {
|
|
color: #c98512;
|
|
background: rgba(245, 158, 11, 0.12);
|
|
border-color: rgba(245, 158, 11, 0.16);
|
|
}
|
|
|
|
.status-approved {
|
|
color: $brand-primary;
|
|
background: rgba($brand-primary, 0.1);
|
|
border-color: rgba($brand-primary, 0.16);
|
|
}
|
|
|
|
.status-rejected {
|
|
color: #cf3c3c;
|
|
background: rgba(239, 68, 68, 0.1);
|
|
border-color: rgba(239, 68, 68, 0.14);
|
|
}
|
|
|
|
.meta-strip {
|
|
display: flex;
|
|
gap: 18rpx;
|
|
margin-top: 22rpx;
|
|
}
|
|
|
|
.meta-chip {
|
|
flex: 1;
|
|
min-width: 0;
|
|
padding: 18rpx 20rpx;
|
|
border-radius: 20rpx;
|
|
background: linear-gradient(180deg, #f9fbfd 0%, #f2f6fa 100%);
|
|
border: 1px solid rgba($brand-primary, 0.08);
|
|
}
|
|
|
|
.meta-chip-label {
|
|
font-size: 20rpx;
|
|
color: #91a0ae;
|
|
}
|
|
|
|
.meta-chip-value {
|
|
margin-top: 10rpx;
|
|
font-size: 24rpx;
|
|
font-weight: 600;
|
|
color: #243447;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.detail-panel {
|
|
margin-top: 20rpx;
|
|
padding: 22rpx;
|
|
border-radius: 24rpx;
|
|
background: #f8fbfe;
|
|
border: 1px solid rgba($brand-primary, 0.08);
|
|
}
|
|
|
|
.info-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
gap: 24rpx;
|
|
font-size: 24rpx;
|
|
line-height: 1.7;
|
|
}
|
|
|
|
.info-row + .info-row {
|
|
margin-top: 12rpx;
|
|
}
|
|
|
|
.info-label {
|
|
flex-shrink: 0;
|
|
width: 116rpx;
|
|
color: #7c8da0;
|
|
}
|
|
|
|
.info-value {
|
|
flex: 1;
|
|
text-align: right;
|
|
color: #233244;
|
|
font-weight: 600;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.sign-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-top: 20rpx;
|
|
padding-top: 20rpx;
|
|
border-top: 1px dashed rgba($brand-primary, 0.14);
|
|
}
|
|
|
|
.sign-preview {
|
|
width: 150rpx;
|
|
height: 64rpx;
|
|
padding: 4rpx 8rpx;
|
|
border-radius: 12rpx;
|
|
background: #ffffff;
|
|
box-shadow: inset 0 0 0 1px rgba($brand-primary, 0.08);
|
|
}
|
|
|
|
.sign-preview image {
|
|
width: 100%;
|
|
height: 100%;
|
|
display: block;
|
|
}
|
|
|
|
.card-footer {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
margin-top: 22rpx;
|
|
}
|
|
|
|
.action-btn {
|
|
min-width: 216rpx;
|
|
height: 76rpx;
|
|
padding: 0 28rpx;
|
|
border-radius: 999rpx;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 26rpx;
|
|
font-weight: 700;
|
|
transition: all 0.3s ease;
|
|
color: #ffffff;
|
|
background: linear-gradient(135deg, rgba($brand-primary, 0.9) 0%, $brand-primary 100%);
|
|
box-shadow: 0 6rpx 16rpx rgba($brand-primary, 0.2);
|
|
}
|
|
|
|
.action-btn:active {
|
|
transform: scale(0.96);
|
|
box-shadow: 0 4rpx 10rpx rgba($brand-primary, 0.15);
|
|
}
|
|
|
|
.action-btn--secondary {
|
|
color: #35506f;
|
|
background: #f4f7fa;
|
|
box-shadow: none;
|
|
}
|
|
|
|
.empty-panel {
|
|
margin-top: 24rpx;
|
|
padding: 56rpx 32rpx;
|
|
border-radius: 30rpx;
|
|
text-align: center;
|
|
}
|
|
|
|
.empty-icon {
|
|
width: 132rpx;
|
|
height: 132rpx;
|
|
margin: 0 auto 22rpx;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: $brand-primary;
|
|
font-size: 66rpx;
|
|
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;
|
|
}
|
|
|
|
@keyframes slide-up {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(32rpx);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
</style>
|