|
|
@ -7,3 +7,4 @@ node_modules/
|
||||||
.swc
|
.swc
|
||||||
*.local
|
*.local
|
||||||
.env
|
.env
|
||||||
|
design/
|
||||||
|
|
@ -22,6 +22,20 @@ export function initCorp (params: Record<string, any> = {}) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function report (data: {
|
||||||
|
contact: string
|
||||||
|
content: string
|
||||||
|
images?: string[]
|
||||||
|
type: string
|
||||||
|
[property: string]: any
|
||||||
|
}) {
|
||||||
|
return request({
|
||||||
|
url: '/api/user/feedback',
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function presign (ext = '') {
|
export function presign (ext = '') {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/presign',
|
url: '/api/presign',
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
export default defineAppConfig({
|
export default defineAppConfig({
|
||||||
pages: [
|
pages: [
|
||||||
'pages/index/index',
|
'pages/index/index',
|
||||||
|
'pages/resubmit/index',
|
||||||
|
'pages/complaint/index',
|
||||||
'pages/map/index',
|
'pages/map/index',
|
||||||
],
|
],
|
||||||
window: {
|
window: {
|
||||||
backgroundTextStyle: 'light',
|
backgroundTextStyle: 'light',
|
||||||
navigationBarBackgroundColor: '#2F4FEE',
|
navigationBarBackgroundColor: '#FFFFFF',
|
||||||
navigationBarTitleText: '网上快办平台',
|
navigationBarTitleText: '网上快办',
|
||||||
navigationBarTextStyle: 'white'
|
navigationBarTextStyle: 'black'
|
||||||
},
|
},
|
||||||
permission: {
|
permission: {
|
||||||
'scope.userLocation': {
|
'scope.userLocation': {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
page {
|
||||||
|
background: #f6f8fc;
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 965 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
|
@ -0,0 +1,17 @@
|
||||||
|
import iconCamera from '@/assets/images/icon-camera.png'
|
||||||
|
import iconClose from '@/assets/images/icon-close.png'
|
||||||
|
import emptyOrders from '@/assets/images/empty-orders.png'
|
||||||
|
import iconInputClear from '@/assets/images/icon-input-clear.png'
|
||||||
|
import iconLogo from '@/assets/images/icon-logo.png'
|
||||||
|
import iconPen from '@/assets/images/icon-pen.png'
|
||||||
|
import iconSearch from '@/assets/images/icon-search.png'
|
||||||
|
|
||||||
|
export const LOCAL_IMAGES = {
|
||||||
|
camera: iconCamera,
|
||||||
|
close: iconClose,
|
||||||
|
empty: emptyOrders,
|
||||||
|
clear: iconInputClear,
|
||||||
|
logo: iconLogo,
|
||||||
|
pen: iconPen,
|
||||||
|
search: iconSearch,
|
||||||
|
}
|
||||||
|
|
@ -5,12 +5,15 @@ export const HOME_ICON_BASE = `${MAP_ASSET_BASE}/images-pc/home`
|
||||||
export const MOBILE_ASSET_BASE = `${MAP_ASSET_BASE}/images-mobile`
|
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_DONE = `${STATIC_CDN}/images/oderDetailCallBackSuccess.png`
|
||||||
export const RECEIPT_STAMP_PENDING = `${STATIC_CDN}/images/oderDetailCallBack.png`
|
export const RECEIPT_STAMP_PENDING = `${STATIC_CDN}/images/oderDetailCallBack.png`
|
||||||
|
|
||||||
export const ENTITY_LABELS = {
|
export const ENTITY_LABELS = {
|
||||||
entityName: '门店名称',
|
entityName: '标注名称',
|
||||||
entityAddress: '门店地址',
|
entityAddress: '标注地址',
|
||||||
entityMapLocation: '门店定位',
|
entityMapLocation: '地图位置',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const COPY_ICON = `${CORP_ASSET_BASE}/images-mobile/icon-copy.png`
|
||||||
|
export const INPUT_ARROW_ICON = `${MAP_ASSET_BASE}/icon_input_arrow@2x.png`
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import { buildFeedbackPayload, COMPLAINT_TYPES } from './feedback'
|
||||||
|
|
||||||
|
describe('complaint types', () => {
|
||||||
|
it('uses the five Chinese labels from the design as type values', () => {
|
||||||
|
expect(COMPLAINT_TYPES.map((item) => item.value)).toEqual([
|
||||||
|
'莫名扣费',
|
||||||
|
'超过7天未解决',
|
||||||
|
'交易被骗',
|
||||||
|
'不满意商家服务',
|
||||||
|
'其他问题',
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('buildFeedbackPayload', () => {
|
||||||
|
it('sends contact, content, images, Chinese type and order identifiers', () => {
|
||||||
|
expect(buildFeedbackPayload({
|
||||||
|
contact: '13800138000',
|
||||||
|
content: '好好好',
|
||||||
|
images: ['https://cdn.example/a.jpg'],
|
||||||
|
type: '其他问题',
|
||||||
|
order: {
|
||||||
|
order_id: '9',
|
||||||
|
out_trade_no: '123456789562323256',
|
||||||
|
goods_name: '地图标注代办服务',
|
||||||
|
},
|
||||||
|
})).toEqual({
|
||||||
|
contact: '13800138000',
|
||||||
|
content: '好好好',
|
||||||
|
images: ['https://cdn.example/a.jpg'],
|
||||||
|
type: '其他问题',
|
||||||
|
order_id: '9',
|
||||||
|
out_trade_no: '123456789562323256',
|
||||||
|
goods_name: '地图标注代办服务',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
export const COMPLAINT_TYPES = [
|
||||||
|
{ value: '莫名扣费', label: '莫名扣费' },
|
||||||
|
{ value: '超过7天未解决', label: '超过7天未解决' },
|
||||||
|
{ value: '交易被骗', label: '交易被骗' },
|
||||||
|
{ value: '不满意商家服务', label: '不满意商家服务' },
|
||||||
|
{ value: '其他问题', label: '其他问题' },
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export type ComplaintType = (typeof COMPLAINT_TYPES)[number]['value']
|
||||||
|
|
||||||
|
export type FeedbackOrder = {
|
||||||
|
order_id?: string
|
||||||
|
out_trade_no?: string
|
||||||
|
goods_name?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildFeedbackPayload (input: {
|
||||||
|
contact: string
|
||||||
|
content: string
|
||||||
|
images?: string[]
|
||||||
|
type: string
|
||||||
|
order?: FeedbackOrder | null
|
||||||
|
}) {
|
||||||
|
return {
|
||||||
|
contact: input.contact,
|
||||||
|
content: input.content,
|
||||||
|
images: input.images || [],
|
||||||
|
type: input.type,
|
||||||
|
order_id: String(input.order?.order_id || ''),
|
||||||
|
out_trade_no: String(input.order?.out_trade_no || ''),
|
||||||
|
goods_name: String(input.order?.goods_name || ''),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import Taro from '@tarojs/taro'
|
||||||
|
|
||||||
|
export function getNavMetrics () {
|
||||||
|
const sys = Taro.getSystemInfoSync()
|
||||||
|
const statusBarHeight = sys.statusBarHeight || 20
|
||||||
|
if (Taro.getEnv() === Taro.ENV_TYPE.WEAPP) {
|
||||||
|
const menu = Taro.getMenuButtonBoundingClientRect()
|
||||||
|
const gap = Math.max((menu.top || statusBarHeight) - statusBarHeight, 4)
|
||||||
|
return {
|
||||||
|
statusBarHeight,
|
||||||
|
navBarHeight: (menu.height || 32) + gap * 2,
|
||||||
|
paddingRight: Math.max((sys.windowWidth || 375) - (menu.left || 280) + 12, 24),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
statusBarHeight,
|
||||||
|
navBarHeight: 44,
|
||||||
|
paddingRight: 24,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,11 +4,14 @@ import {
|
||||||
filterMapLabelOrders,
|
filterMapLabelOrders,
|
||||||
filterOrdersForProduct,
|
filterOrdersForProduct,
|
||||||
formatMapCoordinate,
|
formatMapCoordinate,
|
||||||
|
formatMapCoordinateDisplay,
|
||||||
getOrderAddressName,
|
getOrderAddressName,
|
||||||
getOrderExtraImages,
|
getOrderExtraImages,
|
||||||
getOrderMapAddress,
|
getOrderMapAddress,
|
||||||
|
joinStorefrontSlots,
|
||||||
joinUploadedImageIds,
|
joinUploadedImageIds,
|
||||||
parseCoordinate,
|
parseCoordinate,
|
||||||
|
parseStorefrontSlots,
|
||||||
} from './order'
|
} from './order'
|
||||||
|
|
||||||
const mapOrder = {
|
const mapOrder = {
|
||||||
|
|
@ -88,8 +91,42 @@ describe('order images', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('storefront slots', () => {
|
||||||
|
const front = { id: '123', url: 'https://cdn.example/front.jpg', preview: 'https://cdn.example/front.jpg' }
|
||||||
|
const left = { id: '45', url: 'https://cdn.example/left.jpg', preview: 'https://cdn.example/left.jpg' }
|
||||||
|
|
||||||
|
it('joins three slots and writes null for empty ones', () => {
|
||||||
|
expect(joinStorefrontSlots([front, left, null])).toBe('123,45,null')
|
||||||
|
expect(joinStorefrontSlots([front, null, null])).toBe('123,null,null')
|
||||||
|
expect(joinStorefrontSlots([null, null, null])).toBe('null,null,null')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('parses comma ids into front/left/right, treating null as empty', () => {
|
||||||
|
expect(parseStorefrontSlots({
|
||||||
|
extra: {
|
||||||
|
entity_storefront_image: '123,45,null',
|
||||||
|
entity_storefront_image_url: 'https://cdn.example/front.jpg,https://cdn.example/left.jpg,null',
|
||||||
|
},
|
||||||
|
})).toEqual([front, left, null])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('pads legacy two-image storefront data into three slots', () => {
|
||||||
|
expect(parseStorefrontSlots(mapOrder as any)).toEqual([
|
||||||
|
{ id: '11', url: 'https://cdn.example/a.jpg', preview: 'https://cdn.example/a.jpg' },
|
||||||
|
{ id: '12', url: 'https://cdn.example/b.jpg', preview: 'https://cdn.example/b.jpg' },
|
||||||
|
null,
|
||||||
|
])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('map coordinate display', () => {
|
||||||
|
it('renders northing and easting for the resubmit map field', () => {
|
||||||
|
expect(formatMapCoordinateDisplay({ lng: 120.156892, lat: 30.278956 })).toBe('N30.278956°, E120.156892°')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('buildResubmitPayload', () => {
|
describe('buildResubmitPayload', () => {
|
||||||
it('updates extra while dropping upload-only keys', () => {
|
it('updates extra while dropping upload-only keys and keeping storefront null placeholders', () => {
|
||||||
expect(
|
expect(
|
||||||
buildResubmitPayload({
|
buildResubmitPayload({
|
||||||
order: mapOrder as any,
|
order: mapOrder as any,
|
||||||
|
|
@ -100,8 +137,12 @@ describe('buildResubmitPayload', () => {
|
||||||
entity_phone: '13900139000',
|
entity_phone: '13900139000',
|
||||||
entity_phone2: '13700137000',
|
entity_phone2: '13700137000',
|
||||||
},
|
},
|
||||||
storefrontImages: [{ id: '21', url: 'https://cdn.example/c.jpg', preview: 'p' }],
|
storefrontSlots: [
|
||||||
licenseImages: [{ id: '31', url: 'https://cdn.example/d.jpg', preview: 'p' }],
|
{ id: '123', url: 'https://cdn.example/c.jpg', preview: 'p' },
|
||||||
|
{ id: '45', url: 'https://cdn.example/d.jpg', preview: 'p' },
|
||||||
|
null,
|
||||||
|
],
|
||||||
|
licenseImages: [{ id: '31', url: 'https://cdn.example/e.jpg', preview: 'p' }],
|
||||||
})
|
})
|
||||||
).toEqual({
|
).toEqual({
|
||||||
id: '4',
|
id: '4',
|
||||||
|
|
@ -113,7 +154,7 @@ describe('buildResubmitPayload', () => {
|
||||||
entity_address_name: '淮海路2号',
|
entity_address_name: '淮海路2号',
|
||||||
entity_address: '121.480000,31.240000',
|
entity_address: '121.480000,31.240000',
|
||||||
entity_phone2: '13700137000',
|
entity_phone2: '13700137000',
|
||||||
entity_storefront_image: '21',
|
entity_storefront_image: '123,45,null',
|
||||||
entity_business_license_image: '31',
|
entity_business_license_image: '31',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,47 @@ function splitValues (value: unknown) {
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isEmptySlotToken (value?: string) {
|
||||||
|
return !value || value === 'null'
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitSlotValues (value: unknown) {
|
||||||
|
if (value === undefined || value === null || value === '') return []
|
||||||
|
return String(value).split(',').map((item) => item.trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StorefrontSlot = UploadedImage | null
|
||||||
|
|
||||||
|
export function joinStorefrontSlots (slots: Array<StorefrontSlot | undefined> = []) {
|
||||||
|
return [0, 1, 2].map((index) => {
|
||||||
|
const id = slots[index]?.id?.trim()
|
||||||
|
return id ? id : 'null'
|
||||||
|
}).join(',')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseStorefrontSlots (order: OrderLike | null | undefined): StorefrontSlot[] {
|
||||||
|
const extra = order?.extra || {}
|
||||||
|
const ids = splitSlotValues(extra.entity_storefront_image)
|
||||||
|
const urls = splitSlotValues(extra.entity_storefront_image_url)
|
||||||
|
|
||||||
|
return [0, 1, 2].map((index) => {
|
||||||
|
const id = ids[index] || ''
|
||||||
|
const url = urls[index] || ''
|
||||||
|
if (isEmptySlotToken(id) && isEmptySlotToken(url)) return null
|
||||||
|
if (isEmptySlotToken(id) && !isEmptySlotToken(url)) {
|
||||||
|
return { id: '', url, preview: url }
|
||||||
|
}
|
||||||
|
const resolvedUrl = !isEmptySlotToken(url)
|
||||||
|
? url
|
||||||
|
: (/^https?:\/\//i.test(id) ? id : '')
|
||||||
|
return {
|
||||||
|
id: /^https?:\/\//i.test(id) ? '' : id,
|
||||||
|
url: resolvedUrl,
|
||||||
|
preview: resolvedUrl,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function isCoordinateAddress (address?: string) {
|
export function isCoordinateAddress (address?: string) {
|
||||||
if (!address) return false
|
if (!address) return false
|
||||||
const parts = address.split(',').map((part) => part.trim())
|
const parts = address.split(',').map((part) => part.trim())
|
||||||
|
|
@ -61,6 +102,10 @@ export function formatMapCoordinate (point: { lng: number, lat: number }) {
|
||||||
return `${point.lng.toFixed(6)},${point.lat.toFixed(6)}`
|
return `${point.lng.toFixed(6)},${point.lat.toFixed(6)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatMapCoordinateDisplay (point: { lng: number, lat: number }) {
|
||||||
|
return `N${point.lat.toFixed(6)}°, E${point.lng.toFixed(6)}°`
|
||||||
|
}
|
||||||
|
|
||||||
export function getOrderAddressName (order: OrderLike) {
|
export function getOrderAddressName (order: OrderLike) {
|
||||||
const extra = order.extra || {}
|
const extra = order.extra || {}
|
||||||
return extra.entity_address_name || (!isCoordinateAddress(extra.entity_address) ? extra.entity_address : '') || ''
|
return extra.entity_address_name || (!isCoordinateAddress(extra.entity_address) ? extra.entity_address : '') || ''
|
||||||
|
|
@ -143,7 +188,7 @@ export function joinUploadedImageIds (images: UploadedImage[]) {
|
||||||
export function buildResubmitPayload (input: {
|
export function buildResubmitPayload (input: {
|
||||||
order: OrderLike
|
order: OrderLike
|
||||||
values: ResubmitValues
|
values: ResubmitValues
|
||||||
storefrontImages: UploadedImage[]
|
storefrontSlots: StorefrontSlot[]
|
||||||
licenseImages: UploadedImage[]
|
licenseImages: UploadedImage[]
|
||||||
}) {
|
}) {
|
||||||
const existingExtra = Object.entries(input.order.extra || {}).reduce<Record<string, unknown>>((result, [key, value]) => {
|
const existingExtra = Object.entries(input.order.extra || {}).reduce<Record<string, unknown>>((result, [key, value]) => {
|
||||||
|
|
@ -164,7 +209,7 @@ export function buildResubmitPayload (input: {
|
||||||
entity_address_name: input.values.entity_address_name || '',
|
entity_address_name: input.values.entity_address_name || '',
|
||||||
entity_address: input.values.entity_address || '',
|
entity_address: input.values.entity_address || '',
|
||||||
entity_phone2: input.values.entity_phone2 || '',
|
entity_phone2: input.values.entity_phone2 || '',
|
||||||
entity_storefront_image: joinUploadedImageIds(input.storefrontImages),
|
entity_storefront_image: joinStorefrontSlots(input.storefrontSlots),
|
||||||
entity_business_license_image: joinUploadedImageIds(input.licenseImages),
|
entity_business_license_image: joinUploadedImageIds(input.licenseImages),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import Taro from '@tarojs/taro'
|
||||||
|
import type { OrderLike } from './order'
|
||||||
|
|
||||||
|
const ORDER_DRAFT_KEY = 'orderDraft'
|
||||||
|
|
||||||
|
let memoryDraft: OrderLike | null = null
|
||||||
|
|
||||||
|
export function setOrderDraft (order: OrderLike) {
|
||||||
|
memoryDraft = order
|
||||||
|
try {
|
||||||
|
Taro.setStorageSync(ORDER_DRAFT_KEY, order)
|
||||||
|
} catch {
|
||||||
|
// ignore quota / private-mode failures; in-memory draft still works
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOrderDraft (): OrderLike | null {
|
||||||
|
if (memoryDraft) return memoryDraft
|
||||||
|
try {
|
||||||
|
const stored = Taro.getStorageSync(ORDER_DRAFT_KEY)
|
||||||
|
if (stored && typeof stored === 'object') {
|
||||||
|
memoryDraft = stored
|
||||||
|
return stored
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
import {
|
||||||
|
filterOrdersByTab,
|
||||||
|
getOrderCardActions,
|
||||||
|
getStatusMeta,
|
||||||
|
ORDER_TABS,
|
||||||
|
} from './status'
|
||||||
|
|
||||||
|
describe('order status labels', () => {
|
||||||
|
it('uses the redesigned tab labels for known process statuses', () => {
|
||||||
|
expect(getStatusMeta('4').result).toBe('办理中')
|
||||||
|
expect(getStatusMeta('6').result).toBe('待处理')
|
||||||
|
expect(getStatusMeta('1').result).toBe('待办理')
|
||||||
|
expect(getStatusMeta('3').result).toBe('待办理')
|
||||||
|
expect(getStatusMeta('2').result).toBe('已完成')
|
||||||
|
expect(ORDER_TABS.map((tab) => tab.label)).toEqual(['全部', '办理中', '待处理', '待办理', '已完成'])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('filterOrdersByTab', () => {
|
||||||
|
const orders = [
|
||||||
|
{ order_id: 'doing', process_status: '4' },
|
||||||
|
{ order_id: 'rejected', process_status: '6' },
|
||||||
|
{ order_id: 'waiting', process_status: '1' },
|
||||||
|
{ order_id: 'waiting-3', process_status: '3' },
|
||||||
|
{ order_id: 'done', process_status: '2' },
|
||||||
|
]
|
||||||
|
|
||||||
|
it('filters list orders into the five redesigned tabs', () => {
|
||||||
|
expect(filterOrdersByTab(orders, 'all').map((item) => item.order_id)).toEqual([
|
||||||
|
'doing', 'rejected', 'waiting', 'waiting-3', 'done',
|
||||||
|
])
|
||||||
|
expect(filterOrdersByTab(orders, 'doing').map((item) => item.order_id)).toEqual(['doing'])
|
||||||
|
expect(filterOrdersByTab(orders, 'rejected').map((item) => item.order_id)).toEqual(['rejected'])
|
||||||
|
expect(filterOrdersByTab(orders, 'waiting').map((item) => item.order_id)).toEqual(['waiting', 'waiting-3'])
|
||||||
|
expect(filterOrdersByTab(orders, 'done').map((item) => item.order_id)).toEqual(['done'])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('getOrderCardActions', () => {
|
||||||
|
it('shows complaint plus submit on 办理中 and 待处理', () => {
|
||||||
|
expect(getOrderCardActions({ process_status: '4', goods_type: 'map_label' })).toEqual({
|
||||||
|
complaint: true,
|
||||||
|
submit: true,
|
||||||
|
detail: false,
|
||||||
|
})
|
||||||
|
expect(getOrderCardActions({ process_status: '6', goods_type: 'map_label' })).toEqual({
|
||||||
|
complaint: true,
|
||||||
|
submit: true,
|
||||||
|
detail: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows only complaint on 待办理', () => {
|
||||||
|
expect(getOrderCardActions({ process_status: '1', goods_type: 'map_label' })).toEqual({
|
||||||
|
complaint: true,
|
||||||
|
submit: false,
|
||||||
|
detail: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows view-detail on completed orders and hides complaint', () => {
|
||||||
|
expect(getOrderCardActions({ process_status: '2', goods_type: 'map_label' })).toEqual({
|
||||||
|
complaint: false,
|
||||||
|
submit: false,
|
||||||
|
detail: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('hides submit for corp goods even when status would otherwise allow it', () => {
|
||||||
|
expect(getOrderCardActions({ process_status: '4', goods_type: 'license_year' }).submit).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
|
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: '#FF8E13', bg: '#FFF2E4', result: '处理中' },
|
'4': { color: '#2F7BFF', bg: '#EAF2FF', result: '办理中' },
|
||||||
'1': { color: '#FF431D', bg: '#fff1e6', result: '待办理' },
|
'1': { color: '#FF5A5A', bg: '#FFF1F0', result: '待办理' },
|
||||||
'3': { color: '#FF431D', bg: '#fff1e6', result: '待办理' },
|
'3': { color: '#FF5A5A', bg: '#FFF1F0', result: '待办理' },
|
||||||
'2': { color: '#37D2AD', bg: '#DFF8F2', result: '已完成' },
|
'2': { color: '#22C58B', bg: '#E8F9F2', result: '已完成' },
|
||||||
'5': { color: '#37D2AD', bg: '#DFF8F2', result: '已完成' },
|
'5': { color: '#22C58B', bg: '#E8F9F2', result: '已完成' },
|
||||||
'7': { color: '#37D2AD', bg: '#DFF8F2', result: '已完成' },
|
'7': { color: '#22C58B', bg: '#E8F9F2', result: '已完成' },
|
||||||
'8': { color: '#37D2AD', bg: '#DFF8F2', result: '已完成' },
|
'8': { color: '#22C58B', bg: '#E8F9F2', result: '已完成' },
|
||||||
'6': { color: '#FF8E13', bg: '#FFF2E4', result: '待提交' },
|
'6': { color: '#FF8E13', bg: '#FFF2E4', result: '待处理' },
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PAY_ICON: Record<string, string> = {
|
export const PAY_ICON: Record<string, string> = {
|
||||||
|
|
@ -14,10 +16,42 @@ export const PAY_ICON: Record<string, string> = {
|
||||||
weixin: 'https://cdn.u8t.cn/frontend-static/corp-h5/static/images/weixin.png',
|
weixin: 'https://cdn.u8t.cn/frontend-static/corp-h5/static/images/weixin.png',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ORDER_TABS = [
|
||||||
|
{ key: 'all', label: '全部', statuses: null },
|
||||||
|
{ key: 'doing', label: '办理中', statuses: ['4'] },
|
||||||
|
{ key: 'rejected', label: '待处理', statuses: ['6'] },
|
||||||
|
{ key: 'waiting', label: '待办理', statuses: ['1', '3'] },
|
||||||
|
{ key: 'done', label: '已完成', statuses: ['2', '5', '7', '8'] },
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export type OrderTabKey = (typeof ORDER_TABS)[number]['key']
|
||||||
|
|
||||||
|
const DONE_STATUSES = new Set(['2', '5', '7', '8'])
|
||||||
|
const SUBMIT_STATUSES = new Set(['4', '6'])
|
||||||
|
|
||||||
export function getStatusMeta (status?: string, name?: string) {
|
export function getStatusMeta (status?: string, name?: string) {
|
||||||
const config = STATUS_CONFIG[String(status || '')] || { color: '#666666', bg: '#F5F5F5', result: '未知' }
|
const config = STATUS_CONFIG[String(status || '')]
|
||||||
|
if (config) return { ...config }
|
||||||
return {
|
return {
|
||||||
...config,
|
color: '#666666',
|
||||||
result: name || config.result,
|
bg: '#F5F5F5',
|
||||||
|
result: name || '未知',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function filterOrdersByTab<T extends Record<string, any>> (orders: T[] = [], tab: OrderTabKey) {
|
||||||
|
const current = ORDER_TABS.find((item) => item.key === tab)
|
||||||
|
if (!current || !current.statuses) return orders
|
||||||
|
const statuses = new Set<string>(current.statuses)
|
||||||
|
return orders.filter((order) => statuses.has(String(order.process_status || '')))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOrderCardActions (order: { process_status?: string, goods_type?: string }) {
|
||||||
|
const status = String(order.process_status || '')
|
||||||
|
const detail = DONE_STATUSES.has(status)
|
||||||
|
return {
|
||||||
|
complaint: !detail,
|
||||||
|
submit: SUBMIT_STATUSES.has(status) && !isCorpGoodsType(order.goods_type),
|
||||||
|
detail,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
export default definePageConfig({
|
||||||
|
navigationBarTitleText: '投诉',
|
||||||
|
navigationBarBackgroundColor: '#00C261',
|
||||||
|
navigationBarTextStyle: 'white',
|
||||||
|
backgroundColor: '#F6F8FC',
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,284 @@
|
||||||
|
.complaint {
|
||||||
|
min-height: 100vh;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-bottom: 180px;
|
||||||
|
background: #f6f8fc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-hero {
|
||||||
|
padding: 8px 32px 56px;
|
||||||
|
background: #00c261;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-hero__row {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-hero__name {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
padding-right: 16px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-hero__paid {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 26px;
|
||||||
|
line-height: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-hero__meta {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 20px 24px;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: rgba(255, 255, 255, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-hero__meta-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-hero__meta-row + .complaint-hero__meta-row {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-hero__meta-label {
|
||||||
|
width: 140px;
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-hero__meta-value {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 36px;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-sheet {
|
||||||
|
margin-top: -32px;
|
||||||
|
padding: 32px 32px 0;
|
||||||
|
border-radius: 32px 32px 0 0;
|
||||||
|
background: #f6f8fc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-field {
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: #1d2129;
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-label__required {
|
||||||
|
margin-left: 4px;
|
||||||
|
color: #f53f3f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-label__extra {
|
||||||
|
margin-left: 8px;
|
||||||
|
color: #86909c;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-types {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding: 8px 8px 0;
|
||||||
|
border-radius: 24px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-type {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: calc(50% - 16px);
|
||||||
|
margin: 8px;
|
||||||
|
padding: 20px 16px;
|
||||||
|
border: 2px solid #e5e6eb;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-type--active {
|
||||||
|
border-color: #165dff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-type__icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
margin-right: 8px;
|
||||||
|
border-radius: 22px;
|
||||||
|
background: #f2f3f5;
|
||||||
|
color: #4e5969;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-type--active .complaint-type__icon {
|
||||||
|
background: #e8f3ff;
|
||||||
|
color: #165dff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-type__text {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
color: #1d2129;
|
||||||
|
font-size: 26px;
|
||||||
|
line-height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-type--active .complaint-type__text {
|
||||||
|
color: #165dff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-textarea {
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-height: 280px;
|
||||||
|
padding: 20px 24px 48px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-textarea__input {
|
||||||
|
width: 100%;
|
||||||
|
height: 220px;
|
||||||
|
color: #1d2129;
|
||||||
|
font-size: 28px;
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-textarea__count {
|
||||||
|
position: absolute;
|
||||||
|
right: 24px;
|
||||||
|
bottom: 16px;
|
||||||
|
color: #c9cdd4;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-placeholder {
|
||||||
|
color: #c9cdd4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-uploads {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-upload {
|
||||||
|
position: relative;
|
||||||
|
width: 160px;
|
||||||
|
height: 160px;
|
||||||
|
margin: 0 16px 16px 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-upload--add {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 2px solid #e5e6eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-upload__image,
|
||||||
|
.complaint-upload__camera {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-upload__camera {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-upload__remove {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 18px;
|
||||||
|
background: rgba(29, 33, 41, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-upload__remove-text {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 28px;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-contact {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 88px;
|
||||||
|
padding: 0 24px;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-contact__input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
height: 88px;
|
||||||
|
color: #1d2129;
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-contact__clear {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-footer {
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 10;
|
||||||
|
padding: 16px 32px calc(24px + env(safe-area-inset-bottom));
|
||||||
|
background: #f6f8fc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-submit {
|
||||||
|
height: 88px;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: #165dff;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 88px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-submit::after {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,216 @@
|
||||||
|
import { Button, Image, Input, Text, Textarea, View } from '@tarojs/components'
|
||||||
|
import Taro, { useLoad } from '@tarojs/taro'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { report, uploadImage } from '@/api/user'
|
||||||
|
import { LOCAL_IMAGES } from '@/lib/assets'
|
||||||
|
import { buildFeedbackPayload, COMPLAINT_TYPES } from '@/lib/feedback'
|
||||||
|
import { getOrderDraft } from '@/lib/orderDraft'
|
||||||
|
import type { OrderLike, UploadedImage } from '@/lib/order'
|
||||||
|
import './index.scss'
|
||||||
|
|
||||||
|
const TYPE_ICONS: Record<string, string> = {
|
||||||
|
莫名扣费: '¥',
|
||||||
|
超过7天未解决: '7',
|
||||||
|
交易被骗: '¥',
|
||||||
|
不满意商家服务: '服',
|
||||||
|
其他问题: '…',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ComplaintPage () {
|
||||||
|
const [order, setOrder] = useState<OrderLike | null>(null)
|
||||||
|
const [type, setType] = useState('')
|
||||||
|
const [content, setContent] = useState('')
|
||||||
|
const [contact, setContact] = useState('')
|
||||||
|
const [images, setImages] = useState<UploadedImage[]>([])
|
||||||
|
const [uploading, setUploading] = useState(false)
|
||||||
|
const [submitting, setSubmitting] = useState(false)
|
||||||
|
|
||||||
|
useLoad(() => {
|
||||||
|
const draft = getOrderDraft()
|
||||||
|
if (!draft) {
|
||||||
|
Taro.showToast({ title: '订单不存在', icon: 'none' })
|
||||||
|
setTimeout(() => Taro.navigateBack(), 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setOrder(draft)
|
||||||
|
setContact(String(draft.entity_phone || ''))
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleChooseImage = async () => {
|
||||||
|
if (uploading || images.length >= 5) return
|
||||||
|
const res = await Taro.chooseImage({
|
||||||
|
count: Math.max(1, 5 - images.length),
|
||||||
|
sizeType: ['compressed'],
|
||||||
|
sourceType: ['album', 'camera'],
|
||||||
|
})
|
||||||
|
const paths = res.tempFilePaths || []
|
||||||
|
if (!paths.length) return
|
||||||
|
|
||||||
|
setUploading(true)
|
||||||
|
try {
|
||||||
|
const uploaded: UploadedImage[] = []
|
||||||
|
for (const filePath of paths) {
|
||||||
|
const result = await uploadImage(filePath)
|
||||||
|
uploaded.push({ id: result.id, url: result.url, preview: filePath })
|
||||||
|
}
|
||||||
|
setImages((current) => [...current, ...uploaded].slice(0, 5))
|
||||||
|
} catch (error) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: error instanceof Error ? error.message : '图片上传失败',
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
setUploading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!order || submitting) return
|
||||||
|
if (!type) {
|
||||||
|
Taro.showToast({ title: '请选择投诉类型', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!content.trim()) {
|
||||||
|
Taro.showToast({ title: '请填写问题描述', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (uploading) {
|
||||||
|
Taro.showToast({ title: '图片正在上传,请稍候', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setSubmitting(true)
|
||||||
|
try {
|
||||||
|
await report(buildFeedbackPayload({
|
||||||
|
contact: contact.trim(),
|
||||||
|
content: content.trim(),
|
||||||
|
images: images.map((image) => image.url),
|
||||||
|
type,
|
||||||
|
order,
|
||||||
|
}))
|
||||||
|
Taro.showToast({ title: '提交成功', icon: 'success' })
|
||||||
|
setTimeout(() => Taro.navigateBack(), 500)
|
||||||
|
} catch {
|
||||||
|
Taro.showToast({ title: '提交失败,请稍后再试', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View className='complaint'>
|
||||||
|
<View className='complaint-hero'>
|
||||||
|
<View className='complaint-hero__row'>
|
||||||
|
<Text className='complaint-hero__name'>{order?.goods_name || '-'}</Text>
|
||||||
|
<Text className='complaint-hero__paid'>已支付 ¥{order?.total_fee || '-'}</Text>
|
||||||
|
</View>
|
||||||
|
<View className='complaint-hero__meta'>
|
||||||
|
<View className='complaint-hero__meta-row'>
|
||||||
|
<Text className='complaint-hero__meta-label'>交易ID</Text>
|
||||||
|
<Text className='complaint-hero__meta-value'>{order?.out_trade_no || '-'}</Text>
|
||||||
|
</View>
|
||||||
|
<View className='complaint-hero__meta-row'>
|
||||||
|
<Text className='complaint-hero__meta-label'>交易时间</Text>
|
||||||
|
<Text className='complaint-hero__meta-value'>{order?.create_time || order?.pay_time || '-'}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className='complaint-sheet'>
|
||||||
|
<View className='complaint-field'>
|
||||||
|
<View className='complaint-label'>
|
||||||
|
<Text>投诉类型</Text>
|
||||||
|
<Text className='complaint-label__required'>*</Text>
|
||||||
|
</View>
|
||||||
|
<View className='complaint-types'>
|
||||||
|
{COMPLAINT_TYPES.map((item) => (
|
||||||
|
<View
|
||||||
|
key={item.value}
|
||||||
|
className={`complaint-type ${type === item.value ? 'complaint-type--active' : ''}`}
|
||||||
|
onClick={() => setType(item.value)}
|
||||||
|
>
|
||||||
|
<View className='complaint-type__icon'>
|
||||||
|
<Text>{TYPE_ICONS[item.value]}</Text>
|
||||||
|
</View>
|
||||||
|
<Text className='complaint-type__text'>{item.label}</Text>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className='complaint-field'>
|
||||||
|
<View className='complaint-label'>
|
||||||
|
<Text>问题描述</Text>
|
||||||
|
<Text className='complaint-label__required'>*</Text>
|
||||||
|
</View>
|
||||||
|
<View className='complaint-textarea'>
|
||||||
|
<Textarea
|
||||||
|
className='complaint-textarea__input'
|
||||||
|
maxlength={200}
|
||||||
|
placeholder='请描述您遇到的问题'
|
||||||
|
placeholderClass='complaint-placeholder'
|
||||||
|
value={content}
|
||||||
|
onInput={(event) => setContent(event.detail.value.slice(0, 200))}
|
||||||
|
/>
|
||||||
|
<Text className='complaint-textarea__count'>{content.length}/200</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className='complaint-field'>
|
||||||
|
<View className='complaint-label'>
|
||||||
|
<Text>上传图片</Text>
|
||||||
|
<Text className='complaint-label__extra'>(选填,最多可上传5张)</Text>
|
||||||
|
</View>
|
||||||
|
<View className='complaint-uploads'>
|
||||||
|
{images.map((image, index) => (
|
||||||
|
<View className='complaint-upload' key={`${image.id}-${index}`}>
|
||||||
|
<Image className='complaint-upload__image' src={image.preview || image.url} mode='aspectFill' />
|
||||||
|
<View
|
||||||
|
className='complaint-upload__remove'
|
||||||
|
onClick={() => setImages((current) => current.filter((_, itemIndex) => itemIndex !== index))}
|
||||||
|
>
|
||||||
|
<Text className='complaint-upload__remove-text'>×</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
{images.length < 5 ? (
|
||||||
|
<View className='complaint-upload complaint-upload--add' onClick={() => void handleChooseImage()}>
|
||||||
|
<Image className='complaint-upload__camera' src={LOCAL_IMAGES.camera} />
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className='complaint-field'>
|
||||||
|
<View className='complaint-label'>
|
||||||
|
<Text>联系方式</Text>
|
||||||
|
<Text className='complaint-label__extra'>(选填,可留下您的手机号、微信、邮箱)</Text>
|
||||||
|
</View>
|
||||||
|
<View className='complaint-contact'>
|
||||||
|
<Input
|
||||||
|
className='complaint-contact__input'
|
||||||
|
value={contact}
|
||||||
|
placeholder='请输入联系方式'
|
||||||
|
placeholderClass='complaint-placeholder'
|
||||||
|
onInput={(event) => setContact(event.detail.value)}
|
||||||
|
/>
|
||||||
|
{contact ? (
|
||||||
|
<Image className='complaint-contact__clear' src={LOCAL_IMAGES.clear} onClick={() => setContact('')} />
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className='complaint-footer'>
|
||||||
|
<Button
|
||||||
|
className='complaint-submit'
|
||||||
|
loading={submitting}
|
||||||
|
disabled={uploading}
|
||||||
|
onClick={() => void handleSubmit()}
|
||||||
|
>
|
||||||
|
确定
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
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 { CORP_ASSET_BASE, STATIC_CDN } from '@/lib/cdn'
|
import { CORP_ASSET_BASE } from '@/lib/cdn'
|
||||||
import { pickServiceContact, type ServiceContact } from '@/lib/contact'
|
import type { ServiceContact } from '@/lib/contact'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
open: boolean
|
open: boolean
|
||||||
|
|
@ -91,11 +91,11 @@ export default function ContactModal ({ open, contact, onCancel }: Props) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ContactEntry ({ onClick }: { onClick: () => void }) {
|
export function ContactFab ({ onClick }: { onClick: () => void }) {
|
||||||
return (
|
return (
|
||||||
<View className='contact-entry' onClick={onClick}>
|
<View className='contact-fab' onClick={onClick}>
|
||||||
<Image className='contact-entry__icon' src={`${STATIC_CDN}/images/icon-contract.png`} />
|
<Image className='contact-fab__icon' src={`${CORP_ASSET_BASE}/images-pc/icon-contactus-service.png`} />
|
||||||
<Text className='contact-entry__text'>联系我们</Text>
|
<Text className='contact-fab__text'>客服</Text>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 { 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 {
|
||||||
getReceiptMeta,
|
getReceiptMeta,
|
||||||
|
|
@ -15,7 +16,7 @@ type Props = {
|
||||||
onCancel: () => void
|
onCancel: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DetailModal ({ order, onCancel }: Props) {
|
export default function DetailDrawer ({ order, onCancel }: Props) {
|
||||||
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: [] }
|
||||||
|
|
@ -24,11 +25,12 @@ export default function DetailModal ({ order, onCancel }: Props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='detail-mask' onClick={onCancel}>
|
<View className='detail-mask' onClick={onCancel}>
|
||||||
<View className='detail-dialog' onClick={(event) => event.stopPropagation()}>
|
<View className='detail-drawer' onClick={(event) => event.stopPropagation()}>
|
||||||
|
<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={onCancel}>
|
||||||
<Text className='detail-dialog__close-icon'>×</Text>
|
<Image className='detail-dialog__close-img' src={LOCAL_IMAGES.close} />
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
export default definePageConfig({
|
export default definePageConfig({
|
||||||
navigationBarTitleText: '网上快办平台',
|
navigationStyle: 'custom',
|
||||||
navigationBarBackgroundColor: '#2F4FEE',
|
navigationBarTextStyle: 'black',
|
||||||
navigationBarTextStyle: 'white',
|
navigationBarBackgroundColor: '#F6F8FC',
|
||||||
|
backgroundColor: '#F6F8FC',
|
||||||
|
backgroundColorTop: '#F6F8FC',
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,228 +1,193 @@
|
||||||
.check-page {
|
.home {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding-bottom: 48px;
|
padding-bottom: 48px;
|
||||||
background: #2f4fee;
|
background: #f6f8fc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.check-hero {
|
.home-nav {
|
||||||
position: relative;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
padding-left: 32px;
|
||||||
height: 420px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.check-hero__bg {
|
.home-nav__logo {
|
||||||
position: absolute;
|
width: 48px;
|
||||||
top: 0;
|
height: 48px;
|
||||||
right: 0;
|
margin-right: 12px;
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.check-hero__title {
|
.home-nav__title {
|
||||||
position: relative;
|
color: #165dff;
|
||||||
z-index: 1;
|
font-size: 36px;
|
||||||
color: #fff;
|
|
||||||
font-size: 72px;
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 6px;
|
line-height: 50px;
|
||||||
line-height: 96px;
|
|
||||||
text-align: center;
|
|
||||||
text-shadow: 0 4px 8px #1840ff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.check-card {
|
.home-search {
|
||||||
position: relative;
|
display: flex;
|
||||||
z-index: 1;
|
align-items: center;
|
||||||
margin: 0 24px 48px;
|
margin: 16px 32px 8px;
|
||||||
}
|
padding: 8px 8px 8px 24px;
|
||||||
|
border-radius: 20px;
|
||||||
.check-card--lift {
|
|
||||||
margin-top: -80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.check-card__header {
|
|
||||||
position: relative;
|
|
||||||
height: 154px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.check-card__header-bg {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: 154px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.check-card__header-title {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
bottom: 28px;
|
|
||||||
left: 16px;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 50px;
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 60px;
|
|
||||||
text-align: center;
|
|
||||||
text-shadow: 0 8px 0 #0026b3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.check-card__shell {
|
|
||||||
padding: 0 16px 16px;
|
|
||||||
border-radius: 0 0 28px 28px;
|
|
||||||
background: #cbdeff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.check-card__body,
|
|
||||||
.check-card__list {
|
|
||||||
padding: 40px 24px 40px;
|
|
||||||
border-radius: 0 0 28px 28px;
|
|
||||||
background: #fff;
|
background: #fff;
|
||||||
|
box-shadow: 0 8px 24px rgba(22, 93, 255, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
.check-card__note {
|
.home-search__field {
|
||||||
display: block;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
color: #ff2626;
|
|
||||||
font-size: 24px;
|
|
||||||
line-height: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.check-card__label {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
color: #666;
|
|
||||||
font-size: 28px;
|
|
||||||
line-height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.check-card__label-icon {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.check-card__required {
|
|
||||||
margin-left: 4px;
|
|
||||||
color: #ff2f2f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.check-card__input {
|
|
||||||
box-sizing: border-box;
|
|
||||||
height: 88px;
|
|
||||||
padding: 0 24px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 8px;
|
|
||||||
color: #222;
|
|
||||||
font-size: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.check-card__button {
|
|
||||||
margin-top: 32px;
|
|
||||||
height: 88px;
|
|
||||||
border: 0;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: #0261fc;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 30px;
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 88px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.check-card__button::after {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.check-card__empty {
|
|
||||||
min-height: 200px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #999;
|
|
||||||
font-size: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-card {
|
|
||||||
padding: 24px 0 32px;
|
|
||||||
border-top: 1px solid #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-card--first {
|
|
||||||
padding-top: 0;
|
|
||||||
border-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-card__head {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-card__goods {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-card__goods-icon {
|
.home-search__icon {
|
||||||
width: 40px;
|
width: 36px;
|
||||||
height: 40px;
|
height: 36px;
|
||||||
margin-right: -8px;
|
margin-right: 12px;
|
||||||
border-radius: 20px;
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-search__input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
height: 72px;
|
||||||
|
color: #1d2129;
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-search__placeholder {
|
||||||
|
color: #c9cdd4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-search__button {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 128px;
|
||||||
|
height: 72px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: #165dff;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-search__button::after {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-tabs {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
padding: 16px 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-tabs__item {
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-tabs__label {
|
||||||
|
color: #86909c;
|
||||||
|
font-size: 28px;
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-tabs__item--active .home-tabs__label {
|
||||||
|
color: #165dff;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-tabs__line {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 48px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #165dff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-list {
|
||||||
|
padding: 16px 24px 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-empty {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 160px 48px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-empty__image {
|
||||||
|
width: 360px;
|
||||||
|
height: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-empty__text {
|
||||||
|
margin-top: 24px;
|
||||||
|
color: #86909c;
|
||||||
|
font-size: 28px;
|
||||||
|
line-height: 40px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-card {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
padding: 32px 28px 28px;
|
||||||
|
border-radius: 24px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-card__head {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-card__goods-name {
|
.order-card__goods-name {
|
||||||
margin-left: 16px;
|
flex: 1;
|
||||||
color: #222;
|
min-width: 0;
|
||||||
font-size: 28px;
|
padding-right: 16px;
|
||||||
font-weight: 500;
|
color: #1d2129;
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 44px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-card__status {
|
.order-card__status {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
min-width: 102px;
|
font-size: 26px;
|
||||||
height: 44px;
|
|
||||||
padding: 0 12px;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 24px;
|
|
||||||
line-height: 44px;
|
line-height: 44px;
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-card__reject {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
padding: 16px;
|
|
||||||
border-radius: 8px;
|
|
||||||
background: #fff3f3;
|
|
||||||
color: #ff0707;
|
|
||||||
font-size: 24px;
|
|
||||||
line-height: 36px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-card__row {
|
.order-card__row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
margin-top: 16px;
|
margin-top: 12px;
|
||||||
font-size: 28px;
|
font-size: 26px;
|
||||||
line-height: 36px;
|
line-height: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-card__label {
|
.order-card__label {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
color: #666;
|
color: #86909c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-card__value {
|
.order-card__value {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
color: #222;
|
color: #4e5969;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -239,17 +204,27 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-card__pay-icon {
|
.order-card__pay-icon {
|
||||||
width: 40px;
|
width: 36px;
|
||||||
height: 40px;
|
height: 36px;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-card__fee {
|
.order-card__fee {
|
||||||
color: #ff2f2f;
|
color: #f53f3f;
|
||||||
font-size: 30px;
|
font-size: 28px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.order-card__reject {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #fff1f0;
|
||||||
|
color: #f53f3f;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
.order-card__actions {
|
.order-card__actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|
@ -258,39 +233,49 @@
|
||||||
|
|
||||||
.order-card__action {
|
.order-card__action {
|
||||||
min-width: 160px;
|
min-width: 160px;
|
||||||
height: 64px;
|
height: 72px;
|
||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
border-radius: 12px;
|
padding: 0 28px;
|
||||||
background: #eaf3ff;
|
border-radius: 16px;
|
||||||
color: #0261fc;
|
background: #f2f3f5;
|
||||||
|
color: #4e5969;
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
line-height: 64px;
|
line-height: 72px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-mask,
|
.order-card__action--primary {
|
||||||
.resubmit-mask {
|
background: #165dff;
|
||||||
position: fixed;
|
color: #fff;
|
||||||
inset: 0;
|
font-weight: 600;
|
||||||
z-index: 20;
|
|
||||||
background: rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-mask {
|
.detail-mask {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 20;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-end;
|
||||||
justify-content: center;
|
background: rgba(0, 0, 0, 0.45);
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-dialog {
|
.detail-drawer {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 686px;
|
width: 100%;
|
||||||
max-height: 80vh;
|
max-height: 82vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 20px;
|
border-radius: 24px 24px 0 0;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detail-drawer__handle {
|
||||||
|
width: 64px;
|
||||||
|
height: 8px;
|
||||||
|
margin: 16px auto 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #e5e6eb;
|
||||||
|
}
|
||||||
|
|
||||||
.detail-dialog__head {
|
.detail-dialog__head {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -300,7 +285,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-dialog__title {
|
.detail-dialog__title {
|
||||||
color: #222;
|
color: #1d2129;
|
||||||
font-size: 34px;
|
font-size: 34px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 44px;
|
line-height: 44px;
|
||||||
|
|
@ -317,15 +302,14 @@
|
||||||
height: 48px;
|
height: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-dialog__close-icon {
|
.detail-dialog__close-img {
|
||||||
color: #999;
|
width: 28px;
|
||||||
font-size: 48px;
|
height: 28px;
|
||||||
line-height: 40px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-dialog__body {
|
.detail-dialog__body {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
max-height: calc(80vh - 88px);
|
max-height: calc(82vh - 112px);
|
||||||
padding: 0 32px 40px;
|
padding: 0 32px 40px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
@ -478,220 +462,33 @@
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.resubmit-mask {
|
.contact-fab {
|
||||||
display: flex;
|
position: fixed;
|
||||||
align-items: flex-end;
|
right: 32px;
|
||||||
}
|
bottom: 80px;
|
||||||
|
z-index: 15;
|
||||||
.resubmit {
|
|
||||||
position: relative;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 100%;
|
|
||||||
max-height: 84vh;
|
|
||||||
padding: 32px 24px 40px;
|
|
||||||
overflow-y: auto;
|
|
||||||
border-radius: 24px 24px 0 0;
|
|
||||||
background: linear-gradient(180deg, #c5eeff 0, #fff 136px) #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resubmit__close {
|
|
||||||
position: absolute;
|
|
||||||
top: 24px;
|
|
||||||
right: 24px;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resubmit__close-text {
|
|
||||||
color: #b3b3b3;
|
|
||||||
font-size: 48px;
|
|
||||||
line-height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resubmit__title {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
color: #222;
|
|
||||||
font-size: 34px;
|
|
||||||
font-weight: 500;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resubmit__reject {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
padding: 20px 24px;
|
|
||||||
border-radius: 12px;
|
|
||||||
background: #fff1f1;
|
|
||||||
color: #ff0707;
|
|
||||||
font-size: 24px;
|
|
||||||
line-height: 36px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resubmit__field {
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resubmit__label {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
color: #3d4044;
|
|
||||||
font-size: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resubmit__label-icon {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resubmit__required {
|
|
||||||
margin-left: 4px;
|
|
||||||
color: #ff2f2f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resubmit__input,
|
|
||||||
.resubmit__map-input {
|
|
||||||
box-sizing: border-box;
|
|
||||||
height: 88px;
|
|
||||||
padding: 0 18px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 12px;
|
|
||||||
color: #222;
|
|
||||||
font-size: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resubmit__map-input {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resubmit__map-value {
|
|
||||||
flex: 1;
|
|
||||||
color: #222;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resubmit__map-placeholder {
|
|
||||||
flex: 1;
|
|
||||||
color: #b3b3b3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resubmit__map-arrow {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resubmit__tip {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
color: #999;
|
|
||||||
font-size: 24px;
|
|
||||||
line-height: 36px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resubmit__upload-list {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resubmit__upload-item,
|
|
||||||
.resubmit__upload-slot {
|
|
||||||
position: relative;
|
|
||||||
width: 200px;
|
|
||||||
height: 200px;
|
|
||||||
margin: 0 16px 16px 0;
|
|
||||||
overflow: hidden;
|
|
||||||
border-radius: 12px;
|
|
||||||
background: #f7f8fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resubmit__upload-image {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resubmit__upload-remove {
|
|
||||||
position: absolute;
|
|
||||||
top: 8px;
|
|
||||||
right: 8px;
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
border-radius: 18px;
|
|
||||||
background: rgba(0, 0, 0, 0.5);
|
|
||||||
color: #fff;
|
|
||||||
font-size: 28px;
|
|
||||||
line-height: 36px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resubmit__upload-slot {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border: 1px dashed #cfd6de;
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
border-radius: 60px;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 8px 24px rgba(22, 93, 255, 0.16);
|
||||||
}
|
}
|
||||||
|
|
||||||
.resubmit__upload-plus {
|
.contact-fab__icon {
|
||||||
width: 56px;
|
width: 48px;
|
||||||
height: 56px;
|
height: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.resubmit__upload-text {
|
.contact-fab__text {
|
||||||
margin-top: 12px;
|
margin-top: 4px;
|
||||||
color: #999;
|
color: #165dff;
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
}
|
line-height: 32px;
|
||||||
|
|
||||||
.resubmit__error {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
color: #ff2f2f;
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resubmit__submit {
|
|
||||||
height: 88px;
|
|
||||||
border: 0;
|
|
||||||
border-radius: 12px;
|
|
||||||
background: #0261fc;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 88px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resubmit__submit::after {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contact-entry {
|
|
||||||
position: fixed;
|
|
||||||
top: 24px;
|
|
||||||
right: 0;
|
|
||||||
z-index: 15;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
width: 204px;
|
|
||||||
height: 60px;
|
|
||||||
padding-left: 20px;
|
|
||||||
border-radius: 268px 0 0 268px;
|
|
||||||
background: rgba(0, 0, 0, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.contact-entry__icon {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
margin-right: 4px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contact-entry__text {
|
|
||||||
color: #fff;
|
|
||||||
font-size: 30px;
|
|
||||||
line-height: 44px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact-mask {
|
.contact-mask {
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,43 @@
|
||||||
import { Button, Image, Input, Text, View } from '@tarojs/components'
|
import { Button, Image, Input, Text, View } from '@tarojs/components'
|
||||||
import Taro, { useDidShow, useLoad } from '@tarojs/taro'
|
import Taro, { useDidShow, useLoad } from '@tarojs/taro'
|
||||||
import { useState } from 'react'
|
import { useMemo, useRef, useState } from 'react'
|
||||||
import { getOrdersByPhone } from '@/api/pay'
|
import { getOrdersByPhone } from '@/api/pay'
|
||||||
|
import { LOCAL_IMAGES } from '@/lib/assets'
|
||||||
import { bootstrapSession } from '@/lib/bootstrap'
|
import { bootstrapSession } from '@/lib/bootstrap'
|
||||||
import { getSession } from '@/lib/session'
|
import { COPY_ICON, PAGE_TITLE } from '@/lib/cdn'
|
||||||
import { BANNER_BG, CARD_HEADER, CORP_ASSET_BASE, ENTITY_LABELS, HOME_ICON_BASE, PAGE_TITLE } from '@/lib/cdn'
|
import { getNavMetrics } from '@/lib/nav'
|
||||||
import { pickServiceContact, type ServiceContact } from '@/lib/contact'
|
import { pickServiceContact, type ServiceContact } from '@/lib/contact'
|
||||||
import { isCorpGoodsType } from '@/lib/detail'
|
import { setOrderDraft } from '@/lib/orderDraft'
|
||||||
import {
|
import { filterOrdersForProduct, type OrderLike } from '@/lib/order'
|
||||||
filterOrdersForProduct,
|
|
||||||
getGoodsIcons,
|
|
||||||
getOrderAddressName,
|
|
||||||
parseCoordinate,
|
|
||||||
type OrderLike,
|
|
||||||
type ResubmitValues,
|
|
||||||
} from '@/lib/order'
|
|
||||||
import { isValidPhone } from '@/lib/phone'
|
import { isValidPhone } from '@/lib/phone'
|
||||||
import { getStatusMeta, PAY_ICON } from '@/lib/status'
|
import { getSession } from '@/lib/session'
|
||||||
import { consumeMapPickerResult, type MapPickerResult } from '@/pages/map/picker'
|
import {
|
||||||
import ContactModal, { ContactEntry } from './contact'
|
filterOrdersByTab,
|
||||||
import DetailModal from './detail'
|
getOrderCardActions,
|
||||||
import ResubmitDrawer from './resubmit'
|
getStatusMeta,
|
||||||
|
ORDER_TABS,
|
||||||
|
PAY_ICON,
|
||||||
|
type OrderTabKey,
|
||||||
|
} from '@/lib/status'
|
||||||
|
import ContactModal, { ContactFab } from './contact'
|
||||||
|
import DetailDrawer from './detail'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
const COPY_ICON = `${CORP_ASSET_BASE}/images-mobile/icon-copy.png`
|
|
||||||
|
|
||||||
export default function Index () {
|
export default function Index () {
|
||||||
|
const nav = useMemo(() => getNavMetrics(), [])
|
||||||
const [ready, setReady] = useState(false)
|
const [ready, setReady] = useState(false)
|
||||||
const [inputPhone, setInputPhone] = useState('')
|
const [inputPhone, setInputPhone] = useState('')
|
||||||
const [checking, setChecking] = useState(false)
|
const [checking, setChecking] = useState(false)
|
||||||
const [searched, setSearched] = useState(false)
|
const [searched, setSearched] = useState(false)
|
||||||
|
const [activeTab, setActiveTab] = useState<OrderTabKey>('all')
|
||||||
const [orders, setOrders] = useState<OrderLike[]>([])
|
const [orders, setOrders] = useState<OrderLike[]>([])
|
||||||
const [detailOrder, setDetailOrder] = useState<OrderLike | null>(null)
|
const [detailOrder, setDetailOrder] = useState<OrderLike | null>(null)
|
||||||
const [resubmitOrder, setResubmitOrder] = useState<OrderLike | null>(null)
|
|
||||||
const [mapResult, setMapResult] = useState<MapPickerResult | null>(null)
|
|
||||||
const [showContact, setShowContact] = useState(false)
|
const [showContact, setShowContact] = useState(false)
|
||||||
const [contact, setContact] = useState<ServiceContact>({ online: '', phone: '', weixin: '' })
|
const [contact, setContact] = useState<ServiceContact>({ online: '', phone: '', weixin: '' })
|
||||||
|
const inputPhoneRef = useRef(inputPhone)
|
||||||
|
const searchedRef = useRef(searched)
|
||||||
|
inputPhoneRef.current = inputPhone
|
||||||
|
searchedRef.current = searched
|
||||||
|
|
||||||
useLoad(async (options) => {
|
useLoad(async (options) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -51,13 +53,6 @@ export default function Index () {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
useDidShow(() => {
|
|
||||||
const result = consumeMapPickerResult()
|
|
||||||
if (result) {
|
|
||||||
setMapResult(result)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const queryOrders = async (phone: string) => {
|
const queryOrders = async (phone: string) => {
|
||||||
if (!phone) {
|
if (!phone) {
|
||||||
Taro.showToast({ title: '请输入联系电话', icon: 'none' })
|
Taro.showToast({ title: '请输入联系电话', icon: 'none' })
|
||||||
|
|
@ -74,9 +69,6 @@ export default function Index () {
|
||||||
const nextOrders = filterOrdersForProduct(getSession().goodsType, res.data || [])
|
const nextOrders = filterOrdersForProduct(getSession().goodsType, res.data || [])
|
||||||
setOrders(nextOrders)
|
setOrders(nextOrders)
|
||||||
setSearched(true)
|
setSearched(true)
|
||||||
if (!nextOrders.length) {
|
|
||||||
Taro.showToast({ title: '无记录', icon: 'none' })
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
Taro.showToast({ title: '当前无法查询,请稍后再试', icon: 'none' })
|
Taro.showToast({ title: '当前无法查询,请稍后再试', icon: 'none' })
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -84,95 +76,92 @@ export default function Index () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleChooseMap = (draft: ResubmitValues) => {
|
useDidShow(() => {
|
||||||
const point = parseCoordinate(draft.entity_address)
|
if (searchedRef.current && inputPhoneRef.current) {
|
||||||
const query = [
|
void queryOrders(inputPhoneRef.current)
|
||||||
point ? `lng=${point.lng}` : '',
|
}
|
||||||
point ? `lat=${point.lat}` : '',
|
})
|
||||||
draft.entity_address_name ? `name=${encodeURIComponent(draft.entity_address_name)}` : '',
|
|
||||||
].filter(Boolean).join('&')
|
|
||||||
|
|
||||||
Taro.navigateTo({
|
const visibleOrders = filterOrdersByTab(orders, activeTab)
|
||||||
url: query ? `/pages/map/index?${query}` : '/pages/map/index',
|
const emptyText = searched && orders.length === 0
|
||||||
})
|
? '该手机号未查询到订单,请联系客服处理'
|
||||||
|
: '暂无订单列表'
|
||||||
|
|
||||||
|
const openOrderPage = (path: string, order: OrderLike) => {
|
||||||
|
setOrderDraft(order)
|
||||||
|
Taro.navigateTo({ url: path })
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='check-page'>
|
<View className='home' style={{ paddingTop: nav.statusBarHeight }}>
|
||||||
<View className='check-hero'>
|
<View className='home-nav' style={{ height: nav.navBarHeight, paddingRight: nav.paddingRight }}>
|
||||||
<Image className='check-hero__bg' src={BANNER_BG} mode='aspectFill' />
|
<Image className='home-nav__logo' src={LOCAL_IMAGES.logo} />
|
||||||
<Text className='check-hero__title'>{PAGE_TITLE}</Text>
|
<Text className='home-nav__title'>{PAGE_TITLE}</Text>
|
||||||
</View>
|
</View>
|
||||||
<ContactEntry onClick={() => setShowContact(true)} />
|
|
||||||
|
|
||||||
<View className='check-card check-card--lift'>
|
<View className='home-search'>
|
||||||
<View className='check-card__header'>
|
<View className='home-search__field'>
|
||||||
<Image className='check-card__header-bg' src={CARD_HEADER} mode='scaleToFill' />
|
<Image className='home-search__icon' src={LOCAL_IMAGES.search} />
|
||||||
<Text className='check-card__header-title'>进度查询</Text>
|
<Input
|
||||||
|
className='home-search__input'
|
||||||
|
type='number'
|
||||||
|
maxlength={12}
|
||||||
|
placeholder='请输入手机号查询订单'
|
||||||
|
placeholderClass='home-search__placeholder'
|
||||||
|
value={inputPhone}
|
||||||
|
onInput={(event) => setInputPhone(event.detail.value.replace(/\D/g, ''))}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View className='check-card__shell'>
|
<Button
|
||||||
<View className='check-card__body'>
|
className='home-search__button'
|
||||||
<Text className='check-card__note'>注:无法查询订单请联系客服</Text>
|
loading={checking}
|
||||||
<View className='check-card__label'>
|
disabled={!ready || checking}
|
||||||
<Image className='check-card__label-icon' src={`${HOME_ICON_BASE}/icon_form_phone@2x.png`} />
|
onClick={() => void queryOrders(inputPhone)}
|
||||||
<Text>联系电话</Text>
|
>
|
||||||
<Text className='check-card__required'>*</Text>
|
查询
|
||||||
</View>
|
</Button>
|
||||||
<Input
|
</View>
|
||||||
className='check-card__input'
|
|
||||||
type='number'
|
<View className='home-tabs'>
|
||||||
maxlength={12}
|
{ORDER_TABS.map((tab) => (
|
||||||
placeholder='请输入联系电话'
|
<View
|
||||||
value={inputPhone}
|
key={tab.key}
|
||||||
onInput={(event) => setInputPhone(event.detail.value.replace(/\D/g, ''))}
|
className={`home-tabs__item ${activeTab === tab.key ? 'home-tabs__item--active' : ''}`}
|
||||||
|
onClick={() => setActiveTab(tab.key)}
|
||||||
|
>
|
||||||
|
<Text className='home-tabs__label'>{tab.label}</Text>
|
||||||
|
{activeTab === tab.key ? <View className='home-tabs__line' /> : null}
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{visibleOrders.length ? (
|
||||||
|
<View className='home-list'>
|
||||||
|
{visibleOrders.map((order, index) => (
|
||||||
|
<OrderCard
|
||||||
|
key={order.order_id || order.out_trade_no || index}
|
||||||
|
order={order}
|
||||||
|
onCopy={() => {
|
||||||
|
if (!order.out_trade_no) return
|
||||||
|
Taro.setClipboardData({ data: String(order.out_trade_no) })
|
||||||
|
}}
|
||||||
|
onViewDetail={() => setDetailOrder(order)}
|
||||||
|
onResubmit={() => openOrderPage('/pages/resubmit/index', order)}
|
||||||
|
onComplaint={() => openOrderPage('/pages/complaint/index', order)}
|
||||||
/>
|
/>
|
||||||
<Button
|
))}
|
||||||
className='check-card__button'
|
|
||||||
loading={checking}
|
|
||||||
disabled={!ready || checking}
|
|
||||||
onClick={() => void queryOrders(inputPhone)}
|
|
||||||
>
|
|
||||||
查看订单进度
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
) : (
|
||||||
|
<View className='home-empty'>
|
||||||
|
<Image className='home-empty__image' src={LOCAL_IMAGES.empty} mode='aspectFit' />
|
||||||
|
<Text className='home-empty__text'>{emptyText}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
{searched ? (
|
<ContactFab onClick={() => setShowContact(true)} />
|
||||||
<View className='check-card'>
|
|
||||||
<View className='check-card__header'>
|
|
||||||
<Image className='check-card__header-bg' src={CARD_HEADER} mode='scaleToFill' />
|
|
||||||
<Text className='check-card__header-title'>进度查询</Text>
|
|
||||||
</View>
|
|
||||||
<View className='check-card__shell'>
|
|
||||||
<View className='check-card__list'>
|
|
||||||
{orders.length ? orders.map((order, index) => (
|
|
||||||
<OrderCard
|
|
||||||
key={order.order_id || order.out_trade_no || index}
|
|
||||||
order={order}
|
|
||||||
first={index === 0}
|
|
||||||
onCopy={() => {
|
|
||||||
if (!order.out_trade_no) return
|
|
||||||
Taro.setClipboardData({ data: String(order.out_trade_no) })
|
|
||||||
}}
|
|
||||||
onViewDetail={() => setDetailOrder(order)}
|
|
||||||
onResubmit={() => {
|
|
||||||
setMapResult(null)
|
|
||||||
setResubmitOrder(order)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)) : (
|
|
||||||
<View className='check-card__empty'>
|
|
||||||
<Text>暂无记录</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{detailOrder ? (
|
{detailOrder ? (
|
||||||
<DetailModal order={detailOrder} onCancel={() => setDetailOrder(null)} />
|
<DetailDrawer order={detailOrder} onCancel={() => setDetailOrder(null)} />
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<ContactModal
|
<ContactModal
|
||||||
|
|
@ -180,79 +169,35 @@ export default function Index () {
|
||||||
contact={contact}
|
contact={contact}
|
||||||
onCancel={() => setShowContact(false)}
|
onCancel={() => setShowContact(false)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ResubmitDrawer
|
|
||||||
open={Boolean(resubmitOrder)}
|
|
||||||
order={resubmitOrder}
|
|
||||||
mapResult={mapResult}
|
|
||||||
onCancel={() => {
|
|
||||||
setResubmitOrder(null)
|
|
||||||
setMapResult(null)
|
|
||||||
}}
|
|
||||||
onChooseMap={handleChooseMap}
|
|
||||||
onSuccess={() => {
|
|
||||||
setResubmitOrder(null)
|
|
||||||
setMapResult(null)
|
|
||||||
void queryOrders(inputPhone)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function OrderCard ({
|
function OrderCard ({
|
||||||
order,
|
order,
|
||||||
first,
|
|
||||||
onCopy,
|
onCopy,
|
||||||
onViewDetail,
|
onViewDetail,
|
||||||
onResubmit,
|
onResubmit,
|
||||||
|
onComplaint,
|
||||||
}: {
|
}: {
|
||||||
order: OrderLike
|
order: OrderLike
|
||||||
first: boolean
|
|
||||||
onCopy: () => void
|
onCopy: () => void
|
||||||
onViewDetail: () => void
|
onViewDetail: () => void
|
||||||
onResubmit: () => void
|
onResubmit: () => void
|
||||||
|
onComplaint: () => void
|
||||||
}) {
|
}) {
|
||||||
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 actions = getOrderCardActions(order)
|
||||||
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'>
|
||||||
<View className='order-card__head'>
|
<View className='order-card__head'>
|
||||||
<View className='order-card__goods'>
|
<Text className='order-card__goods-name'>{order.goods_name || '-'}</Text>
|
||||||
{goodsIcons.map((icon: string, index: number) => (
|
<Text className='order-card__status' style={{ color: status.color }}>{status.result}</Text>
|
||||||
<Image className='order-card__goods-icon' key={`${icon}-${index}`} src={icon} />
|
|
||||||
))}
|
|
||||||
<Text className='order-card__goods-name'>{order.goods_name || '-'}</Text>
|
|
||||||
</View>
|
|
||||||
<View className='order-card__status' style={{ color: status.color, background: status.bg }}>
|
|
||||||
<Text>{status.result}</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{order.reject_reason ? (
|
<InfoLine label='联系电话:' value={order.entity_phone || '-'} />
|
||||||
<View className='order-card__reject'>驳回理由:{order.reject_reason}</View>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{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 || '-'} />
|
<InfoLine label='订单时间:' value={order.create_time || order.pay_time || '-'} />
|
||||||
|
|
||||||
<View className='order-card__row'>
|
<View className='order-card__row'>
|
||||||
|
|
@ -269,14 +214,23 @@ function OrderCard ({
|
||||||
<Text className='order-card__fee'>¥{order.total_fee || '-'}</Text>
|
<Text className='order-card__fee'>¥{order.total_fee || '-'}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View className='order-card__actions'>
|
{order.reject_reason ? (
|
||||||
{showDetail ? (
|
<View className='order-card__reject'>驳回理由:{order.reject_reason}</View>
|
||||||
<View className='order-card__action' onClick={onViewDetail}>查看详情</View>
|
) : null}
|
||||||
) : null}
|
|
||||||
{showResubmit ? (
|
{actions.complaint || actions.submit || actions.detail ? (
|
||||||
<View className='order-card__action' onClick={onResubmit}>补充资料</View>
|
<View className='order-card__actions'>
|
||||||
) : null}
|
{actions.complaint ? (
|
||||||
</View>
|
<View className='order-card__action' onClick={onComplaint}>投诉</View>
|
||||||
|
) : null}
|
||||||
|
{actions.detail ? (
|
||||||
|
<View className='order-card__action' onClick={onViewDetail}>查看详情</View>
|
||||||
|
) : null}
|
||||||
|
{actions.submit ? (
|
||||||
|
<View className='order-card__action order-card__action--primary' onClick={onResubmit}>立即提交</View>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,331 +0,0 @@
|
||||||
import { Button, Image, Input, Text, View } from '@tarojs/components'
|
|
||||||
import Taro from '@tarojs/taro'
|
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { uploadImage } from '@/api/user'
|
|
||||||
import { updateOrderExtra } from '@/api/pay'
|
|
||||||
import { ENTITY_LABELS, HOME_ICON_BASE, MAP_ASSET_BASE } from '@/lib/cdn'
|
|
||||||
import { isValidPhone } from '@/lib/phone'
|
|
||||||
import {
|
|
||||||
buildResubmitPayload,
|
|
||||||
formatMapCoordinate,
|
|
||||||
getInitialPhone2,
|
|
||||||
getOrderAddressName,
|
|
||||||
getOrderExtraImages,
|
|
||||||
getOrderMapAddress,
|
|
||||||
parseCoordinate,
|
|
||||||
type OrderLike,
|
|
||||||
type ResubmitValues,
|
|
||||||
type UploadedImage,
|
|
||||||
} from '@/lib/order'
|
|
||||||
import type { MapPickerResult } from '@/pages/map/picker'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
open: boolean
|
|
||||||
order: OrderLike | null
|
|
||||||
mapResult?: MapPickerResult | null
|
|
||||||
onCancel: () => void
|
|
||||||
onChooseMap: (draft: ResubmitValues) => void
|
|
||||||
onSuccess: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
function FieldLabel ({ icon, text, required }: { icon: string, text: string, required?: boolean }) {
|
|
||||||
return (
|
|
||||||
<View className='resubmit__label'>
|
|
||||||
<Image className='resubmit__label-icon' src={icon} />
|
|
||||||
<Text>{text}</Text>
|
|
||||||
{required ? <Text className='resubmit__required'>*</Text> : null}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function UploadField ({
|
|
||||||
label,
|
|
||||||
values,
|
|
||||||
multiple = false,
|
|
||||||
uploading,
|
|
||||||
progress,
|
|
||||||
onChange,
|
|
||||||
onRemove,
|
|
||||||
}: {
|
|
||||||
label: string
|
|
||||||
values: UploadedImage[]
|
|
||||||
multiple?: boolean
|
|
||||||
uploading: boolean
|
|
||||||
progress: number
|
|
||||||
onChange: (filePaths: string[]) => void
|
|
||||||
onRemove: (index: number) => void
|
|
||||||
}) {
|
|
||||||
const showUpload = multiple || values.length === 0
|
|
||||||
|
|
||||||
const handleChoose = async () => {
|
|
||||||
const res = await Taro.chooseImage({
|
|
||||||
count: multiple ? 6 : 1,
|
|
||||||
sizeType: ['compressed'],
|
|
||||||
sourceType: ['album', 'camera'],
|
|
||||||
})
|
|
||||||
if (res.tempFilePaths?.length) {
|
|
||||||
onChange(res.tempFilePaths)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View className='resubmit__upload-list'>
|
|
||||||
{values.map((value, index) => (
|
|
||||||
<View className='resubmit__upload-item' key={`${value.id}-${index}`}>
|
|
||||||
<Image className='resubmit__upload-image' src={value.preview || value.url} mode='aspectFill' />
|
|
||||||
<View className='resubmit__upload-remove' onClick={() => onRemove(index)}>
|
|
||||||
<Text>×</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
))}
|
|
||||||
{showUpload ? (
|
|
||||||
<View className='resubmit__upload-slot' onClick={() => void handleChoose()}>
|
|
||||||
<Image className='resubmit__upload-plus' src={`${MAP_ASSET_BASE}/pay-success-upload.png`} />
|
|
||||||
<Text className='resubmit__upload-text'>
|
|
||||||
{uploading ? `上传中 ${Math.round(progress)}%` : (multiple && values.length > 0 ? '继续上传' : `请上传${label}`)}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ResubmitDrawer ({
|
|
||||||
open,
|
|
||||||
order,
|
|
||||||
mapResult,
|
|
||||||
onCancel,
|
|
||||||
onChooseMap,
|
|
||||||
onSuccess,
|
|
||||||
}: Props) {
|
|
||||||
const [values, setValues] = useState<ResubmitValues>({})
|
|
||||||
const [storefrontImages, setStorefrontImages] = useState<UploadedImage[]>([])
|
|
||||||
const [licenseImages, setLicenseImages] = useState<UploadedImage[]>([])
|
|
||||||
const [uploadingField, setUploadingField] = useState<'storefront' | 'license' | null>(null)
|
|
||||||
const [uploadProgress, setUploadProgress] = useState(0)
|
|
||||||
const [formError, setFormError] = useState('')
|
|
||||||
const [submitting, setSubmitting] = useState(false)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!order || !open) return
|
|
||||||
|
|
||||||
const mapAddress = mapResult ? formatMapCoordinate(mapResult) : ''
|
|
||||||
const mapAddressName = mapResult?.address || mapResult?.name || ''
|
|
||||||
setValues({
|
|
||||||
entity_name: values.entity_name || order.entity_name || mapResult?.name || '',
|
|
||||||
entity_address_name: mapResult ? mapAddressName || getOrderAddressName(order) : values.entity_address_name || getOrderAddressName(order),
|
|
||||||
entity_address: mapResult ? mapAddress : values.entity_address || getOrderMapAddress(order),
|
|
||||||
entity_phone: values.entity_phone || order.entity_phone || '',
|
|
||||||
entity_phone2: values.entity_phone2 || getInitialPhone2(order),
|
|
||||||
})
|
|
||||||
if (storefrontImages.length === 0) {
|
|
||||||
setStorefrontImages(getOrderExtraImages(order, ['entity_storefront_image', 'storefront_image']))
|
|
||||||
}
|
|
||||||
if (licenseImages.length === 0) {
|
|
||||||
setLicenseImages(getOrderExtraImages(order, ['entity_business_license_image', 'business_license_image']).slice(0, 1))
|
|
||||||
}
|
|
||||||
setFormError('')
|
|
||||||
// Only seed from the current order / returned map point.
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [open, order, mapResult])
|
|
||||||
|
|
||||||
const updateField = <K extends keyof ResubmitValues>(field: K, value: ResubmitValues[K]) => {
|
|
||||||
setValues((current) => ({ ...current, [field]: value }))
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleUpload = async (field: 'storefront' | 'license', filePaths: string[]) => {
|
|
||||||
setUploadingField(field)
|
|
||||||
setUploadProgress(0)
|
|
||||||
setFormError('')
|
|
||||||
try {
|
|
||||||
const images: UploadedImage[] = []
|
|
||||||
for (const [index, filePath] of filePaths.entries()) {
|
|
||||||
const uploaded = await uploadImage(filePath, (percent) => {
|
|
||||||
setUploadProgress(((index + percent / 100) / filePaths.length) * 100)
|
|
||||||
})
|
|
||||||
images.push({
|
|
||||||
id: uploaded.id,
|
|
||||||
url: uploaded.url,
|
|
||||||
preview: filePath,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (field === 'storefront') {
|
|
||||||
setStorefrontImages((current) => [...current, ...images])
|
|
||||||
} else {
|
|
||||||
setLicenseImages(images.slice(0, 1))
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
setFormError(error instanceof Error ? error.message : '图片上传失败,请稍后重试')
|
|
||||||
} finally {
|
|
||||||
setUploadingField(null)
|
|
||||||
setUploadProgress(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
if (!order || submitting) return
|
|
||||||
if (uploadingField) {
|
|
||||||
setFormError('图片正在上传,请稍候')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!values.entity_name?.trim()) {
|
|
||||||
Taro.showToast({ title: `请输入${ENTITY_LABELS.entityName}`, icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!values.entity_address_name?.trim()) {
|
|
||||||
Taro.showToast({ title: `请输入${ENTITY_LABELS.entityAddress}`, icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!values.entity_address?.trim()) {
|
|
||||||
Taro.showToast({ title: `请选择${ENTITY_LABELS.entityMapLocation}`, icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!isValidPhone(values.entity_phone || '')) {
|
|
||||||
Taro.showToast({ title: '请填写正确的联系电话1', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!isValidPhone(values.entity_phone2 || '')) {
|
|
||||||
Taro.showToast({ title: '请填写正确的联系电话2', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (storefrontImages.length === 0) {
|
|
||||||
Taro.showToast({ title: '请上传门头照片', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setSubmitting(true)
|
|
||||||
setFormError('')
|
|
||||||
try {
|
|
||||||
await updateOrderExtra(buildResubmitPayload({
|
|
||||||
order,
|
|
||||||
values,
|
|
||||||
storefrontImages,
|
|
||||||
licenseImages,
|
|
||||||
}))
|
|
||||||
Taro.showToast({ title: '重新提交成功', icon: 'success' })
|
|
||||||
onSuccess()
|
|
||||||
} catch {
|
|
||||||
Taro.showToast({ title: '重新提交失败,请稍后再试', icon: 'none' })
|
|
||||||
} finally {
|
|
||||||
setSubmitting(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!open || !order) return null
|
|
||||||
|
|
||||||
const mapPoint = parseCoordinate(values.entity_address)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View className='resubmit-mask'>
|
|
||||||
<View className='resubmit'>
|
|
||||||
<View className='resubmit__close' onClick={onCancel}>
|
|
||||||
<Text className='resubmit__close-text'>×</Text>
|
|
||||||
</View>
|
|
||||||
<Text className='resubmit__title'>重新提交</Text>
|
|
||||||
{order.reject_reason ? (
|
|
||||||
<View className='resubmit__reject'>驳回理由:{order.reject_reason}</View>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<View className='resubmit__form'>
|
|
||||||
<View className='resubmit__field'>
|
|
||||||
<FieldLabel icon={`${HOME_ICON_BASE}/icon_form_marker@2x.png`} text={ENTITY_LABELS.entityName} required />
|
|
||||||
<Input
|
|
||||||
className='resubmit__input'
|
|
||||||
value={values.entity_name}
|
|
||||||
placeholder='请输入店铺/公司名称'
|
|
||||||
onInput={(event) => updateField('entity_name', event.detail.value)}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View className='resubmit__field'>
|
|
||||||
<FieldLabel icon={`${HOME_ICON_BASE}/icon_form_location@2x.png`} text={ENTITY_LABELS.entityAddress} required />
|
|
||||||
<Input
|
|
||||||
className='resubmit__input'
|
|
||||||
value={values.entity_address_name}
|
|
||||||
placeholder='请输入实际经营地址'
|
|
||||||
onInput={(event) => updateField('entity_address_name', event.detail.value)}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View className='resubmit__field'>
|
|
||||||
<FieldLabel icon={`${HOME_ICON_BASE}/icon_form_location_lnglat@2x.png`} text={ENTITY_LABELS.entityMapLocation} required />
|
|
||||||
<View
|
|
||||||
className='resubmit__map-input'
|
|
||||||
onClick={() => onChooseMap({
|
|
||||||
...values,
|
|
||||||
entity_address: mapPoint ? formatMapCoordinate(mapPoint) : values.entity_address,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Text className={values.entity_address ? 'resubmit__map-value' : 'resubmit__map-placeholder'}>
|
|
||||||
{values.entity_address || '请选择地图位置'}
|
|
||||||
</Text>
|
|
||||||
<Image className='resubmit__map-arrow' src={`${MAP_ASSET_BASE}/icon_input_arrow@2x.png`} />
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View className='resubmit__field'>
|
|
||||||
<FieldLabel icon={`${HOME_ICON_BASE}/icon_form_phone@2x.png`} text='联系电话1' required />
|
|
||||||
<Input
|
|
||||||
className='resubmit__input'
|
|
||||||
type='number'
|
|
||||||
maxlength={12}
|
|
||||||
value={values.entity_phone}
|
|
||||||
placeholder='请输入'
|
|
||||||
onInput={(event) => updateField('entity_phone', event.detail.value.replace(/\D/g, ''))}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View className='resubmit__field'>
|
|
||||||
<FieldLabel icon={`${HOME_ICON_BASE}/icon_form_phone@2x.png`} text='联系电话2' required />
|
|
||||||
<Input
|
|
||||||
className='resubmit__input'
|
|
||||||
type='number'
|
|
||||||
maxlength={12}
|
|
||||||
value={values.entity_phone2}
|
|
||||||
placeholder='请输入'
|
|
||||||
onInput={(event) => updateField('entity_phone2', event.detail.value.replace(/\D/g, ''))}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Text className='resubmit__tip'>温馨提示:请确保您的电话畅通,工作人员会在1-7个工作日联系您</Text>
|
|
||||||
|
|
||||||
<View className='resubmit__field'>
|
|
||||||
<FieldLabel icon={`${MAP_ASSET_BASE}/pay-success-storefront.png`} text='门头照片' required />
|
|
||||||
<UploadField
|
|
||||||
label='门头照片'
|
|
||||||
values={storefrontImages}
|
|
||||||
multiple
|
|
||||||
uploading={uploadingField === 'storefront'}
|
|
||||||
progress={uploadProgress}
|
|
||||||
onChange={(paths) => void handleUpload('storefront', paths)}
|
|
||||||
onRemove={(index) => setStorefrontImages((current) => current.filter((_, itemIndex) => itemIndex !== index))}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View className='resubmit__field'>
|
|
||||||
<FieldLabel icon={`${MAP_ASSET_BASE}/pay-success-license.png`} text='营业执照' />
|
|
||||||
<UploadField
|
|
||||||
label='营业执照'
|
|
||||||
values={licenseImages}
|
|
||||||
uploading={uploadingField === 'license'}
|
|
||||||
progress={uploadProgress}
|
|
||||||
onChange={(paths) => void handleUpload('license', paths)}
|
|
||||||
onRemove={() => setLicenseImages([])}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{formError ? <Text className='resubmit__error'>{formError}</Text> : null}
|
|
||||||
|
|
||||||
<Button
|
|
||||||
className='resubmit__submit'
|
|
||||||
loading={submitting}
|
|
||||||
disabled={Boolean(uploadingField)}
|
|
||||||
onClick={() => void handleSubmit()}
|
|
||||||
>
|
|
||||||
立即提交
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
export default definePageConfig({
|
||||||
|
navigationBarTitleText: '重新提交',
|
||||||
|
navigationBarBackgroundColor: '#ffffff',
|
||||||
|
navigationBarTextStyle: 'black',
|
||||||
|
backgroundColor: '#F7F8FA',
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,218 @@
|
||||||
|
.resubmit-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: #f7f8fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit-page__reject {
|
||||||
|
padding: 20px 32px;
|
||||||
|
background: #fff1f0;
|
||||||
|
color: #f53f3f;
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit-page__body {
|
||||||
|
padding: 24px 24px calc(172px + env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit-card {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 702px;
|
||||||
|
padding: 32px 24px 40px;
|
||||||
|
border-radius: 24px;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 0 20px rgba(0, 78, 206, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit__field {
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit-card .resubmit__field:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit__label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: #1d2129;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit__label-icon {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit__required {
|
||||||
|
margin-left: 4px;
|
||||||
|
color: #f53f3f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit__control {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-height: 88px;
|
||||||
|
padding: 0 24px;
|
||||||
|
border: 2px solid #e5e6eb;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit__control--nav {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit__input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
height: 88px;
|
||||||
|
color: #1d2129;
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit__placeholder {
|
||||||
|
color: #c9cdd4;
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit__control-value {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
color: #1d2129;
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit__clear,
|
||||||
|
.resubmit__arrow {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
margin-left: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit__hint {
|
||||||
|
display: block;
|
||||||
|
margin-top: 12px;
|
||||||
|
color: #f53f3f;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit__shot {
|
||||||
|
position: relative;
|
||||||
|
width: 240px;
|
||||||
|
height: 180px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit__shot--empty {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 2px solid #e5e6eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit__shot-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit__shot-camera {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit__shot-text {
|
||||||
|
margin-top: 8px;
|
||||||
|
color: #c9cdd4;
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit__shot-edit {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 0 16px 0 16px;
|
||||||
|
background: rgba(29, 33, 41, 0.55);
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit__shot-edit-icon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit__disclaimer,
|
||||||
|
.resubmit__online {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 24px;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 36px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit__disclaimer-label {
|
||||||
|
color: #ff2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit__online {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit__disclaimer-text {
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit__error {
|
||||||
|
display: block;
|
||||||
|
margin-top: 16px;
|
||||||
|
color: #f53f3f;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit-page__footer {
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 10;
|
||||||
|
padding: 60px 32px calc(24px + env(safe-area-inset-bottom));
|
||||||
|
background: #f7f8fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit__submit {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 88px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: #165dff;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resubmit__submit::after {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,362 @@
|
||||||
|
import { Button, Image, Input, Text, View } from '@tarojs/components'
|
||||||
|
import Taro, { useDidShow, useLoad } from '@tarojs/taro'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { updateOrderExtra } from '@/api/pay'
|
||||||
|
import { uploadImage } from '@/api/user'
|
||||||
|
import { LOCAL_IMAGES } from '@/lib/assets'
|
||||||
|
import { ENTITY_LABELS, HOME_ICON_BASE, INPUT_ARROW_ICON, MAP_ASSET_BASE } from '@/lib/cdn'
|
||||||
|
import { getOrderDraft } from '@/lib/orderDraft'
|
||||||
|
import { isValidPhone } from '@/lib/phone'
|
||||||
|
import {
|
||||||
|
buildResubmitPayload,
|
||||||
|
formatMapCoordinate,
|
||||||
|
formatMapCoordinateDisplay,
|
||||||
|
getInitialPhone2,
|
||||||
|
getOrderAddressName,
|
||||||
|
getOrderExtraImages,
|
||||||
|
getOrderMapAddress,
|
||||||
|
parseCoordinate,
|
||||||
|
parseStorefrontSlots,
|
||||||
|
type OrderLike,
|
||||||
|
type ResubmitValues,
|
||||||
|
type StorefrontSlot,
|
||||||
|
type UploadedImage,
|
||||||
|
} from '@/lib/order'
|
||||||
|
import { consumeMapPickerResult } from '@/pages/map/picker'
|
||||||
|
import './index.scss'
|
||||||
|
|
||||||
|
const STOREFRONT_LABELS = ['门头正面', '门头左面', '门头右面']
|
||||||
|
|
||||||
|
const DISCLAIMER_BODY =
|
||||||
|
'我们只是代客户提交商户、企业位置资料,不是地图标注平台方,所提供服务为商业有偿帮助咨询服务,全程都是人工提交资料,自身并不能对第三方网站的原始内容进行编辑,请知悉。'
|
||||||
|
|
||||||
|
function FieldLabel ({ icon, text, required }: { icon: string, text: string, required?: boolean }) {
|
||||||
|
return (
|
||||||
|
<View className='resubmit__label'>
|
||||||
|
<Image className='resubmit__label-icon' src={icon} />
|
||||||
|
<Text>{text}</Text>
|
||||||
|
{required ? <Text className='resubmit__required'>*</Text> : null}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ClearInput ({
|
||||||
|
value,
|
||||||
|
placeholder,
|
||||||
|
type,
|
||||||
|
maxlength,
|
||||||
|
onChange,
|
||||||
|
}: {
|
||||||
|
value?: string
|
||||||
|
placeholder: string
|
||||||
|
type?: 'text' | 'number'
|
||||||
|
maxlength?: number
|
||||||
|
onChange: (value: string) => void
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<View className='resubmit__control'>
|
||||||
|
<Input
|
||||||
|
className='resubmit__input'
|
||||||
|
type={type}
|
||||||
|
maxlength={maxlength}
|
||||||
|
value={value}
|
||||||
|
placeholder={placeholder}
|
||||||
|
placeholderClass='resubmit__placeholder'
|
||||||
|
onInput={(event) => onChange(event.detail.value)}
|
||||||
|
/>
|
||||||
|
{value ? (
|
||||||
|
<Image
|
||||||
|
className='resubmit__clear'
|
||||||
|
src={LOCAL_IMAGES.clear}
|
||||||
|
onClick={() => onChange('')}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ImageSlot ({
|
||||||
|
value,
|
||||||
|
uploading,
|
||||||
|
progress,
|
||||||
|
onChange,
|
||||||
|
}: {
|
||||||
|
value: StorefrontSlot
|
||||||
|
uploading: boolean
|
||||||
|
progress: number
|
||||||
|
onChange: (filePath: string) => void
|
||||||
|
}) {
|
||||||
|
const handleChoose = async () => {
|
||||||
|
const res = await Taro.chooseImage({
|
||||||
|
count: 1,
|
||||||
|
sizeType: ['compressed'],
|
||||||
|
sourceType: ['album', 'camera'],
|
||||||
|
})
|
||||||
|
const filePath = res.tempFilePaths?.[0]
|
||||||
|
if (filePath) onChange(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
return (
|
||||||
|
<View className='resubmit__shot'>
|
||||||
|
<Image
|
||||||
|
className='resubmit__shot-image'
|
||||||
|
src={value.preview || value.url}
|
||||||
|
mode='aspectFill'
|
||||||
|
onClick={() => Taro.previewImage({ current: value.preview || value.url, urls: [value.preview || value.url] })}
|
||||||
|
/>
|
||||||
|
<View className='resubmit__shot-edit' onClick={() => void handleChoose()}>
|
||||||
|
<Image className='resubmit__shot-edit-icon' src={LOCAL_IMAGES.pen} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View className='resubmit__shot resubmit__shot--empty' onClick={() => void handleChoose()}>
|
||||||
|
<Image className='resubmit__shot-camera' src={LOCAL_IMAGES.camera} />
|
||||||
|
<Text className='resubmit__shot-text'>
|
||||||
|
{uploading ? `上传中 ${Math.round(progress)}%` : '请上传图片'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ResubmitPage () {
|
||||||
|
const [order, setOrder] = useState<OrderLike | null>(null)
|
||||||
|
const [values, setValues] = useState<ResubmitValues>({})
|
||||||
|
const [storefrontSlots, setStorefrontSlots] = useState<StorefrontSlot[]>([null, null, null])
|
||||||
|
const [licenseImages, setLicenseImages] = useState<UploadedImage[]>([])
|
||||||
|
const [uploadingField, setUploadingField] = useState<'storefront0' | 'storefront1' | 'storefront2' | 'license' | null>(null)
|
||||||
|
const [uploadProgress, setUploadProgress] = useState(0)
|
||||||
|
const [formError, setFormError] = useState('')
|
||||||
|
const [submitting, setSubmitting] = useState(false)
|
||||||
|
|
||||||
|
useLoad(() => {
|
||||||
|
const draft = getOrderDraft()
|
||||||
|
if (!draft) {
|
||||||
|
Taro.showToast({ title: '订单不存在', icon: 'none' })
|
||||||
|
setTimeout(() => Taro.navigateBack(), 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setOrder(draft)
|
||||||
|
setValues({
|
||||||
|
entity_name: draft.entity_name || '',
|
||||||
|
entity_address_name: getOrderAddressName(draft),
|
||||||
|
entity_address: getOrderMapAddress(draft),
|
||||||
|
entity_phone: draft.entity_phone || '',
|
||||||
|
entity_phone2: getInitialPhone2(draft),
|
||||||
|
})
|
||||||
|
setStorefrontSlots(parseStorefrontSlots(draft))
|
||||||
|
setLicenseImages(getOrderExtraImages(draft, ['entity_business_license_image', 'business_license_image']).slice(0, 1))
|
||||||
|
})
|
||||||
|
|
||||||
|
useDidShow(() => {
|
||||||
|
const result = consumeMapPickerResult()
|
||||||
|
if (!result) return
|
||||||
|
const mapAddress = formatMapCoordinate(result)
|
||||||
|
const mapAddressName = result.address || result.name || ''
|
||||||
|
setValues((current) => ({
|
||||||
|
...current,
|
||||||
|
entity_address: mapAddress,
|
||||||
|
entity_address_name: mapAddressName || current.entity_address_name,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateField = <K extends keyof ResubmitValues>(field: K, value: ResubmitValues[K]) => {
|
||||||
|
setValues((current) => ({ ...current, [field]: value }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUpload = async (field: 'storefront0' | 'storefront1' | 'storefront2' | 'license', filePath: string) => {
|
||||||
|
setUploadingField(field)
|
||||||
|
setUploadProgress(0)
|
||||||
|
setFormError('')
|
||||||
|
try {
|
||||||
|
const uploaded = await uploadImage(filePath, (percent) => setUploadProgress(percent))
|
||||||
|
const image: UploadedImage = { id: uploaded.id, url: uploaded.url, preview: filePath }
|
||||||
|
if (field === 'license') {
|
||||||
|
setLicenseImages([image])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const index = Number(field.replace('storefront', ''))
|
||||||
|
setStorefrontSlots((current) => {
|
||||||
|
const next = [...current]
|
||||||
|
next[index] = image
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
setFormError(error instanceof Error ? error.message : '图片上传失败,请稍后重试')
|
||||||
|
} finally {
|
||||||
|
setUploadingField(null)
|
||||||
|
setUploadProgress(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChooseMap = () => {
|
||||||
|
const point = parseCoordinate(values.entity_address)
|
||||||
|
const query = [
|
||||||
|
point ? `lng=${point.lng}` : '',
|
||||||
|
point ? `lat=${point.lat}` : '',
|
||||||
|
values.entity_address_name ? `name=${encodeURIComponent(values.entity_address_name)}` : '',
|
||||||
|
].filter(Boolean).join('&')
|
||||||
|
Taro.navigateTo({
|
||||||
|
url: query ? `/pages/map/index?${query}` : '/pages/map/index',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!order || submitting) return
|
||||||
|
if (uploadingField) {
|
||||||
|
setFormError('图片正在上传,请稍候')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!values.entity_name?.trim()) {
|
||||||
|
Taro.showToast({ title: `请输入${ENTITY_LABELS.entityName}`, icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!values.entity_address_name?.trim()) {
|
||||||
|
Taro.showToast({ title: `请输入${ENTITY_LABELS.entityAddress}`, icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!values.entity_address?.trim()) {
|
||||||
|
Taro.showToast({ title: `请选择${ENTITY_LABELS.entityMapLocation}`, icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!isValidPhone(values.entity_phone || '')) {
|
||||||
|
Taro.showToast({ title: '请填写正确的联系电话1', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!isValidPhone(values.entity_phone2 || '')) {
|
||||||
|
Taro.showToast({ title: '请填写正确的联系电话2', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setSubmitting(true)
|
||||||
|
setFormError('')
|
||||||
|
try {
|
||||||
|
await updateOrderExtra(buildResubmitPayload({
|
||||||
|
order,
|
||||||
|
values,
|
||||||
|
storefrontSlots,
|
||||||
|
licenseImages,
|
||||||
|
}))
|
||||||
|
Taro.showToast({ title: '重新提交成功', icon: 'success' })
|
||||||
|
setTimeout(() => Taro.navigateBack(), 500)
|
||||||
|
} catch {
|
||||||
|
Taro.showToast({ title: '重新提交失败,请稍后再试', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapPoint = parseCoordinate(values.entity_address)
|
||||||
|
const mapDisplay = mapPoint ? formatMapCoordinateDisplay(mapPoint) : (values.entity_address || '')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View className='resubmit-page'>
|
||||||
|
{order?.reject_reason ? (
|
||||||
|
<View className='resubmit-page__reject'>驳回理由:{order.reject_reason}</View>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<View className='resubmit-page__body'>
|
||||||
|
<View className='resubmit-card'>
|
||||||
|
<View className='resubmit__field'>
|
||||||
|
<FieldLabel icon={`${HOME_ICON_BASE}/icon_form_marker@2x.png`} text={ENTITY_LABELS.entityName} required />
|
||||||
|
<ClearInput
|
||||||
|
value={values.entity_name}
|
||||||
|
placeholder='请输入标注名称'
|
||||||
|
onChange={(value) => updateField('entity_name', value)}
|
||||||
|
/>
|
||||||
|
<Text className='resubmit__hint'>请确保您的标注名称与门头照片店名一致</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className='resubmit__field'>
|
||||||
|
<FieldLabel icon={`${HOME_ICON_BASE}/icon_form_location@2x.png`} text={ENTITY_LABELS.entityAddress} />
|
||||||
|
<View className='resubmit__control resubmit__control--nav' onClick={handleChooseMap}>
|
||||||
|
<Text className={values.entity_address_name ? 'resubmit__control-value' : 'resubmit__placeholder'}>
|
||||||
|
{values.entity_address_name || '请选择标注地址'}
|
||||||
|
</Text>
|
||||||
|
<Image className='resubmit__arrow' src={INPUT_ARROW_ICON} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className='resubmit__field'>
|
||||||
|
<FieldLabel icon={`${HOME_ICON_BASE}/icon_form_location_lnglat@2x.png`} text={ENTITY_LABELS.entityMapLocation} />
|
||||||
|
<View className='resubmit__control resubmit__control--nav' onClick={handleChooseMap}>
|
||||||
|
<Text className={mapDisplay ? 'resubmit__control-value' : 'resubmit__placeholder'}>
|
||||||
|
{mapDisplay || '请选择地图位置'}
|
||||||
|
</Text>
|
||||||
|
<Image className='resubmit__arrow' src={INPUT_ARROW_ICON} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className='resubmit__field'>
|
||||||
|
<FieldLabel icon={`${HOME_ICON_BASE}/icon_form_phone@2x.png`} text='联系电话1' required />
|
||||||
|
<ClearInput
|
||||||
|
type='number'
|
||||||
|
maxlength={12}
|
||||||
|
value={values.entity_phone}
|
||||||
|
placeholder='请输入'
|
||||||
|
onChange={(value) => updateField('entity_phone', value.replace(/\D/g, ''))}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className='resubmit__field'>
|
||||||
|
<FieldLabel icon={`${HOME_ICON_BASE}/icon_form_phone@2x.png`} text='联系电话2' required />
|
||||||
|
<ClearInput
|
||||||
|
type='number'
|
||||||
|
maxlength={12}
|
||||||
|
value={values.entity_phone2}
|
||||||
|
placeholder='请输入'
|
||||||
|
onChange={(value) => updateField('entity_phone2', value.replace(/\D/g, ''))}
|
||||||
|
/>
|
||||||
|
<Text className='resubmit__hint'>请确保您的电话畅通,工作人员会在1-7个工作日联系您</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{STOREFRONT_LABELS.map((label, index) => (
|
||||||
|
<View className='resubmit__field' key={label}>
|
||||||
|
<FieldLabel icon={`${MAP_ASSET_BASE}/pay-success-storefront.png`} text={label} />
|
||||||
|
<ImageSlot
|
||||||
|
value={storefrontSlots[index] || null}
|
||||||
|
uploading={uploadingField === `storefront${index}`}
|
||||||
|
progress={uploadProgress}
|
||||||
|
onChange={(filePath) => void handleUpload(`storefront${index}` as 'storefront0' | 'storefront1' | 'storefront2', filePath)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<View className='resubmit__field'>
|
||||||
|
<FieldLabel icon={`${MAP_ASSET_BASE}/pay-success-license.png`} text='营业执照' />
|
||||||
|
<ImageSlot
|
||||||
|
value={licenseImages[0] || null}
|
||||||
|
uploading={uploadingField === 'license'}
|
||||||
|
progress={uploadProgress}
|
||||||
|
onChange={(filePath) => void handleUpload('license', filePath)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Text className='resubmit__disclaimer'>
|
||||||
|
<Text className='resubmit__disclaimer-label'>申请说明:</Text>
|
||||||
|
<Text className='resubmit__disclaimer-text'>{DISCLAIMER_BODY}</Text>
|
||||||
|
</Text>
|
||||||
|
<Text className='resubmit__online'>
|
||||||
|
<Text className='resubmit__disclaimer-label'>上线时间:</Text>
|
||||||
|
<Text className='resubmit__disclaimer-text'>1-7个工作日完成上线,一次添加长久有效</Text>
|
||||||
|
</Text>
|
||||||
|
{formError ? <Text className='resubmit__error'>{formError}</Text> : null}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className='resubmit-page__footer'>
|
||||||
|
<Button
|
||||||
|
className='resubmit__submit'
|
||||||
|
loading={submitting}
|
||||||
|
disabled={Boolean(uploadingField)}
|
||||||
|
onClick={() => void handleSubmit()}
|
||||||
|
>
|
||||||
|
立即提交
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||