🐛 fix(vscode): 申请移交改到工单详情标题栏

This commit is contained in:
cruldra committed 2026-09-28 08:09:15 +08:00
1 parent b53b989bbe
commit d15c6ceedd
6 files changed
+86 -95

No files matched your search

+4 -4
View File
@@ -393,10 +393,6 @@ export function App() {
onDependencyClear={clearDependency}
me={me}
avatars={avatars}
onAskHandoff={askHandoff}
onCancelHandoffRequest={cancelHandoffRequest}
onAcceptHandoffRequest={acceptHandoffRequest}
onRejectHandoffRequest={rejectHandoffRequest}
/>
</div>
<div className="h-[40vh] shrink-0">
@@ -441,6 +437,10 @@ export function App() {
requestHandoffUsers(n)
}}
onAcceptHandoff={acceptHandoff}
onAskHandoff={askHandoff}
onCancelHandoffRequest={cancelHandoffRequest}
onAcceptHandoffRequest={acceptHandoffRequest}
onRejectHandoffRequest={rejectHandoffRequest}
isHandoffRunning={isHandoffRunning}
profileData={profileData}
onProfileSave={saveProfiles}
@@ -62,6 +62,10 @@ interface BottomTabsProps {
avatars?: Record<string, string>
onStartHandoff: (issueNumber: number) => void
onAcceptHandoff: (issueNumber: number) => void
onAskHandoff: (issueNumber: number) => void
onCancelHandoffRequest: (issueNumber: number) => void
onAcceptHandoffRequest: (issueNumber: number) => void
onRejectHandoffRequest: (issueNumber: number) => void
isHandoffRunning: (issueNumber: number) => boolean
// Profile tab props
@@ -166,6 +170,10 @@ export function BottomTabs(props: BottomTabsProps) {
avatars={props.avatars}
onStartHandoff={props.onStartHandoff}
onAcceptHandoff={props.onAcceptHandoff}
onAskHandoff={props.onAskHandoff}
onCancelHandoffRequest={props.onCancelHandoffRequest}
onAcceptHandoffRequest={props.onAcceptHandoffRequest}
onRejectHandoffRequest={props.onRejectHandoffRequest}
isHandoffRunning={props.isHandoffRunning}
/>
</div>
+2 -77
View File
@@ -1,11 +1,10 @@
import type { ReactNode, Ref } from 'react'
import type { Ref } from 'react'
import type { Issue } from '../types'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import { Lock } from 'lucide-react'
import { forwardRef } from 'react'
import { cn } from '@/lib/utils'
import { canAnswerHandoffRequest, canAskHandoff, canCancelHandoffRequest } from '../../../src/panel/handlers/handoffRequest'
import { Card } from './ui/card'
interface IssueCardProps {
@@ -27,11 +26,6 @@ interface IssueCardProps {
ownerLogin?: string
/** 归属人头像 data URI;缺失时回退首字母圆点。 */
ownerAvatar?: string
me?: string
onAskHandoff?: (issueNumber: number) => void
onCancelHandoffRequest?: (issueNumber: number) => void
onAcceptHandoffRequest?: (issueNumber: number) => void
onRejectHandoffRequest?: (issueNumber: number) => void
}
/** 同事工单的归属人标记:有头像用头像,没有就用 login 首字母。 */
@@ -57,69 +51,8 @@ function OwnerBadge({ login, avatar }: { login: string, avatar?: string }) {
)
}
function HandoffRequestActions({
issue,
me,
onAsk,
onCancel,
onAccept,
onReject,
}: {
issue: Issue
me?: string
onAsk?: (issueNumber: number) => void
onCancel?: (issueNumber: number) => void
onAccept?: (issueNumber: number) => void
onReject?: (issueNumber: number) => void
}) {
const button = 'rounded border border-[var(--vscode-contrastBorder)] px-1.5 py-0.5 text-[10px] hover:bg-[var(--vscode-toolbar-hoverBackground)]'
let body: ReactNode = null
if (canAnswerHandoffRequest(issue, me)) {
body = (
<>
<span className="text-[10px] opacity-80">
{issue.handoffRequestFrom}
{' '}
想要
</span>
{issue.handoffRequestError && issue.handoffRequestError !== 'rejected'
? <span className="text-[10px] text-[var(--vscode-errorForeground)]">{issue.handoffRequestError}</span>
: null}
<button type="button" className={button} onClick={() => onAccept?.(issue.number)}>同意</button>
<button type="button" className={button} onClick={() => onReject?.(issue.number)}>拒绝</button>
</>
)
}
else if (canCancelHandoffRequest(issue, me)) {
body = (
<>
<span className="text-[10px] opacity-80">
{issue.handoffRequestError && issue.handoffRequestError !== 'rejected' ? issue.handoffRequestError : '已申请'}
</span>
<button type="button" className={button} onClick={() => onCancel?.(issue.number)}>取消</button>
</>
)
}
else if (canAskHandoff(issue, me)) {
body = <button type="button" className={button} onClick={() => onAsk?.(issue.number)}>申请移交</button>
}
else if (issue.column === 'review' && issue.handoffRequestFrom && issue.source !== 'youtrack' && !issue.handoffAttachmentId) {
body = <span className="text-[10px] opacity-60">申请中</span>
}
if (!body)
return null
return (
<div
className="mb-1 flex flex-wrap items-center gap-1"
onPointerDown={event => event.stopPropagation()}
>
{body}
</div>
)
}
export const IssueCard = forwardRef<HTMLDivElement, IssueCardProps>(
({ issue, selected, onSelect, depth = 0, locked, lockedPrerequisite, readOnly, ownerLogin, ownerAvatar, me, onAskHandoff, onCancelHandoffRequest, onAcceptHandoffRequest, onRejectHandoffRequest }, ref) => {
({ issue, selected, onSelect, depth = 0, locked, lockedPrerequisite, readOnly, ownerLogin, ownerAvatar }, ref) => {
const { attributes, listeners, setNodeRef, transform, transition, isDragging }
= useSortable({ id: issue.id, data: { column: issue.column }, disabled: readOnly })
@@ -178,14 +111,6 @@ export const IssueCard = forwardRef<HTMLDivElement, IssueCardProps>(
</span>
)
: null}
<HandoffRequestActions
issue={issue}
me={me}
onAsk={onAskHandoff}
onCancel={onCancelHandoffRequest}
onAccept={onAcceptHandoffRequest}
onReject={onRejectHandoffRequest}
/>
<div className={cn('truncate', (locked || (readOnly && ownerLogin)) && 'pr-5', locked && readOnly && ownerLogin && 'pr-10')}>
<span className="font-mono opacity-60">
{issue.source === 'youtrack' ? issue.externalId : `#${issue.number}`}
@@ -3,6 +3,7 @@ import type { Issue, IssueColumn } from '../types'
import type { PropertyGroup, RowAction } from './property-grid'
import { ArrowRightLeft, CircleSlash, Download, Eraser, ExternalLink, GitMerge, Loader2, Play, RotateCcw, Terminal, Trash2, X } from 'lucide-react'
import { useMemo, useRef } from 'react'
import { canAnswerHandoffRequest, canAskHandoff, canCancelHandoffRequest } from '../../../src/panel/handlers/handoffRequest'
import { isIssueLocked } from '../lib/dependencies'
import { isIssueMine, issueOwnerLogin } from '../lib/issueOwnership'
import { isSessionMissing } from '../lib/missingSession'
@@ -134,6 +135,10 @@ interface IssueDetailPanelProps {
me?: string
onStartHandoff: (issueNumber: number) => void
onAcceptHandoff: (issueNumber: number) => void
onAskHandoff: (issueNumber: number) => void
onCancelHandoffRequest: (issueNumber: number) => void
onAcceptHandoffRequest: (issueNumber: number) => void
onRejectHandoffRequest: (issueNumber: number) => void
isHandoffRunning: (issueNumber: number) => boolean
/** Gitea login → 头像 data URI;只读工单在标题旁贴归属人头像。 */
avatars?: Record<string, string>
@@ -185,6 +190,10 @@ export function IssueDetailPanel({
isSessionTabPending,
onStartHandoff,
onAcceptHandoff,
onAskHandoff,
onCancelHandoffRequest,
onAcceptHandoffRequest,
onRejectHandoffRequest,
isHandoffRunning,
avatars,
}: IssueDetailPanelProps) {
@@ -847,6 +856,68 @@ export function IssueDetailPanel({
<ExternalLink className="size-3.5" />
{issue.source === 'youtrack' ? '在 YouTrack 打开' : '在 Gitea 打开'}
</button>
{canAskHandoff(issue, me) && (
<button
type="button"
data-tooltip="让负责人把这个工单移交给你"
onClick={() => onAskHandoff(issue.number)}
className="inline-flex shrink-0 items-center gap-1 rounded border border-[var(--vscode-panel-border)] px-2 py-1 text-xs hover:bg-black/10 dark:hover:bg-white/10"
>
申请移交
</button>
)}
{canCancelHandoffRequest(issue, me) && (
<>
<span
className="max-w-[16rem] truncate text-xs opacity-70"
data-tooltip={issue.handoffRequestError && issue.handoffRequestError !== 'rejected' ? issue.handoffRequestError : '等对方同意'}
>
{issue.handoffRequestError && issue.handoffRequestError !== 'rejected' ? issue.handoffRequestError : '已申请'}
</span>
<button
type="button"
onClick={() => onCancelHandoffRequest(issue.number)}
className="inline-flex shrink-0 items-center gap-1 rounded border border-[var(--vscode-panel-border)] px-2 py-1 text-xs hover:bg-black/10 dark:hover:bg-white/10"
>
取消申请
</button>
</>
)}
{canAnswerHandoffRequest(issue, me) && (
<>
<span className="shrink-0 text-xs opacity-80">
{issue.handoffRequestFrom}
{' '}
想要这个工单
</span>
{issue.handoffRequestError && issue.handoffRequestError !== 'rejected'
? (
<span
className="max-w-[16rem] truncate text-xs text-[var(--vscode-errorForeground)]"
data-tooltip={issue.handoffRequestError}
>
{issue.handoffRequestError}
</span>
)
: null}
<button
type="button"
disabled={isHandoffRunning(issue.number)}
onClick={() => onAcceptHandoffRequest(issue.number)}
className="inline-flex shrink-0 items-center gap-1 rounded border border-green-500/60 bg-green-500/10 px-2 py-1 text-xs text-green-500 hover:bg-green-500/20 disabled:opacity-50"
>
{isHandoffRunning(issue.number) ? <Loader2 className="size-3.5 animate-spin" /> : null}
同意
</button>
<button
type="button"
onClick={() => onRejectHandoffRequest(issue.number)}
className="inline-flex shrink-0 items-center gap-1 rounded border border-[var(--vscode-panel-border)] px-2 py-1 text-xs hover:bg-black/10 dark:hover:bg-white/10"
>
拒绝
</button>
</>
)}
{/* Close/delete write to gitea by number — hide for youtrack cards to
avoid acting on a gitea issue of the same number. */}
{issue.source !== 'youtrack' && !readOnly && (
@@ -45,10 +45,6 @@ interface KanbanBoardProps {
me?: string
/** Gitea login → 头像 data URI。 */
avatars?: Record<string, string>
onAskHandoff?: (issueNumber: number) => void
onCancelHandoffRequest?: (issueNumber: number) => void
onAcceptHandoffRequest?: (issueNumber: number) => void
onRejectHandoffRequest?: (issueNumber: number) => void
}
const kanbanCollisionDetection: CollisionDetection = (args) => {
@@ -123,10 +119,6 @@ export function KanbanBoard({
onDependencyClear,
me,
avatars,
onAskHandoff,
onCancelHandoffRequest,
onAcceptHandoffRequest,
onRejectHandoffRequest,
}: KanbanBoardProps) {
const [activeId, setActiveId] = useState<string | null>(null)
@@ -289,11 +281,6 @@ export function KanbanBoard({
readOnly={readOnly}
ownerLogin={ownerLogin}
ownerAvatar={ownerLogin ? avatars?.[ownerLogin] : undefined}
me={me}
onAskHandoff={onAskHandoff}
onCancelHandoffRequest={onCancelHandoffRequest}
onAcceptHandoffRequest={onAcceptHandoffRequest}
onRejectHandoffRequest={onRejectHandoffRequest}
/>
)
})}