This commit is contained in:
yelan
2026-09-17 16:48:59 +08:00
22 changed files with 620 additions and 66 deletions
@@ -5,6 +5,7 @@ defineProps<{
artifacts: SigningArtifact[]
loading: boolean
error: boolean
canDownload: boolean
}>()
const emit = defineEmits<{
@@ -51,7 +52,12 @@ function formatFileSize(size: number) {
<span>{{ artifact.fileName }} · {{ formatFileSize(artifact.sizeBytes) }}</span>
<small v-if="artifact.createdAt">生成于 {{ artifact.createdAt }}</small>
</div>
<button type="button" class="artifact-download" @click="emit('download', artifact)">
<button
v-if="canDownload"
type="button"
class="artifact-download"
@click="emit('download', artifact)"
>
下载
</button>
</li>
@@ -13,6 +13,7 @@ const props = defineProps<{
departments: SigningFilterOption[]
documents: SigningFilterOption[]
visitTypes: SigningFilterOption<VisitType | 'all'>[]
canCreate: boolean
}>()
const emit = defineEmits<{
@@ -109,7 +110,9 @@ function submit() {
<span class="filter-spacer" />
<button type="submit" class="action-button action-button--ghost">查询</button>
<button type="button" class="action-button" @click="emit('add')">+ 新增签署任务</button>
<button v-if="canCreate" type="button" class="action-button" @click="emit('add')">
+ 新增签署任务
</button>
</form>
</template>
@@ -27,6 +27,12 @@ defineProps<{
artifactsError: boolean
padSessionStatus: string | null
padSessionLoading: boolean
canDeliveryWrite: boolean
canReopen: boolean
canVoid: boolean
canViewAudit: boolean
canViewArtifacts: boolean
canDownloadArtifact: boolean
}>()
const emit = defineEmits<{
@@ -70,7 +76,7 @@ defineExpose({ getDocumentElement })
转为短信发送
</button>
</template>
<template v-else>
<template v-else-if="canDeliveryWrite">
<button
type="button"
class="action-button"
@@ -105,6 +111,7 @@ defineExpose({ getDocumentElement })
转手写板签署
</button>
<button
v-if="canDeliveryWrite"
type="button"
class="action-button action-button--ghost"
@click="emit('action', 'resend-sms')"
@@ -113,15 +120,13 @@ defineExpose({ getDocumentElement })
</button>
</template>
<!-- 处理失败与已超时都需要「重新发起」这条出路:只给「作废」的话,
一次偶发的后端处理失败就得把整个任务废掉重来 -->
<template v-else-if="task.status === 'expired' || task.status === 'failed'">
<template v-else-if="task.status === 'expired' && canReopen">
<button type="button" class="action-button" @click="emit('action', 'reopen')">
↻ 重新发起
</button>
</template>
<template v-else-if="task.status === 'signed'">
<template v-else-if="task.status === 'signed' && canDownloadArtifact">
<button
type="button"
class="action-button action-button--ghost"
@@ -147,10 +152,11 @@ defineExpose({ getDocumentElement })
<button
v-if="
task.status === 'pending' ||
task.status === 'expired' ||
task.status === 'signing' ||
task.status === 'failed'
canVoid &&
(task.status === 'pending' ||
task.status === 'expired' ||
task.status === 'signing' ||
task.status === 'failed')
"
type="button"
class="action-button action-button--danger"
@@ -171,11 +177,18 @@ defineExpose({ getDocumentElement })
@field-change="(answers, progress) => emit('fieldChange', answers, progress)"
/>
<SigningDocumentPreview v-else :task="task" />
<TaskAuditTimeline :events="auditEvents" :loading="auditLoading" :error="auditError" />
<TaskAuditTimeline
v-if="canViewAudit"
:events="auditEvents"
:loading="auditLoading"
:error="auditError"
/>
<SigningArtifactList
v-if="canViewArtifacts"
:artifacts="artifacts"
:loading="artifactsLoading"
:error="artifactsError"
:can-download="canDownloadArtifact"
@download="emit('downloadArtifact', $event)"
/>
</template>
@@ -37,6 +37,7 @@ import type {
} from '@/api/workbench/types'
import NewSigningTaskDialog from '@/components/signing/NewSigningTaskDialog.vue'
import { useAppStore } from '@/stores/app'
import { useLoginStore } from '@/stores/login'
import type { SigningFieldProgress } from '@/utils/signing-document'
import { downloadSigningPdf } from '@/utils/signing-pdf'
@@ -50,6 +51,7 @@ import type { SigningFilterForm, SigningFilterOption, SigningTaskAction } from '
const route = useRoute()
const appStore = useAppStore()
const loginStore = useLoginStore()
const STATUS_VALUES: SigningTaskStatus[] = [
'pending',
@@ -117,6 +119,24 @@ const pdfExporting = ref(false)
let listRequestId = 0
let detailRequestId = 0
function canWrite(permission: string) {
return (
!loginStore.isReadOnly && (isSigningMockEnabled || loginStore.hasWritePermission(permission))
)
}
function canRead(permission: string) {
return isSigningMockEnabled || loginStore.hasPermission(permission)
}
const canCreateTask = computed(() => canWrite('sign:task:create'))
const canDeliveryWrite = computed(() => canWrite('sign:delivery:write'))
const canReopenTask = computed(() => canWrite('sign:task:reopen'))
const canVoidTask = computed(() => canWrite('sign:task:void'))
const canViewEvents = computed(() => canRead('sign:task:event:query'))
const canViewArtifacts = computed(() => canRead('sign:artifact:query'))
const canDownloadArtifacts = computed(() => canRead('sign:artifact:download'))
const campusOptions = computed<SigningFilterOption<WorkbenchCampus | 'all'>[]>(() => [
{ label: '全部院区', value: 'all' },
...appStore.campuses.map((campus) => ({ label: campus, value: campus })),
@@ -248,9 +268,9 @@ async function loadTaskDetail(id: string | null) {
}
detailLoading.value = true
eventsLoading.value = true
eventsLoading.value = canViewEvents.value
eventsError.value = false
artifactsLoading.value = true
artifactsLoading.value = canViewArtifacts.value
artifactsError.value = false
taskEvents.value = []
artifacts.value = []
@@ -259,8 +279,8 @@ async function loadTaskDetail(id: string | null) {
try {
const [detailResult, eventsResult, artifactsResult] = await Promise.allSettled([
getSigningTaskDetail(id, signingApiOptions.value),
getSigningTaskEvents(id),
getSigningArtifacts(id),
canViewEvents.value ? getSigningTaskEvents(id) : Promise.resolve([]),
canViewArtifacts.value ? getSigningArtifacts(id) : Promise.resolve([]),
])
if (requestId === detailRequestId) {
@@ -431,6 +451,20 @@ async function handleTaskAction(action: SigningTaskAction) {
return
}
const allowed =
isSigningMockEnabled ||
(action === 'create-pad-session' && canDeliveryWrite.value) ||
(action === 'resend-sms' && canDeliveryWrite.value) ||
(action === 'reopen' && canReopenTask.value) ||
(action === 'void' && canVoidTask.value) ||
((action === 'download-pdf' || action === 'download-signature' || action === 'print') &&
canDownloadArtifacts.value)
if (!allowed) {
ElMessage.warning('当前账号没有执行此操作的权限')
return
}
if (action === 'pad-sign') {
if (!isSigningMockEnabled) {
ElMessage.info('手写板设备签署接口待接入')
@@ -585,6 +619,11 @@ async function exportSignedPdf() {
}
async function downloadArtifact(artifact: SigningArtifact) {
if (!canDownloadArtifacts.value) {
ElMessage.warning('当前账号没有下载签署文件的权限')
return
}
try {
const blob = await downloadSigningArtifact(artifact.id)
const url = URL.createObjectURL(blob)
@@ -729,6 +768,7 @@ onMounted(() => {
:departments="departmentOptions"
:documents="documentOptions"
:visit-types="visitTypeOptions"
:can-create="canCreateTask"
@add="newDialogVisible = true"
@search="handleSearch"
/>
@@ -757,6 +797,12 @@ onMounted(() => {
:artifacts-error="artifactsError"
:pad-session-status="padSessionStatus"
:pad-session-loading="padSessionLoading"
:can-delivery-write="canDeliveryWrite"
:can-reopen="canReopenTask"
:can-void="canVoidTask"
:can-view-audit="canViewEvents"
:can-view-artifacts="canViewArtifacts"
:can-download-artifact="canDownloadArtifacts"
@action="handleTaskAction"
@download-artifact="downloadArtifact"
@selection-change="handleSelectionChange"