788 lines
23 KiB
Vue
788 lines
23 KiB
Vue
<template>
|
||
<view class="application-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 tn-padding" :style="{ paddingTop: vuex_custom_bar_height + 'px', paddingBottom: '48rpx' }">
|
||
<view class="hero-panel tn-margin-bottom-lg" :class="heroThemeClass">
|
||
<view class="hero-top-row">
|
||
<view class="hero-badge" :class="heroBadgeClass">{{ applicationTypeText }}</view>
|
||
<view class="hero-status" :class="['hero-status--' + statusType]">{{ statusText }}</view>
|
||
</view>
|
||
<view class="hero-title tn-margin-top">{{ displayTitle }}</view>
|
||
<view class="hero-meta-row tn-margin-top">
|
||
<view class="hero-meta-chip">
|
||
<text class="tn-icon-time tn-margin-right-xs"></text>
|
||
<text>{{ formatDateTime(detail.createTime) }}</text>
|
||
</view>
|
||
<view class="hero-meta-chip">
|
||
<text class="tn-icon-location tn-margin-right-xs"></text>
|
||
<text>{{ detail.intendedAreaName || '--' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view
|
||
v-if="statusReminderText"
|
||
class="status-reminder-panel tn-margin-bottom-lg"
|
||
:class="{
|
||
'status-reminder-panel--warning': shouldPayDeposit(detail),
|
||
'status-reminder-panel--info': isWaitingRoomAssignment(detail)
|
||
}"
|
||
>
|
||
<view class="status-reminder-panel__icon">
|
||
<text :class="shouldPayDeposit(detail) ? 'tn-icon-warning-fill' : 'tn-icon-time-fill'"></text>
|
||
</view>
|
||
<view class="status-reminder-panel__content">
|
||
<view class="status-reminder-panel__title">{{ t('statusReminderTitle') }}</view>
|
||
<view class="status-reminder-panel__text">{{ statusReminderText }}</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="content-panel tn-margin-bottom-lg">
|
||
<view class="panel-title">{{ t('basicInfoTitle') }}</view>
|
||
<view class="meta-row tn-margin-top">
|
||
<text class="meta-label">{{ t('applicationCodeLabel') }}</text>
|
||
<view class="meta-action-wrap">
|
||
<text class="meta-value">{{ detail.detailCode || '--' }}</text>
|
||
<view v-if="detail.detailCode" class="copy-btn" @click="copyCode">
|
||
<text class="tn-text-xs tn-text-bold">{{ t('copy') }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="meta-row">
|
||
<text class="meta-label">{{ t('applicationTypeLabel') }}</text>
|
||
<text class="meta-value">{{ applicationTypeText }}</text>
|
||
</view>
|
||
<view class="meta-row">
|
||
<text class="meta-label">{{ t('statusLabel') }}</text>
|
||
<text class="meta-value">{{ statusText }}</text>
|
||
</view>
|
||
<view class="meta-row">
|
||
<text class="meta-label">{{ t('applicantLabel') }}</text>
|
||
<text class="meta-value">{{ detail.userName || detail.phone || '--' }}</text>
|
||
</view>
|
||
<view class="meta-row">
|
||
<text class="meta-label">{{ t('apartmentLabel') }}</text>
|
||
<text class="meta-value">{{ detail.intendedAreaName || '--' }}</text>
|
||
</view>
|
||
<view class="meta-row" v-if="detail.intendedRoomTypeName">
|
||
<text class="meta-label">{{ isViewingApplication ? t('intendedRoomTypeLabel') : t('appliedRoomTypeLabel') }}</text>
|
||
<text class="meta-value">{{ detail.intendedRoomTypeName }}</text>
|
||
</view>
|
||
<view class="meta-row">
|
||
<text class="meta-label">{{ isViewingApplication ? t('viewingTimeLabel') : t('leaseTermLabel') }}</text>
|
||
<text class="meta-value">{{ displayDateRange }}</text>
|
||
</view>
|
||
<view class="meta-row">
|
||
<text class="meta-label">{{ t('submittedAtLabel') }}</text>
|
||
<text class="meta-value">{{ formatDateTime(detail.createTime) }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="content-panel tn-margin-bottom-lg" v-if="viewingMeta.length">
|
||
<view class="panel-title">{{ t('viewingInfoTitle') }}</view>
|
||
<view
|
||
v-for="item in viewingMeta"
|
||
:key="item.label"
|
||
class="meta-row tn-margin-top"
|
||
>
|
||
<text class="meta-label">{{ item.label }}</text>
|
||
<text class="meta-value">{{ item.value }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="content-panel tn-margin-bottom-lg">
|
||
<view class="panel-title">{{ isViewingApplication ? t('applicationRemarkTitle') : t('remarkTitle') }}</view>
|
||
<view class="remark-panel tn-margin-top">
|
||
<view
|
||
v-for="(line, index) in remarkLines"
|
||
:key="`${line}-${index}`"
|
||
class="remark-line"
|
||
>
|
||
{{ line }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="action-row">
|
||
<view class="ghost-btn" @click="goBackList">
|
||
<text class="tn-text-bold">{{ t('backList') }}</text>
|
||
</view>
|
||
<view
|
||
v-if="canCancelOrder"
|
||
class="danger-btn"
|
||
:class="{ 'danger-btn--loading': canceling }"
|
||
@tap.stop="handleCancelOrder"
|
||
>
|
||
<text class="tn-text-bold">{{ canceling ? t('cancelOrderLoading') : t('cancelOrder') }}</text>
|
||
</view>
|
||
<view
|
||
v-if="canViewBill"
|
||
class="action-btn"
|
||
@tap.stop="goToBillPage"
|
||
>
|
||
<text class="tn-text-bold">{{ t('viewBill') }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { userDetailCancel } from '@/api/index.js'
|
||
import { getMyApplicationList, saveActiveApplicationCode, saveMyApplicationList } from '@/utils/app-state'
|
||
|
||
function normalizeApplicationList(list = []) {
|
||
if (!Array.isArray(list)) {
|
||
return []
|
||
}
|
||
return list
|
||
.filter(item => item && typeof item === 'object')
|
||
.map((item) => {
|
||
const remark = item && item.remark ? String(item.remark) : ''
|
||
const isViewingApplication = /预约看房申请|看房时间[::]|意向房型[::]|意向区域[::]|viewing request|viewing time[::]|preferred room type[::]|preferred area[::]/i.test(remark)
|
||
return {
|
||
...item,
|
||
isViewingApplication
|
||
}
|
||
})
|
||
}
|
||
|
||
export default {
|
||
i18n: {
|
||
zh: {
|
||
navTitle: '申请详情',
|
||
basicInfoTitle: '基础信息',
|
||
applicationCodeLabel: '申请编号',
|
||
copy: '复制',
|
||
applicationTypeLabel: '申请类型',
|
||
statusLabel: '当前状态',
|
||
applicantLabel: '申请人',
|
||
apartmentLabel: '所属公寓',
|
||
intendedRoomTypeLabel: '意向房型',
|
||
appliedRoomTypeLabel: '申请房型',
|
||
viewingTimeLabel: '预约时间',
|
||
leaseTermLabel: '预计租期',
|
||
submittedAtLabel: '提交时间',
|
||
viewingInfoTitle: '带看信息',
|
||
applicationRemarkTitle: '申请说明',
|
||
remarkTitle: '补充说明',
|
||
backList: '返回列表',
|
||
viewBill: '查看关联账单',
|
||
viewingApplicationTitle: '预约看房申请',
|
||
stayApplicationTitle: '入住申请',
|
||
viewingApplicationType: '预约看房',
|
||
stayApplicationType: '入住申请',
|
||
statusPending: '待审核',
|
||
statusApproved: '已通过',
|
||
statusRejected: '已拒绝',
|
||
statusFinished: '已结束',
|
||
statusProcessing: '处理中',
|
||
statusPendingPayment: '请支付定金',
|
||
statusWaitingRoomAssign: '等待预订中心分配房间',
|
||
statusReminderTitle: '当前进度提醒',
|
||
emptyRemark: '暂无补充说明',
|
||
codeCopied: '申请编号已复制',
|
||
missingBillCode: '申请单编号缺失',
|
||
cancelOrder: '取消订单',
|
||
cancelOrderLoading: '取消中...',
|
||
cancelOrderTitle: '取消订单',
|
||
cancelOrderContent: '取消后将关闭当前未支付的定金订单,是否继续?',
|
||
cancelOrderSuccess: '订单已取消',
|
||
cancelOrderFailed: '取消订单失败',
|
||
viewingTimeKey: '看房时间',
|
||
leaseDurationKey: '预计租住时长',
|
||
budgetKey: '租金预算',
|
||
commuteKey: '工作/通勤地点',
|
||
preferredAreaKey: '意向区域',
|
||
preferredRoomTypeKey: '意向房型',
|
||
paymentMethodKey: '支付方式',
|
||
dateRangeJoiner: ' 至 '
|
||
},
|
||
en: {
|
||
navTitle: 'Application Detail',
|
||
basicInfoTitle: 'Basic Information',
|
||
applicationCodeLabel: 'Application No.',
|
||
copy: 'Copy',
|
||
applicationTypeLabel: 'Application Type',
|
||
statusLabel: 'Status',
|
||
applicantLabel: 'Applicant',
|
||
apartmentLabel: 'Apartment',
|
||
intendedRoomTypeLabel: 'Preferred Room Type',
|
||
appliedRoomTypeLabel: 'Requested Room Type',
|
||
viewingTimeLabel: 'Viewing Time',
|
||
leaseTermLabel: 'Lease Term',
|
||
submittedAtLabel: 'Submitted At',
|
||
viewingInfoTitle: 'Viewing Details',
|
||
applicationRemarkTitle: 'Application Notes',
|
||
remarkTitle: 'Remarks',
|
||
backList: 'Back to List',
|
||
viewBill: 'View Related Bill',
|
||
viewingApplicationTitle: 'Viewing Request',
|
||
stayApplicationTitle: 'Stay Application',
|
||
viewingApplicationType: 'Viewing Request',
|
||
stayApplicationType: 'Stay Application',
|
||
statusPending: 'Pending',
|
||
statusApproved: 'Approved',
|
||
statusRejected: 'Rejected',
|
||
statusFinished: 'Closed',
|
||
statusProcessing: 'Processing',
|
||
statusPendingPayment: 'Please Pay Deposit',
|
||
statusWaitingRoomAssign: 'Waiting for Room Assignment',
|
||
statusReminderTitle: 'Current Progress',
|
||
emptyRemark: 'No additional remarks',
|
||
codeCopied: 'Application number copied',
|
||
missingBillCode: 'Missing application bill number',
|
||
cancelOrder: 'Cancel Order',
|
||
cancelOrderLoading: 'Canceling...',
|
||
cancelOrderTitle: 'Cancel Order',
|
||
cancelOrderContent: 'This will close the current unpaid deposit order. Continue?',
|
||
cancelOrderSuccess: 'Order canceled',
|
||
cancelOrderFailed: 'Failed to cancel order',
|
||
viewingTimeKey: 'Viewing Time',
|
||
leaseDurationKey: 'Expected Stay Duration',
|
||
budgetKey: 'Budget',
|
||
commuteKey: 'Work / Commute Location',
|
||
preferredAreaKey: 'Preferred Area',
|
||
preferredRoomTypeKey: 'Preferred Room Type',
|
||
paymentMethodKey: 'Payment Method',
|
||
dateRangeJoiner: ' to '
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
detailCode: '',
|
||
detail: {},
|
||
canceling: false
|
||
}
|
||
},
|
||
computed: {
|
||
isViewingApplication() {
|
||
return !!(this.detail && this.detail.isViewingApplication)
|
||
},
|
||
displayTitle() {
|
||
if (this.isViewingApplication) {
|
||
return this.detail.intendedRoomTypeName || this.t('viewingApplicationTitle')
|
||
}
|
||
return this.detail.intendedRoomTypeName || this.t('stayApplicationTitle')
|
||
},
|
||
applicationTypeText() {
|
||
return this.isViewingApplication ? this.t('viewingApplicationType') : this.t('stayApplicationType')
|
||
},
|
||
statusText() {
|
||
if (this.language !== 'en' && this.detail && this.detail.statusDesc) {
|
||
return this.detail.statusDesc
|
||
}
|
||
const map = {
|
||
0: this.t('statusPending'),
|
||
10: this.t('statusApproved'),
|
||
20: this.t('statusRejected'),
|
||
99: this.t('statusFinished')
|
||
}
|
||
return map[this.detail.status] || this.t('statusProcessing')
|
||
},
|
||
statusType() {
|
||
if (this.detail.status === 10) {
|
||
return 'approved'
|
||
}
|
||
if (this.detail.status === 20) {
|
||
return 'rejected'
|
||
}
|
||
if (this.detail.status === 99) {
|
||
return 'finished'
|
||
}
|
||
return 'pending'
|
||
},
|
||
heroThemeClass() {
|
||
return this.isViewingApplication ? 'hero-viewing' : 'hero-stay'
|
||
},
|
||
heroBadgeClass() {
|
||
return this.isViewingApplication ? 'badge-viewing' : 'badge-stay'
|
||
},
|
||
displayDateRange() {
|
||
if (!this.detail.start && !this.detail.end) {
|
||
return '--'
|
||
}
|
||
if (this.isViewingApplication) {
|
||
return `${this.formatDateTime(this.detail.start)}${this.t('dateRangeJoiner')}${this.formatDateTime(this.detail.end)}`
|
||
}
|
||
return `${this.formatDate(this.detail.start)}${this.t('dateRangeJoiner')}${this.formatDate(this.detail.end)}`
|
||
},
|
||
remarkLines() {
|
||
const text = this.detail && this.detail.remark ? String(this.detail.remark).trim() : ''
|
||
if (!text) {
|
||
return [this.t('emptyRemark')]
|
||
}
|
||
return text.split(/\r?\n/).filter(Boolean)
|
||
},
|
||
viewingMeta() {
|
||
if (!this.isViewingApplication) {
|
||
return []
|
||
}
|
||
return this.parseViewingRemark()
|
||
},
|
||
statusReminderText() {
|
||
if (this.shouldPayDeposit(this.detail)) {
|
||
return this.t('statusPendingPayment')
|
||
}
|
||
if (this.isWaitingRoomAssignment(this.detail)) {
|
||
return this.t('statusWaitingRoomAssign')
|
||
}
|
||
return ''
|
||
},
|
||
canViewBill() {
|
||
return this.shouldPayDeposit(this.detail)
|
||
&& !this.isViewingApplication
|
||
&& this.detail
|
||
&& this.detail.detailCode
|
||
&& this.statusType === 'pending'
|
||
},
|
||
canCancelOrder() {
|
||
return this.shouldPayDeposit(this.detail)
|
||
&& !this.isViewingApplication
|
||
&& this.detail
|
||
&& this.detail.detailCode
|
||
&& !this.canceling
|
||
}
|
||
},
|
||
onLoad(options = {}) {
|
||
this.detailCode = options.code || ''
|
||
this.restoreDetail(options.payload || '')
|
||
},
|
||
methods: {
|
||
t(key, params) {
|
||
return this.$tm('pkgService.application_detail.application_detail', key, params)
|
||
},
|
||
isPaidApplication(item = {}) {
|
||
return !item.isViewingApplication && Number(item.payStatus) === 10
|
||
},
|
||
shouldPayDeposit(item = {}) {
|
||
return !item.isViewingApplication
|
||
&& Number(item.status) === 0
|
||
&& Number(item.payStatus) !== 10
|
||
},
|
||
isWaitingRoomAssignment(item = {}) {
|
||
return !item.isViewingApplication
|
||
&& Number(item.status) === 0
|
||
&& Number(item.payStatus) === 10
|
||
},
|
||
restoreDetail(payload = '') {
|
||
if (payload) {
|
||
try {
|
||
const parsed = JSON.parse(decodeURIComponent(payload))
|
||
this.detail = parsed && typeof parsed === 'object' ? parsed : {}
|
||
return
|
||
} catch (error) {
|
||
console.error('parse application detail payload error', error)
|
||
}
|
||
}
|
||
|
||
const list = normalizeApplicationList(getMyApplicationList())
|
||
const matched = list.find(item => String(item.detailCode || '') === String(this.detailCode || ''))
|
||
this.detail = matched || {
|
||
detailCode: this.detailCode,
|
||
isViewingApplication: false
|
||
}
|
||
},
|
||
parseViewingRemark() {
|
||
const lines = this.remarkLines
|
||
const targets = [
|
||
{ zh: '看房时间', en: 'Viewing Time' },
|
||
{ zh: '预计租住时长', en: 'Expected Stay Duration' },
|
||
{ zh: '租金预算', en: 'Budget' },
|
||
{ zh: '工作/通勤地点', en: 'Work / Commute Location' },
|
||
{ zh: '意向区域', en: 'Preferred Area' },
|
||
{ zh: '意向房型', en: 'Preferred Room Type' },
|
||
{ zh: '支付方式', en: 'Payment Method' }
|
||
]
|
||
return lines
|
||
.map((line) => {
|
||
const matched = targets.find(item => {
|
||
return line.startsWith(`${item.zh}:`)
|
||
|| line.startsWith(`${item.zh}:`)
|
||
|| line.startsWith(`${item.en}:`)
|
||
|| line.startsWith(`${item.en}:`)
|
||
})
|
||
if (!matched) {
|
||
return null
|
||
}
|
||
const localizedLabelMap = {
|
||
'看房时间': this.t('viewingTimeKey'),
|
||
'预计租住时长': this.t('leaseDurationKey'),
|
||
'租金预算': this.t('budgetKey'),
|
||
'工作/通勤地点': this.t('commuteKey'),
|
||
'意向区域': this.t('preferredAreaKey'),
|
||
'意向房型': this.t('preferredRoomTypeKey'),
|
||
'支付方式': this.t('paymentMethodKey')
|
||
}
|
||
const rawValue = line
|
||
.replace(`${matched.zh}:`, '')
|
||
.replace(`${matched.zh}:`, '')
|
||
.replace(`${matched.en}:`, '')
|
||
.replace(`${matched.en}:`, '')
|
||
return {
|
||
label: localizedLabelMap[matched.zh] || matched.en,
|
||
value: rawValue || '--'
|
||
}
|
||
})
|
||
.filter(Boolean)
|
||
},
|
||
formatDateTime(value) {
|
||
if (!value) {
|
||
return '--'
|
||
}
|
||
const text = String(value).replace('T', ' ')
|
||
return text.length > 16 ? text.slice(0, 16) : text
|
||
},
|
||
formatDate(value) {
|
||
if (!value) {
|
||
return '--'
|
||
}
|
||
return String(value).slice(0, 10)
|
||
},
|
||
copyCode() {
|
||
if (!this.detail.detailCode) {
|
||
return
|
||
}
|
||
uni.setClipboardData({
|
||
data: String(this.detail.detailCode),
|
||
success: () => {
|
||
uni.showToast({
|
||
title: this.t('codeCopied'),
|
||
icon: 'none'
|
||
})
|
||
}
|
||
})
|
||
},
|
||
confirmCancelOrder() {
|
||
return new Promise((resolve) => {
|
||
uni.showModal({
|
||
title: this.t('cancelOrderTitle'),
|
||
content: this.t('cancelOrderContent'),
|
||
confirmColor: '#ef4444',
|
||
success: (res) => resolve(!!(res && res.confirm)),
|
||
fail: () => resolve(false)
|
||
})
|
||
})
|
||
},
|
||
syncCanceledDetailToCache() {
|
||
const list = normalizeApplicationList(getMyApplicationList())
|
||
if (!list.length) {
|
||
return
|
||
}
|
||
const nextList = list.map((item) => {
|
||
if (String(item.detailCode || '') !== String(this.detail.detailCode || '')) {
|
||
return item
|
||
}
|
||
return {
|
||
...item,
|
||
status: 99,
|
||
statusDesc: this.t('statusFinished')
|
||
}
|
||
})
|
||
saveMyApplicationList(nextList)
|
||
},
|
||
handleCancelOrder() {
|
||
if (!this.canCancelOrder || !this.detail.detailCode) {
|
||
return
|
||
}
|
||
this.confirmCancelOrder()
|
||
.then((confirmed) => {
|
||
if (!confirmed) {
|
||
return
|
||
}
|
||
this.canceling = true
|
||
return userDetailCancel({
|
||
detailCode: this.detail.detailCode,
|
||
code: this.detail.detailCode,
|
||
referenceTypeNo: this.detail.detailCode
|
||
}, this.language)
|
||
.then(() => {
|
||
this.detail = {
|
||
...this.detail,
|
||
status: 99,
|
||
statusDesc: this.t('statusFinished')
|
||
}
|
||
this.syncCanceledDetailToCache()
|
||
uni.showToast({
|
||
title: this.t('cancelOrderSuccess'),
|
||
icon: 'success'
|
||
})
|
||
})
|
||
})
|
||
.catch((error) => {
|
||
uni.showToast({
|
||
title: (error && (error.message || error.msg)) || this.t('cancelOrderFailed'),
|
||
icon: 'none'
|
||
})
|
||
})
|
||
.finally(() => {
|
||
this.canceling = false
|
||
})
|
||
},
|
||
goBackList() {
|
||
uni.navigateBack()
|
||
},
|
||
goToBillPage() {
|
||
if (!this.detail.detailCode) {
|
||
uni.showToast({
|
||
title: this.t('missingBillCode'),
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
saveActiveApplicationCode(this.detail.detailCode)
|
||
uni.navigateTo({
|
||
url: `/pkgService/utility_recharge/utility_recharge?referenceTypeNo=${encodeURIComponent(this.detail.detailCode)}`
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.application-detail-page {
|
||
min-height: 100vh;
|
||
background: linear-gradient(180deg, #F0F6FF 0%, #F8FAFF 100%);
|
||
}
|
||
|
||
.hero-panel,
|
||
.content-panel {
|
||
background: #FFFFFF;
|
||
border-radius: 24rpx;
|
||
padding: 32rpx;
|
||
box-shadow: 0 8rpx 20rpx rgba(15, 23, 42, 0.04);
|
||
border: 1px solid rgba($brand-primary, 0.05);
|
||
}
|
||
|
||
.hero-viewing {
|
||
background: linear-gradient(135deg, rgba($brand-primary, 0.12) 0%, rgba($brand-primary, 0.04) 100%);
|
||
}
|
||
|
||
.hero-stay {
|
||
background: linear-gradient(135deg, rgba(64, 203, 242, 0.12) 0%, rgba($brand-primary, 0.04) 100%);
|
||
}
|
||
|
||
.status-reminder-panel {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 20rpx;
|
||
padding: 28rpx 32rpx;
|
||
border-radius: 24rpx;
|
||
box-shadow: 0 8rpx 20rpx rgba(15, 23, 42, 0.04);
|
||
}
|
||
|
||
.status-reminder-panel--warning {
|
||
background: linear-gradient(180deg, rgba(255, 247, 237, 0.96) 0%, rgba(255, 251, 245, 0.98) 100%);
|
||
border: 1px solid rgba(249, 115, 22, 0.16);
|
||
}
|
||
|
||
.status-reminder-panel--info {
|
||
background: linear-gradient(180deg, rgba(239, 246, 255, 0.96) 0%, rgba(248, 250, 252, 0.98) 100%);
|
||
border: 1px solid rgba(59, 130, 246, 0.14);
|
||
}
|
||
|
||
.status-reminder-panel__icon {
|
||
width: 68rpx;
|
||
height: 68rpx;
|
||
border-radius: 20rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
font-size: 34rpx;
|
||
}
|
||
|
||
.status-reminder-panel--warning .status-reminder-panel__icon {
|
||
color: #C2410C;
|
||
background: rgba(249, 115, 22, 0.12);
|
||
}
|
||
|
||
.status-reminder-panel--info .status-reminder-panel__icon {
|
||
color: #1D4ED8;
|
||
background: rgba(59, 130, 246, 0.12);
|
||
}
|
||
|
||
.status-reminder-panel__content {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.status-reminder-panel__title {
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
color: #0F172A;
|
||
}
|
||
|
||
.status-reminder-panel__text {
|
||
margin-top: 8rpx;
|
||
font-size: 30rpx;
|
||
line-height: 1.6;
|
||
font-weight: 600;
|
||
color: #334155;
|
||
}
|
||
|
||
.hero-top-row,
|
||
.hero-meta-row,
|
||
.meta-row,
|
||
.action-row {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.hero-top-row,
|
||
.meta-row {
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.hero-meta-row,
|
||
.action-row {
|
||
gap: 16rpx;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.hero-badge,
|
||
.hero-status,
|
||
.hero-meta-chip,
|
||
.copy-btn,
|
||
.ghost-btn,
|
||
.action-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.hero-badge,
|
||
.hero-status {
|
||
padding: 10rpx 22rpx;
|
||
border-radius: 999rpx;
|
||
font-size: 22rpx;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.badge-viewing,
|
||
.badge-stay {
|
||
background: rgba($brand-primary, 0.14);
|
||
color: $brand-primary;
|
||
}
|
||
|
||
.hero-status--pending {
|
||
background: rgba(245, 158, 11, 0.12);
|
||
color: #B45309;
|
||
}
|
||
|
||
.hero-status--approved {
|
||
background: rgba($brand-primary, 0.12);
|
||
color: $brand-primary;
|
||
}
|
||
|
||
.hero-status--rejected {
|
||
background: rgba(239, 68, 68, 0.12);
|
||
color: #B91C1C;
|
||
}
|
||
|
||
.hero-status--finished {
|
||
background: rgba(100, 116, 139, 0.12);
|
||
color: #475569;
|
||
}
|
||
|
||
.hero-title,
|
||
.panel-title {
|
||
font-size: 34rpx;
|
||
font-weight: 700;
|
||
color: #1A2233;
|
||
}
|
||
|
||
.hero-meta-chip {
|
||
height: 56rpx;
|
||
padding: 0 20rpx;
|
||
border-radius: 999rpx;
|
||
background: rgba(255, 255, 255, 0.78);
|
||
color: #51607A;
|
||
font-size: 22rpx;
|
||
}
|
||
|
||
.meta-row {
|
||
padding: 18rpx 0;
|
||
border-bottom: 1px dashed rgba($brand-primary, 0.14);
|
||
}
|
||
|
||
.meta-row:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.meta-label {
|
||
color: #6A7A99;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.meta-value {
|
||
max-width: 62%;
|
||
text-align: right;
|
||
color: #1A2233;
|
||
font-size: 26rpx;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.meta-action-wrap {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.copy-btn {
|
||
height: 48rpx;
|
||
padding: 0 18rpx;
|
||
border-radius: 999rpx;
|
||
background: rgba($brand-primary, 0.1);
|
||
color: $brand-primary;
|
||
}
|
||
|
||
.remark-panel {
|
||
padding: 24rpx;
|
||
border-radius: 20rpx;
|
||
background: rgba($brand-primary, 0.03);
|
||
border: 1rpx solid rgba($brand-primary, 0.12);
|
||
}
|
||
|
||
.remark-line {
|
||
color: #1A2233;
|
||
line-height: 1.8;
|
||
font-size: 27rpx;
|
||
}
|
||
|
||
.ghost-btn,
|
||
.danger-btn,
|
||
.action-btn {
|
||
flex: 1;
|
||
min-height: 92rpx;
|
||
border-radius: 999rpx;
|
||
}
|
||
|
||
.ghost-btn {
|
||
background: #FFFFFF;
|
||
border: 1rpx solid rgba($brand-primary, 0.2);
|
||
color: $brand-primary;
|
||
box-shadow: 0 6rpx 16rpx rgba($brand-primary, 0.06);
|
||
}
|
||
|
||
.danger-btn {
|
||
background: #FFFFFF;
|
||
border: 1rpx solid rgba(239, 68, 68, 0.22);
|
||
color: #DC2626;
|
||
box-shadow: 0 6rpx 16rpx rgba(239, 68, 68, 0.08);
|
||
}
|
||
|
||
.danger-btn--loading {
|
||
opacity: 0.72;
|
||
}
|
||
|
||
.action-btn {
|
||
background: linear-gradient(135deg, rgba($brand-primary, 0.9) 0%, $brand-primary 100%);
|
||
color: #FFFFFF;
|
||
box-shadow: 0 12rpx 32rpx rgba($brand-primary, 0.25);
|
||
}
|
||
</style>
|