提交
This commit is contained in:
1 parent
417d6c9c51
commit
c8fe0cdc20
102 files changed
+19847
-12224
No files matched your search
@@ -260,7 +260,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { computed, onActivated, onMounted, reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { UploadRequestOptions } from 'element-plus'
|
||||
@@ -536,10 +536,14 @@
|
||||
{ debounce: 200 }
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
async function refresh() {
|
||||
await loadGroups()
|
||||
await loadData()
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(refresh)
|
||||
// KeepAlive 下从设计器返回时不会再次 onMounted,需在激活时刷新列表
|
||||
onActivated(refresh)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
<div v-show="activeStep === 'form'" class="oa-designer__form-step">
|
||||
<OaFormDesignerEmbed
|
||||
v-if="formDesignerMounted"
|
||||
:key="formDesignerEpoch"
|
||||
ref="formEmbedRef"
|
||||
v-model="formSchema"
|
||||
:flow-condition-fields="flowConditionFields"
|
||||
@@ -108,7 +109,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { computed, nextTick, onMounted, reactive, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { UploadRequestOptions } from 'element-plus'
|
||||
@@ -137,10 +138,13 @@
|
||||
const activeStep = ref('basic')
|
||||
const draft = ref<any>(null)
|
||||
/** iframe 表单设计器(与主应用 Tailwind 隔离) */
|
||||
const formEmbedRef = ref<{ fetchSchema: () => Promise<{ rule: unknown; option: unknown }> } | null>(
|
||||
null
|
||||
)
|
||||
const formEmbedRef = ref<{
|
||||
fetchSchema: () => Promise<{ rule: unknown; option: unknown }>
|
||||
reapplySchema?: () => void
|
||||
} | null>(null)
|
||||
const formDesignerMounted = ref(false)
|
||||
/** 每次进入表单 Tab 递增,强制重建 iframe,避免 display:none 后画布丢控件 */
|
||||
const formDesignerEpoch = ref(0)
|
||||
const formSchema = ref<{ rule: unknown; option: unknown }>({ rule: [], option: {} })
|
||||
|
||||
const basic = reactive({
|
||||
@@ -268,6 +272,19 @@
|
||||
advancedSettingsJson: { ...advanced }
|
||||
})
|
||||
draft.value = data
|
||||
// 以服务端回包为准同步本地,避免与 iframe 内容漂移
|
||||
const fs = data?.formSchemaJson
|
||||
if (fs) {
|
||||
try {
|
||||
const plain = JSON.parse(JSON.stringify(fs))
|
||||
formSchema.value = {
|
||||
rule: Array.isArray(plain?.rule) ? plain.rule : Array.isArray(plain) ? plain : [],
|
||||
option: plain?.option && typeof plain.option === 'object' ? plain.option : {}
|
||||
}
|
||||
} catch {
|
||||
/* keep collected schema */
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
@@ -307,13 +324,20 @@
|
||||
}
|
||||
}
|
||||
if (name === 'form') {
|
||||
// 先卸载再挂载:隐藏态 iframe 内 FormCreate 画布易丢控件,重建后按 formSchema 回显
|
||||
formDesignerMounted.value = false
|
||||
formDesignerEpoch.value += 1
|
||||
await nextTick()
|
||||
formDesignerMounted.value = true
|
||||
await nextTick()
|
||||
formEmbedRef.value?.reapplySchema?.()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadDraft()
|
||||
if (activeStep.value === 'form') {
|
||||
formDesignerEpoch.value += 1
|
||||
formDesignerMounted.value = true
|
||||
}
|
||||
})
|
||||
|
||||
@@ -153,6 +153,10 @@
|
||||
import OaProgressChain from '@/components/oa-approval/OaProgressChain.vue'
|
||||
import { MAX_ADMIN_LIST_PAGE_SIZE } from '@/utils/fetch-paged-list'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import {
|
||||
OA_FC_DEFAULT_GUTTER,
|
||||
ensureOaUploadRuleProps
|
||||
} from '@/utils/oa-form-create-upload'
|
||||
|
||||
defineOptions({ name: 'OaApprovalInstanceDetail' })
|
||||
|
||||
@@ -166,6 +170,7 @@
|
||||
const viewRule = ref<any[]>([])
|
||||
const viewOption = ref({
|
||||
form: { disabled: true, labelPosition: 'top' },
|
||||
row: { gutter: OA_FC_DEFAULT_GUTTER },
|
||||
submitBtn: false,
|
||||
resetBtn: false
|
||||
})
|
||||
@@ -182,7 +187,7 @@
|
||||
const list = detail.value?.progress
|
||||
if (!Array.isArray(list) || !list.length) return []
|
||||
return list.map((p: any, idx: number) => ({
|
||||
key: `${p.kind}-${p.taskId || p.nodeKey || idx}-${p.roundNo || 1}`,
|
||||
key: `${p.kind}-${p.nodeKey || p.taskId || idx}-${p.roundNo || 1}`,
|
||||
title: p.title || '-',
|
||||
status: p.status || 'info',
|
||||
timestamp: p.timestamp || '',
|
||||
@@ -191,7 +196,18 @@
|
||||
commentText: p.commentText || '',
|
||||
taskType: p.taskType || '',
|
||||
kind: p.kind || '',
|
||||
roundNo: p.roundNo || 1
|
||||
roundNo: p.roundNo || 1,
|
||||
signMode: p.signMode || '',
|
||||
people: Array.isArray(p.people)
|
||||
? p.people.map((x: any) => ({
|
||||
id: x.id || '',
|
||||
nickName: x.nickName || x.id || '',
|
||||
status: x.status || '',
|
||||
timestamp: x.timestamp || '',
|
||||
commentText: x.commentText || '',
|
||||
taskId: x.taskId || ''
|
||||
}))
|
||||
: []
|
||||
}))
|
||||
})
|
||||
|
||||
@@ -246,10 +262,16 @@
|
||||
function applySchema(schema: any, data: any) {
|
||||
viewFormData.value = { ...(data || {}) }
|
||||
const rule = Array.isArray(schema?.rule) ? schema.rule : Array.isArray(schema) ? schema : []
|
||||
viewRule.value = rule
|
||||
viewRule.value = ensureOaUploadRuleProps(rule)
|
||||
const savedGutter = Number(schema?.option?.row?.gutter)
|
||||
viewOption.value = {
|
||||
...(schema?.option || {}),
|
||||
form: { ...(schema?.option?.form || {}), disabled: true, labelPosition: 'top' },
|
||||
// 顶层字段 col.span 布局用;栅格组件 fcRow 由 ensureOaUploadRuleProps 处理
|
||||
row: {
|
||||
...(schema?.option?.row || {}),
|
||||
gutter: savedGutter > 0 ? savedGutter : OA_FC_DEFAULT_GUTTER
|
||||
},
|
||||
submitBtn: false,
|
||||
resetBtn: false
|
||||
}
|
||||
|
||||
@@ -218,25 +218,81 @@
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="绑定用户">
|
||||
<el-select
|
||||
v-model="memberUserIds"
|
||||
multiple
|
||||
filterable
|
||||
clearable
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
placeholder="请选择用户"
|
||||
style="width: 100%"
|
||||
:loading="memberLoading"
|
||||
>
|
||||
<el-option
|
||||
v-for="u in userOptions"
|
||||
:key="u.id"
|
||||
:label="`${u.nickName}(${u.userNo})`"
|
||||
:value="u.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
绑定用户
|
||||
<span class="form-label-tip">可多选,已选成员将展示在下方列表</span>
|
||||
</template>
|
||||
<div v-loading="memberLoading" class="member-picker">
|
||||
<el-select
|
||||
v-model="memberPickBuffer"
|
||||
filterable
|
||||
clearable
|
||||
:loading="memberLoading"
|
||||
placeholder="搜索姓名 / 工号,点击添加"
|
||||
style="width: 100%"
|
||||
popper-class="oa-role-member-select-popper"
|
||||
@change="onMemberPick"
|
||||
>
|
||||
<el-option
|
||||
v-for="u in userOptions"
|
||||
:key="u.id"
|
||||
:label="memberOptionLabel(u)"
|
||||
:value="u.id"
|
||||
:disabled="memberUserIds.includes(u.id)"
|
||||
>
|
||||
<div class="member-option">
|
||||
<span class="member-avatar" :style="{ background: avatarColor(u.id) }">
|
||||
{{ avatarInitial(u.nickName) }}
|
||||
</span>
|
||||
<span class="member-option__meta">
|
||||
<span class="member-option__name">{{ u.nickName || '未命名' }}</span>
|
||||
<span v-if="memberOptionSub(u)" class="member-option__sub">
|
||||
{{ memberOptionSub(u) }}
|
||||
</span>
|
||||
</span>
|
||||
<el-tag
|
||||
v-if="memberUserIds.includes(u.id)"
|
||||
size="small"
|
||||
type="info"
|
||||
effect="plain"
|
||||
class="member-option__tag"
|
||||
>
|
||||
已选
|
||||
</el-tag>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
|
||||
<div
|
||||
class="member-chips"
|
||||
:class="{ 'is-empty': !memberUserIds.length }"
|
||||
>
|
||||
<template v-if="memberUserIds.length">
|
||||
<span v-for="id in memberUserIds" :key="id" class="member-chip">
|
||||
<span
|
||||
class="member-avatar member-avatar--sm"
|
||||
:style="{ background: avatarColor(id) }"
|
||||
>
|
||||
{{ avatarInitial(resolveUser(id)?.nickName) }}
|
||||
</span>
|
||||
<span class="member-chip__name" :title="chipTitle(id)">
|
||||
{{ resolveUser(id)?.nickName || id }}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="member-chip__rm"
|
||||
:aria-label="`移除 ${resolveUser(id)?.nickName || id}`"
|
||||
@click="removeMember(id)"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>暂未选择成员,请从上方搜索添加</template>
|
||||
</div>
|
||||
<p class="member-picked-count">已选 {{ memberUserIds.length }} 人</p>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -261,7 +317,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { formatDisplayOrDash } from '@/utils/datetime-display'
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { nextTick, onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
Plus,
|
||||
@@ -310,12 +366,74 @@
|
||||
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
|
||||
}
|
||||
|
||||
type UserOption = { id: string; nickName: string; userNo: string }
|
||||
|
||||
const AVATAR_COLORS = [
|
||||
'#059669',
|
||||
'#0ea5e9',
|
||||
'#8b5cf6',
|
||||
'#d97706',
|
||||
'#ef4444',
|
||||
'#14b8a6',
|
||||
'#6366f1',
|
||||
'#ec4899'
|
||||
]
|
||||
|
||||
const memberVisible = ref(false)
|
||||
const memberRole = ref<any>(null)
|
||||
const memberUserIds = ref<string[]>([])
|
||||
const memberPickBuffer = ref('')
|
||||
const memberLoading = ref(false)
|
||||
const memberSubmitLoading = ref(false)
|
||||
const userOptions = ref<Array<{ id: string; nickName: string; userNo: string }>>([])
|
||||
const userOptions = ref<UserOption[]>([])
|
||||
|
||||
function avatarColor(id: string) {
|
||||
let hash = 0
|
||||
for (let i = 0; i < id.length; i++) hash = (hash * 31 + id.charCodeAt(i)) >>> 0
|
||||
return AVATAR_COLORS[hash % AVATAR_COLORS.length]
|
||||
}
|
||||
|
||||
function avatarInitial(name?: string) {
|
||||
const t = (name || '?').trim()
|
||||
return t ? t.slice(0, 1) : '?'
|
||||
}
|
||||
|
||||
function resolveUser(id: string) {
|
||||
return userOptions.value.find((u) => u.id === id)
|
||||
}
|
||||
|
||||
function memberOptionLabel(u: UserOption) {
|
||||
const name = (u.nickName || '').trim() || '未命名'
|
||||
const no = (u.userNo || '').trim()
|
||||
if (no && no !== name) return `${name}(${no})`
|
||||
return name
|
||||
}
|
||||
|
||||
function memberOptionSub(u: UserOption) {
|
||||
const name = (u.nickName || '').trim()
|
||||
const no = (u.userNo || '').trim()
|
||||
if (!no || no === name) return ''
|
||||
return `账号 ${no}`
|
||||
}
|
||||
|
||||
function chipTitle(id: string) {
|
||||
const u = resolveUser(id)
|
||||
return u ? memberOptionLabel(u) : id
|
||||
}
|
||||
|
||||
function onMemberPick(id: string) {
|
||||
if (!id) return
|
||||
if (!memberUserIds.value.includes(id)) {
|
||||
memberUserIds.value = [...memberUserIds.value, id]
|
||||
}
|
||||
nextTick(() => {
|
||||
memberPickBuffer.value = ''
|
||||
})
|
||||
}
|
||||
|
||||
function removeMember(id: string) {
|
||||
memberUserIds.value = memberUserIds.value.filter((x) => x !== id)
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
@@ -426,6 +544,8 @@
|
||||
|
||||
async function handleMembers(row: any) {
|
||||
memberRole.value = row
|
||||
memberPickBuffer.value = ''
|
||||
memberUserIds.value = []
|
||||
memberVisible.value = true
|
||||
memberLoading.value = true
|
||||
try {
|
||||
@@ -468,4 +588,180 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.member-picker {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.member-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
min-height: 44px;
|
||||
margin-top: 12px;
|
||||
padding: 12px;
|
||||
background: #f8fafc;
|
||||
border: 1px dashed #cbd5e1;
|
||||
border-radius: 8px;
|
||||
|
||||
&.is-empty {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--mint-text-muted, #94a3b8);
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.member-chip {
|
||||
display: inline-flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
padding: 4px 8px 4px 4px;
|
||||
background: #fff;
|
||||
border: 1px solid var(--mint-border, #e2e8f0);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.member-chip__name {
|
||||
max-width: 120px;
|
||||
overflow: hidden;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.member-chip__rm {
|
||||
display: grid;
|
||||
flex-shrink: 0;
|
||||
place-items: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 0;
|
||||
color: var(--mint-text-muted, #64748b);
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
background: #f1f5f9;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: #dc2626;
|
||||
background: #fee2e2;
|
||||
}
|
||||
}
|
||||
|
||||
.member-picked-count {
|
||||
margin: 8px 0 0;
|
||||
color: var(--mint-text-muted, #94a3b8);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.member-avatar {
|
||||
display: inline-flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
|
||||
&--sm {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- 下拉挂 body,须非 scoped -->
|
||||
<style lang="scss">
|
||||
.oa-role-member-select-popper {
|
||||
.el-select-dropdown__list {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.el-select-dropdown__item {
|
||||
height: auto !important;
|
||||
min-height: 56px;
|
||||
margin-bottom: 8px;
|
||||
padding: 10px 12px !important;
|
||||
line-height: normal !important;
|
||||
border-radius: 8px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&.is-disabled {
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
&.hover,
|
||||
&:hover {
|
||||
background: var(--primary-bg, #ecfdf5);
|
||||
}
|
||||
}
|
||||
|
||||
.member-option {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-height: 36px;
|
||||
}
|
||||
|
||||
.member-option__meta {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
justify-content: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.member-option__name {
|
||||
overflow: hidden;
|
||||
color: var(--mint-text, #0f172a);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.member-option__sub {
|
||||
overflow: hidden;
|
||||
color: var(--mint-text-muted, #64748b);
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.member-option__tag {
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.member-avatar {
|
||||
display: inline-flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -131,6 +131,8 @@
|
||||
v-if="!detailLoading"
|
||||
:steps="flowSteps"
|
||||
:loading="previewLoading"
|
||||
:resolved="flowResolved"
|
||||
:pending-message="flowPendingMessage"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
@@ -166,6 +168,10 @@
|
||||
import OaFlowPreview from '@/components/oa-approval/OaFlowPreview.vue'
|
||||
import { MAX_ADMIN_LIST_PAGE_SIZE } from '@/utils/fetch-paged-list'
|
||||
import { resolveOaAppIconUrl } from '@/utils/oa-app-icons'
|
||||
import {
|
||||
OA_FC_DEFAULT_GUTTER,
|
||||
ensureOaUploadRuleProps
|
||||
} from '@/utils/oa-form-create-upload'
|
||||
|
||||
defineOptions({ name: 'OaApprovalStart' })
|
||||
|
||||
@@ -181,7 +187,11 @@
|
||||
const picked = ref<any>(null)
|
||||
const formData = ref<Record<string, any>>({})
|
||||
const formRule = ref<any[]>([])
|
||||
const formOption = ref<Record<string, any>>({ submitBtn: false, resetBtn: false })
|
||||
const formOption = ref<Record<string, any>>({
|
||||
submitBtn: false,
|
||||
resetBtn: false,
|
||||
row: { gutter: OA_FC_DEFAULT_GUTTER }
|
||||
})
|
||||
const fapi = ref<any>(null)
|
||||
const submitting = ref(false)
|
||||
const needInitiatorSelect = ref(false)
|
||||
@@ -189,7 +199,10 @@
|
||||
const userLoading = ref(false)
|
||||
const userOptions = ref<Array<{ id: string; nickName: string; userNo: string }>>([])
|
||||
const flowSteps = ref<OaFlowPreviewStep[]>([])
|
||||
const flowResolved = ref(true)
|
||||
const flowPendingMessage = ref('必填信息填写后,流程将自动显示')
|
||||
const previewLoading = ref(false)
|
||||
let previewTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const displayGroups = computed(() => {
|
||||
const kw = (searchKeyword.value || '').trim().toLowerCase()
|
||||
@@ -267,18 +280,32 @@
|
||||
async function loadFlowPreview(appId: string) {
|
||||
previewLoading.value = true
|
||||
try {
|
||||
const preview = await fetchOaFlowPreview(
|
||||
appId,
|
||||
needInitiatorSelect.value ? [...initiatorSelectUserIds.value] : undefined
|
||||
)
|
||||
flowSteps.value = preview?.steps || []
|
||||
const preview = await fetchOaFlowPreview(appId, {
|
||||
formData: { ...formData.value },
|
||||
initiatorSelectUserIds: needInitiatorSelect.value
|
||||
? [...initiatorSelectUserIds.value]
|
||||
: undefined
|
||||
})
|
||||
flowResolved.value = preview?.resolved !== false
|
||||
flowPendingMessage.value =
|
||||
preview?.message || '必填信息填写后,流程将自动显示'
|
||||
flowSteps.value = flowResolved.value ? preview?.steps || [] : []
|
||||
} catch {
|
||||
flowSteps.value = []
|
||||
flowResolved.value = true
|
||||
} finally {
|
||||
previewLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleFlowPreview() {
|
||||
if (!dlgVisible.value || !picked.value?.id) return
|
||||
if (previewTimer) clearTimeout(previewTimer)
|
||||
previewTimer = setTimeout(() => {
|
||||
loadFlowPreview(picked.value.id)
|
||||
}, 300)
|
||||
}
|
||||
|
||||
async function onPick(t: any) {
|
||||
picked.value = t
|
||||
formData.value = {}
|
||||
@@ -286,19 +313,32 @@
|
||||
needInitiatorSelect.value = false
|
||||
initiatorSelectUserIds.value = []
|
||||
flowSteps.value = []
|
||||
flowResolved.value = false
|
||||
flowPendingMessage.value = '必填信息填写后,流程将自动显示'
|
||||
dlgVisible.value = true
|
||||
detailLoading.value = true
|
||||
previewLoading.value = true
|
||||
try {
|
||||
const detail = await fetchOaTemplateDetail(t.id)
|
||||
const schema = detail?.formSchemaJson || {}
|
||||
formRule.value = Array.isArray(schema.rule) ? schema.rule : Array.isArray(schema) ? schema : []
|
||||
const rawRule = Array.isArray(schema.rule)
|
||||
? schema.rule
|
||||
: Array.isArray(schema)
|
||||
? schema
|
||||
: []
|
||||
formRule.value = ensureOaUploadRuleProps(rawRule)
|
||||
const savedGutter = Number(schema.option?.row?.gutter)
|
||||
formOption.value = {
|
||||
...(schema.option || {}),
|
||||
form: {
|
||||
...(schema.option?.form || {}),
|
||||
labelPosition: 'top'
|
||||
},
|
||||
// 顶层字段 col.span 布局用;栅格组件 fcRow 由 ensureOaUploadRuleProps 处理
|
||||
row: {
|
||||
...(schema.option?.row || {}),
|
||||
gutter: savedGutter > 0 ? savedGutter : OA_FC_DEFAULT_GUTTER
|
||||
},
|
||||
submitBtn: false,
|
||||
resetBtn: false
|
||||
}
|
||||
@@ -312,17 +352,26 @@
|
||||
} catch {
|
||||
formRule.value = []
|
||||
flowSteps.value = []
|
||||
flowResolved.value = true
|
||||
previewLoading.value = false
|
||||
} finally {
|
||||
detailLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
formData,
|
||||
() => {
|
||||
scheduleFlowPreview()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
initiatorSelectUserIds,
|
||||
() => {
|
||||
if (dlgVisible.value && picked.value?.id && needInitiatorSelect.value) {
|
||||
loadFlowPreview(picked.value.id)
|
||||
scheduleFlowPreview()
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
|
||||
+16
-18
@@ -104,24 +104,22 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="protocol-card-compact__field protocol-card-compact__field--split-row">
|
||||
<div class="protocol-card-compact__field-col">
|
||||
<label>母血采集</label>
|
||||
<span
|
||||
class="protocol-card__kv-value"
|
||||
:title="String(row.maternalBloodCollectTime || '')"
|
||||
>
|
||||
{{ formatDisplayOrDash(row.maternalBloodCollectTime) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="protocol-card-compact__field-col protocol-card-compact__field-col--attachment">
|
||||
<ProtocolAttachmentMiniReadonly
|
||||
:label="attachmentMeta(row).label"
|
||||
:files="attachmentMeta(row).files"
|
||||
@preview="handleAttachmentPreview"
|
||||
@download="handleAttachmentDownload"
|
||||
/>
|
||||
</div>
|
||||
<div class="protocol-card-compact__field">
|
||||
<label>母血采集</label>
|
||||
<span
|
||||
class="protocol-card__kv-value"
|
||||
:title="String(row.maternalBloodCollectTime || '')"
|
||||
>
|
||||
{{ formatDisplayOrDash(row.maternalBloodCollectTime) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="protocol-card-compact__field protocol-card-compact__field--attachment-only">
|
||||
<ProtocolAttachmentMiniReadonly
|
||||
:label="attachmentMeta(row).label"
|
||||
:files="attachmentMeta(row).files"
|
||||
@preview="handleAttachmentPreview"
|
||||
@download="handleAttachmentDownload"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
|
||||
Reference in new issue
Block a user