This commit is contained in:
zhangjianjun 2026-08-20 16:47:44 +08:00
parent 8e1f3fbcc8
commit a7c23b9baf
7 changed files with 173 additions and 56 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -15,6 +15,13 @@ describe('order status labels', () => {
expect(getStatusMeta('2').result).toBe('已完成')
expect(ORDER_TABS.map((tab) => tab.label)).toEqual(['全部', '办理中', '待处理', '待办理', '已完成'])
})
it('uses filled badge colors from the order card design', () => {
expect(getStatusMeta('4')).toMatchObject({ color: '#0261FC', bg: '#E5EFFE' })
expect(getStatusMeta('6')).toMatchObject({ color: '#FC6202', bg: '#FEEFE5' })
expect(getStatusMeta('1')).toMatchObject({ color: '#FF2E2E', bg: '#FFEAEA' })
expect(getStatusMeta('2')).toMatchObject({ color: '#07C160', bg: '#E6F8EF' })
})
})
describe('filterOrdersByTab', () => {

View File

@ -1,14 +1,14 @@
import { isCorpGoodsType } from './detail'
export const STATUS_CONFIG: Record<string, { color: string, bg: string, result: string }> = {
'4': { color: '#2F7BFF', bg: '#EAF2FF', result: '办理中' },
'1': { color: '#FF5A5A', bg: '#FFF1F0', result: '待办理' },
'3': { color: '#FF5A5A', bg: '#FFF1F0', result: '待办理' },
'2': { color: '#22C58B', bg: '#E8F9F2', result: '已完成' },
'5': { color: '#22C58B', bg: '#E8F9F2', result: '已完成' },
'7': { color: '#22C58B', bg: '#E8F9F2', result: '已完成' },
'8': { color: '#22C58B', bg: '#E8F9F2', result: '已完成' },
'6': { color: '#FF8E13', bg: '#FFF2E4', result: '待处理' },
'4': { color: '#0261FC', bg: '#E5EFFE', result: '办理中' },
'1': { color: '#FF2E2E', bg: '#FFEAEA', result: '待办理' },
'3': { color: '#FF2E2E', bg: '#FFEAEA', result: '待办理' },
'2': { color: '#07C160', bg: '#E6F8EF', result: '已完成' },
'5': { color: '#07C160', bg: '#E6F8EF', result: '已完成' },
'7': { color: '#07C160', bg: '#E6F8EF', result: '已完成' },
'8': { color: '#07C160', bg: '#E6F8EF', result: '已完成' },
'6': { color: '#FC6202', bg: '#FEEFE5', result: '待处理' },
}
export const PAY_ICON: Record<string, string> = {

View File

@ -1,5 +1,6 @@
import { Image, Text, View } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { useEffect, useRef, useState } from 'react'
import { LOCAL_IMAGES } from '@/lib/assets'
import { RECEIPT_STAMP_DONE, RECEIPT_STAMP_PENDING } from '@/lib/cdn'
import {
@ -11,25 +12,48 @@ import {
} from '@/lib/detail'
import type { OrderLike } from '@/lib/order'
const DRAWER_ANIMATION_MS = 300
type Props = {
order: OrderLike
onCancel: () => void
}
export default function DetailDrawer ({ order, onCancel }: Props) {
const [leaving, setLeaving] = useState(false)
const onCancelRef = useRef(onCancel)
onCancelRef.current = onCancel
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))
const handleClose = () => {
if (leaving) return
setLeaving(true)
}
useEffect(() => {
if (!leaving) return
const timer = setTimeout(() => onCancelRef.current(), DRAWER_ANIMATION_MS)
return () => clearTimeout(timer)
}, [leaving])
return (
<View className='detail-mask' onClick={onCancel}>
<View className='detail-drawer' onClick={(event) => event.stopPropagation()}>
<View
className={`detail-mask${leaving ? ' detail-mask--leave' : ''}`}
onClick={handleClose}
>
<View
className={`detail-drawer${leaving ? ' detail-drawer--leave' : ''}`}
onClick={(event) => event.stopPropagation()}
>
<View className='detail-drawer__handle' />
<View className='detail-dialog__head'>
<Text className='detail-dialog__title'></Text>
<View className='detail-dialog__close' onClick={onCancel}>
<View className='detail-dialog__close' onClick={handleClose}>
<Image className='detail-dialog__close-img' src={LOCAL_IMAGES.close} />
</View>
</View>

View File

@ -125,7 +125,7 @@
}
.home-list {
padding: 16px 24px 160px;
padding: 24px 24px 160px;
}
.home-empty {
@ -149,52 +149,69 @@
}
.order-card {
margin-bottom: 24px;
padding: 32px 28px 28px;
box-sizing: border-box;
width: 100%;
margin-bottom: 32px;
padding: 32px 24px 24px;
border-radius: 24px;
background: #fff;
box-shadow: 0 0 20px rgba(0, 78, 206, 0.1);
}
.order-card__head {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 16px;
margin-bottom: 8px;
}
.order-card__goods-name {
flex: 1;
min-width: 0;
padding-right: 16px;
color: #1d2129;
color: #222;
font-size: 32px;
font-weight: 700;
font-weight: 500;
line-height: 44px;
}
.order-card__status {
flex-shrink: 0;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
min-width: 106px;
height: 48px;
padding: 0 14px;
border-radius: 6px;
}
.order-card__status-text {
font-size: 26px;
line-height: 44px;
font-weight: 400;
line-height: 36px;
}
.order-card__row {
display: flex;
align-items: flex-start;
margin-top: 12px;
font-size: 26px;
line-height: 36px;
align-items: center;
margin-top: 16px;
font-size: 28px;
line-height: 40px;
}
.order-card__label {
flex-shrink: 0;
color: #86909c;
color: #666;
font-weight: 400;
}
.order-card__value {
flex: 1;
min-width: 0;
color: #4e5969;
color: #222;
font-weight: 400;
word-break: break-all;
}
@ -232,6 +249,14 @@
line-height: 36px;
}
.order-card__divider {
width: 100%;
height: 0;
margin-top: 32px;
border: 0;
border-top: 2px solid #eee;
}
.order-card__actions {
display: flex;
justify-content: flex-end;
@ -263,7 +288,13 @@
z-index: 20;
display: flex;
align-items: flex-end;
overflow: hidden;
background: rgba(0, 0, 0, 0.45);
animation: detail-mask-in 0.3s ease both;
}
.detail-mask--leave {
animation: detail-mask-out 0.3s ease both;
}
.detail-drawer {
@ -273,6 +304,31 @@
overflow: hidden;
border-radius: 24px 24px 0 0;
background: #fff;
animation: detail-drawer-in 0.3s cubic-bezier(0.32, 0.72, 0, 1) both;
}
.detail-drawer--leave {
animation: detail-drawer-out 0.3s cubic-bezier(0.32, 0.72, 0, 1) both;
}
@keyframes detail-mask-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes detail-mask-out {
from { opacity: 1; }
to { opacity: 0; }
}
@keyframes detail-drawer-in {
from { transform: translate3d(0, 100%, 0); }
to { transform: translate3d(0, 0, 0); }
}
@keyframes detail-drawer-out {
from { transform: translate3d(0, 0, 0); }
to { transform: translate3d(0, 100%, 0); }
}
.detail-drawer__handle {
@ -300,18 +356,20 @@
.detail-dialog__close {
position: absolute;
top: 20px;
right: 20px;
top: 16px;
right: 16px;
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
width: 56px;
height: 56px;
border-radius: 50%;
}
.detail-dialog__close-img {
width: 28px;
height: 28px;
width: 40px;
height: 40px;
border-radius: 50%;
}
.detail-dialog__body {

View File

@ -201,7 +201,9 @@ function OrderCard ({
<View className='order-card'>
<View className='order-card__head'>
<Text className='order-card__goods-name'>{order.goods_name || '-'}</Text>
<Text className='order-card__status' style={{ color: status.color }}>{status.result}</Text>
<View className='order-card__status' style={{ backgroundColor: status.bg }}>
<Text className='order-card__status-text' style={{ color: status.color }}>{status.result}</Text>
</View>
</View>
<InfoLine label='联系电话:' value={order.entity_phone || '-'} />
@ -226,6 +228,8 @@ function OrderCard ({
) : null}
{actions.complaint || actions.submit || actions.detail ? (
<>
<View className='order-card__divider' />
<View className='order-card__actions'>
{actions.complaint ? (
<View className='order-card__action' onClick={onComplaint}></View>
@ -234,9 +238,10 @@ function OrderCard ({
<View className='order-card__action' onClick={onViewDetail}></View>
) : null}
{actions.submit ? (
<View className='order-card__action order-card__action--primary' onClick={onResubmit}></View>
<View className='order-card__action order-card__action--primary' onClick={onResubmit}></View>
) : null}
</View>
</>
) : null}
</View>
)

View File

@ -3,6 +3,26 @@ import Taro from '@tarojs/taro'
import { useEffect, useState } from 'react'
import { buildPublicQrImageUrl } from '@/lib/qrImage'
function toastQrFail (err?: { errMsg?: string }) {
const message = String(err?.errMsg || '')
if (/合法域名/.test(message) || /not in domain list/i.test(message)) {
Taro.showToast({ title: '二维码域名未生效,请完全退出微信后重试', icon: 'none', duration: 3000 })
return
}
Taro.showToast({ title: '二维码加载失败', icon: 'none' })
}
function writeQrFile (filePath: string, data: ArrayBuffer | string) {
return new Promise<void>((resolve, reject) => {
Taro.getFileSystemManager().writeFile({
filePath,
data,
success: () => resolve(),
fail: reject,
})
})
}
export default function WeixinQrImage ({ url }: { url: string }) {
const [src, setSrc] = useState('')
@ -17,21 +37,24 @@ export default function WeixinQrImage ({ url }: { url: string }) {
return
}
Taro.downloadFile({
const filePath = `${Taro.env.USER_DATA_PATH}/weixin-qr.png`
Taro.request({
url: remote,
success (res) {
method: 'GET',
responseType: 'arraybuffer',
dataType: '其他',
timeout: 15000,
}).then(async (res) => {
if (cancelled) return
if (res.statusCode === 200 && res.tempFilePath) {
setSrc(res.tempFilePath)
if (res.statusCode !== 200 || !res.data) {
toastQrFail()
return
}
Taro.showToast({ title: '二维码加载失败', icon: 'none' })
},
fail () {
if (!cancelled) {
Taro.showToast({ title: '二维码加载失败', icon: 'none' })
}
},
await writeQrFile(filePath, res.data as ArrayBuffer)
if (!cancelled) setSrc(filePath)
}).catch((err) => {
if (!cancelled) toastQrFail(err)
})
return () => {