优化打手订单与界面
This commit is contained in:
@@ -215,6 +215,7 @@ export type ListInput = {
|
|||||||
categoryId?: number
|
categoryId?: number
|
||||||
workerSharingId?: number
|
workerSharingId?: number
|
||||||
visibleAfterIso?: string
|
visibleAfterIso?: string
|
||||||
|
sort?: 'id_desc' | 'worker_claimed_at_desc'
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ProductRuleListInput = {
|
export type ProductRuleListInput = {
|
||||||
|
|||||||
@@ -743,6 +743,7 @@ export async function listWorkOrders({
|
|||||||
categoryId = 0,
|
categoryId = 0,
|
||||||
workerSharingId = 0,
|
workerSharingId = 0,
|
||||||
visibleAfterIso = '',
|
visibleAfterIso = '',
|
||||||
|
sort = 'id_desc',
|
||||||
}: ListInput = {}): Promise<{ items: WorkOrderRow[]; total: number }> {
|
}: ListInput = {}): Promise<{ items: WorkOrderRow[]; total: number }> {
|
||||||
const { whereClause, params } = buildWorkOrderWhere({
|
const { whereClause, params } = buildWorkOrderWhere({
|
||||||
status,
|
status,
|
||||||
@@ -761,11 +762,32 @@ export async function listWorkOrders({
|
|||||||
params,
|
params,
|
||||||
)
|
)
|
||||||
const offset = (page - 1) * pageSize
|
const offset = (page - 1) * pageSize
|
||||||
|
const orderBy =
|
||||||
|
sort === 'worker_claimed_at_desc' && workerSharingId
|
||||||
|
? (() => {
|
||||||
|
params.push(workerSharingId)
|
||||||
|
const workerIdPlaceholder = `$${params.length}`
|
||||||
|
return `COALESCE(
|
||||||
|
CASE
|
||||||
|
WHEN wo.assigned_worker_id = ${workerIdPlaceholder} THEN wo.assigned_at
|
||||||
|
END,
|
||||||
|
(
|
||||||
|
SELECT wos.created_at
|
||||||
|
FROM work_order_shares wos
|
||||||
|
WHERE wos.work_order_id = wo.id
|
||||||
|
AND wos.worker_id = ${workerIdPlaceholder}
|
||||||
|
AND wos.status != 'cancelled'
|
||||||
|
ORDER BY wos.created_at DESC
|
||||||
|
LIMIT 1
|
||||||
|
)
|
||||||
|
) DESC NULLS LAST, wo.id DESC`
|
||||||
|
})()
|
||||||
|
: 'wo.id DESC'
|
||||||
params.push(pageSize, offset)
|
params.push(pageSize, offset)
|
||||||
const itemsResult = await query<WorkOrderRow>(
|
const itemsResult = await query<WorkOrderRow>(
|
||||||
`${WORK_ORDER_SELECT}
|
`${WORK_ORDER_SELECT}
|
||||||
${whereClause}
|
${whereClause}
|
||||||
ORDER BY wo.id DESC
|
ORDER BY ${orderBy}
|
||||||
LIMIT $${params.length - 1} OFFSET $${params.length}`,
|
LIMIT $${params.length - 1} OFFSET $${params.length}`,
|
||||||
params,
|
params,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -994,6 +994,7 @@ export async function listWorkerMyOrders(query: JsonObject = {}, session: Worker
|
|||||||
status: String(query.status || '').trim(),
|
status: String(query.status || '').trim(),
|
||||||
keyword: String(query.keyword || '').trim(),
|
keyword: String(query.keyword || '').trim(),
|
||||||
workerSharingId: worker.id,
|
workerSharingId: worker.id,
|
||||||
|
sort: 'worker_claimed_at_desc',
|
||||||
})
|
})
|
||||||
const permissions = resolveWorkerPermissions(worker)
|
const permissions = resolveWorkerPermissions(worker)
|
||||||
const myShares = await listWorkerSharesByWorker(worker.id)
|
const myShares = await listWorkerSharesByWorker(worker.id)
|
||||||
|
|||||||
@@ -51,13 +51,13 @@ import { getImageIdentity } from '@/utils/image-identity'
|
|||||||
import { useIsMobile } from '@/utils/use-is-mobile'
|
import { useIsMobile } from '@/utils/use-is-mobile'
|
||||||
|
|
||||||
const STATUS_OPTIONS = [
|
const STATUS_OPTIONS = [
|
||||||
{ value: '', label: '全部订单' },
|
|
||||||
{ value: 'in_progress', label: '代练中' },
|
{ value: 'in_progress', label: '代练中' },
|
||||||
{ value: 'pending_acceptance', label: '待验收' },
|
{ value: 'pending_acceptance', label: '待验收' },
|
||||||
{ value: 'problem', label: '问题单' },
|
{ value: 'problem', label: '问题单' },
|
||||||
{ value: 'open', label: '已超时' },
|
{ value: 'open', label: '已超时' },
|
||||||
{ value: 'accepted', label: '已验收' },
|
{ value: 'accepted', label: '已验收' },
|
||||||
{ value: 'cancelled', label: '已取消' },
|
{ value: 'cancelled', label: '已取消' },
|
||||||
|
{ value: '', label: '全部订单' },
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
type AcceptanceFormValues = {
|
type AcceptanceFormValues = {
|
||||||
@@ -68,6 +68,97 @@ type WorkerNoteFormValues = {
|
|||||||
note?: string
|
note?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NoteTheme = 'success' | 'processing' | 'danger' | 'warning' | 'purple' | 'default'
|
||||||
|
|
||||||
|
const PRESET_NOTE_TAGS: Array<{ label: string; theme: NoteTheme }> = [
|
||||||
|
{ label: '已安排', theme: 'success' },
|
||||||
|
{ label: '打单中', theme: 'processing' },
|
||||||
|
{ label: '账号问题', theme: 'danger' },
|
||||||
|
{ label: '排队中', theme: 'purple' },
|
||||||
|
{ label: '未安排', theme: 'warning' },
|
||||||
|
{ label: '暂缓做单', theme: 'warning' },
|
||||||
|
]
|
||||||
|
|
||||||
|
function resolveNoteTheme(note?: string): NoteTheme {
|
||||||
|
const text = String(note || '').trim().toLowerCase()
|
||||||
|
if (!text) return 'default'
|
||||||
|
|
||||||
|
// 1. 危险 / 异常 / 问题类 (Red)
|
||||||
|
if (
|
||||||
|
text.includes('问题') ||
|
||||||
|
text.includes('异常') ||
|
||||||
|
text.includes('密码') ||
|
||||||
|
text.includes('封号') ||
|
||||||
|
text.includes('错误') ||
|
||||||
|
text.includes('无法') ||
|
||||||
|
text.includes('投诉') ||
|
||||||
|
text.includes('退') ||
|
||||||
|
text.includes('失败') ||
|
||||||
|
text.includes('被挤')
|
||||||
|
) {
|
||||||
|
return 'danger'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 暂缓 / 未安排 / 延后 / 暂停类 (Orange)
|
||||||
|
if (
|
||||||
|
text.includes('未安排') ||
|
||||||
|
text.includes('未') ||
|
||||||
|
text.includes('暂缓') ||
|
||||||
|
text.includes('暂停') ||
|
||||||
|
text.includes('稍后') ||
|
||||||
|
text.includes('延后') ||
|
||||||
|
text.includes('挂起') ||
|
||||||
|
text.includes('待定')
|
||||||
|
) {
|
||||||
|
return 'warning'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 排队 / 等待类 (Purple)
|
||||||
|
if (
|
||||||
|
text.includes('排队') ||
|
||||||
|
text.includes('等待') ||
|
||||||
|
text.includes('等买家') ||
|
||||||
|
text.includes('等扫码') ||
|
||||||
|
text.includes('等验证') ||
|
||||||
|
text.includes('待扫')
|
||||||
|
) {
|
||||||
|
return 'purple'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 进行中 / 打单中 / 上号 (Blue)
|
||||||
|
if (
|
||||||
|
text.includes('打单') ||
|
||||||
|
text.includes('做单') ||
|
||||||
|
text.includes('进行') ||
|
||||||
|
text.includes('处理') ||
|
||||||
|
text.includes('上号') ||
|
||||||
|
text.includes('练级') ||
|
||||||
|
text.includes('正在') ||
|
||||||
|
text.includes('开打') ||
|
||||||
|
text.includes('肝')
|
||||||
|
) {
|
||||||
|
return 'processing'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 已安排 / 已就绪 / 完成 / 正常 (Green)
|
||||||
|
if (
|
||||||
|
text.includes('已安排') ||
|
||||||
|
text.includes('安排') ||
|
||||||
|
text.includes('已就绪') ||
|
||||||
|
text.includes('就绪') ||
|
||||||
|
text.includes('已联系') ||
|
||||||
|
text.includes('完成') ||
|
||||||
|
text.includes('已好') ||
|
||||||
|
text.includes('搞定') ||
|
||||||
|
text.includes('正常') ||
|
||||||
|
text.includes('ok')
|
||||||
|
) {
|
||||||
|
return 'success'
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'default'
|
||||||
|
}
|
||||||
|
|
||||||
type DetailFieldItem = {
|
type DetailFieldItem = {
|
||||||
key: string
|
key: string
|
||||||
label: string
|
label: string
|
||||||
@@ -79,7 +170,7 @@ export default function WorkerOrdersPage() {
|
|||||||
const isMobile = useIsMobile()
|
const isMobile = useIsMobile()
|
||||||
const { message } = App.useApp()
|
const { message } = App.useApp()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const [status, setStatus] = useState('')
|
const [status, setStatus] = useState('in_progress')
|
||||||
const [keywordInput, setKeywordInput] = useState('')
|
const [keywordInput, setKeywordInput] = useState('')
|
||||||
const [keyword, setKeyword] = useState('')
|
const [keyword, setKeyword] = useState('')
|
||||||
const [page, setPage] = useState(1)
|
const [page, setPage] = useState(1)
|
||||||
@@ -401,10 +492,14 @@ export default function WorkerOrdersPage() {
|
|||||||
width: 180,
|
width: 180,
|
||||||
render: (_, row) => {
|
render: (_, row) => {
|
||||||
const note = String(row.workerNote || '').trim()
|
const note = String(row.workerNote || '').trim()
|
||||||
|
const theme = resolveNoteTheme(note)
|
||||||
return (
|
return (
|
||||||
<div className="worker-note-cell">
|
<div className="worker-note-cell">
|
||||||
{note ? (
|
{note ? (
|
||||||
<div className="worker-note-badge" onClick={() => openNoteModal(row)}>
|
<div
|
||||||
|
className={`worker-note-badge theme-${theme}`}
|
||||||
|
onClick={() => openNoteModal(row)}
|
||||||
|
>
|
||||||
<span className="worker-note-text" title={note}>
|
<span className="worker-note-text" title={note}>
|
||||||
{note}
|
{note}
|
||||||
</span>
|
</span>
|
||||||
@@ -690,7 +785,10 @@ export default function WorkerOrdersPage() {
|
|||||||
<div className="worker-order-mobile-footer-row">
|
<div className="worker-order-mobile-footer-row">
|
||||||
<div className="worker-order-mobile-note-block">
|
<div className="worker-order-mobile-note-block">
|
||||||
{workerNote ? (
|
{workerNote ? (
|
||||||
<div className="worker-note-badge" onClick={() => openNoteModal(order)}>
|
<div
|
||||||
|
className={`worker-note-badge theme-${resolveNoteTheme(workerNote)}`}
|
||||||
|
onClick={() => openNoteModal(order)}
|
||||||
|
>
|
||||||
<span className="worker-note-text" title={workerNote}>
|
<span className="worker-note-text" title={workerNote}>
|
||||||
{workerNote}
|
{workerNote}
|
||||||
</span>
|
</span>
|
||||||
@@ -1012,13 +1110,13 @@ export default function WorkerOrdersPage() {
|
|||||||
<Form form={noteForm} layout="vertical" onFinish={saveOrderNote}>
|
<Form form={noteForm} layout="vertical" onFinish={saveOrderNote}>
|
||||||
<Form.Item label="快捷选择标签" style={{ marginBottom: 12 }}>
|
<Form.Item label="快捷选择标签" style={{ marginBottom: 12 }}>
|
||||||
<div className="worker-note-preset-tags">
|
<div className="worker-note-preset-tags">
|
||||||
{['已安排', '未安排', '打单中', '账号问题', '排队中', '暂缓做单'].map((tag) => (
|
{PRESET_NOTE_TAGS.map((item) => (
|
||||||
<Tag
|
<Tag
|
||||||
key={tag}
|
key={item.label}
|
||||||
className="worker-note-preset-tag"
|
className={`worker-note-preset-tag theme-${item.theme}`}
|
||||||
onClick={() => noteForm.setFieldsValue({ note: tag })}
|
onClick={() => noteForm.setFieldsValue({ note: item.label })}
|
||||||
>
|
>
|
||||||
{tag}
|
{item.label}
|
||||||
</Tag>
|
</Tag>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,26 +13,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.worker-page-head {
|
.worker-page-head {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-areas:
|
|
||||||
'main'
|
|
||||||
'search';
|
|
||||||
grid-template-columns: minmax(0, 1fr);
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
gap: 12px 16px;
|
gap: 12px 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.worker-page-head h3 {
|
.worker-page-head h3,
|
||||||
margin: 0;
|
.worker-page-head > .ant-typography {
|
||||||
|
margin: 0 !important;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.worker-page-head-title-row {
|
.worker-page-head-title-row {
|
||||||
grid-area: main;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
@@ -223,6 +222,7 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
color: #24292f;
|
color: #24292f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,11 +232,96 @@
|
|||||||
color: #0958d9;
|
color: #0958d9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 绿色主题:已安排 / 已就绪 / 完成 / 正常 */
|
||||||
|
.worker-note-badge.theme-success {
|
||||||
|
background: #f6ffed;
|
||||||
|
border-color: #b7eb8f;
|
||||||
|
color: #237804;
|
||||||
|
}
|
||||||
|
|
||||||
|
.worker-note-badge.theme-success .worker-note-edit-icon {
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.worker-note-badge.theme-success:hover {
|
||||||
|
background: #d9f7be;
|
||||||
|
border-color: #73d13d;
|
||||||
|
color: #135200;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 蓝色主题:打单中 / 处理中 / 进行中 / 上号中 */
|
||||||
|
.worker-note-badge.theme-processing {
|
||||||
|
background: #e6f4ff;
|
||||||
|
border-color: #91caff;
|
||||||
|
color: #0958d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.worker-note-badge.theme-processing .worker-note-edit-icon {
|
||||||
|
color: #1677ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.worker-note-badge.theme-processing:hover {
|
||||||
|
background: #bae0ff;
|
||||||
|
border-color: #69b1ff;
|
||||||
|
color: #003eb3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 红色主题:账号问题 / 密码错误 / 异常 / 封号 */
|
||||||
|
.worker-note-badge.theme-danger {
|
||||||
|
background: #fff1f0;
|
||||||
|
border-color: #ffccc7;
|
||||||
|
color: #cf1322;
|
||||||
|
}
|
||||||
|
|
||||||
|
.worker-note-badge.theme-danger .worker-note-edit-icon {
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.worker-note-badge.theme-danger:hover {
|
||||||
|
background: #ffccc7;
|
||||||
|
border-color: #ff7875;
|
||||||
|
color: #a8071a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 紫色主题:排队中 / 等待中 / 等买家扫码 */
|
||||||
|
.worker-note-badge.theme-purple {
|
||||||
|
background: #f9f0ff;
|
||||||
|
border-color: #d3adf7;
|
||||||
|
color: #531dab;
|
||||||
|
}
|
||||||
|
|
||||||
|
.worker-note-badge.theme-purple .worker-note-edit-icon {
|
||||||
|
color: #722ed1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.worker-note-badge.theme-purple:hover {
|
||||||
|
background: #efdbff;
|
||||||
|
border-color: #b37feb;
|
||||||
|
color: #391085;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 橙色主题:未安排 / 暂缓做单 / 暂停 / 待定 */
|
||||||
|
.worker-note-badge.theme-warning {
|
||||||
|
background: #fff7e6;
|
||||||
|
border-color: #ffd591;
|
||||||
|
color: #d46b08;
|
||||||
|
}
|
||||||
|
|
||||||
|
.worker-note-badge.theme-warning .worker-note-edit-icon {
|
||||||
|
color: #fa8c16;
|
||||||
|
}
|
||||||
|
|
||||||
|
.worker-note-badge.theme-warning:hover {
|
||||||
|
background: #ffe7ba;
|
||||||
|
border-color: #ffc069;
|
||||||
|
color: #ad4e00;
|
||||||
|
}
|
||||||
|
|
||||||
.worker-note-text {
|
.worker-note-text {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
max-width: 110px;
|
max-width: 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.worker-note-edit-icon {
|
.worker-note-edit-icon {
|
||||||
@@ -246,7 +331,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.worker-note-badge:hover .worker-note-edit-icon {
|
.worker-note-badge:hover .worker-note-edit-icon {
|
||||||
color: #0958d9;
|
opacity: 0.85;
|
||||||
}
|
}
|
||||||
|
|
||||||
.worker-note-add-btn {
|
.worker-note-add-btn {
|
||||||
@@ -264,15 +349,49 @@
|
|||||||
.worker-note-preset-tag {
|
.worker-note-preset-tag {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
padding: 2px 10px;
|
padding: 4px 12px;
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
border: 1px solid #d0d7de;
|
||||||
|
background: #f6f8fa;
|
||||||
|
color: #24292f;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.worker-note-preset-tag:hover {
|
.worker-note-preset-tag.theme-success {
|
||||||
border-color: #1677ff;
|
background: #f6ffed;
|
||||||
color: #1677ff;
|
border-color: #b7eb8f;
|
||||||
|
color: #237804;
|
||||||
|
}
|
||||||
|
|
||||||
|
.worker-note-preset-tag.theme-processing {
|
||||||
background: #e6f4ff;
|
background: #e6f4ff;
|
||||||
|
border-color: #91caff;
|
||||||
|
color: #0958d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.worker-note-preset-tag.theme-danger {
|
||||||
|
background: #fff1f0;
|
||||||
|
border-color: #ffccc7;
|
||||||
|
color: #cf1322;
|
||||||
|
}
|
||||||
|
|
||||||
|
.worker-note-preset-tag.theme-purple {
|
||||||
|
background: #f9f0ff;
|
||||||
|
border-color: #d3adf7;
|
||||||
|
color: #531dab;
|
||||||
|
}
|
||||||
|
|
||||||
|
.worker-note-preset-tag.theme-warning {
|
||||||
|
background: #fff7e6;
|
||||||
|
border-color: #ffd591;
|
||||||
|
color: #d46b08;
|
||||||
|
}
|
||||||
|
|
||||||
|
.worker-note-preset-tag:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 接单平台顶部统计条 (PC) */
|
/* 接单平台顶部统计条 (PC) */
|
||||||
|
|||||||
Reference in New Issue
Block a user