<script lang="ts" setup>
|
/**
|
* 培训任务表单(对齐说明书 6.2.18)
|
* - 空白新增 = 临时培训:内容字段可填
|
* - 计划带出任务(年计划/岗计划等):编号/来源只读,其余内容与执行信息均可维护后发布
|
*/
|
import type { TaskEntity, TaskFile } from './types';
|
|
import { computed, h, onMounted, reactive, ref, watch } from 'vue';
|
import { useRoute, useRouter } from 'vue-router';
|
|
import { useMessage } from '@jnpf/hooks';
|
import { BasicForm, useForm } from '@jnpf/ui/form';
|
|
import { Alert as AAlert, Steps as ASteps } from 'ant-design-vue';
|
import dayjs from 'dayjs';
|
|
import { getCoursePaperOptions } from '#/api/x/tms/course';
|
import { getPlaceOptions } from '#/api/x/tms/place';
|
import { createTask, getTaskInfo, publishTask, updateTask } from '#/api/x/tms/task';
|
import { JnpfPopupSelect } from '#/components/Jnpf/popupSelect';
|
import { TMS_DIC_FIELD_NAMES } from '#/views/x/tms/shared/dic';
|
import { TMS_BTN } from '#/views/x/tms/shared/ui';
|
|
import { EVAL_MODE_OPTIONS, loadTaskDics, SOURCE_TYPE_OPTIONS, TASK_CATEGORY_OPTIONS, TASK_KIND_OPTIONS, TRAIN_MODE_OPTIONS } from './constants';
|
|
defineOptions({ name: 'TmsTaskForm' });
|
|
const DT_FMT = 'YYYY-MM-DD HH:mm:ss';
|
|
/** 日期组件校验类型是 number,字符串会被当成空值 */
|
function toTimeNumber(value?: null | number | string) {
|
if (value == null || value === '') return undefined;
|
if (typeof value === 'number' && !Number.isNaN(value)) return value;
|
const text = String(value).trim();
|
if (/^\d{10,13}$/.test(text)) {
|
const n = Number(text);
|
return text.length === 10 ? n * 1000 : n;
|
}
|
const date = dayjs(text);
|
return date.isValid() ? date.valueOf() : undefined;
|
}
|
|
function formatTaskTime(value?: null | number | string) {
|
if (value == null || value === '') return undefined;
|
const date = dayjs(value);
|
return date.isValid() ? date.format(DT_FMT) : undefined;
|
}
|
/** 可由空白新增的来源(说明书:临时;文件等由系统带出) */
|
const BLANK_CREATE_SOURCES = new Set(['temp']);
|
|
const route = useRoute();
|
const router = useRouter();
|
const { createMessage } = useMessage();
|
|
const loading = ref(false);
|
const submitting = ref(false);
|
const paperOptions = ref<{ fullName: string; id: string }[]>([]);
|
const placeOptions = ref<{ fullName: string; id: string; placeNo?: string }[]>([]);
|
/** 非草稿等场景锁内容;计划带出默认可改 */
|
const contentLocked = ref(false);
|
const taskFiles = ref<TaskFile[]>([]);
|
const uploadFiles = ref<any[]>([]);
|
|
/** 来自计划/系统带出:主题等内容只读 */
|
const fromPlan = ref(false);
|
/** 1=培训内容 2=执行安排 */
|
const currentStep = ref(1);
|
const state = reactive({ id: '', publishStatus: 'draft' as string });
|
|
const isEdit = computed(() => !!route.params.id && route.params.id !== 'create');
|
const pageTitle = computed(() => {
|
if (!isEdit.value) return '新增临时培训任务';
|
return fromPlan.value ? '维护培训任务(计划带出)' : '编辑临时培训任务';
|
});
|
const tipText = computed(() => {
|
if (currentStep.value === 1) {
|
if (fromPlan.value) {
|
return '计划已带出主题、方式、对象等;可继续补全或修改后进入下一步。编号与来源不可改。';
|
}
|
return '先填写培训内容,下一步再安排时间、地点、试卷等执行信息。';
|
}
|
if (fromPlan.value) {
|
return '请补全执行安排后保存或发布。可返回上一步修改培训内容。';
|
}
|
return '请填写执行安排;可返回上一步修改培训内容。';
|
});
|
const stepItems = computed(() => [{ title: fromPlan.value ? '计划内容(可维护)' : '培训内容' }, { title: fromPlan.value ? '执行安排(发布)' : '执行安排' }]);
|
|
const [registerContentForm, contentFormApi] = useForm({
|
labelWidth: 120,
|
baseColProps: { span: 12 },
|
schemas: [
|
{
|
field: 'taskNo',
|
label: '培训编号',
|
component: 'Input',
|
componentProps: { disabled: true, placeholder: '草稿号保存生成,发布后正式编号' },
|
},
|
{
|
field: 'publishStatusLabel',
|
label: '发布状态',
|
component: 'Input',
|
componentProps: { disabled: true },
|
ifShow: () => isEdit.value,
|
},
|
{
|
field: 'sourceType',
|
label: '来源类型',
|
component: 'Select',
|
defaultValue: 'temp',
|
componentProps: {
|
disabled: true,
|
options: [],
|
fieldNames: TMS_DIC_FIELD_NAMES,
|
},
|
},
|
{
|
field: 'category',
|
label: '培训分类',
|
component: 'Select',
|
defaultValue: 'temp',
|
componentProps: {
|
disabled: true,
|
options: [],
|
fieldNames: TMS_DIC_FIELD_NAMES,
|
},
|
},
|
{
|
field: 'sourcePlanNo',
|
label: '来源计划号',
|
component: 'Input',
|
componentProps: { disabled: true, placeholder: '计划带出' },
|
ifShow: () => fromPlan.value,
|
},
|
{
|
field: 'subject',
|
label: '培训主题',
|
component: 'Input',
|
componentProps: { placeholder: '请输入培训主题', maxlength: 200 },
|
rules: [{ required: true, message: '必填', trigger: 'blur' }],
|
},
|
{
|
field: 'trainMode',
|
label: '培训方式',
|
component: 'Select',
|
componentProps: {
|
allowClear: true,
|
placeholder: '请选择',
|
options: [],
|
fieldNames: TMS_DIC_FIELD_NAMES,
|
onChange: (val: string) => syncModeRules(val),
|
},
|
rules: [{ required: true, message: '必填', trigger: 'change' }],
|
},
|
{
|
field: 'evalMode',
|
label: '考核方式',
|
component: 'Select',
|
componentProps: {
|
allowClear: true,
|
placeholder: '请选择',
|
options: [],
|
fieldNames: TMS_DIC_FIELD_NAMES,
|
onChange: (val: string) => syncEvalRules(val),
|
},
|
rules: [{ required: true, message: '必填', trigger: 'change' }],
|
},
|
{
|
field: 'teacherId',
|
label: '培训师',
|
component: 'UserSelect',
|
componentProps: { placeholder: '请选择培训师' },
|
},
|
{
|
field: 'traineeIds',
|
label: '培训对象',
|
component: 'UserSelect',
|
componentProps: { placeholder: '请选择培训对象', multiple: true },
|
rules: [{ required: true, type: 'array', min: 1, message: '必填', trigger: 'change' }],
|
},
|
{
|
field: 'materialId',
|
label: '培训教材',
|
component: 'Input',
|
render: ({ model }) =>
|
h(JnpfPopupSelect, {
|
columnOptions: [
|
{ label: '教材编号', value: 'materialno' },
|
{ label: '教材名称', value: 'fullname' },
|
{
|
label: '文件类型',
|
value: 'filetype',
|
jnpfKey: 'select',
|
props: { label: 'fullName', value: 'enCode' },
|
__config__: {
|
jnpfKey: 'select',
|
dataType: 'dictionary',
|
dictionaryType: 'tmsdt008',
|
},
|
},
|
],
|
disabled: contentLocked.value,
|
hasPage: true,
|
pageSize: 20,
|
interfaceId: '871690100000000001',
|
placeholder: '请选择培训教材',
|
popupTitle: '选择数据',
|
popupType: 'popover',
|
popupWidth: '800px',
|
propsValue: 'id',
|
relationField: 'fullname',
|
value: model.materialId || undefined,
|
'onUpdate:value': (id?: string) => {
|
contentFormApi.setFieldsValue({ materialId: id || undefined });
|
contentFormApi.clearValidate(['materialId']);
|
if (!id) return;
|
},
|
onChange: (_id: string, row: any) => {
|
const data = row || {};
|
contentFormApi.setFieldsValue({
|
materialNo: data.materialno || undefined,
|
});
|
ensureMainFile(data);
|
},
|
}),
|
},
|
{
|
field: 'taskKind',
|
label: '任务类型',
|
component: 'Select',
|
defaultValue: 'new',
|
componentProps: {
|
options: [],
|
fieldNames: TMS_DIC_FIELD_NAMES,
|
},
|
},
|
{
|
field: 'prevTaskNo',
|
label: '上次任务编号',
|
component: 'Input',
|
componentProps: { placeholder: '再维护时由系统带出', disabled: true },
|
ifShow: ({ values }) => values.taskKind === 'continue',
|
},
|
{
|
field: 'keyPoints',
|
label: '培训要点',
|
component: 'Textarea',
|
colProps: { span: 24 },
|
componentProps: { rows: 2, placeholder: '选填', maxlength: 2000 },
|
},
|
],
|
});
|
|
const [registerExecForm, execFormApi] = useForm({
|
labelWidth: 120,
|
baseColProps: { span: 12 },
|
schemas: [
|
{
|
field: 'startTime',
|
label: '开始时间',
|
component: 'DatePicker',
|
componentProps: {
|
showTime: true,
|
format: DT_FMT,
|
valueFormat: DT_FMT,
|
style: { width: '100%' },
|
},
|
rules: [{ required: true, message: '必填', trigger: 'change' }],
|
},
|
{
|
field: 'endTime',
|
label: '结束时间',
|
component: 'DatePicker',
|
componentProps: {
|
showTime: true,
|
format: DT_FMT,
|
valueFormat: DT_FMT,
|
style: { width: '100%' },
|
},
|
rules: [{ required: true, message: '必填', trigger: 'change' }],
|
},
|
{
|
field: 'closeTime',
|
label: '关闭时间',
|
component: 'DatePicker',
|
componentProps: {
|
showTime: true,
|
format: DT_FMT,
|
valueFormat: DT_FMT,
|
style: { width: '100%' },
|
},
|
rules: [{ required: true, message: '必填', trigger: 'change' }],
|
},
|
{
|
field: 'placeId',
|
label: '培训地点',
|
component: 'Select',
|
componentProps: {
|
allowClear: true,
|
showSearch: true,
|
placeholder: '集中/操作授课必选',
|
options: [],
|
optionFilterProp: 'fullName',
|
},
|
},
|
{
|
field: 'paperId',
|
label: '考核试卷',
|
component: 'Select',
|
componentProps: {
|
allowClear: true,
|
showSearch: true,
|
placeholder: '在线考试时必选',
|
options: [],
|
optionFilterProp: 'fullName',
|
},
|
},
|
{
|
field: 'evalMode',
|
label: '考核方式',
|
component: 'Input',
|
ifShow: () => false,
|
},
|
{
|
field: 'examStart',
|
label: '考试开始',
|
component: 'DatePicker',
|
componentProps: {
|
showTime: true,
|
format: DT_FMT,
|
valueFormat: DT_FMT,
|
style: { width: '100%' },
|
},
|
ifShow: ({ values }) => values.evalMode === 'exam',
|
},
|
{
|
field: 'examEnd',
|
label: '考试结束',
|
component: 'DatePicker',
|
componentProps: {
|
showTime: true,
|
format: DT_FMT,
|
valueFormat: DT_FMT,
|
style: { width: '100%' },
|
},
|
ifShow: ({ values }) => values.evalMode === 'exam',
|
},
|
{
|
field: 'retakeLimit',
|
label: '补考次数',
|
component: 'InputNumber',
|
defaultValue: 1,
|
componentProps: { min: 0, max: 99, precision: 0, style: { width: '100%' } },
|
helpMessage: '不合格可补考次数,默认 1',
|
},
|
{
|
field: 'passScore',
|
label: '合格分',
|
component: 'InputNumber',
|
componentProps: { min: 0, max: 999, precision: 1, style: { width: '100%' } },
|
},
|
{
|
field: 'overdueRemind',
|
label: '逾期提醒',
|
component: 'Select',
|
defaultValue: '0',
|
componentProps: {
|
options: [
|
{ id: '1', enCode: '1', fullName: '是' },
|
{ id: '0', enCode: '0', fullName: '否' },
|
],
|
fieldNames: TMS_DIC_FIELD_NAMES,
|
},
|
},
|
{
|
field: 'inviteFlag',
|
label: '可邀请旁听',
|
component: 'Select',
|
defaultValue: '0',
|
componentProps: {
|
options: [
|
{ id: '1', enCode: '1', fullName: '是' },
|
{ id: '0', enCode: '0', fullName: '否' },
|
],
|
fieldNames: TMS_DIC_FIELD_NAMES,
|
},
|
},
|
{
|
field: 'remark',
|
label: '备注',
|
component: 'Textarea',
|
colProps: { span: 24 },
|
componentProps: { rows: 2, placeholder: '选填', maxlength: 500 },
|
},
|
],
|
});
|
|
onMounted(async () => {
|
loading.value = true;
|
try {
|
await loadTaskDics();
|
await Promise.all([loadPapers(), loadPlaces()]);
|
refreshOptions();
|
if (isEdit.value) {
|
await loadDetail(String(route.params.id));
|
} else {
|
fromPlan.value = false;
|
contentFormApi.resetFields();
|
execFormApi.resetFields();
|
contentFormApi.setFieldsValue({
|
sourceType: 'temp',
|
category: 'temp',
|
taskKind: 'new',
|
});
|
execFormApi.setFieldsValue({
|
retakeLimit: 1,
|
overdueRemind: '0',
|
inviteFlag: '0',
|
startTime: dayjs().valueOf(),
|
endTime: dayjs().add(2, 'hour').valueOf(),
|
closeTime: dayjs().add(1, 'day').valueOf(),
|
});
|
applyContentEditable(true);
|
currentStep.value = 1;
|
}
|
} finally {
|
loading.value = false;
|
}
|
});
|
|
watch(
|
() => route.params.id,
|
async (id) => {
|
if (!id || id === 'create') return;
|
loading.value = true;
|
try {
|
await loadDetail(String(id));
|
} finally {
|
loading.value = false;
|
}
|
},
|
);
|
|
function isPlanSourced(info: TaskEntity) {
|
if (info.sourceId || info.sourceItemId || info.sourcePlanNo) return true;
|
const st = info.sourceType || '';
|
return !!st && !BLANK_CREATE_SOURCES.has(st);
|
}
|
|
function applyContentEditable(editable: boolean) {
|
contentLocked.value = !editable;
|
const disabled = !editable;
|
contentFormApi.updateSchema([
|
{ field: 'subject', componentProps: { disabled, placeholder: disabled ? '' : '请输入培训主题', maxlength: 200 } },
|
{
|
field: 'trainMode',
|
componentProps: {
|
disabled,
|
allowClear: !disabled,
|
options: TRAIN_MODE_OPTIONS.value,
|
fieldNames: TMS_DIC_FIELD_NAMES,
|
onChange: (val: string) => syncModeRules(val),
|
},
|
},
|
{
|
field: 'evalMode',
|
componentProps: {
|
disabled,
|
allowClear: !disabled,
|
options: EVAL_MODE_OPTIONS.value,
|
fieldNames: TMS_DIC_FIELD_NAMES,
|
onChange: (val: string) => {
|
syncEvalRules(val);
|
},
|
},
|
},
|
{ field: 'teacherId', componentProps: { disabled, placeholder: '请选择培训师' } },
|
{ field: 'traineeIds', componentProps: { disabled, placeholder: '请选择培训对象', multiple: true } },
|
{
|
field: 'taskKind',
|
componentProps: {
|
disabled,
|
options: TASK_KIND_OPTIONS.value,
|
fieldNames: TMS_DIC_FIELD_NAMES,
|
},
|
},
|
{ field: 'keyPoints', componentProps: { disabled, rows: 2, placeholder: disabled ? '' : '选填', maxlength: 2000 } },
|
]);
|
}
|
|
function refreshOptions() {
|
contentFormApi.updateSchema([
|
{
|
field: 'sourceType',
|
componentProps: {
|
disabled: true,
|
options: SOURCE_TYPE_OPTIONS.value,
|
fieldNames: TMS_DIC_FIELD_NAMES,
|
},
|
},
|
{
|
field: 'category',
|
componentProps: {
|
disabled: true,
|
options: TASK_CATEGORY_OPTIONS.value,
|
fieldNames: TMS_DIC_FIELD_NAMES,
|
},
|
},
|
{
|
field: 'trainMode',
|
componentProps: {
|
allowClear: true,
|
options: TRAIN_MODE_OPTIONS.value,
|
fieldNames: TMS_DIC_FIELD_NAMES,
|
onChange: (val: string) => syncModeRules(val),
|
},
|
},
|
{
|
field: 'evalMode',
|
componentProps: {
|
allowClear: true,
|
options: EVAL_MODE_OPTIONS.value,
|
fieldNames: TMS_DIC_FIELD_NAMES,
|
onChange: (val: string) => {
|
syncEvalRules(val);
|
},
|
},
|
},
|
{
|
field: 'taskKind',
|
componentProps: {
|
options: TASK_KIND_OPTIONS.value,
|
fieldNames: TMS_DIC_FIELD_NAMES,
|
},
|
},
|
]);
|
execFormApi.updateSchema([
|
{
|
field: 'placeId',
|
componentProps: {
|
allowClear: true,
|
showSearch: true,
|
options: placeOptions.value,
|
optionFilterProp: 'fullName',
|
},
|
},
|
{
|
field: 'paperId',
|
componentProps: {
|
allowClear: true,
|
showSearch: true,
|
options: paperOptions.value,
|
optionFilterProp: 'fullName',
|
},
|
},
|
]);
|
}
|
|
function syncModeRules(mode?: string) {
|
const needPlace = mode === 'onsite' || mode === 'practice';
|
const needMaterial = mode === 'online';
|
execFormApi.updateSchema([
|
{
|
field: 'placeId',
|
rules: needPlace ? [{ required: true, message: '现场类须选地点', trigger: 'change' }] : [],
|
},
|
]);
|
contentFormApi.updateSchema([
|
{
|
field: 'materialId',
|
rules: needMaterial ? [{ required: true, message: '在线学习须选教材', trigger: 'change' }] : [],
|
},
|
]);
|
execFormApi.clearValidate(['placeId']);
|
contentFormApi.clearValidate(['materialId']);
|
}
|
|
function ensureMainFile(row: any) {
|
const materialId = row?.id;
|
const fileName = row?.fullname;
|
if (!materialId || !fileName) return;
|
if (taskFiles.value.some((item) => item.materialId === materialId)) return;
|
taskFiles.value = [
|
{
|
materialId,
|
fileNo: row.materialno,
|
fileName,
|
canDownload: '0',
|
},
|
...taskFiles.value,
|
];
|
}
|
|
function removeTaskFile(index: number) {
|
const removed = taskFiles.value[index];
|
taskFiles.value = taskFiles.value.filter((_, i) => i !== index);
|
if (!removed?.fileJson) return;
|
try {
|
const file = JSON.parse(removed.fileJson);
|
uploadFiles.value = uploadFiles.value.filter((item) => item.fileId !== file.fileId);
|
} catch {
|
// 非上传附件
|
}
|
}
|
|
function onUploadChange(list: any[]) {
|
const files = Array.isArray(list) ? list : [];
|
uploadFiles.value = files;
|
for (const file of files) {
|
if (!file?.name) continue;
|
const existed = taskFiles.value.some((row) => {
|
if (!row.fileJson) return false;
|
try {
|
return JSON.parse(row.fileJson).fileId === file.fileId;
|
} catch {
|
return false;
|
}
|
});
|
if (existed) continue;
|
taskFiles.value = [
|
...taskFiles.value,
|
{
|
fileNo: file.fileId,
|
fileName: file.name,
|
fileJson: JSON.stringify(file),
|
canDownload: '1',
|
},
|
];
|
}
|
}
|
|
function syncEvalRules(evalMode?: string) {
|
execFormApi.updateSchema([
|
{
|
field: 'paperId',
|
rules: evalMode === 'exam' ? [{ required: true, message: '在线考试须选试卷', trigger: 'change' }] : [],
|
},
|
]);
|
execFormApi.setFieldsValue({ evalMode });
|
execFormApi.clearValidate(['paperId']);
|
}
|
|
async function loadPapers() {
|
try {
|
const list = await getCoursePaperOptions();
|
paperOptions.value = (Array.isArray(list) ? list : []).map((x: any) => ({
|
id: x.id,
|
fullName: x.fullName || x.paperName || x.id,
|
}));
|
} catch {
|
paperOptions.value = [];
|
}
|
}
|
|
async function loadPlaces() {
|
try {
|
const list = await getPlaceOptions();
|
placeOptions.value = (Array.isArray(list) ? list : []).map((x: any) => ({
|
id: x.id,
|
fullName: x.fullName || x.placeName || x.id,
|
placeNo: x.placeNo,
|
}));
|
} catch {
|
placeOptions.value = [];
|
}
|
}
|
|
async function loadDetail(id: string) {
|
const info = await getTaskInfo(id);
|
if (info.publishStatus && info.publishStatus !== 'draft') {
|
createMessage.warning('仅未发布任务可编辑,已跳转详情');
|
router.replace(`/tms/task/detail/${id}`);
|
return;
|
}
|
state.id = info.id || id;
|
state.publishStatus = info.publishStatus || 'draft';
|
fromPlan.value = isPlanSourced(info);
|
// 计划带出也从内容步开始,便于补全培训师/教材等
|
currentStep.value = 1;
|
|
const traineeIds = Array.isArray(info.traineeIds)
|
? info.traineeIds
|
: String(info.trainees || '')
|
.split(/[,;,;\s]+/)
|
.filter(Boolean);
|
|
const statusMap: Record<string, string> = {
|
draft: '未发布',
|
published: '已发布',
|
cancelled: '已取消',
|
done: '已完成',
|
};
|
|
contentFormApi.setFieldsValue({
|
...info,
|
traineeIds,
|
materialId: String(info.materialId || '').split(',')[0] || undefined,
|
publishStatusLabel: statusMap[info.publishStatus || 'draft'] || info.publishStatus,
|
});
|
taskFiles.value = Array.isArray(info.files) ? info.files : [];
|
uploadFiles.value = taskFiles.value
|
.map((row) => {
|
if (!row.fileJson) return null;
|
try {
|
return JSON.parse(row.fileJson);
|
} catch {
|
return null;
|
}
|
})
|
.filter(Boolean);
|
execFormApi.setFieldsValue({
|
...info,
|
evalMode: info.evalMode,
|
startTime: toTimeNumber(info.startTime),
|
endTime: toTimeNumber(info.endTime),
|
closeTime: toTimeNumber(info.closeTime),
|
examStart: toTimeNumber(info.examStart),
|
examEnd: toTimeNumber(info.examEnd),
|
});
|
refreshOptions();
|
// 计划带出:仅编号/来源类只读,业务内容可维护
|
applyContentEditable(true);
|
syncModeRules(info.trainMode);
|
syncEvalRules(info.evalMode);
|
}
|
|
async function goNext() {
|
try {
|
await contentFormApi.validate();
|
const content = contentFormApi.getFieldsValue() as TaskEntity;
|
syncModeRules(content.trainMode);
|
syncEvalRules(content.evalMode);
|
currentStep.value = 2;
|
} catch {
|
// 校验失败由表单提示
|
}
|
}
|
|
function goPrev() {
|
currentStep.value = 1;
|
}
|
|
async function handleSubmit(andPublish = false) {
|
const [content, exec] = await Promise.all([contentFormApi.validate(), execFormApi.validate()]);
|
submitting.value = true;
|
try {
|
let traineeIds = (content as TaskEntity).traineeIds as string | string[] | undefined;
|
if (typeof traineeIds === 'string') {
|
traineeIds = traineeIds.split(/[,;,;\s]+/).filter(Boolean);
|
}
|
const teacherRaw = (content as TaskEntity).teacherId as any;
|
const materialRaw = (content as TaskEntity).materialId;
|
const materialId =
|
String(materialRaw || '')
|
.split(/[,;,;]+/)[0]
|
?.trim() || undefined;
|
const contentData = content as TaskEntity;
|
const execData = exec as TaskEntity;
|
// 执行表单加载时写入了整条任务,后写入会盖掉第一步改过的培训方式/考核方式
|
const payload: Partial<TaskEntity> = {
|
...execData,
|
...contentData,
|
sourceType: fromPlan.value ? contentData.sourceType : 'temp',
|
category: fromPlan.value ? contentData.category : 'temp',
|
traineeIds: Array.isArray(traineeIds) ? traineeIds : [],
|
teacherId: Array.isArray(teacherRaw) ? teacherRaw[0] : teacherRaw,
|
materialId,
|
trainMode: contentData.trainMode,
|
evalMode: contentData.evalMode,
|
files: taskFiles.value.filter((row) => row.fileName),
|
startTime: formatTaskTime(execData.startTime),
|
endTime: formatTaskTime(execData.endTime),
|
closeTime: formatTaskTime(execData.closeTime),
|
examStart: formatTaskTime(execData.examStart),
|
examEnd: formatTaskTime(execData.examEnd),
|
placeId: execData.placeId,
|
placeName: execData.placeName,
|
paperId: execData.paperId,
|
retakeLimit: execData.retakeLimit,
|
passScore: execData.passScore,
|
overdueRemind: execData.overdueRemind,
|
inviteFlag: execData.inviteFlag,
|
remark: execData.remark,
|
};
|
delete (payload as any).publishStatusLabel;
|
|
let id = state.id;
|
if (isEdit.value && id) {
|
await updateTask({ ...payload, id });
|
createMessage.success('更新成功');
|
} else {
|
id = await createTask(payload);
|
state.id = id;
|
createMessage.success('创建成功');
|
}
|
if (andPublish && id) {
|
await publishTask(id);
|
createMessage.success('发布成功');
|
}
|
router.push('/tms/task');
|
} catch (error: any) {
|
if (error?.errorFields) return;
|
createMessage.error(error?.message || '保存失败');
|
} finally {
|
submitting.value = false;
|
}
|
}
|
|
function handleCancel() {
|
router.push('/tms/task');
|
}
|
</script>
|
|
<template>
|
<div class="jnpf-content-wrapper tms-task-form-page" v-loading="loading">
|
<div class="jnpf-content-wrapper-center">
|
<div class="jnpf-content-wrapper-content tms-task-form-wrap">
|
<div class="tms-task-form-scroll">
|
<div class="tms-task-page-title">{{ pageTitle }}</div>
|
<ASteps class="tms-task-steps" size="small" :current="currentStep - 1" :items="stepItems" />
|
<AAlert class="tms-task-tip" type="info" show-icon :message="tipText" />
|
|
<a-card v-show="currentStep === 1" :title="fromPlan ? '计划带出(可维护)' : '培训内容'" :bordered="false" class="tms-task-section">
|
<BasicForm @register="registerContentForm" />
|
<div class="tms-task-files">
|
<div class="tms-task-files-head">
|
<span>教材/附件</span>
|
<JnpfUploadFile
|
v-if="!contentLocked"
|
v-model:value="uploadFiles"
|
accept="*"
|
button-text="上传附件"
|
:file-size="0"
|
:limit="0"
|
:show-all-download="false"
|
:show-upload-list="false"
|
tip-text="支持文档和视频"
|
show-tip
|
@change="onUploadChange" />
|
</div>
|
<a-table
|
size="small"
|
:pagination="false"
|
row-key="materialId"
|
:data-source="taskFiles"
|
:columns="[
|
{ title: '编号', dataIndex: 'fileNo', width: 160 },
|
{ title: '文件名称', dataIndex: 'fileName' },
|
{ title: '可下载', dataIndex: 'canDownload', width: 90 },
|
{ title: '操作', dataIndex: 'action', width: 80 },
|
]">
|
<template #bodyCell="{ column, record, index }">
|
<template v-if="column.dataIndex === 'fileName'">
|
<a-input v-model:value="record.fileName" :disabled="contentLocked" maxlength="300" />
|
</template>
|
<template v-else-if="column.dataIndex === 'canDownload'">
|
<a-switch
|
:checked="record.canDownload === '1'"
|
:disabled="contentLocked"
|
checked-children="是"
|
un-checked-children="否"
|
@change="(checked) => (record.canDownload = checked ? '1' : '0')" />
|
</template>
|
<template v-else-if="column.dataIndex === 'action'">
|
<a-button type="link" danger :disabled="contentLocked" @click="removeTaskFile(index)">删除</a-button>
|
</template>
|
</template>
|
</a-table>
|
</div>
|
</a-card>
|
|
<a-card v-show="currentStep === 2" :title="fromPlan ? '执行安排(补全后发布)' : '执行安排'" :bordered="false" class="tms-task-section">
|
<BasicForm @register="registerExecForm" />
|
</a-card>
|
</div>
|
|
<div class="tms-task-form-footer">
|
<a-space>
|
<a-button @click="handleCancel">{{ TMS_BTN.cancel }}</a-button>
|
<a-button v-if="currentStep === 2" @click="goPrev">上一步</a-button>
|
<a-button v-if="currentStep === 1" type="primary" @click="goNext">下一步</a-button>
|
<template v-if="currentStep === 2">
|
<a-button type="primary" ghost :loading="submitting" @click="handleSubmit(false)">
|
{{ TMS_BTN.saveDraft }}
|
</a-button>
|
<a-button type="primary" :loading="submitting" @click="handleSubmit(true)">
|
{{ TMS_BTN.savePublish }}
|
</a-button>
|
</template>
|
</a-space>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<style scoped>
|
.tms-task-form-page {
|
height: 100%;
|
min-height: 0;
|
}
|
|
.tms-task-form-wrap {
|
display: flex;
|
flex-direction: column;
|
height: 100%;
|
min-height: 0;
|
overflow: hidden;
|
background: #fff;
|
}
|
|
.tms-task-form-scroll {
|
flex: 1;
|
min-height: 0;
|
overflow-x: hidden;
|
overflow-y: auto;
|
padding: 16px 16px 8px;
|
}
|
|
.tms-task-page-title {
|
margin-bottom: 12px;
|
font-size: 16px;
|
font-weight: 600;
|
}
|
|
.tms-task-steps {
|
max-width: 480px;
|
margin-bottom: 12px;
|
}
|
|
.tms-task-tip {
|
margin-bottom: 12px;
|
}
|
|
.tms-task-section {
|
margin-bottom: 12px;
|
}
|
|
.tms-task-files {
|
margin-top: 8px;
|
}
|
|
.tms-task-files-head {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
margin-bottom: 8px;
|
font-weight: 600;
|
}
|
|
.tms-task-form-footer {
|
flex-shrink: 0;
|
padding: 12px 16px;
|
border-top: 1px solid #f0f0f0;
|
text-align: right;
|
background: #fff;
|
}
|
</style>
|