feat: align completed order detail modal with H5

This commit is contained in:
zhangjianjun 2026-08-18 18:29:06 +08:00
parent 3a42f65c5a
commit b31c68dcfc
9 changed files with 677 additions and 56 deletions

View File

@ -6,6 +6,8 @@ export const MOBILE_ASSET_BASE = `${MAP_ASSET_BASE}/images-mobile`
export const BANNER_BG = `${MOBILE_ASSET_BASE}/bg-banner.png` export const BANNER_BG = `${MOBILE_ASSET_BASE}/bg-banner.png`
export const CARD_HEADER = `${MOBILE_ASSET_BASE}/card_header.png` export const CARD_HEADER = `${MOBILE_ASSET_BASE}/card_header.png`
export const PAGE_TITLE = '网上快办平台' export const PAGE_TITLE = '网上快办平台'
export const RECEIPT_STAMP_DONE = `${STATIC_CDN}/images/oderDetailCallBackSuccess.png`
export const RECEIPT_STAMP_PENDING = `${STATIC_CDN}/images/oderDetailCallBack.png`
export const ENTITY_LABELS = { export const ENTITY_LABELS = {
entityName: '门店名称', entityName: '门店名称',

85
src/lib/detail.test.ts Normal file
View File

@ -0,0 +1,85 @@
import { describe, expect, it } from 'vitest'
import { getReceiptMeta, numberToUpperCase, pickGoodsType, shouldShowReceipt, splitOrderFiles } from './detail'
describe('splitOrderFiles', () => {
it('separates screenshot images from pdf reports', () => {
expect(splitOrderFiles([
{ id: '1', url: 'https://cdn.example/a.jpg' },
{ id: '2', url: 'http://cdn.example/report.pdf' },
{ id: '3', url: 'https://cdn.example/b.png?ver=1' },
{ id: '4', url: 'https://cdn.example/note.PDF' },
])).toEqual({
images: [
{ id: '1', url: 'https://cdn.example/a.jpg' },
{ id: '3', url: 'https://cdn.example/b.png?ver=1' },
],
pdfs: [
{ id: '2', url: 'https://cdn.example/report.pdf' },
{ id: '4', url: 'https://cdn.example/note.PDF' },
],
})
})
it('returns empty groups when files are missing', () => {
expect(splitOrderFiles()).toEqual({ images: [], pdfs: [] })
expect(splitOrderFiles([])).toEqual({ images: [], pdfs: [] })
})
})
describe('shouldShowReceipt', () => {
it('shows receipt for corp goods types and hides it for map_label', () => {
expect(shouldShowReceipt('license_year')).toBe(true)
expect(shouldShowReceipt('license_destory')).toBe(true)
expect(shouldShowReceipt('credit_repair')).toBe(true)
expect(shouldShowReceipt('map_label')).toBe(false)
expect(shouldShowReceipt('unknown')).toBe(false)
expect(shouldShowReceipt()).toBe(false)
})
})
describe('getReceiptMeta', () => {
it('returns title and remark for each corp goods type', () => {
expect(getReceiptMeta('license_year').title).toBe('信息申报回执单')
expect(getReceiptMeta('license_year').description).toContain('年报国家公示官网')
expect(getReceiptMeta('license_destory').title).toBe('工商信息注销回执单')
expect(getReceiptMeta('license_destory').description).toContain('登记状态')
expect(getReceiptMeta('credit_repair').title).toBe('工商信息修复回执单')
expect(getReceiptMeta('credit_repair').description).toContain('creditchina.gov.cn')
})
it('falls back to goods name when type is unknown', () => {
expect(getReceiptMeta('unknown', '自定义业务')).toEqual({
title: '自定义业务回执单',
description: '',
})
})
})
describe('numberToUpperCase', () => {
it('converts receipt amounts the same way as H5', () => {
expect(numberToUpperCase('')).toBe('')
expect(numberToUpperCase('0')).toBe('零元整')
expect(numberToUpperCase('1')).toBe('壹元整')
expect(numberToUpperCase('10')).toBe('壹拾元整')
expect(numberToUpperCase('100.5')).toBe('壹佰元伍角')
expect(numberToUpperCase('123.45')).toBe('壹佰贰拾叁元肆角伍分')
})
})
describe('pickGoodsType', () => {
it('prefers prev_info goods_type then first goods item', () => {
expect(pickGoodsType({
data: {
prev_info: { goods_type: 'license_year' },
goods: [{ goods_type: 'map_label' }],
},
})).toBe('license_year')
expect(pickGoodsType({
prevInfo: { type: 'credit_repair' },
})).toBe('credit_repair')
expect(pickGoodsType({
data: { goods: [{ goods_type: 'map_label' }] },
})).toBe('map_label')
expect(pickGoodsType({})).toBe('')
})
})

146
src/lib/detail.ts Normal file
View File

@ -0,0 +1,146 @@
export const CORP_GOODS_TYPES = ['license_year', 'license_destory', 'credit_repair'] as const
export type CorpGoodsType = (typeof CORP_GOODS_TYPES)[number]
const RECEIPT_META: Record<CorpGoodsType, { title: string, description: string }> = {
credit_repair: {
title: '工商信息修复回执单',
description:
'信用网站官网 https://www.creditchina.gov.cn/\n' +
'搜索企业: 在首页搜索框输入公司全称或统一社会信用代码进行查询\n' +
'查看信用信息: 进入企业信用信息详情页;\n' +
'重点关注:\n' +
'1."行政处罚"栏目: 查看该条被修复的行政处罚信息状态。如果修复成功,通常会显示"已修复"、"已信用修复"、"已撤下"等状态标识,或者该条信息已不再显示在公示列表中;\n' +
'2."信用修复"栏目(如果有): 有些平台会单独列出企业的信用修复申请记录及状态(如:申请中、审核通过/修复成功、审核不通过);\n' +
'3.查看"失信信息"或"重点关注名单" 如果该处罚曾导致企业被列入失信名单或重点关注名单,确认这些名单中是否已移除该公司;',
},
license_destory: {
title: '工商信息注销回执单',
description:
'访问官网http://www.gsxt.gov.cn输入企业名称或统一社会信用代码进行搜索。\n' +
'在企业详情页面查看“登记状态”栏是否标注为“注销”\n' +
'注意注销信息通常需1-3天完成公示更新建议完成注销流程后次日查询。',
},
license_year: {
title: '信息申报回执单',
description:
'年报国家公示官网https://www.gsxt.gov.cn\n' +
'1.年报公示日期为7个工作日到期后可自行登陆国家企业信用信息官网查询\n' +
'2.您的个人信息(姓名、电话、身份证),只用于您办理业务时使用;\n' +
'3.企业一站式服务平台为第三方企业服务公司,办理业务方便快捷,办理不成功退全部费用;',
},
}
export function isCorpGoodsType (goodsType?: string): goodsType is CorpGoodsType {
return (CORP_GOODS_TYPES as readonly string[]).includes(goodsType || '')
}
export function shouldShowReceipt (goodsType?: string) {
return isCorpGoodsType(goodsType)
}
export function pickGoodsType (payload: any): string {
const data = payload?.data ?? payload ?? {}
const prev = data.prev_info ?? data.prevInfo ?? payload?.prevInfo ?? {}
const fromPrev = String(prev.goods_type || prev.type || '').trim()
if (fromPrev) return fromPrev
const goods = Array.isArray(data.goods) ? data.goods : []
const first = goods[0] || {}
return String(first.goods_type || first.type || '').trim()
}
export function getReceiptMeta (goodsType?: string, goodsName?: string) {
if (isCorpGoodsType(goodsType)) {
return RECEIPT_META[goodsType]
}
const name = String(goodsName || '').trim()
return {
title: name ? `${name}回执单` : '回执单',
description: '',
}
}
export type OrderFile = {
id?: string
url?: string
}
function isPdfUrl (url: string) {
return /\.pdf(?:$|[?#])/i.test(url)
}
export function splitOrderFiles (files?: OrderFile[] | null) {
const images: OrderFile[] = []
const pdfs: OrderFile[] = []
for (const file of files || []) {
const url = typeof file?.url === 'string' ? file.url.replace(/^http:\/\//i, 'https://') : ''
if (!url) continue
const nextFile = url === file.url ? file : { ...file, url }
if (isPdfUrl(url)) pdfs.push(nextFile)
else images.push(nextFile)
}
return { images, pdfs }
}
export function numberToUpperCase (money: unknown): string {
const cnNums = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']
const cnIntRadice = ['', '拾', '佰', '仟']
const cnIntUnits = ['', '万', '亿', '兆']
const cnDecUnits = ['角', '分']
const cnInteger = '整'
const cnIntLast = '元'
if (money === '' || money === undefined || money === null) return ''
const text = String(money)
const cashText = text.charAt(0) === '-' ? text.slice(1) : text
let cash = parseFloat(cashText)
if (cash === 0) return `${cnNums[0]}${cnIntLast}${cnInteger}`
if (!Number.isFinite(cash)) return ''
const [integerNum, rawDecimal = ''] = cash.toString().split('.')
const decimalNum = rawDecimal.substr(0, 2)
let chineseStr = ''
if (integerNum !== '-' && parseInt(integerNum, 10) > 0) {
const intLen = integerNum.length
let zero = 0
for (let i = 0; i < intLen; i++) {
const intChar = integerNum.substr(i, 1)
const intSlen = intLen - i - 1
const divided = intSlen / 4
const remain = intSlen % 4
if (intChar === '0') {
zero += 1
} else {
if (zero > 0) chineseStr += cnNums[0]
zero = 0
chineseStr += cnNums[parseInt(intChar, 10)] + cnIntRadice[remain]
}
if (remain === 0 && divided > 0) {
chineseStr += cnIntUnits[divided]
}
}
chineseStr += cnIntLast
}
if (decimalNum) {
for (let i = 0; i < decimalNum.length; i++) {
const decChar = decimalNum.substr(i, 1)
if (decChar !== '0') {
chineseStr += cnNums[parseInt(decChar, 10)] + cnDecUnits[i]
}
if (decChar === '0' && parseInt(integerNum, 10) > 0) {
chineseStr += cnNums[0] + cnDecUnits[i]
}
}
} else {
chineseStr += cnInteger
}
return chineseStr
}

View File

@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'
import { import {
buildResubmitPayload, buildResubmitPayload,
filterMapLabelOrders, filterMapLabelOrders,
filterOrdersForProduct,
formatMapCoordinate, formatMapCoordinate,
getOrderAddressName, getOrderAddressName,
getOrderExtraImages, getOrderExtraImages,
@ -40,6 +41,31 @@ describe('filterMapLabelOrders', () => {
}) })
}) })
describe('filterOrdersForProduct', () => {
it('keeps every order when querying from a corp goods type', () => {
const orders = filterOrdersForProduct('license_year', [
mapOrder,
{ ...mapOrder, order_id: '5', goods_type: 'license_year' },
] as any)
expect(orders.map((item) => item.order_id)).toEqual(['4', '5'])
expect(orders[0].extra.goods_param).toEqual({
'client.goods.icon': ['https://cdn.example/i.png'],
})
})
it('keeps only matching orders for map_label and unknown types', () => {
const mixed = [
mapOrder,
{ ...mapOrder, order_id: '5', goods_type: 'license_year' },
] as any
expect(filterOrdersForProduct('map_label', mixed).map((item) => item.order_id)).toEqual(['4'])
expect(filterOrdersForProduct('', mixed).map((item) => item.order_id)).toEqual(['4'])
expect(filterOrdersForProduct('unknown', mixed)).toEqual([])
})
})
describe('order address helpers', () => { describe('order address helpers', () => {
it('splits named address and coordinate address', () => { it('splits named address and coordinate address', () => {
expect(getOrderAddressName(mapOrder as any)).toBe('南京路1号') expect(getOrderAddressName(mapOrder as any)).toBe('南京路1号')

View File

@ -1,3 +1,5 @@
import { isCorpGoodsType } from './detail'
export type UploadedImage = { export type UploadedImage = {
id: string id: string
url: string url: string
@ -79,21 +81,30 @@ export function getGoodsIcons (order: OrderLike) {
return Array.isArray(icons) ? icons.filter((item) => typeof item === 'string') : [] return Array.isArray(icons) ? icons.filter((item) => typeof item === 'string') : []
} }
export function filterMapLabelOrders<T extends OrderLike> (orders: T[] = []) { export function normalizeOrders<T extends OrderLike> (orders: T[] = []) {
return orders return orders.map((item) => {
.filter((item) => item.goods_type === 'map_label') const nextItem = { ...item, extra: { ...(item.extra || {}) } }
.map((item) => { const goodsParam = nextItem.extra.goods_param
const nextItem = { ...item, extra: { ...(item.extra || {}) } } if (typeof goodsParam === 'string') {
const goodsParam = nextItem.extra.goods_param try {
if (typeof goodsParam === 'string') { nextItem.extra.goods_param = JSON.parse(goodsParam)
try { } catch {
nextItem.extra.goods_param = JSON.parse(goodsParam) nextItem.extra.goods_param = null
} catch {
nextItem.extra.goods_param = null
}
} }
return nextItem }
}) return nextItem
})
}
export function filterOrdersForProduct<T extends OrderLike> (goodsType: string, orders: T[] = []) {
const normalized = normalizeOrders(orders)
if (isCorpGoodsType(goodsType)) return normalized
const target = goodsType || 'map_label'
return normalized.filter((item) => item.goods_type === target)
}
export function filterMapLabelOrders<T extends OrderLike> (orders: T[] = []) {
return filterOrdersForProduct('map_label', orders)
} }
export function getOrderExtraImages (order: OrderLike | null | undefined, keys: string[]): UploadedImage[] { export function getOrderExtraImages (order: OrderLike | null | undefined, keys: string[]): UploadedImage[] {

View File

@ -1,5 +1,6 @@
import Taro from '@tarojs/taro' import Taro from '@tarojs/taro'
import { mergeServiceConfig } from './contact' import { mergeServiceConfig } from './contact'
import { pickGoodsType } from './detail'
import { resolveLaunchQuery, type LaunchQuery } from './launch' import { resolveLaunchQuery, type LaunchQuery } from './launch'
import { hostFromOrigin, resolveApiOrigin } from './origin' import { hostFromOrigin, resolveApiOrigin } from './origin'
import { createDeviceId } from './uuid' import { createDeviceId } from './uuid'
@ -9,6 +10,7 @@ const SESSION_KEY = 'corpMpSession'
export type SessionState = LaunchQuery & { export type SessionState = LaunchQuery & {
deviceId: string deviceId: string
config: Record<string, any> config: Record<string, any>
goodsType: string
} }
const EMPTY_SESSION: SessionState = { const EMPTY_SESSION: SessionState = {
@ -22,6 +24,7 @@ const EMPTY_SESSION: SessionState = {
phone: '', phone: '',
deviceId: '', deviceId: '',
config: {}, config: {},
goodsType: '',
} }
function readStorage (): Partial<SessionState> { function readStorage (): Partial<SessionState> {
@ -96,10 +99,12 @@ export function applyCorpInit (payload: any) {
const data = payload?.data ?? payload const data = payload?.data ?? payload
const token = typeof data?.token === 'string' ? data.token.trim() : '' const token = typeof data?.token === 'string' ? data.token.trim() : ''
const packageId = data?.package === undefined || data?.package === null ? '' : String(data.package) const packageId = data?.package === undefined || data?.package === null ? '' : String(data.package)
const goodsType = pickGoodsType(payload)
const patch: Partial<SessionState> = { const patch: Partial<SessionState> = {
config: mergeServiceConfig(getSession().config, payload), config: mergeServiceConfig(getSession().config, payload),
} }
if (token) patch.token = token if (token) patch.token = token
if (packageId) patch.packageId = packageId if (packageId) patch.packageId = packageId
if (goodsType) patch.goodsType = goodsType
return setSession(patch) return setSession(patch)
} }

167
src/pages/index/detail.tsx Normal file
View File

@ -0,0 +1,167 @@
import { Image, Text, View } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { RECEIPT_STAMP_DONE, RECEIPT_STAMP_PENDING } from '@/lib/cdn'
import {
getReceiptMeta,
numberToUpperCase,
shouldShowReceipt,
splitOrderFiles,
type OrderFile,
} from '@/lib/detail'
import type { OrderLike } from '@/lib/order'
type Props = {
order: OrderLike
onCancel: () => void
}
export default function DetailModal ({ order, onCancel }: Props) {
const showReceipt = shouldShowReceipt(order.goods_type)
const receipt = getReceiptMeta(order.goods_type, order.goods_name)
const files = order.process_status === '2' ? splitOrderFiles(order.files) : { images: [], pdfs: [] }
const empty = !showReceipt && files.images.length === 0 && files.pdfs.length === 0
const imageUrls = files.images.map((file) => String(file.url))
return (
<View className='detail-mask' onClick={onCancel}>
<View className='detail-dialog' onClick={(event) => event.stopPropagation()}>
<View className='detail-dialog__head'>
<Text className='detail-dialog__title'></Text>
<View className='detail-dialog__close' onClick={onCancel}>
<Text className='detail-dialog__close-icon'>×</Text>
</View>
</View>
<View className='detail-dialog__body'>
{showReceipt ? <ReceiptCard order={order} title={receipt.title} description={receipt.description} /> : null}
{files.images.length ? (
<View className='detail-dialog__section'>
<Text className='detail-dialog__section-title'></Text>
{files.images.map((file, index) => (
<Image
key={file.id || file.url || index}
className='detail-dialog__shot'
src={String(file.url)}
mode='widthFix'
onClick={() => previewImages(imageUrls, String(file.url))}
/>
))}
</View>
) : null}
{files.pdfs.length ? (
<View className='detail-dialog__section'>
<Text className='detail-dialog__section-title'></Text>
{files.pdfs.map((file, index) => (
<View
key={file.id || file.url || index}
className='detail-dialog__pdf'
onClick={() => void openPdf(file)}
>
<Text className='detail-dialog__pdf-name'>{files.pdfs.length > 1 ? index + 1 : ''}</Text>
</View>
))}
</View>
) : null}
{empty ? (
<Text className='detail-dialog__empty'></Text>
) : null}
</View>
</View>
</View>
)
}
function ReceiptCard ({
order,
title,
description,
}: {
order: OrderLike
title: string
description: string
}) {
const stamp = order.process_status === '2' ? RECEIPT_STAMP_DONE : RECEIPT_STAMP_PENDING
const remarkLines = description.split('\n').filter(Boolean)
return (
<View className='detail-dialog__section'>
<Text className='detail-dialog__section-title'></Text>
<View className='detail-receipt'>
<Text className='detail-receipt__title'>{title}</Text>
<View className='detail-receipt__rule' />
<View className='detail-receipt__rule detail-receipt__rule--thin' />
<View className='detail-receipt__box'>
<View className='detail-receipt__inner'>
<View className='detail-receipt__fields'>
<Image className='detail-receipt__stamp' src={stamp} />
<ReceiptLine label='名称:' value={order.entity_name || ''} />
<ReceiptLine label='申报内容:' value={order.goods_name || ''} />
<ReceiptLine label='法人:' value={order.entity_legal || ''} />
<ReceiptLine label='已受理时间:' value={order.pay_time || ''} />
<ReceiptLine label='统一社会信用代码:' value={order.entity_id || ''} />
<ReceiptLine label='数量:' value='1' />
<ReceiptLine label='支付时间:' value={order.pay_time || ''} />
<ReceiptLine label='金额(小写):' value={order.total_fee ? `${order.total_fee}` : ''} />
<ReceiptLine label='订单号码:' value={order.out_trade_no || ''} />
<ReceiptLine label='金额(大写):' value={numberToUpperCase(order.total_fee)} />
</View>
<View className='detail-receipt__remark'>
<View className='detail-receipt__remark-label'>
<Text className='detail-receipt__remark-label-text'></Text>
</View>
<View className='detail-receipt__remark-body'>
{remarkLines.map((line, index) => (
<Text key={`${index}-${line}`} className='detail-receipt__remark-line'>{line}</Text>
))}
</View>
</View>
</View>
</View>
</View>
</View>
)
}
function ReceiptLine ({ label, value }: { label: string, value: string }) {
return (
<View className='detail-receipt__row'>
<Text className='detail-receipt__label'>{label}</Text>
<Text className='detail-receipt__value'>{value}</Text>
</View>
)
}
function previewImages (urls: string[], current: string) {
if (!urls.length) return
Taro.previewImage({ current, urls })
}
async function openPdf (file: OrderFile) {
const url = String(file.url || '')
if (!url) return
try {
Taro.showLoading({ title: '打开中...', mask: true })
if (Taro.getEnv() === Taro.ENV_TYPE.WEAPP) {
const downloaded = await Taro.downloadFile({ url })
if (downloaded.statusCode !== 200 || !downloaded.tempFilePath) {
throw new Error('download failed')
}
await Taro.openDocument({
filePath: downloaded.tempFilePath,
fileType: 'pdf',
showMenu: true,
})
return
}
Taro.setClipboardData({ data: url })
} catch {
Taro.showToast({ title: '报告打开失败', icon: 'none' })
} finally {
Taro.hideLoading()
}
}

View File

@ -38,7 +38,11 @@
.check-card { .check-card {
position: relative; position: relative;
z-index: 1; z-index: 1;
margin: -80px 24px 24px; margin: 0 24px 48px;
}
.check-card--lift {
margin-top: -80px;
} }
.check-card__header { .check-card__header {
@ -272,39 +276,206 @@
background: rgba(0, 0, 0, 0.8); background: rgba(0, 0, 0, 0.8);
} }
.detail-mask {
display: flex;
align-items: center;
justify-content: center;
}
.detail-dialog { .detail-dialog {
position: absolute; box-sizing: border-box;
top: 50%; width: 686px;
right: 32px; max-height: 80vh;
left: 32px; overflow: hidden;
padding: 32px 24px 24px; border-radius: 20px;
border-radius: 16px;
background: #fff; background: #fff;
transform: translateY(-50%); }
.detail-dialog__head {
position: relative;
display: flex;
align-items: center;
justify-content: center;
height: 88px;
} }
.detail-dialog__title { .detail-dialog__title {
display: block;
margin-bottom: 24px;
color: #222; color: #222;
font-size: 32px; font-size: 34px;
font-weight: 600; font-weight: 700;
text-align: center; line-height: 44px;
} }
.detail-dialog__close { .detail-dialog__close {
margin-top: 32px; position: absolute;
height: 80px; top: 20px;
border: 0; right: 20px;
border-radius: 8px; display: flex;
background: #0261fc; align-items: center;
color: #fff; justify-content: center;
font-size: 30px; width: 48px;
line-height: 80px; height: 48px;
} }
.detail-dialog__close::after { .detail-dialog__close-icon {
border: 0; color: #999;
font-size: 48px;
line-height: 40px;
}
.detail-dialog__body {
box-sizing: border-box;
max-height: calc(80vh - 88px);
padding: 0 32px 40px;
overflow-y: auto;
}
.detail-dialog__section + .detail-dialog__section {
margin-top: 40px;
}
.detail-dialog__section-title {
display: block;
margin-bottom: 20px;
color: #1a1a1a;
font-size: 34px;
line-height: 44px;
}
.detail-dialog__shot {
display: block;
width: 100%;
margin-bottom: 20px;
background: #f3f3f3;
}
.detail-dialog__pdf {
display: flex;
align-items: center;
height: 80px;
margin-bottom: 16px;
padding: 0 24px;
border-radius: 12px;
background: #eaf3ff;
}
.detail-dialog__pdf-name {
color: #0261fc;
font-size: 28px;
}
.detail-dialog__empty {
display: block;
padding: 40px 0;
color: #999;
font-size: 28px;
text-align: center;
}
.detail-receipt {
display: flex;
flex-direction: column;
align-items: center;
padding-bottom: 8px;
}
.detail-receipt__title {
color: #1a1a1a;
font-size: 36px;
line-height: 52px;
text-align: center;
}
.detail-receipt__rule {
width: 280px;
height: 4px;
margin-top: 8px;
background: #1a1a1a;
}
.detail-receipt__rule--thin {
height: 2px;
margin-top: 4px;
}
.detail-receipt__box {
box-sizing: border-box;
width: 100%;
margin-top: 24px;
padding: 6px;
border: 2px solid #1a1a1a;
}
.detail-receipt__inner {
border: 2px solid #1a1a1a;
}
.detail-receipt__fields {
position: relative;
padding: 28px 20px 20px;
}
.detail-receipt__stamp {
position: absolute;
top: -16px;
right: 8px;
width: 160px;
height: 160px;
}
.detail-receipt__row {
display: flex;
align-items: flex-start;
margin-bottom: 10px;
}
.detail-receipt__label {
flex-shrink: 0;
color: #1a1a1a;
font-size: 26px;
line-height: 36px;
}
.detail-receipt__value {
flex: 1;
min-width: 0;
color: #1a1a1a;
font-size: 24px;
line-height: 36px;
word-break: break-all;
}
.detail-receipt__remark {
display: flex;
border-top: 2px solid #1a1a1a;
}
.detail-receipt__remark-label {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
border-right: 2px solid #1a1a1a;
}
.detail-receipt__remark-label-text {
color: #1a1a1a;
font-size: 22px;
writing-mode: vertical-lr;
letter-spacing: 4px;
}
.detail-receipt__remark-body {
flex: 1;
padding: 16px 16px 20px;
}
.detail-receipt__remark-line {
display: block;
color: #666;
font-size: 22px;
line-height: 32px;
white-space: pre-wrap;
} }
.resubmit-mask { .resubmit-mask {

View File

@ -3,10 +3,12 @@ import Taro, { useDidShow, useLoad } from '@tarojs/taro'
import { useState } from 'react' import { useState } from 'react'
import { getOrdersByPhone } from '@/api/pay' import { getOrdersByPhone } from '@/api/pay'
import { bootstrapSession } from '@/lib/bootstrap' import { bootstrapSession } from '@/lib/bootstrap'
import { getSession } from '@/lib/session'
import { BANNER_BG, CARD_HEADER, CORP_ASSET_BASE, ENTITY_LABELS, HOME_ICON_BASE, PAGE_TITLE } from '@/lib/cdn' import { BANNER_BG, CARD_HEADER, CORP_ASSET_BASE, ENTITY_LABELS, HOME_ICON_BASE, PAGE_TITLE } from '@/lib/cdn'
import { pickServiceContact, type ServiceContact } from '@/lib/contact' import { pickServiceContact, type ServiceContact } from '@/lib/contact'
import { isCorpGoodsType } from '@/lib/detail'
import { import {
filterMapLabelOrders, filterOrdersForProduct,
getGoodsIcons, getGoodsIcons,
getOrderAddressName, getOrderAddressName,
parseCoordinate, parseCoordinate,
@ -17,6 +19,7 @@ import { isValidPhone } from '@/lib/phone'
import { getStatusMeta, PAY_ICON } from '@/lib/status' import { getStatusMeta, PAY_ICON } from '@/lib/status'
import { consumeMapPickerResult, type MapPickerResult } from '@/pages/map/picker' import { consumeMapPickerResult, type MapPickerResult } from '@/pages/map/picker'
import ContactModal, { ContactEntry } from './contact' import ContactModal, { ContactEntry } from './contact'
import DetailModal from './detail'
import ResubmitDrawer from './resubmit' import ResubmitDrawer from './resubmit'
import './index.scss' import './index.scss'
@ -68,7 +71,7 @@ export default function Index () {
setChecking(true) setChecking(true)
try { try {
const res = await getOrdersByPhone(phone) const res = await getOrdersByPhone(phone)
const nextOrders = filterMapLabelOrders(res.data || []) const nextOrders = filterOrdersForProduct(getSession().goodsType, res.data || [])
setOrders(nextOrders) setOrders(nextOrders)
setSearched(true) setSearched(true)
if (!nextOrders.length) { if (!nextOrders.length) {
@ -102,7 +105,7 @@ export default function Index () {
</View> </View>
<ContactEntry onClick={() => setShowContact(true)} /> <ContactEntry onClick={() => setShowContact(true)} />
<View className='check-card'> <View className='check-card check-card--lift'>
<View className='check-card__header'> <View className='check-card__header'>
<Image className='check-card__header-bg' src={CARD_HEADER} mode='scaleToFill' /> <Image className='check-card__header-bg' src={CARD_HEADER} mode='scaleToFill' />
<Text className='check-card__header-title'></Text> <Text className='check-card__header-title'></Text>
@ -169,17 +172,7 @@ export default function Index () {
) : null} ) : null}
{detailOrder ? ( {detailOrder ? (
<View className='detail-mask' onClick={() => setDetailOrder(null)}> <DetailModal order={detailOrder} onCancel={() => setDetailOrder(null)} />
<View className='detail-dialog' onClick={(event) => event.stopPropagation()}>
<Text className='detail-dialog__title'></Text>
<InfoLine label={`${ENTITY_LABELS.entityName}`} value={detailOrder.entity_name || '-'} />
<InfoLine label='联系电话:' value={detailOrder.entity_phone || '-'} />
<InfoLine label={`${ENTITY_LABELS.entityAddress}`} value={getOrderAddressName(detailOrder) || '-'} />
<InfoLine label={`${ENTITY_LABELS.entityMapLocation}`} value={detailOrder.extra?.entity_address || '-'} />
<InfoLine label='办理说明:' value='各大平台门店管理审核时间一般为1-7个工作日审核通过后可在对应地图平台查询。' />
<Button className='detail-dialog__close' onClick={() => setDetailOrder(null)}></Button>
</View>
</View>
) : null} ) : null}
<ContactModal <ContactModal
@ -223,7 +216,9 @@ function OrderCard ({
const status = getStatusMeta(order.process_status, order.process_status_name) const status = getStatusMeta(order.process_status, order.process_status_name)
const goodsIcons = getGoodsIcons(order) const goodsIcons = getGoodsIcons(order)
const showDetail = order.process_status === '2' const showDetail = order.process_status === '2'
const showResubmit = !isCorpGoodsType(order.goods_type)
const payIcon = PAY_ICON[order.pay_type] const payIcon = PAY_ICON[order.pay_type]
const isCorpOrder = isCorpGoodsType(order.goods_type)
return ( return (
<View className={`order-card ${first ? 'order-card--first' : ''}`}> <View className={`order-card ${first ? 'order-card--first' : ''}`}>
@ -243,10 +238,21 @@ function OrderCard ({
<View className='order-card__reject'>{order.reject_reason}</View> <View className='order-card__reject'>{order.reject_reason}</View>
) : null} ) : null}
<InfoLine label={`${ENTITY_LABELS.entityName}`} value={order.entity_name || '-'} /> {isCorpOrder ? (
<InfoLine label='联系电话:' value={order.entity_phone || '-'} /> <>
<InfoLine label={`${ENTITY_LABELS.entityAddress}`} value={getOrderAddressName(order) || '-'} /> <InfoLine label='企业名称:' value={order.entity_name || '-'} />
<InfoLine label={`${ENTITY_LABELS.entityMapLocation}`} value={order.extra?.entity_address || '-'} /> <InfoLine label='法人:' value={order.entity_legal || '-'} />
<InfoLine label='统一社会信用代码:' value={order.entity_id || '-'} />
<InfoLine label='联系电话:' value={order.entity_phone || '-'} />
</>
) : (
<>
<InfoLine label={`${ENTITY_LABELS.entityName}`} value={order.entity_name || '-'} />
<InfoLine label='联系电话:' value={order.entity_phone || '-'} />
<InfoLine label={`${ENTITY_LABELS.entityAddress}`} value={getOrderAddressName(order) || '-'} />
<InfoLine label={`${ENTITY_LABELS.entityMapLocation}`} value={order.extra?.entity_address || '-'} />
</>
)}
<InfoLine label='订单时间:' value={order.create_time || order.pay_time || '-'} /> <InfoLine label='订单时间:' value={order.create_time || order.pay_time || '-'} />
<View className='order-card__row'> <View className='order-card__row'>
@ -267,7 +273,9 @@ function OrderCard ({
{showDetail ? ( {showDetail ? (
<View className='order-card__action' onClick={onViewDetail}></View> <View className='order-card__action' onClick={onViewDetail}></View>
) : null} ) : null}
<View className='order-card__action' onClick={onResubmit}></View> {showResubmit ? (
<View className='order-card__action' onClick={onResubmit}></View>
) : null}
</View> </View>
</View> </View>
) )