fix(personal-item): 个人事项&任务添加type类型字段
This commit is contained in:
@@ -85,12 +85,20 @@ export const RDMS_PROJECT_EXECUTION_TYPE_DICT_CODE = 'rdms_project_execution_typ
|
|||||||
export const OBJECT_STATUS_MODEL_OBJECT_TYPE_DICT_CODE = 'object_status_model_object_type';
|
export const OBJECT_STATUS_MODEL_OBJECT_TYPE_DICT_CODE = 'object_status_model_object_type';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工作日志完成难度字典编码
|
* 工作日志难度字典编码
|
||||||
*
|
*
|
||||||
* 对应业务字段:任务/个人事项工作日志中的 difficulty
|
* 对应业务字段:任务/个人事项工作日志中的 difficulty
|
||||||
* 来源口径:后端工作日志表 `rdms_task_worklog.difficulty` 字段注释明确使用字典 `rdms_worklog_difficulty`
|
* 来源口径:用户明确指定任务/个人事项工作日志难度下拉来自运行时字典 rdms_task&item_worklog_difficulty
|
||||||
*/
|
*/
|
||||||
export const RDMS_WORKLOG_DIFFICULTY_DICT_CODE = 'rdms_worklog_difficulty';
|
export const RDMS_WORKLOG_DIFFICULTY_DICT_CODE = 'rdms_task&item_worklog_difficulty';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务/个人事项类型字典编码
|
||||||
|
*
|
||||||
|
* 对应业务字段:任务、个人事项中的 type
|
||||||
|
* 来源口径:用户明确指定任务/个人事项类型下拉来自运行时字典 rdms_task&item_type
|
||||||
|
*/
|
||||||
|
export const RDMS_TASK_ITEM_TYPE_DICT_CODE = 'rdms_task&item_type';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 需求允许删除的状态字典编码
|
* 需求允许删除的状态字典编码
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ type PersonalItemExecutionOptionResponse = ProjectExecutionResponse & {
|
|||||||
type PersonalItemSaveRequest = {
|
type PersonalItemSaveRequest = {
|
||||||
executionId?: string;
|
executionId?: string;
|
||||||
taskTitle: string;
|
taskTitle: string;
|
||||||
|
type: string;
|
||||||
progressRate?: number;
|
progressRate?: number;
|
||||||
plannedStartDate?: string;
|
plannedStartDate?: string;
|
||||||
plannedEndDate?: string;
|
plannedEndDate?: string;
|
||||||
@@ -86,7 +87,7 @@ type PersonalItemWorklogSaveRequest = {
|
|||||||
size?: number;
|
size?: number;
|
||||||
contentType?: string;
|
contentType?: string;
|
||||||
}>;
|
}>;
|
||||||
difficulty?: string;
|
difficulty: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PERSONAL_ITEM_PREFIX = `${WEB_SERVICE_PREFIX}/project/personal-items`;
|
const PERSONAL_ITEM_PREFIX = `${WEB_SERVICE_PREFIX}/project/personal-items`;
|
||||||
@@ -163,6 +164,7 @@ function normalizePersonalItem(response: PersonalItemResponse): Api.PersonalItem
|
|||||||
return {
|
return {
|
||||||
id: normalizeStringId(response.id),
|
id: normalizeStringId(response.id),
|
||||||
taskTitle: response.taskTitle ?? '',
|
taskTitle: response.taskTitle ?? '',
|
||||||
|
type: response.type ?? '',
|
||||||
ownerId: normalizeStringId(response.ownerId),
|
ownerId: normalizeStringId(response.ownerId),
|
||||||
statusCode: response.statusCode,
|
statusCode: response.statusCode,
|
||||||
terminal: normalizeBooleanFlag(response.terminal),
|
terminal: normalizeBooleanFlag(response.terminal),
|
||||||
@@ -214,6 +216,7 @@ function toPersonalItemSaveRequest(data: Api.PersonalItem.SavePersonalItemParams
|
|||||||
return {
|
return {
|
||||||
executionId: data.executionId ?? undefined,
|
executionId: data.executionId ?? undefined,
|
||||||
taskTitle: data.taskTitle.trim(),
|
taskTitle: data.taskTitle.trim(),
|
||||||
|
type: data.type,
|
||||||
progressRate: typeof data.progressRate === 'number' ? data.progressRate : undefined,
|
progressRate: typeof data.progressRate === 'number' ? data.progressRate : undefined,
|
||||||
plannedStartDate: data.plannedStartDate ?? undefined,
|
plannedStartDate: data.plannedStartDate ?? undefined,
|
||||||
plannedEndDate: data.plannedEndDate ?? undefined,
|
plannedEndDate: data.plannedEndDate ?? undefined,
|
||||||
@@ -246,7 +249,7 @@ function toPersonalItemWorklogSaveRequest(
|
|||||||
size: item.size,
|
size: item.size,
|
||||||
contentType: item.contentType
|
contentType: item.contentType
|
||||||
})) ?? undefined,
|
})) ?? undefined,
|
||||||
difficulty: data.difficulty ?? undefined
|
difficulty: data.difficulty
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,6 +344,7 @@ function createSeedItems(): PersonalItemRecord[] {
|
|||||||
{
|
{
|
||||||
id: 'personal-item-1',
|
id: 'personal-item-1',
|
||||||
taskTitle: '整理供应商沟通纪要',
|
taskTitle: '整理供应商沟通纪要',
|
||||||
|
type: 'daily',
|
||||||
ownerId: CURRENT_USER_ID,
|
ownerId: CURRENT_USER_ID,
|
||||||
statusCode: 'active',
|
statusCode: 'active',
|
||||||
progressRate: 45,
|
progressRate: 45,
|
||||||
@@ -362,6 +366,7 @@ function createSeedItems(): PersonalItemRecord[] {
|
|||||||
{
|
{
|
||||||
id: 'personal-item-2',
|
id: 'personal-item-2',
|
||||||
taskTitle: '清理浏览器收藏夹里的项目入口',
|
taskTitle: '清理浏览器收藏夹里的项目入口',
|
||||||
|
type: 'daily',
|
||||||
ownerId: CURRENT_USER_ID,
|
ownerId: CURRENT_USER_ID,
|
||||||
statusCode: 'pending',
|
statusCode: 'pending',
|
||||||
progressRate: 0,
|
progressRate: 0,
|
||||||
@@ -383,6 +388,7 @@ function createSeedItems(): PersonalItemRecord[] {
|
|||||||
{
|
{
|
||||||
id: 'personal-item-3',
|
id: 'personal-item-3',
|
||||||
taskTitle: '补充账号开通说明截图',
|
taskTitle: '补充账号开通说明截图',
|
||||||
|
type: 'support',
|
||||||
ownerId: CURRENT_USER_ID,
|
ownerId: CURRENT_USER_ID,
|
||||||
statusCode: 'completed',
|
statusCode: 'completed',
|
||||||
progressRate: 100,
|
progressRate: 100,
|
||||||
@@ -587,6 +593,7 @@ function syncItemFromWorklogs(itemId: string) {
|
|||||||
|
|
||||||
function applySaveFields(target: PersonalItemRecord, payload: Api.PersonalItem.SavePersonalItemParams) {
|
function applySaveFields(target: PersonalItemRecord, payload: Api.PersonalItem.SavePersonalItemParams) {
|
||||||
target.taskTitle = payload.taskTitle.trim();
|
target.taskTitle = payload.taskTitle.trim();
|
||||||
|
target.type = payload.type;
|
||||||
target.ownerId = payload.ownerId || target.ownerId;
|
target.ownerId = payload.ownerId || target.ownerId;
|
||||||
target.ownerName = CURRENT_USER_NAME;
|
target.ownerName = CURRENT_USER_NAME;
|
||||||
target.plannedStartDate = payload.plannedStartDate;
|
target.plannedStartDate = payload.plannedStartDate;
|
||||||
@@ -661,6 +668,7 @@ export async function fetchCreatePersonalItem(data: Api.PersonalItem.SavePersona
|
|||||||
const createdItem: PersonalItemRecord = {
|
const createdItem: PersonalItemRecord = {
|
||||||
id: mapped.data,
|
id: mapped.data,
|
||||||
taskTitle: data.taskTitle.trim(),
|
taskTitle: data.taskTitle.trim(),
|
||||||
|
type: data.type,
|
||||||
ownerId: data.ownerId || CURRENT_USER_ID,
|
ownerId: data.ownerId || CURRENT_USER_ID,
|
||||||
statusCode: 'pending',
|
statusCode: 'pending',
|
||||||
progressRate: typeof data.progressRate === 'number' ? data.progressRate : 0,
|
progressRate: typeof data.progressRate === 'number' ? data.progressRate : 0,
|
||||||
|
|||||||
@@ -133,10 +133,14 @@ export type ProjectTaskResponse = Omit<
|
|||||||
totalSpentHours?: number | null;
|
totalSpentHours?: number | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TaskWorklogResponse = Omit<Api.Project.TaskWorklog, 'id' | 'taskId' | 'userId' | 'attachments'> & {
|
export type TaskWorklogResponse = Omit<
|
||||||
|
Api.Project.TaskWorklog,
|
||||||
|
'id' | 'taskId' | 'userId' | 'difficulty' | 'attachments'
|
||||||
|
> & {
|
||||||
id: StringIdResponse;
|
id: StringIdResponse;
|
||||||
taskId: StringIdResponse;
|
taskId: StringIdResponse;
|
||||||
userId: StringIdResponse;
|
userId: StringIdResponse;
|
||||||
|
difficulty?: string | null;
|
||||||
attachments?: AttachmentItemResponse[] | null;
|
attachments?: AttachmentItemResponse[] | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -290,6 +294,7 @@ export function normalizeProjectTask(response: ProjectTaskResponse): Api.Project
|
|||||||
projectId: normalizeStringId(response.projectId),
|
projectId: normalizeStringId(response.projectId),
|
||||||
executionId: normalizeStringId(response.executionId),
|
executionId: normalizeStringId(response.executionId),
|
||||||
parentTaskId: normalizeNullableStringId(response.parentTaskId),
|
parentTaskId: normalizeNullableStringId(response.parentTaskId),
|
||||||
|
type: response.type ?? '',
|
||||||
ownerId: normalizeStringId(response.ownerId),
|
ownerId: normalizeStringId(response.ownerId),
|
||||||
ownerNickname: response.ownerNickname ?? null,
|
ownerNickname: response.ownerNickname ?? null,
|
||||||
statusName: response.statusName ?? null,
|
statusName: response.statusName ?? null,
|
||||||
@@ -322,6 +327,7 @@ export function normalizeTaskWorklog(response: TaskWorklogResponse): Api.Project
|
|||||||
userId: normalizeStringId(response.userId),
|
userId: normalizeStringId(response.userId),
|
||||||
userNickname: response.userNickname ?? null,
|
userNickname: response.userNickname ?? null,
|
||||||
workContent: response.workContent ?? null,
|
workContent: response.workContent ?? null,
|
||||||
|
difficulty: response.difficulty ?? '',
|
||||||
attachments: normalizeAttachments(response.attachments),
|
attachments: normalizeAttachments(response.attachments),
|
||||||
progressRate: typeof response.progressRate === 'number' ? response.progressRate : 0
|
progressRate: typeof response.progressRate === 'number' ? response.progressRate : 0
|
||||||
};
|
};
|
||||||
|
|||||||
2
src/typings/api/personal-item.d.ts
vendored
2
src/typings/api/personal-item.d.ts
vendored
@@ -16,6 +16,7 @@ declare namespace Api {
|
|||||||
interface PersonalItem {
|
interface PersonalItem {
|
||||||
id: string;
|
id: string;
|
||||||
taskTitle: string;
|
taskTitle: string;
|
||||||
|
type: string;
|
||||||
ownerId: string;
|
ownerId: string;
|
||||||
statusCode: PersonalItemStatusCode;
|
statusCode: PersonalItemStatusCode;
|
||||||
terminal?: boolean;
|
terminal?: boolean;
|
||||||
@@ -56,6 +57,7 @@ declare namespace Api {
|
|||||||
|
|
||||||
interface SavePersonalItemParams {
|
interface SavePersonalItemParams {
|
||||||
taskTitle: string;
|
taskTitle: string;
|
||||||
|
type: string;
|
||||||
ownerId?: string;
|
ownerId?: string;
|
||||||
executionId?: string | null;
|
executionId?: string | null;
|
||||||
progressRate?: number | null;
|
progressRate?: number | null;
|
||||||
|
|||||||
8
src/typings/api/project.d.ts
vendored
8
src/typings/api/project.d.ts
vendored
@@ -214,6 +214,7 @@ declare namespace Api {
|
|||||||
executionId: string;
|
executionId: string;
|
||||||
parentTaskId: string | null;
|
parentTaskId: string | null;
|
||||||
taskTitle: string;
|
taskTitle: string;
|
||||||
|
type: string;
|
||||||
ownerId: string;
|
ownerId: string;
|
||||||
ownerNickname?: string | null;
|
ownerNickname?: string | null;
|
||||||
/** 所属执行的负责人 userId(按钮可见度公式用) */
|
/** 所属执行的负责人 userId(按钮可见度公式用) */
|
||||||
@@ -350,6 +351,7 @@ declare namespace Api {
|
|||||||
interface SaveProjectTaskParams {
|
interface SaveProjectTaskParams {
|
||||||
parentTaskId: string | null;
|
parentTaskId: string | null;
|
||||||
taskTitle: string;
|
taskTitle: string;
|
||||||
|
type: string;
|
||||||
ownerId: string | null;
|
ownerId: string | null;
|
||||||
progressRate?: number;
|
progressRate?: number;
|
||||||
plannedStartDate: string | null;
|
plannedStartDate: string | null;
|
||||||
@@ -380,7 +382,8 @@ declare namespace Api {
|
|||||||
durationHours: number;
|
durationHours: number;
|
||||||
/** 本次填报进度(0~100,scale=2) */
|
/** 本次填报进度(0~100,scale=2) */
|
||||||
progressRate: number;
|
progressRate: number;
|
||||||
difficulty?: string | null;
|
/** 难度,来自字典 rdms_task&item_worklog_difficulty */
|
||||||
|
difficulty: string;
|
||||||
workContent: string | null;
|
workContent: string | null;
|
||||||
attachments?: AttachmentItem[] | null;
|
attachments?: AttachmentItem[] | null;
|
||||||
createTime: string;
|
createTime: string;
|
||||||
@@ -404,7 +407,8 @@ declare namespace Api {
|
|||||||
durationHours: number;
|
durationHours: number;
|
||||||
/** 本次填报进度(0~100,scale=2,必填) */
|
/** 本次填报进度(0~100,scale=2,必填) */
|
||||||
progressRate: number;
|
progressRate: number;
|
||||||
difficulty?: string | null;
|
/** 难度,来自字典 rdms_task&item_worklog_difficulty */
|
||||||
|
difficulty: string;
|
||||||
workContent?: string | null;
|
workContent?: string | null;
|
||||||
/** 编辑语义:null 保留原值 / [] 清空 / [...] 替换 */
|
/** 编辑语义:null 保留原值 / [] 清空 / [...] 替换 */
|
||||||
attachments?: AttachmentItem[] | null;
|
attachments?: AttachmentItem[] | null;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { computed, nextTick, reactive, ref, watch } from 'vue';
|
import { computed, nextTick, reactive, ref, watch } from 'vue';
|
||||||
import { useResizeObserver } from '@vueuse/core';
|
import { useResizeObserver } from '@vueuse/core';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { RDMS_TASK_ITEM_TYPE_DICT_CODE } from '@/constants/dict';
|
||||||
import { fetchCreatePersonalItem, fetchGetPersonalItemDetail, fetchUpdatePersonalItem } from '@/service/api';
|
import { fetchCreatePersonalItem, fetchGetPersonalItemDetail, fetchUpdatePersonalItem } from '@/service/api';
|
||||||
import { useAuthStore } from '@/store/modules/auth';
|
import { useAuthStore } from '@/store/modules/auth';
|
||||||
import { useForm, useFormRules } from '@/hooks/common/form';
|
import { useForm, useFormRules } from '@/hooks/common/form';
|
||||||
@@ -9,6 +10,7 @@ import BusinessAttachmentUploader from '@/components/custom/business-attachment-
|
|||||||
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
|
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
|
||||||
import BusinessFormSection from '@/components/custom/business-form-section.vue';
|
import BusinessFormSection from '@/components/custom/business-form-section.vue';
|
||||||
import BusinessRichTextEditor from '@/components/custom/business-rich-text-editor.vue';
|
import BusinessRichTextEditor from '@/components/custom/business-rich-text-editor.vue';
|
||||||
|
import DictSelect from '@/components/custom/dict-select.vue';
|
||||||
import { isEmptyRichText } from './personal-item-shared';
|
import { isEmptyRichText } from './personal-item-shared';
|
||||||
|
|
||||||
defineOptions({ name: 'PersonalItemOperateDialog' });
|
defineOptions({ name: 'PersonalItemOperateDialog' });
|
||||||
@@ -57,6 +59,7 @@ const submitting = ref(false);
|
|||||||
|
|
||||||
interface Model {
|
interface Model {
|
||||||
taskTitle: string;
|
taskTitle: string;
|
||||||
|
type: string;
|
||||||
plannedStartDate: string | null;
|
plannedStartDate: string | null;
|
||||||
plannedEndDate: string | null;
|
plannedEndDate: string | null;
|
||||||
taskDesc: string | null;
|
taskDesc: string | null;
|
||||||
@@ -76,6 +79,7 @@ const title = computed(() => {
|
|||||||
function createDefaultModel(): Model {
|
function createDefaultModel(): Model {
|
||||||
return {
|
return {
|
||||||
taskTitle: '',
|
taskTitle: '',
|
||||||
|
type: '',
|
||||||
plannedStartDate: null,
|
plannedStartDate: null,
|
||||||
plannedEndDate: null,
|
plannedEndDate: null,
|
||||||
taskDesc: null,
|
taskDesc: null,
|
||||||
@@ -108,6 +112,7 @@ const rules = computed(
|
|||||||
trigger: 'blur'
|
trigger: 'blur'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
type: [createRequiredRule('请选择事项类型')],
|
||||||
plannedStartDate: [createRequiredRule('请选择计划开始日期')],
|
plannedStartDate: [createRequiredRule('请选择计划开始日期')],
|
||||||
plannedEndDate: [
|
plannedEndDate: [
|
||||||
createRequiredRule('请选择计划结束日期'),
|
createRequiredRule('请选择计划结束日期'),
|
||||||
@@ -136,6 +141,7 @@ async function initModel() {
|
|||||||
|
|
||||||
if (!error && data) {
|
if (!error && data) {
|
||||||
model.taskTitle = data.taskTitle;
|
model.taskTitle = data.taskTitle;
|
||||||
|
model.type = data.type;
|
||||||
model.plannedStartDate = data.plannedStartDate;
|
model.plannedStartDate = data.plannedStartDate;
|
||||||
model.plannedEndDate = data.plannedEndDate;
|
model.plannedEndDate = data.plannedEndDate;
|
||||||
model.taskDesc = data.taskDesc;
|
model.taskDesc = data.taskDesc;
|
||||||
@@ -166,6 +172,7 @@ async function handleSubmit() {
|
|||||||
|
|
||||||
const payload: Api.PersonalItem.SavePersonalItemParams = {
|
const payload: Api.PersonalItem.SavePersonalItemParams = {
|
||||||
taskTitle: model.taskTitle.trim(),
|
taskTitle: model.taskTitle.trim(),
|
||||||
|
type: model.type,
|
||||||
ownerId: currentUserId.value,
|
ownerId: currentUserId.value,
|
||||||
plannedStartDate: model.plannedStartDate,
|
plannedStartDate: model.plannedStartDate,
|
||||||
plannedEndDate: model.plannedEndDate,
|
plannedEndDate: model.plannedEndDate,
|
||||||
@@ -235,6 +242,16 @@ watch(
|
|||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
|
||||||
|
<ElFormItem label="事项类型" prop="type">
|
||||||
|
<DictSelect
|
||||||
|
v-model="model.type"
|
||||||
|
:dict-code="RDMS_TASK_ITEM_TYPE_DICT_CODE"
|
||||||
|
:clearable="!isView"
|
||||||
|
:disabled="isView"
|
||||||
|
placeholder="请选择事项类型"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
|
||||||
<ElFormItem label="计划开始日期" prop="plannedStartDate">
|
<ElFormItem label="计划开始日期" prop="plannedStartDate">
|
||||||
<ElDatePicker
|
<ElDatePicker
|
||||||
v-model="model.plannedStartDate"
|
v-model="model.plannedStartDate"
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ const rules = computed(
|
|||||||
trigger: 'change'
|
trigger: 'change'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
difficulty: [createRequiredRule('请选择完成难度')],
|
difficulty: [createRequiredRule('请选择难度')],
|
||||||
workContent: [
|
workContent: [
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
@@ -356,7 +356,7 @@ defineExpose({
|
|||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
<ElCol :span="12">
|
<ElCol :span="12">
|
||||||
<ElFormItem label="完成难度" prop="difficulty">
|
<ElFormItem label="难度" prop="difficulty">
|
||||||
<DictSelect
|
<DictSelect
|
||||||
v-model="model.difficulty"
|
v-model="model.difficulty"
|
||||||
:dict-code="RDMS_WORKLOG_DIFFICULTY_DICT_CODE"
|
:dict-code="RDMS_WORKLOG_DIFFICULTY_DICT_CODE"
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { ElPopconfirm, ElTag, ElTooltip } from 'element-plus';
|
import { ElMessageBox, ElPopconfirm, ElTag, ElTooltip } from 'element-plus';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { Plus } from '@element-plus/icons-vue';
|
import { Plus } from '@element-plus/icons-vue';
|
||||||
import { RDMS_WORKLOG_DIFFICULTY_DICT_CODE } from '@/constants/dict';
|
import { RDMS_WORKLOG_DIFFICULTY_DICT_CODE } from '@/constants/dict';
|
||||||
import {
|
import {
|
||||||
|
fetchCompletePersonalItem,
|
||||||
fetchCreatePersonalItemWorklog,
|
fetchCreatePersonalItemWorklog,
|
||||||
fetchDeletePersonalItemWorklog,
|
fetchDeletePersonalItemWorklog,
|
||||||
|
fetchGetPersonalItemDetail,
|
||||||
fetchGetPersonalItemWorklogPage,
|
fetchGetPersonalItemWorklogPage,
|
||||||
fetchUpdatePersonalItemWorklog
|
fetchUpdatePersonalItemWorklog
|
||||||
} from '@/service/api';
|
} from '@/service/api';
|
||||||
@@ -52,6 +54,8 @@ const currentUserName = computed(
|
|||||||
|
|
||||||
const PAGE_SIZE = 10;
|
const PAGE_SIZE = 10;
|
||||||
const TABLE_HEIGHT = 390;
|
const TABLE_HEIGHT = 390;
|
||||||
|
const COMPLETED_STATUS_CODE: Api.PersonalItem.PersonalItemStatusCode = 'completed';
|
||||||
|
const COMPLETE_ACTION_CODE = 'complete';
|
||||||
|
|
||||||
const pageNo = ref(1);
|
const pageNo = ref(1);
|
||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
@@ -189,6 +193,56 @@ async function loadRecords() {
|
|||||||
total.value = data.total;
|
total.value = data.total;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function canPromptCompleteItem(item: Api.PersonalItem.PersonalItem) {
|
||||||
|
if (item.statusCode === COMPLETED_STATUS_CODE || item.terminal) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
item.progressRate >= 100 && (item.availableActions ?? []).some(action => action.actionCode === COMPLETE_ACTION_CODE)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchLatestItem() {
|
||||||
|
const { error, data } = await fetchGetPersonalItemDetail(props.item.id);
|
||||||
|
|
||||||
|
if (error || !data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function promptCompleteItemIfNeeded() {
|
||||||
|
const latestItem = await fetchLatestItem();
|
||||||
|
|
||||||
|
if (!latestItem || !canPromptCompleteItem(latestItem)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm('事项进度已达 100%,是否完成当前事项?', '完成确认', {
|
||||||
|
confirmButtonText: '完成事项',
|
||||||
|
cancelButtonText: '仅保留工时',
|
||||||
|
type: 'info'
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { error } = await fetchCompletePersonalItem(latestItem.id);
|
||||||
|
|
||||||
|
if (!error) {
|
||||||
|
window.$message?.success('个人事项已完成');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reloadAfterWorklogChanged() {
|
||||||
|
await loadRecords();
|
||||||
|
await promptCompleteItemIfNeeded();
|
||||||
|
emit('changed');
|
||||||
|
}
|
||||||
|
|
||||||
function handlePageChange(page: number) {
|
function handlePageChange(page: number) {
|
||||||
pageNo.value = page;
|
pageNo.value = page;
|
||||||
loadRecords();
|
loadRecords();
|
||||||
@@ -225,8 +279,7 @@ async function handleDelete(row: Api.PersonalItem.PersonalItemWorklog) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.$message?.success('工作日志删除成功');
|
window.$message?.success('工作日志删除成功');
|
||||||
await loadRecords();
|
await reloadAfterWorklogChanged();
|
||||||
emit('changed');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSubmit(payload: Api.PersonalItem.SavePersonalItemWorklogParams) {
|
async function handleSubmit(payload: Api.PersonalItem.SavePersonalItemWorklogParams) {
|
||||||
@@ -249,8 +302,7 @@ async function handleSubmit(payload: Api.PersonalItem.SavePersonalItemWorklogPar
|
|||||||
|
|
||||||
window.$message?.success(formMode.value === 'edit' ? '工作日志修改成功' : '工作日志新增成功');
|
window.$message?.success(formMode.value === 'edit' ? '工作日志修改成功' : '工作日志新增成功');
|
||||||
formVisible.value = false;
|
formVisible.value = false;
|
||||||
await loadRecords();
|
await reloadAfterWorklogChanged();
|
||||||
emit('changed');
|
|
||||||
} finally {
|
} finally {
|
||||||
submitting.value = false;
|
submitting.value = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
import { RDMS_TASK_ITEM_TYPE_DICT_CODE } from '@/constants/dict';
|
||||||
import BusinessAttachmentUploader from '@/components/custom/business-attachment-uploader.vue';
|
import BusinessAttachmentUploader from '@/components/custom/business-attachment-uploader.vue';
|
||||||
import BusinessFormSection from '@/components/custom/business-form-section.vue';
|
import BusinessFormSection from '@/components/custom/business-form-section.vue';
|
||||||
import BusinessRichTextEditor from '@/components/custom/business-rich-text-editor.vue';
|
import BusinessRichTextEditor from '@/components/custom/business-rich-text-editor.vue';
|
||||||
import BusinessUserSelect from '@/components/custom/business-user-select.vue';
|
import BusinessUserSelect from '@/components/custom/business-user-select.vue';
|
||||||
|
import DictSelect from '@/components/custom/dict-select.vue';
|
||||||
|
|
||||||
defineOptions({ name: 'ProjectExecutionTaskInfoReadonly' });
|
defineOptions({ name: 'ProjectExecutionTaskInfoReadonly' });
|
||||||
|
|
||||||
@@ -19,6 +21,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const taskTitle = computed(() => props.task?.taskTitle ?? '');
|
const taskTitle = computed(() => props.task?.taskTitle ?? '');
|
||||||
|
const taskType = computed(() => props.task?.type ?? '');
|
||||||
const taskDesc = computed(() => props.task?.taskDesc ?? '');
|
const taskDesc = computed(() => props.task?.taskDesc ?? '');
|
||||||
const ownerId = computed(() => props.task?.ownerId ?? null);
|
const ownerId = computed(() => props.task?.ownerId ?? null);
|
||||||
const parentTaskId = computed(() => props.task?.parentTaskId ?? null);
|
const parentTaskId = computed(() => props.task?.parentTaskId ?? null);
|
||||||
@@ -46,6 +49,9 @@ const parentTaskOptions = computed(() => {
|
|||||||
<ElFormItem label="任务名称">
|
<ElFormItem label="任务名称">
|
||||||
<ElInput :model-value="taskTitle" readonly placeholder="--" />
|
<ElInput :model-value="taskTitle" readonly placeholder="--" />
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
<ElFormItem label="任务类型">
|
||||||
|
<DictSelect :model-value="taskType" :dict-code="RDMS_TASK_ITEM_TYPE_DICT_CODE" disabled placeholder="--" />
|
||||||
|
</ElFormItem>
|
||||||
<ElFormItem label="父任务">
|
<ElFormItem label="父任务">
|
||||||
<ElSelect :model-value="parentTaskId" disabled clearable filterable class="w-full" placeholder="无">
|
<ElSelect :model-value="parentTaskId" disabled clearable filterable class="w-full" placeholder="无">
|
||||||
<ElOption v-for="item in parentTaskOptions" :key="item.id" :label="item.taskTitle" :value="item.id" />
|
<ElOption v-for="item in parentTaskOptions" :key="item.id" :label="item.taskTitle" :value="item.id" />
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
import { computed, nextTick, reactive, ref, watch } from 'vue';
|
import { computed, nextTick, reactive, ref, watch } from 'vue';
|
||||||
import { useResizeObserver } from '@vueuse/core';
|
import { useResizeObserver } from '@vueuse/core';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { RDMS_TASK_ITEM_TYPE_DICT_CODE } from '@/constants/dict';
|
||||||
import { useForm, useFormRules } from '@/hooks/common/form';
|
import { useForm, useFormRules } from '@/hooks/common/form';
|
||||||
import BusinessAttachmentUploader from '@/components/custom/business-attachment-uploader.vue';
|
import BusinessAttachmentUploader from '@/components/custom/business-attachment-uploader.vue';
|
||||||
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
|
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
|
||||||
import BusinessFormSection from '@/components/custom/business-form-section.vue';
|
import BusinessFormSection from '@/components/custom/business-form-section.vue';
|
||||||
import BusinessRichTextEditor from '@/components/custom/business-rich-text-editor.vue';
|
import BusinessRichTextEditor from '@/components/custom/business-rich-text-editor.vue';
|
||||||
import BusinessUserSelect from '@/components/custom/business-user-select.vue';
|
import BusinessUserSelect from '@/components/custom/business-user-select.vue';
|
||||||
|
import DictSelect from '@/components/custom/dict-select.vue';
|
||||||
defineOptions({ name: 'ProjectExecutionTaskOperateDialog' });
|
defineOptions({ name: 'ProjectExecutionTaskOperateDialog' });
|
||||||
|
|
||||||
type OperateMode = 'create' | 'edit';
|
type OperateMode = 'create' | 'edit';
|
||||||
@@ -58,6 +60,7 @@ const richTextEditorRef = ref<InstanceType<typeof BusinessRichTextEditor> | null
|
|||||||
interface FormModel {
|
interface FormModel {
|
||||||
parentTaskId: string | null;
|
parentTaskId: string | null;
|
||||||
taskTitle: string;
|
taskTitle: string;
|
||||||
|
type: string;
|
||||||
ownerId: string | null;
|
ownerId: string | null;
|
||||||
plannedStartDate: string | null;
|
plannedStartDate: string | null;
|
||||||
plannedEndDate: string | null;
|
plannedEndDate: string | null;
|
||||||
@@ -69,6 +72,7 @@ interface FormModel {
|
|||||||
const model = reactive<FormModel>({
|
const model = reactive<FormModel>({
|
||||||
parentTaskId: null,
|
parentTaskId: null,
|
||||||
taskTitle: '',
|
taskTitle: '',
|
||||||
|
type: '',
|
||||||
ownerId: null,
|
ownerId: null,
|
||||||
plannedStartDate: null,
|
plannedStartDate: null,
|
||||||
plannedEndDate: null,
|
plannedEndDate: null,
|
||||||
@@ -120,6 +124,7 @@ const rules = computed(
|
|||||||
() =>
|
() =>
|
||||||
({
|
({
|
||||||
taskTitle: [createRequiredRule('请输入任务名称')],
|
taskTitle: [createRequiredRule('请输入任务名称')],
|
||||||
|
type: [createRequiredRule('请选择任务类型')],
|
||||||
ownerId: model.parentTaskId ? [] : [createRequiredRule('请选择负责人')],
|
ownerId: model.parentTaskId ? [] : [createRequiredRule('请选择负责人')],
|
||||||
plannedStartDate: [createRequiredRule('请选择计划开始日期')],
|
plannedStartDate: [createRequiredRule('请选择计划开始日期')],
|
||||||
plannedEndDate: [
|
plannedEndDate: [
|
||||||
@@ -227,6 +232,7 @@ async function handleConfirm() {
|
|||||||
const payload: Api.Project.SaveProjectTaskParams = {
|
const payload: Api.Project.SaveProjectTaskParams = {
|
||||||
parentTaskId: model.parentTaskId || null,
|
parentTaskId: model.parentTaskId || null,
|
||||||
taskTitle: model.taskTitle.trim(),
|
taskTitle: model.taskTitle.trim(),
|
||||||
|
type: model.type,
|
||||||
ownerId: model.ownerId || null,
|
ownerId: model.ownerId || null,
|
||||||
plannedStartDate: model.plannedStartDate,
|
plannedStartDate: model.plannedStartDate,
|
||||||
plannedEndDate: model.plannedEndDate,
|
plannedEndDate: model.plannedEndDate,
|
||||||
@@ -254,6 +260,7 @@ function applyRowDataToModel() {
|
|||||||
model.parentTaskId =
|
model.parentTaskId =
|
||||||
props.mode === 'create' ? (props.defaultParentTaskId ?? null) : props.rowData?.parentTaskId || null;
|
props.mode === 'create' ? (props.defaultParentTaskId ?? null) : props.rowData?.parentTaskId || null;
|
||||||
model.taskTitle = props.rowData?.taskTitle || '';
|
model.taskTitle = props.rowData?.taskTitle || '';
|
||||||
|
model.type = props.rowData?.type || '';
|
||||||
model.ownerId = props.rowData?.ownerId || null;
|
model.ownerId = props.rowData?.ownerId || null;
|
||||||
model.plannedStartDate = props.rowData?.plannedStartDate || null;
|
model.plannedStartDate = props.rowData?.plannedStartDate || null;
|
||||||
model.plannedEndDate = props.rowData?.plannedEndDate || null;
|
model.plannedEndDate = props.rowData?.plannedEndDate || null;
|
||||||
@@ -318,6 +325,14 @@ defineExpose({
|
|||||||
<ElInput v-model="model.taskTitle" maxlength="200" placeholder="请输入任务名称" />
|
<ElInput v-model="model.taskTitle" maxlength="200" placeholder="请输入任务名称" />
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
|
||||||
|
<ElFormItem label="任务类型" prop="type">
|
||||||
|
<DictSelect
|
||||||
|
v-model="model.type"
|
||||||
|
:dict-code="RDMS_TASK_ITEM_TYPE_DICT_CODE"
|
||||||
|
placeholder="请选择任务类型"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
|
||||||
<ElFormItem label="父任务">
|
<ElFormItem label="父任务">
|
||||||
<ElSelect v-model="model.parentTaskId" clearable filterable class="w-full" placeholder="请选择父任务">
|
<ElSelect v-model="model.parentTaskId" clearable filterable class="w-full" placeholder="请选择父任务">
|
||||||
<ElOption
|
<ElOption
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, nextTick, reactive, ref, watch } from 'vue';
|
import { computed, nextTick, reactive, ref, watch } from 'vue';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { RDMS_WORKLOG_DIFFICULTY_DICT_CODE } from '@/constants/dict';
|
||||||
import { fetchGetProjectTaskWorklogPage } from '@/service/api/project';
|
import { fetchGetProjectTaskWorklogPage } from '@/service/api/project';
|
||||||
import { useAuthStore } from '@/store/modules/auth';
|
import { useAuthStore } from '@/store/modules/auth';
|
||||||
import { useForm, useFormRules } from '@/hooks/common/form';
|
import { useForm, useFormRules } from '@/hooks/common/form';
|
||||||
import BusinessAttachmentUploader from '@/components/custom/business-attachment-uploader.vue';
|
import BusinessAttachmentUploader from '@/components/custom/business-attachment-uploader.vue';
|
||||||
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
|
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
|
||||||
|
import DictSelect from '@/components/custom/dict-select.vue';
|
||||||
|
|
||||||
defineOptions({ name: 'ProjectExecutionTaskWorklogFormDialog' });
|
defineOptions({ name: 'ProjectExecutionTaskWorklogFormDialog' });
|
||||||
|
|
||||||
@@ -60,6 +62,7 @@ interface FormModel {
|
|||||||
/** 0.5 颗粒小时数 */
|
/** 0.5 颗粒小时数 */
|
||||||
durationHours: number | null;
|
durationHours: number | null;
|
||||||
progressRate: number;
|
progressRate: number;
|
||||||
|
difficulty: string;
|
||||||
workContent: string | null;
|
workContent: string | null;
|
||||||
attachments: Api.Project.AttachmentItem[];
|
attachments: Api.Project.AttachmentItem[];
|
||||||
}
|
}
|
||||||
@@ -75,6 +78,7 @@ const model = reactive<FormModel>({
|
|||||||
weekDate: null,
|
weekDate: null,
|
||||||
durationHours: null,
|
durationHours: null,
|
||||||
progressRate: 0,
|
progressRate: 0,
|
||||||
|
difficulty: '2',
|
||||||
workContent: null,
|
workContent: null,
|
||||||
attachments: []
|
attachments: []
|
||||||
});
|
});
|
||||||
@@ -180,6 +184,7 @@ const rules = computed(
|
|||||||
trigger: 'change'
|
trigger: 'change'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
difficulty: [createRequiredRule('请选择难度')],
|
||||||
workContent: [
|
workContent: [
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
@@ -282,6 +287,7 @@ async function handleConfirm() {
|
|||||||
endDate,
|
endDate,
|
||||||
durationHours: Number(model.durationHours!.toFixed(1)),
|
durationHours: Number(model.durationHours!.toFixed(1)),
|
||||||
progressRate: Number(model.progressRate.toFixed(2)),
|
progressRate: Number(model.progressRate.toFixed(2)),
|
||||||
|
difficulty: model.difficulty,
|
||||||
workContent: model.workContent?.trim() || null,
|
workContent: model.workContent?.trim() || null,
|
||||||
attachments: [...model.attachments]
|
attachments: [...model.attachments]
|
||||||
};
|
};
|
||||||
@@ -309,6 +315,7 @@ watch(
|
|||||||
model.weekDate = null;
|
model.weekDate = null;
|
||||||
}
|
}
|
||||||
model.durationHours = typeof row.durationHours === 'number' ? row.durationHours : null;
|
model.durationHours = typeof row.durationHours === 'number' ? row.durationHours : null;
|
||||||
|
model.difficulty = row.difficulty || '2';
|
||||||
model.workContent = row.workContent || null;
|
model.workContent = row.workContent || null;
|
||||||
model.attachments = row.attachments ? [...row.attachments] : [];
|
model.attachments = row.attachments ? [...row.attachments] : [];
|
||||||
} else {
|
} else {
|
||||||
@@ -316,6 +323,7 @@ watch(
|
|||||||
model.workDate = dayjs().format('YYYY-MM-DD');
|
model.workDate = dayjs().format('YYYY-MM-DD');
|
||||||
model.weekDate = null;
|
model.weekDate = null;
|
||||||
model.durationHours = null;
|
model.durationHours = null;
|
||||||
|
model.difficulty = '2';
|
||||||
model.workContent = null;
|
model.workContent = null;
|
||||||
model.attachments = [];
|
model.attachments = [];
|
||||||
}
|
}
|
||||||
@@ -409,6 +417,16 @@ defineExpose({
|
|||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
|
<ElCol :span="12">
|
||||||
|
<ElFormItem label="难度" prop="difficulty">
|
||||||
|
<DictSelect
|
||||||
|
v-model="model.difficulty"
|
||||||
|
:dict-code="RDMS_WORKLOG_DIFFICULTY_DICT_CODE"
|
||||||
|
:disabled="isView"
|
||||||
|
:clearable="false"
|
||||||
|
/>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
<ElCol :span="24">
|
<ElCol :span="24">
|
||||||
<ElFormItem label="工作内容" prop="workContent">
|
<ElFormItem label="工作内容" prop="workContent">
|
||||||
<ElInput
|
<ElInput
|
||||||
|
|||||||
Reference in New Issue
Block a user