🐞 fix: 修改一些小问题

This commit is contained in:
砂糖
2025-12-30 17:49:45 +08:00
parent a623c5673f
commit d193f43f30
11 changed files with 179 additions and 99 deletions

View File

@@ -29,4 +29,12 @@ public class HrmFlowInstanceBo extends BaseEntity {
private Long startUserId; private Long startUserId;
private String remark; private String remark;
private String startTime;
private String endTime;
private String hours;
private String bizTitle;
} }

View File

@@ -57,6 +57,7 @@
"jsbarcode": "^3.12.1", "jsbarcode": "^3.12.1",
"jsencrypt": "3.0.0-rc.1", "jsencrypt": "3.0.0-rc.1",
"jspdf": "^2.5.2", "jspdf": "^2.5.2",
"pdfjs-dist": "^3.6.172",
"konva": "^10.0.2", "konva": "^10.0.2",
"mqtt": "^5.13.3", "mqtt": "^5.13.3",
"nprogress": "0.2.0", "nprogress": "0.2.0",

View File

@@ -54,7 +54,7 @@ export function transferFlowTask(taskId, data) {
return request({ return request({
url: `/hrm/flow/task/${taskId}/transfer`, url: `/hrm/flow/task/${taskId}/transfer`,
method: 'post', method: 'post',
data params: data
}) })
} }

View File

@@ -131,7 +131,7 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="版本" prop="version"> <el-form-item label="版本" prop="version">
<el-input-number v-model="form.version" :min="1" :step="1" controls-position="right" style="width: 100%" /> <el-input-number v-model="form.version" :min="1" :step="1" control="false" style="width: 100%" />
<div class="hint-text">同一业务可存在多版本建议先从 v1 开始</div> <div class="hint-text">同一业务可存在多版本建议先从 v1 开始</div>
</el-form-item> </el-form-item>
<el-form-item label="启用" prop="enabled"> <el-form-item label="启用" prop="enabled">

View File

@@ -72,7 +72,7 @@
> >
<el-table-column label="申请类型" min-width="100"> <el-table-column label="申请类型" min-width="100">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag :type="getTypeTagType(scope.row.procDefKey)">{{ getTypeText(scope.row.procDefKey) }}</el-tag> <el-tag :type="getTypeTagType(scope.row.bizType)">{{ getTypeText(scope.row.bizType) }}</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="申请人" min-width="120"> <el-table-column label="申请人" min-width="120">
@@ -96,7 +96,7 @@
</el-table-column> </el-table-column>
<el-table-column label="状态" prop="status" min-width="110"> <el-table-column label="状态" prop="status" min-width="110">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag :type="statusType(scope.row.procStatus)">{{ statusText(scope.row.procStatus) }}</el-tag> <el-tag :type="statusType(scope.row.status)">{{ statusText(scope.row.status) }}</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="申请时间" prop="createTime" min-width="160"> <el-table-column label="申请时间" prop="createTime" min-width="160">
@@ -202,12 +202,12 @@ export default {
return '-' return '-'
}, },
statusText(status) { statusText(status) {
const map = { pending: '审批中', draft: '草稿', approved: '已通过', rejected: '已驳回', finished: '已完成' } const map = { running: '审批中', draft: '草稿', approved: '已通过', rejected: '已驳回', finished: '已完成' }
return map[status] || status || '-' return map[status] || status || '-'
}, },
statusType(status) { statusType(status) {
if (!status) return 'info' if (!status) return 'info'
const map = { pending: 'warning', draft: 'info', approved: 'success', rejected: 'danger', finished: 'success' } const map = { running: 'warning', draft: 'info', approved: 'success', rejected: 'danger', finished: 'success' }
return map[status] || 'info' return map[status] || 'info'
}, },
formatDate(val) { formatDate(val) {
@@ -244,11 +244,11 @@ export default {
} }
}, },
goDetail(row) { goDetail(row) {
if (!row || !row.businessKey) { if (!row || !row.bizId || !row.bizType) {
this.$message.warning('缺少businessKey无法打开详情') this.$message.warning('缺少businessKey无法打开详情')
return return
} }
const [type, bizId] = row.businessKey.split(':') const { bizId, bizType: type } = row
if (!bizId) { if (!bizId) {
this.$message.warning('businessKey格式不正确无法解析业务ID') this.$message.warning('businessKey格式不正确无法解析业务ID')
return return

View File

@@ -173,8 +173,8 @@
<div class="line"></div> <div class="line"></div>
<div class="flow-step"><div class="dot"></div><div class="txt">提交</div></div> <div class="flow-step"><div class="dot"></div><div class="txt">提交</div></div>
<template v-for="(n, idx) in flowNodes"> <template v-for="(n, idx) in flowNodes">
<div :key="`line-${n.nodeId || idx}`" class="line"></div> <div class="line"></div>
<div :key="`node-${n.nodeId || idx}`" class="flow-step"> <div class="flow-step">
<div class="dot" :class="{ success: idx === flowNodes.length - 1 }"></div> <div class="dot" :class="{ success: idx === flowNodes.length - 1 }"></div>
<div class="txt">{{ nodePreviewText(n, idx) }}</div> <div class="txt">{{ nodePreviewText(n, idx) }}</div>
</div> </div>
@@ -223,7 +223,7 @@ export default {
flowLoading: false, flowLoading: false,
flowTpl: null, flowTpl: null,
flowNodes: [], flowNodes: [],
approverMode: 'template', approverMode: 'manual',
availableTpls: [], availableTpls: [],
tplId: null, tplId: null,
assigneeUserId: null, assigneeUserId: null,

View File

@@ -139,8 +139,8 @@
<div class="line"></div> <div class="line"></div>
<div class="flow-step"><div class="dot"></div><div class="txt">提交</div></div> <div class="flow-step"><div class="dot"></div><div class="txt">提交</div></div>
<template v-for="(n, idx) in flowNodes"> <template v-for="(n, idx) in flowNodes">
<div :key="`line-${n.nodeId || idx}`" class="line"></div> <div class="line"></div>
<div :key="`node-${n.nodeId || idx}`" class="flow-step"> <div class="flow-step">
<div class="dot" :class="{ success: idx === flowNodes.length - 1 }"></div> <div class="dot" :class="{ success: idx === flowNodes.length - 1 }"></div>
<div class="txt">{{ nodePreviewText(n, idx) }}</div> <div class="txt">{{ nodePreviewText(n, idx) }}</div>
</div> </div>
@@ -190,7 +190,7 @@ export default {
flowLoading: false, flowLoading: false,
flowTpl: null, flowTpl: null,
flowNodes: [], flowNodes: [],
approverMode: 'template', approverMode: 'manual',
availableTpls: [], availableTpls: [],
tplId: null, tplId: null,
assigneeUserId: null, assigneeUserId: null,

View File

@@ -169,8 +169,8 @@
<div class="line"></div> <div class="line"></div>
<div class="flow-step"><div class="dot"></div><div class="txt">提交</div></div> <div class="flow-step"><div class="dot"></div><div class="txt">提交</div></div>
<template v-for="(n, idx) in flowNodes"> <template v-for="(n, idx) in flowNodes">
<div :key="`line-${n.nodeId || idx}`" class="line"></div> <div class="line"></div>
<div :key="`node-${n.nodeId || idx}`" class="flow-step"> <div class="flow-step">
<div class="dot" :class="{ success: idx === flowNodes.length - 1 }"></div> <div class="dot" :class="{ success: idx === flowNodes.length - 1 }"></div>
<div class="txt">{{ nodePreviewText(n, idx) }}</div> <div class="txt">{{ nodePreviewText(n, idx) }}</div>
</div> </div>
@@ -221,7 +221,7 @@ export default {
flowLoading: false, flowLoading: false,
flowTpl: null, flowTpl: null,
flowNodes: [], flowNodes: [],
approverMode: 'template', approverMode: 'manual',
availableTpls: [], availableTpls: [],
tplId: null, tplId: null,
assigneeUserId: null, assigneeUserId: null,

View File

@@ -29,15 +29,8 @@
<el-row :gutter="14"> <el-row :gutter="14">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="出差类型" prop="travelType"> <el-form-item label="出差类型" prop="travelType">
<el-select <el-select v-model="form.travelType" filterable allow-create default-first-option clearable
v-model="form.travelType" placeholder="选择或输入(如:客户拜访/项目支持/培训学习)" style="width: 100%">
filterable
allow-create
default-first-option
clearable
placeholder="选择或输入(如:客户拜访/项目支持/培训学习)"
style="width: 100%"
>
<el-option v-for="t in travelTypeOptions" :key="t" :label="t" :value="t" /> <el-option v-for="t in travelTypeOptions" :key="t" :label="t" :value="t" />
</el-select> </el-select>
<div class="hint-text">优先选择若公司类型未配置可直接输入</div> <div class="hint-text">优先选择若公司类型未配置可直接输入</div>
@@ -49,12 +42,14 @@
<el-row :gutter="14"> <el-row :gutter="14">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="开始时间" prop="startTime"> <el-form-item label="开始时间" prop="startTime">
<el-date-picker v-model="form.startTime" type="datetime" placeholder="请选择开始时间" style="width: 100%" :picker-options="pickerOptions" /> <el-date-picker v-model="form.startTime" type="datetime" placeholder="请选择开始时间" style="width: 100%"
:picker-options="pickerOptions" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="结束时间" prop="endTime"> <el-form-item label="结束时间" prop="endTime">
<el-date-picker v-model="form.endTime" type="datetime" placeholder="请选择结束时间" style="width: 100%" :picker-options="pickerOptions" /> <el-date-picker v-model="form.endTime" type="datetime" placeholder="请选择结束时间" style="width: 100%"
:picker-options="pickerOptions" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
@@ -66,17 +61,13 @@
<div class="block-title">出差说明</div> <div class="block-title">出差说明</div>
<el-form-item label="事由" prop="reason"> <el-form-item label="事由" prop="reason">
<el-input v-model="form.reason" type="textarea" :rows="4" placeholder="请说明出差目的、任务目标、预期成果等" show-word-limit maxlength="200" /> <el-input v-model="form.reason" type="textarea" :rows="4" placeholder="请说明出差目的、任务目标、预期成果等" show-word-limit
maxlength="200" />
</el-form-item> </el-form-item>
<el-form-item label="交通/住宿/行程附件" prop="accessoryApplyIds"> <el-form-item label="交通/住宿/行程附件" prop="accessoryApplyIds">
<file-upload <file-upload v-model="form.accessoryApplyIds" :limit="8" :file-size="50"
v-model="form.accessoryApplyIds" :file-type="['pdf', 'jpg', 'jpeg', 'png', 'doc', 'docx']" multiple />
:limit="8"
:file-size="50"
:file-type="['pdf', 'jpg', 'jpeg', 'png', 'doc', 'docx']"
multiple
/>
<div class="hint-text">上传机票酒店行程单等pdf/jpg/png/doc/docx便于审批与后续报销</div> <div class="hint-text">上传机票酒店行程单等pdf/jpg/png/doc/docx便于审批与后续报销</div>
</el-form-item> </el-form-item>
@@ -93,21 +84,10 @@
<div class="approve-row"> <div class="approve-row">
<div class="k">流程模板</div> <div class="k">流程模板</div>
<div class="v"> <div class="v">
<el-select <el-select v-model="tplId" size="small" clearable filterable placeholder="请选择流程模板"
v-model="tplId" style="width: 360px" @change="onTplChange">
size="small" <el-option v-for="t in availableTpls" :key="t.tplId"
clearable :label="`${t.tplName}${t.version ? ' (v' + t.version + ')' : ''}`" :value="t.tplId" />
filterable
placeholder="请选择流程模板"
style="width: 360px"
@change="onTplChange"
>
<el-option
v-for="t in availableTpls"
:key="t.tplId"
:label="`${t.tplName}${t.version ? ' (v' + t.version + ')' : ''}`"
:value="t.tplId"
/>
</el-select> </el-select>
</div> </div>
</div> </div>
@@ -130,13 +110,8 @@
</div> </div>
<el-form-item label="回执附件(可选)" prop="accessoryReceiptIds"> <el-form-item label="回执附件(可选)" prop="accessoryReceiptIds">
<file-upload <file-upload v-model="form.accessoryReceiptIds" :limit="8" :file-size="50"
v-model="form.accessoryReceiptIds" :file-type="['pdf', 'jpg', 'jpeg', 'png', 'doc', 'docx']" multiple />
:limit="8"
:file-size="50"
:file-type="['pdf', 'jpg', 'jpeg', 'png', 'doc', 'docx']"
multiple
/>
<div class="hint-text">可选上传回执发票盖章回单等审核/归档使用</div> <div class="hint-text">可选上传回执发票盖章回单等审核/归档使用</div>
</el-form-item> </el-form-item>
@@ -170,7 +145,8 @@
</el-row> </el-row>
<el-form-item label="备注"> <el-form-item label="备注">
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="可选:补充说明、特殊要求等" show-word-limit maxlength="200" /> <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="可选:补充说明、特殊要求等" show-word-limit
maxlength="200" />
</el-form-item> </el-form-item>
<!-- 提交流程提示真实节点配置 / 手动一次性审批预览 --> <!-- 提交流程提示真实节点配置 / 手动一次性审批预览 -->
@@ -189,12 +165,18 @@
<!-- 模板模式 --> <!-- 模板模式 -->
<div v-if="approverMode === 'template'"> <div v-if="approverMode === 'template'">
<div v-if="flowNodes && flowNodes.length" class="flow-steps"> <div v-if="flowNodes && flowNodes.length" class="flow-steps">
<div class="flow-step"><div class="dot"></div><div class="txt">填写申请</div></div> <div class="flow-step">
<div class="dot"></div>
<div class="txt">填写申请</div>
</div>
<div class="line"></div> <div class="line"></div>
<div class="flow-step"><div class="dot"></div><div class="txt">提交</div></div> <div class="flow-step">
<div class="dot"></div>
<div class="txt">提交</div>
</div>
<template v-for="(n, idx) in flowNodes"> <template v-for="(n, idx) in flowNodes">
<div :key="`line-${n.nodeId || idx}`" class="line"></div> <div class="line"></div>
<div :key="`node-${n.nodeId || idx}`" class="flow-step"> <div class="flow-step">
<div class="dot" :class="{ success: idx === flowNodes.length - 1 }"></div> <div class="dot" :class="{ success: idx === flowNodes.length - 1 }"></div>
<div class="txt">{{ nodePreviewText(n, idx) }}</div> <div class="txt">{{ nodePreviewText(n, idx) }}</div>
</div> </div>
@@ -207,11 +189,20 @@
<!-- 手动审批模式 --> <!-- 手动审批模式 -->
<div v-else class="flow-steps"> <div v-else class="flow-steps">
<div class="flow-step"><div class="dot"></div><div class="txt">填写申请</div></div> <div class="flow-step">
<div class="dot"></div>
<div class="txt">填写申请</div>
</div>
<div class="line"></div> <div class="line"></div>
<div class="flow-step"><div class="dot"></div><div class="txt">提交审批{{ assigneeUserName || '请选择' }}</div></div> <div class="flow-step">
<div class="dot"></div>
<div class="txt">提交审批{{ assigneeUserName || '请选择' }}</div>
</div>
<div class="line"></div> <div class="line"></div>
<div class="flow-step"><div class="dot success"></div><div class="txt">审批结束</div></div> <div class="flow-step">
<div class="dot success"></div>
<div class="txt">审批结束</div>
</div>
</div> </div>
</div> </div>
@@ -248,7 +239,7 @@ export default {
flowLoading: false, flowLoading: false,
flowTpl: null, flowTpl: null,
flowNodes: [], flowNodes: [],
approverMode: 'template', approverMode: 'manual',
availableTpls: [], availableTpls: [],
tplId: null, tplId: null,
assigneeUserId: null, assigneeUserId: null,
@@ -440,6 +431,7 @@ export default {
padding: 16px 20px 32px; padding: 16px 20px 32px;
background: #f8f9fb; background: #f8f9fb;
} }
.form-card { .form-card {
max-width: 980px; max-width: 980px;
margin: 0 auto; margin: 0 auto;
@@ -447,6 +439,7 @@ export default {
border-radius: 12px; border-radius: 12px;
background: #ffffff; background: #ffffff;
} }
.card-header { .card-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@@ -454,11 +447,16 @@ export default {
font-weight: 700; font-weight: 700;
color: #2b2f36; color: #2b2f36;
} }
.actions { .actions {
display: flex; display: flex;
gap: 8px; gap: 8px;
} }
.metal-form { padding-right: 8px; }
.metal-form {
padding-right: 8px;
}
.block-title { .block-title {
margin: 20px 0 12px; margin: 20px 0 12px;
padding-left: 10px; padding-left: 10px;
@@ -466,12 +464,14 @@ export default {
color: #2f3440; color: #2f3440;
border-left: 3px solid #9aa3b2; border-left: 3px solid #9aa3b2;
} }
.hint-text { .hint-text {
margin-top: 6px; margin-top: 6px;
font-size: 12px; font-size: 12px;
color: #8a8f99; color: #8a8f99;
line-height: 1.4; line-height: 1.4;
} }
.form-summary { .form-summary {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@@ -483,20 +483,57 @@ export default {
border-radius: 10px; border-radius: 10px;
background: linear-gradient(180deg, #ffffff 0%, #fbfcfe 100%); background: linear-gradient(180deg, #ffffff 0%, #fbfcfe 100%);
} }
.summary-title { font-size: 16px; font-weight: 800; color: #2b2f36; }
.summary-sub { margin-top: 4px; font-size: 12px; color: #8a8f99; } .summary-title {
.summary-right { display: flex; gap: 16px; } font-size: 16px;
.summary-item .k { font-size: 12px; color: #8a8f99; } font-weight: 800;
.summary-item .v { margin-top: 2px; font-weight: 700; color: #2b2f36; } color: #2b2f36;
}
.summary-sub {
margin-top: 4px;
font-size: 12px;
color: #8a8f99;
}
.summary-right {
display: flex;
gap: 16px;
}
.summary-item .k {
font-size: 12px;
color: #8a8f99;
}
.summary-item .v {
margin-top: 2px;
font-weight: 700;
color: #2b2f36;
}
.approve-mode { .approve-mode {
padding: 12px; padding: 12px;
border: 1px solid #e6e8ed; border: 1px solid #e6e8ed;
border-radius: 10px; border-radius: 10px;
background: #fcfdff; background: #fcfdff;
} }
.approve-panel { margin-top: 12px; }
.approve-row { display: flex; align-items: center; gap: 12px; } .approve-panel {
.approve-row .k { font-size: 14px; color: #606266; } margin-top: 12px;
}
.approve-row {
display: flex;
align-items: center;
gap: 12px;
}
.approve-row .k {
font-size: 14px;
color: #606266;
}
.flow-preview { .flow-preview {
margin-top: 20px; margin-top: 20px;
padding: 12px; padding: 12px;
@@ -504,8 +541,18 @@ export default {
border-radius: 10px; border-radius: 10px;
background: #fcfdff; background: #fcfdff;
} }
.flow-title { font-weight: 800; color: #2b2f36; }
.flow-sub { margin-top: 4px; font-size: 12px; color: #8a8f99; } .flow-title {
font-weight: 800;
color: #2b2f36;
}
.flow-sub {
margin-top: 4px;
font-size: 12px;
color: #8a8f99;
}
.flow-steps { .flow-steps {
margin-top: 10px; margin-top: 10px;
display: flex; display: flex;
@@ -513,6 +560,7 @@ export default {
gap: 10px; gap: 10px;
flex-wrap: wrap; flex-wrap: wrap;
} }
.flow-step { .flow-step {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -522,17 +570,40 @@ export default {
border: 1px solid #e6e8ed; border: 1px solid #e6e8ed;
background: #fff; background: #fff;
} }
.flow-step .dot { width: 8px; height: 8px; border-radius: 50%; background: #9aa3b2; }
.flow-step .dot.success { background: #67c23a; } .flow-step .dot {
.flow-step .txt { font-size: 12px; color: #2b2f36; font-weight: 600; } width: 8px;
.flow-steps .line { width: 26px; height: 1px; background: #e6e8ed; } height: 8px;
border-radius: 50%;
background: #9aa3b2;
}
.flow-step .dot.success {
background: #67c23a;
}
.flow-step .txt {
font-size: 12px;
color: #2b2f36;
font-weight: 600;
}
.flow-steps .line {
width: 26px;
height: 1px;
background: #e6e8ed;
}
.form-actions { .form-actions {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
gap: 12px; gap: 12px;
margin-top: 24px; margin-top: 24px;
} }
@media (max-width: 1200px) { @media (max-width: 1200px) {
.summary-right { display: none; } .summary-right {
display: none;
}
} }
</style> </style>