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(getStatusMeta('2').result).toBe('已完成')
expect(ORDER_TABS.map((tab) => tab.label)).toEqual(['全部', '办理中', '待处理', '待办理', '已完成']) 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', () => { describe('filterOrdersByTab', () => {

View File

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

View File

@ -1,5 +1,6 @@
import { Image, Text, View } from '@tarojs/components' import { Image, Text, View } from '@tarojs/components'
import Taro from '@tarojs/taro' import Taro from '@tarojs/taro'
import { useEffect, useRef, useState } from 'react'
import { LOCAL_IMAGES } from '@/lib/assets' import { LOCAL_IMAGES } from '@/lib/assets'
import { RECEIPT_STAMP_DONE, RECEIPT_STAMP_PENDING } from '@/lib/cdn' import { RECEIPT_STAMP_DONE, RECEIPT_STAMP_PENDING } from '@/lib/cdn'
import { import {
@ -11,25 +12,48 @@ import {
} from '@/lib/detail' } from '@/lib/detail'
import type { OrderLike } from '@/lib/order' import type { OrderLike } from '@/lib/order'
const DRAWER_ANIMATION_MS = 300
type Props = { type Props = {
order: OrderLike order: OrderLike
onCancel: () => void onCancel: () => void
} }
export default function DetailDrawer ({ order, onCancel }: Props) { 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 showReceipt = shouldShowReceipt(order.goods_type)
const receipt = getReceiptMeta(order.goods_type, order.goods_name) const receipt = getReceiptMeta(order.goods_type, order.goods_name)
const files = order.process_status === '2' ? splitOrderFiles(order.files) : { images: [], pdfs: [] } const files = order.process_status === '2' ? splitOrderFiles(order.files) : { images: [], pdfs: [] }
const empty = !showReceipt && files.images.length === 0 && files.pdfs.length === 0 const empty = !showReceipt && files.images.length === 0 && files.pdfs.length === 0
const imageUrls = files.images.map((file) => String(file.url)) 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 ( return (
<View className='detail-mask' onClick={onCancel}> <View
<View className='detail-drawer' onClick={(event) => event.stopPropagation()}> 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-drawer__handle' />
<View className='detail-dialog__head'> <View className='detail-dialog__head'>
<Text className='detail-dialog__title'></Text> <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} /> <Image className='detail-dialog__close-img' src={LOCAL_IMAGES.close} />
</View> </View>
</View> </View>

View File

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

View File

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

View File

@ -3,6 +3,26 @@ import Taro from '@tarojs/taro'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { buildPublicQrImageUrl } from '@/lib/qrImage' 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 }) { export default function WeixinQrImage ({ url }: { url: string }) {
const [src, setSrc] = useState('') const [src, setSrc] = useState('')
@ -17,21 +37,24 @@ export default function WeixinQrImage ({ url }: { url: string }) {
return return
} }
Taro.downloadFile({ const filePath = `${Taro.env.USER_DATA_PATH}/weixin-qr.png`
Taro.request({
url: remote, url: remote,
success (res) { method: 'GET',
responseType: 'arraybuffer',
dataType: '其他',
timeout: 15000,
}).then(async (res) => {
if (cancelled) return if (cancelled) return
if (res.statusCode === 200 && res.tempFilePath) { if (res.statusCode !== 200 || !res.data) {
setSrc(res.tempFilePath) toastQrFail()
return return
} }
Taro.showToast({ title: '二维码加载失败', icon: 'none' }) await writeQrFile(filePath, res.data as ArrayBuffer)
}, if (!cancelled) setSrc(filePath)
fail () { }).catch((err) => {
if (!cancelled) { if (!cancelled) toastQrFail(err)
Taro.showToast({ title: '二维码加载失败', icon: 'none' })
}
},
}) })
return () => { return () => {