feat: align completed order detail modal with H5
This commit is contained in:
parent
3a42f65c5a
commit
b31c68dcfc
|
|
@ -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 CARD_HEADER = `${MOBILE_ASSET_BASE}/card_header.png`
|
||||
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 = {
|
||||
entityName: '门店名称',
|
||||
|
|
|
|||
|
|
@ -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('')
|
||||
})
|
||||
})
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'
|
|||
import {
|
||||
buildResubmitPayload,
|
||||
filterMapLabelOrders,
|
||||
filterOrdersForProduct,
|
||||
formatMapCoordinate,
|
||||
getOrderAddressName,
|
||||
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', () => {
|
||||
it('splits named address and coordinate address', () => {
|
||||
expect(getOrderAddressName(mapOrder as any)).toBe('南京路1号')
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { isCorpGoodsType } from './detail'
|
||||
|
||||
export type UploadedImage = {
|
||||
id: string
|
||||
url: string
|
||||
|
|
@ -79,21 +81,30 @@ export function getGoodsIcons (order: OrderLike) {
|
|||
return Array.isArray(icons) ? icons.filter((item) => typeof item === 'string') : []
|
||||
}
|
||||
|
||||
export function filterMapLabelOrders<T extends OrderLike> (orders: T[] = []) {
|
||||
return orders
|
||||
.filter((item) => item.goods_type === 'map_label')
|
||||
.map((item) => {
|
||||
const nextItem = { ...item, extra: { ...(item.extra || {}) } }
|
||||
const goodsParam = nextItem.extra.goods_param
|
||||
if (typeof goodsParam === 'string') {
|
||||
try {
|
||||
nextItem.extra.goods_param = JSON.parse(goodsParam)
|
||||
} catch {
|
||||
nextItem.extra.goods_param = null
|
||||
}
|
||||
export function normalizeOrders<T extends OrderLike> (orders: T[] = []) {
|
||||
return orders.map((item) => {
|
||||
const nextItem = { ...item, extra: { ...(item.extra || {}) } }
|
||||
const goodsParam = nextItem.extra.goods_param
|
||||
if (typeof goodsParam === 'string') {
|
||||
try {
|
||||
nextItem.extra.goods_param = JSON.parse(goodsParam)
|
||||
} 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[] {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import Taro from '@tarojs/taro'
|
||||
import { mergeServiceConfig } from './contact'
|
||||
import { pickGoodsType } from './detail'
|
||||
import { resolveLaunchQuery, type LaunchQuery } from './launch'
|
||||
import { hostFromOrigin, resolveApiOrigin } from './origin'
|
||||
import { createDeviceId } from './uuid'
|
||||
|
|
@ -9,6 +10,7 @@ const SESSION_KEY = 'corpMpSession'
|
|||
export type SessionState = LaunchQuery & {
|
||||
deviceId: string
|
||||
config: Record<string, any>
|
||||
goodsType: string
|
||||
}
|
||||
|
||||
const EMPTY_SESSION: SessionState = {
|
||||
|
|
@ -22,6 +24,7 @@ const EMPTY_SESSION: SessionState = {
|
|||
phone: '',
|
||||
deviceId: '',
|
||||
config: {},
|
||||
goodsType: '',
|
||||
}
|
||||
|
||||
function readStorage (): Partial<SessionState> {
|
||||
|
|
@ -96,10 +99,12 @@ export function applyCorpInit (payload: any) {
|
|||
const data = payload?.data ?? payload
|
||||
const token = typeof data?.token === 'string' ? data.token.trim() : ''
|
||||
const packageId = data?.package === undefined || data?.package === null ? '' : String(data.package)
|
||||
const goodsType = pickGoodsType(payload)
|
||||
const patch: Partial<SessionState> = {
|
||||
config: mergeServiceConfig(getSession().config, payload),
|
||||
}
|
||||
if (token) patch.token = token
|
||||
if (packageId) patch.packageId = packageId
|
||||
if (goodsType) patch.goodsType = goodsType
|
||||
return setSession(patch)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -38,7 +38,11 @@
|
|||
.check-card {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin: -80px 24px 24px;
|
||||
margin: 0 24px 48px;
|
||||
}
|
||||
|
||||
.check-card--lift {
|
||||
margin-top: -80px;
|
||||
}
|
||||
|
||||
.check-card__header {
|
||||
|
|
@ -272,39 +276,206 @@
|
|||
background: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.detail-mask {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.detail-dialog {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 32px;
|
||||
left: 32px;
|
||||
padding: 32px 24px 24px;
|
||||
border-radius: 16px;
|
||||
box-sizing: border-box;
|
||||
width: 686px;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
border-radius: 20px;
|
||||
background: #fff;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.detail-dialog__head {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 88px;
|
||||
}
|
||||
|
||||
.detail-dialog__title {
|
||||
display: block;
|
||||
margin-bottom: 24px;
|
||||
color: #222;
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
font-size: 34px;
|
||||
font-weight: 700;
|
||||
line-height: 44px;
|
||||
}
|
||||
|
||||
.detail-dialog__close {
|
||||
margin-top: 32px;
|
||||
height: 80px;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
background: #0261fc;
|
||||
color: #fff;
|
||||
font-size: 30px;
|
||||
line-height: 80px;
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.detail-dialog__close::after {
|
||||
border: 0;
|
||||
.detail-dialog__close-icon {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@ import Taro, { useDidShow, useLoad } from '@tarojs/taro'
|
|||
import { useState } from 'react'
|
||||
import { getOrdersByPhone } from '@/api/pay'
|
||||
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 { pickServiceContact, type ServiceContact } from '@/lib/contact'
|
||||
import { isCorpGoodsType } from '@/lib/detail'
|
||||
import {
|
||||
filterMapLabelOrders,
|
||||
filterOrdersForProduct,
|
||||
getGoodsIcons,
|
||||
getOrderAddressName,
|
||||
parseCoordinate,
|
||||
|
|
@ -17,6 +19,7 @@ import { isValidPhone } from '@/lib/phone'
|
|||
import { getStatusMeta, PAY_ICON } from '@/lib/status'
|
||||
import { consumeMapPickerResult, type MapPickerResult } from '@/pages/map/picker'
|
||||
import ContactModal, { ContactEntry } from './contact'
|
||||
import DetailModal from './detail'
|
||||
import ResubmitDrawer from './resubmit'
|
||||
import './index.scss'
|
||||
|
||||
|
|
@ -68,7 +71,7 @@ export default function Index () {
|
|||
setChecking(true)
|
||||
try {
|
||||
const res = await getOrdersByPhone(phone)
|
||||
const nextOrders = filterMapLabelOrders(res.data || [])
|
||||
const nextOrders = filterOrdersForProduct(getSession().goodsType, res.data || [])
|
||||
setOrders(nextOrders)
|
||||
setSearched(true)
|
||||
if (!nextOrders.length) {
|
||||
|
|
@ -102,7 +105,7 @@ export default function Index () {
|
|||
</View>
|
||||
<ContactEntry onClick={() => setShowContact(true)} />
|
||||
|
||||
<View className='check-card'>
|
||||
<View className='check-card check-card--lift'>
|
||||
<View className='check-card__header'>
|
||||
<Image className='check-card__header-bg' src={CARD_HEADER} mode='scaleToFill' />
|
||||
<Text className='check-card__header-title'>进度查询</Text>
|
||||
|
|
@ -169,17 +172,7 @@ export default function Index () {
|
|||
) : null}
|
||||
|
||||
{detailOrder ? (
|
||||
<View className='detail-mask' onClick={() => 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>
|
||||
<DetailModal order={detailOrder} onCancel={() => setDetailOrder(null)} />
|
||||
) : null}
|
||||
|
||||
<ContactModal
|
||||
|
|
@ -223,7 +216,9 @@ function OrderCard ({
|
|||
const status = getStatusMeta(order.process_status, order.process_status_name)
|
||||
const goodsIcons = getGoodsIcons(order)
|
||||
const showDetail = order.process_status === '2'
|
||||
const showResubmit = !isCorpGoodsType(order.goods_type)
|
||||
const payIcon = PAY_ICON[order.pay_type]
|
||||
const isCorpOrder = isCorpGoodsType(order.goods_type)
|
||||
|
||||
return (
|
||||
<View className={`order-card ${first ? 'order-card--first' : ''}`}>
|
||||
|
|
@ -243,10 +238,21 @@ function OrderCard ({
|
|||
<View className='order-card__reject'>驳回理由:{order.reject_reason}</View>
|
||||
) : null}
|
||||
|
||||
<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 || '-'} />
|
||||
{isCorpOrder ? (
|
||||
<>
|
||||
<InfoLine label='企业名称:' value={order.entity_name || '-'} />
|
||||
<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 || '-'} />
|
||||
|
||||
<View className='order-card__row'>
|
||||
|
|
@ -267,7 +273,9 @@ function OrderCard ({
|
|||
{showDetail ? (
|
||||
<View className='order-card__action' onClick={onViewDetail}>查看详情</View>
|
||||
) : null}
|
||||
<View className='order-card__action' onClick={onResubmit}>补充资料</View>
|
||||
{showResubmit ? (
|
||||
<View className='order-card__action' onClick={onResubmit}>补充资料</View>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue