fix(我的事项、产品/项目、工作台我的待办): 修复已知的一些问题。
This commit is contained in:
@@ -255,9 +255,9 @@ function openView(row: Api.PersonalItem.PersonalItem) {
|
|||||||
function buildRowActions(row: Api.PersonalItem.PersonalItem): PersonalItemRowAction[] {
|
function buildRowActions(row: Api.PersonalItem.PersonalItem): PersonalItemRowAction[] {
|
||||||
currentStatusItem.value = row;
|
currentStatusItem.value = row;
|
||||||
|
|
||||||
|
const isCompleted = row.statusCode === 'completed';
|
||||||
const rawLifecycleActions = [...(row.availableActions ?? [])];
|
const rawLifecycleActions = [...(row.availableActions ?? [])];
|
||||||
const pauseAction = rawLifecycleActions.find(action => action.actionCode === 'pause') ?? null;
|
const pauseAction = rawLifecycleActions.find(action => action.actionCode === 'pause') ?? null;
|
||||||
const cancelAction = rawLifecycleActions.find(action => action.actionCode === 'cancel') ?? null;
|
|
||||||
const completeAction = rawLifecycleActions.find(action => action.actionCode === 'complete') ?? null;
|
const completeAction = rawLifecycleActions.find(action => action.actionCode === 'complete') ?? null;
|
||||||
|
|
||||||
const lifecycleActions = rawLifecycleActions
|
const lifecycleActions = rawLifecycleActions
|
||||||
@@ -292,6 +292,8 @@ function buildRowActions(row: Api.PersonalItem.PersonalItem): PersonalItemRowAct
|
|||||||
openOperateDialog();
|
openOperateDialog();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
...(!isCompleted
|
||||||
|
? ([
|
||||||
{
|
{
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
tooltip: '删除',
|
tooltip: '删除',
|
||||||
@@ -311,7 +313,9 @@ function buildRowActions(row: Api.PersonalItem.PersonalItem): PersonalItemRowAct
|
|||||||
actionName: pauseAction?.actionName ?? '暂停',
|
actionName: pauseAction?.actionName ?? '暂停',
|
||||||
needReason: pauseAction?.needReason ?? false
|
needReason: pauseAction?.needReason ?? false
|
||||||
})
|
})
|
||||||
},
|
}
|
||||||
|
] satisfies PersonalItemRowAction[])
|
||||||
|
: []),
|
||||||
// {
|
// {
|
||||||
// key: 'status-cancel',
|
// key: 'status-cancel',
|
||||||
// tooltip: cancelAction?.actionName ?? '取消',
|
// tooltip: cancelAction?.actionName ?? '取消',
|
||||||
@@ -326,6 +330,8 @@ function buildRowActions(row: Api.PersonalItem.PersonalItem): PersonalItemRowAct
|
|||||||
// })
|
// })
|
||||||
// },
|
// },
|
||||||
...lifecycleActions,
|
...lifecycleActions,
|
||||||
|
...(!isCompleted
|
||||||
|
? ([
|
||||||
{
|
{
|
||||||
key: 'status-complete',
|
key: 'status-complete',
|
||||||
tooltip: completeAction?.actionName ?? '完成',
|
tooltip: completeAction?.actionName ?? '完成',
|
||||||
@@ -339,6 +345,8 @@ function buildRowActions(row: Api.PersonalItem.PersonalItem): PersonalItemRowAct
|
|||||||
needReason: completeAction?.needReason ?? false
|
needReason: completeAction?.needReason ?? false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
] satisfies PersonalItemRowAction[])
|
||||||
|
: [])
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,7 +460,7 @@ async function handleStatusActionSubmit(reason: string | null) {
|
|||||||
|
|
||||||
async function handleDelete(row: Api.PersonalItem.PersonalItem) {
|
async function handleDelete(row: Api.PersonalItem.PersonalItem) {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确定删除个人事项“${row.taskTitle}”吗?`, '删除确认', {
|
await ElMessageBox.confirm(`确定删除我的事项“${row.taskTitle}”吗?`, '删除确认', {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: '取消'
|
cancelButtonText: '取消'
|
||||||
@@ -473,12 +481,12 @@ async function handleDelete(row: Api.PersonalItem.PersonalItem) {
|
|||||||
|
|
||||||
async function handleBatchDelete() {
|
async function handleBatchDelete() {
|
||||||
if (!checkedRowIds.value.length) {
|
if (!checkedRowIds.value.length) {
|
||||||
window.$message?.warning('请先选择个人事项');
|
window.$message?.warning('请先选择我的事项');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确定删除选中的 ${selectedCount.value} 条个人事项吗?`, '删除确认', {
|
await ElMessageBox.confirm(`确定删除选中的 ${selectedCount.value} 条我的事项吗?`, '删除确认', {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: '取消'
|
cancelButtonText: '取消'
|
||||||
@@ -500,7 +508,7 @@ async function handleBatchDelete() {
|
|||||||
|
|
||||||
function handleOpenBindExecution() {
|
function handleOpenBindExecution() {
|
||||||
if (!checkedRowIds.value.length) {
|
if (!checkedRowIds.value.length) {
|
||||||
window.$message?.warning('请先选择个人事项');
|
window.$message?.warning('请先选择我的事项');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,7 +549,7 @@ onActivated(() => {
|
|||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center justify-between gap-12px">
|
<div class="flex items-center justify-between gap-12px">
|
||||||
<div class="flex items-center gap-10px">
|
<div class="flex items-center gap-10px">
|
||||||
<p>个人事项</p>
|
<p>我的事项</p>
|
||||||
<ElTag effect="plain">{{ mobilePagination.total || data.length }}</ElTag>
|
<ElTag effect="plain">{{ mobilePagination.total || data.length }}</ElTag>
|
||||||
</div>
|
</div>
|
||||||
<TableHeaderOperation v-model:columns="columnChecks" :loading="loading" @refresh="reloadTable">
|
<TableHeaderOperation v-model:columns="columnChecks" :loading="loading" @refresh="reloadTable">
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, reactive, ref, watch } from 'vue';
|
import { computed, reactive, ref, watch } from 'vue';
|
||||||
import { fetchGetPersonalItemExecutionOptions } from '@/service/api';
|
import {
|
||||||
|
fetchGetMyOwnedProjectPage,
|
||||||
|
fetchGetMyParticipatedProjectPage,
|
||||||
|
fetchGetProjectExecutionPage
|
||||||
|
} from '@/service/api';
|
||||||
import { useForm, useFormRules } from '@/hooks/common/form';
|
import { useForm, useFormRules } from '@/hooks/common/form';
|
||||||
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
|
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
|
||||||
|
|
||||||
@@ -28,44 +32,113 @@ const visible = defineModel<boolean>('visible', {
|
|||||||
const { formRef, validate } = useForm();
|
const { formRef, validate } = useForm();
|
||||||
const { createRequiredRule } = useFormRules();
|
const { createRequiredRule } = useFormRules();
|
||||||
|
|
||||||
const loading = ref(false);
|
interface ProjectOption {
|
||||||
const executionOptions = ref<Api.PersonalItem.PersonalItemExecutionOption[]>([]);
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExecutionOption {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectLoading = ref(false);
|
||||||
|
const executionLoading = ref(false);
|
||||||
|
const projectOptions = ref<ProjectOption[]>([]);
|
||||||
|
const executionOptions = ref<ExecutionOption[]>([]);
|
||||||
|
const loading = computed(() => projectLoading.value || executionLoading.value);
|
||||||
|
|
||||||
const model = reactive({
|
const model = reactive({
|
||||||
|
projectId: '',
|
||||||
executionId: ''
|
executionId: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
const rules = computed(
|
const rules = computed(
|
||||||
() =>
|
() =>
|
||||||
({
|
({
|
||||||
|
projectId: [createRequiredRule('请选择项目')],
|
||||||
executionId: [createRequiredRule('请选择执行')]
|
executionId: [createRequiredRule('请选择执行')]
|
||||||
}) satisfies Record<string, App.Global.FormRule[]>
|
}) satisfies Record<string, App.Global.FormRule[]>
|
||||||
);
|
);
|
||||||
|
|
||||||
function getExecutionOptionLabel(option: Api.PersonalItem.PersonalItemExecutionOption) {
|
function createProjectOptions(
|
||||||
if (option.projectName?.trim()) {
|
participated: Api.Project.MyParticipatedProjectItem[],
|
||||||
return `${option.projectName} / ${option.executionName}`;
|
owned: Api.Project.MyOwnedProjectItem[]
|
||||||
|
): ProjectOption[] {
|
||||||
|
const optionMap = new Map<string, ProjectOption>();
|
||||||
|
|
||||||
|
owned.forEach(item => {
|
||||||
|
if (!item.id || !item.name?.trim()) return;
|
||||||
|
optionMap.set(item.id, {
|
||||||
|
label: item.name.trim(),
|
||||||
|
value: item.id
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
participated.forEach(item => {
|
||||||
|
if (!item.id || !item.name?.trim()) return;
|
||||||
|
if (item.statusCode === 'completed' || item.statusCode === 'cancelled' || item.statusCode === 'archived') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!optionMap.has(item.id)) {
|
||||||
|
optionMap.set(item.id, {
|
||||||
|
label: item.name.trim(),
|
||||||
|
value: item.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(optionMap.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
return option.executionName;
|
async function loadProjectOptions() {
|
||||||
|
projectLoading.value = true;
|
||||||
|
const [participatedResult, ownedResult] = await Promise.all([
|
||||||
|
fetchGetMyParticipatedProjectPage({ pageNo: 1, pageSize: -1 }),
|
||||||
|
fetchGetMyOwnedProjectPage({ pageNo: 1, pageSize: -1 })
|
||||||
|
]);
|
||||||
|
projectLoading.value = false;
|
||||||
|
|
||||||
|
if (participatedResult.error || ownedResult.error || !participatedResult.data || !ownedResult.data) {
|
||||||
|
projectOptions.value = [];
|
||||||
|
executionOptions.value = [];
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadExecutionOptions() {
|
projectOptions.value = createProjectOptions(participatedResult.data.list, ownedResult.data.list);
|
||||||
loading.value = true;
|
}
|
||||||
const { error, data } = await fetchGetPersonalItemExecutionOptions();
|
|
||||||
loading.value = false;
|
async function loadExecutionOptions(projectId: string) {
|
||||||
|
if (!projectId) {
|
||||||
|
executionOptions.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
executionLoading.value = true;
|
||||||
|
const { error, data } = await fetchGetProjectExecutionPage(projectId, {
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: -1
|
||||||
|
});
|
||||||
|
executionLoading.value = false;
|
||||||
|
|
||||||
if (error || !data) {
|
if (error || !data) {
|
||||||
executionOptions.value = [];
|
executionOptions.value = [];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
executionOptions.value = data.map(item => ({ ...item }));
|
executionOptions.value = data.list
|
||||||
|
.filter(item => !item.terminal && item.statusCode !== 'completed' && item.statusCode !== 'cancelled')
|
||||||
|
.map(item => ({
|
||||||
|
label: item.executionName,
|
||||||
|
value: item.id
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initDialog() {
|
async function initDialog() {
|
||||||
|
model.projectId = '';
|
||||||
model.executionId = '';
|
model.executionId = '';
|
||||||
await loadExecutionOptions();
|
executionOptions.value = [];
|
||||||
|
await loadProjectOptions();
|
||||||
formRef.value?.clearValidate();
|
formRef.value?.clearValidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,25 +158,64 @@ watch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => model.projectId,
|
||||||
|
async (projectId, previousProjectId) => {
|
||||||
|
if (projectId === previousProjectId) return;
|
||||||
|
|
||||||
|
model.executionId = '';
|
||||||
|
|
||||||
|
if (!projectId) {
|
||||||
|
executionOptions.value = [];
|
||||||
|
formRef.value?.clearValidate('executionId');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await loadExecutionOptions(projectId);
|
||||||
|
formRef.value?.clearValidate('executionId');
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BusinessFormDialog
|
<BusinessFormDialog
|
||||||
v-model="visible"
|
v-model="visible"
|
||||||
title="批量关联执行"
|
title="批量关联执行"
|
||||||
preset="sm"
|
preset="md"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:confirm-loading="props.submitLoading"
|
:confirm-loading="props.submitLoading"
|
||||||
@confirm="handleConfirm"
|
@confirm="handleConfirm"
|
||||||
>
|
>
|
||||||
<ElAlert
|
<ElAlert
|
||||||
:title="`已选中 ${props.selectedCount} 条个人事项,关联成功后这些事项会从当前列表移除。`"
|
:title="`已选中 ${props.selectedCount} 条我的事项,关联成功后这些事项会从当前列表移除。`"
|
||||||
type="info"
|
type="info"
|
||||||
:closable="false"
|
:closable="false"
|
||||||
class="mb-16px"
|
class="mb-16px"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ElForm ref="formRef" :model="model" :rules="rules" label-position="top" :validate-on-rule-change="false">
|
<ElForm ref="formRef" :model="model" :rules="rules" label-position="top" :validate-on-rule-change="false">
|
||||||
|
<ElRow :gutter="16">
|
||||||
|
<ElCol :span="12">
|
||||||
|
<ElFormItem label="项目" prop="projectId">
|
||||||
|
<ElSelect
|
||||||
|
v-model="model.projectId"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
placeholder="请选择项目"
|
||||||
|
class="w-full"
|
||||||
|
:loading="projectLoading"
|
||||||
|
>
|
||||||
|
<ElOption
|
||||||
|
v-for="option in projectOptions"
|
||||||
|
:key="option.value"
|
||||||
|
:label="option.label"
|
||||||
|
:value="option.value"
|
||||||
|
/>
|
||||||
|
</ElSelect>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :span="12">
|
||||||
<ElFormItem label="执行" prop="executionId">
|
<ElFormItem label="执行" prop="executionId">
|
||||||
<ElSelect
|
<ElSelect
|
||||||
v-model="model.executionId"
|
v-model="model.executionId"
|
||||||
@@ -111,16 +223,19 @@ watch(
|
|||||||
filterable
|
filterable
|
||||||
placeholder="请选择执行"
|
placeholder="请选择执行"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
:loading="loading"
|
:loading="executionLoading"
|
||||||
|
:disabled="!model.projectId"
|
||||||
>
|
>
|
||||||
<ElOption
|
<ElOption
|
||||||
v-for="option in executionOptions"
|
v-for="option in executionOptions"
|
||||||
:key="option.executionId"
|
:key="option.value"
|
||||||
:label="getExecutionOptionLabel(option)"
|
:label="option.label"
|
||||||
:value="option.executionId"
|
:value="option.value"
|
||||||
/>
|
/>
|
||||||
</ElSelect>
|
</ElSelect>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
</ElForm>
|
</ElForm>
|
||||||
</BusinessFormDialog>
|
</BusinessFormDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ async function promptCompleteItemIfNeeded() {
|
|||||||
const { error } = await fetchCompletePersonalItem(detailData.value.id);
|
const { error } = await fetchCompletePersonalItem(detailData.value.id);
|
||||||
|
|
||||||
if (!error) {
|
if (!error) {
|
||||||
window.$message?.success('个人事项已完成');
|
window.$message?.success('我的事项已完成');
|
||||||
await refreshDetail();
|
await refreshDetail();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,10 +70,10 @@ const model = reactive<Model>(createDefaultModel());
|
|||||||
|
|
||||||
const title = computed(() => {
|
const title = computed(() => {
|
||||||
if (isView.value) {
|
if (isView.value) {
|
||||||
return '个人事项详情';
|
return '我的事项详情';
|
||||||
}
|
}
|
||||||
|
|
||||||
return isEdit.value ? '编辑个人事项' : '新增个人事项';
|
return isEdit.value ? '编辑我的事项' : '新增我的事项';
|
||||||
});
|
});
|
||||||
|
|
||||||
function createDefaultModel(): Model {
|
function createDefaultModel(): Model {
|
||||||
@@ -195,7 +195,7 @@ async function handleSubmit() {
|
|||||||
|
|
||||||
await Promise.all([attachmentUploaderRef.value?.commit(), richTextEditorRef.value?.commit()]);
|
await Promise.all([attachmentUploaderRef.value?.commit(), richTextEditorRef.value?.commit()]);
|
||||||
|
|
||||||
window.$message?.success(isEdit.value ? '个人事项修改成功' : '个人事项创建成功');
|
window.$message?.success(isEdit.value ? '我的事项修改成功' : '我的事项创建成功');
|
||||||
visible.value = false;
|
visible.value = false;
|
||||||
emit('submitted');
|
emit('submitted');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const searchParams = reactive(getInitSearchParams());
|
|||||||
const navKey = ref<ProjectListNavKey>('active');
|
const navKey = ref<ProjectListNavKey>('active');
|
||||||
const groupPage = ref<Api.Project.ProjectGroupPageResult>(createEmptyGroupPage());
|
const groupPage = ref<Api.Project.ProjectGroupPageResult>(createEmptyGroupPage());
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const allCollapsed = ref(false);
|
const allCollapsed = ref(true);
|
||||||
/** 状态看板项(overview-summary items,状态机动态下发),左栏与"全部"口径派生均以此为源 */
|
/** 状态看板项(overview-summary items,状态机动态下发),左栏与"全部"口径派生均以此为源 */
|
||||||
const statusBoardItems = ref<Api.Project.OverviewStatusItem[]>([]);
|
const statusBoardItems = ref<Api.Project.OverviewStatusItem[]>([]);
|
||||||
/** "全部"口径总数(后端 total,前端不自行求和) */
|
/** "全部"口径总数(后端 total,前端不自行求和) */
|
||||||
@@ -171,7 +171,7 @@ async function handleResetSearch() {
|
|||||||
|
|
||||||
async function handleNavChange(key: ProjectListNavKey) {
|
async function handleNavChange(key: ProjectListNavKey) {
|
||||||
navKey.value = key;
|
navKey.value = key;
|
||||||
allCollapsed.value = false;
|
allCollapsed.value = true;
|
||||||
await Promise.all([loadOverviewData(), loadGroupPage(1)]);
|
await Promise.all([loadOverviewData(), loadGroupPage(1)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,10 +63,10 @@ export function useTaskActions(emits: TaskActionEmits) {
|
|||||||
function createActions(row: Api.Project.ProjectTask): TaskAction[] {
|
function createActions(row: Api.Project.ProjectTask): TaskAction[] {
|
||||||
const actions: TaskAction[] = [];
|
const actions: TaskAction[] = [];
|
||||||
|
|
||||||
// 工作日志:行操作入口始终显示——查看人人可看;新增/编辑由弹层内 canSubmit 按身份与状态控制
|
// 填报:行操作入口始终显示——查看人人可看;新增/编辑由弹层内 canSubmit 按身份与状态控制
|
||||||
actions.push({
|
actions.push({
|
||||||
key: 'report',
|
key: 'report',
|
||||||
tooltip: '工作日志',
|
tooltip: '填报',
|
||||||
icon: markRaw(IconMdiClipboardEditOutline),
|
icon: markRaw(IconMdiClipboardEditOutline),
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
onClick: () => emits.report(row)
|
onClick: () => emits.report(row)
|
||||||
|
|||||||
@@ -171,7 +171,13 @@ const deleteDialogVisible = ref(false);
|
|||||||
const deleteExecutionDependentSummary = ref<string | null>(null);
|
const deleteExecutionDependentSummary = ref<string | null>(null);
|
||||||
const { canCreateTopLevelTask } = useTaskPermissions();
|
const { canCreateTopLevelTask } = useTaskPermissions();
|
||||||
|
|
||||||
const canCreateTask = computed(() => (selectedExecution.value ? canCreateTopLevelTask(selectedExecution.value) : true));
|
const canCreateTask = computed(() => {
|
||||||
|
if (selectedExecution.value) {
|
||||||
|
return canCreateTopLevelTask(selectedExecution.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buttonCodeSet.value.has('project:task:create');
|
||||||
|
});
|
||||||
|
|
||||||
const workspaceTitle = computed(() => {
|
const workspaceTitle = computed(() => {
|
||||||
if (selectedExecution.value) return selectedExecution.value.executionName || '执行任务';
|
if (selectedExecution.value) return selectedExecution.value.executionName || '执行任务';
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
// 项目色:纯分类色,不含红/橙系。告警语义留给等级色(红/橙/绿),避免视觉混淆。
|
// 项目色:纯分类色,不含红/橙系。告警语义留给等级色(红/橙/绿),避免视觉混淆。
|
||||||
const PROJECT_COLORS = ['#5B8FF9', '#5AD8A6', '#5D7092', '#F6BD16', '#6DC8EC', '#73D13D', '#36CFC9', '#36495D'];
|
const PROJECT_COLORS = ['#5B8FF9', '#5AD8A6', '#5D7092', '#F6BD16', '#6DC8EC', '#73D13D', '#36CFC9', '#36495D'];
|
||||||
// 个人事项独占紫色,与项目色明显区分
|
// 我的事项独占紫色,与项目色明显区分
|
||||||
const PERSONAL_COLOR = '#9254DE';
|
const PERSONAL_COLOR = '#9254DE';
|
||||||
const OTHER_COLOR = '#BFBFBF';
|
const OTHER_COLOR = '#BFBFBF';
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export interface WorkbenchTodoItemSource {
|
|||||||
deadline: string | null;
|
deadline: string | null;
|
||||||
/** 来源(提交人/项目名/工单号) */
|
/** 来源(提交人/项目名/工单号) */
|
||||||
source: string;
|
source: string;
|
||||||
/** 进度百分比,仅任务和个人事项使用 */
|
/** 进度百分比,仅任务和我的事项使用 */
|
||||||
progressRate?: number | null;
|
progressRate?: number | null;
|
||||||
/** 优先级,用于前端排序与高亮 */
|
/** 优先级,用于前端排序与高亮 */
|
||||||
priority?: WorkbenchTodoPriority;
|
priority?: WorkbenchTodoPriority;
|
||||||
@@ -67,7 +67,7 @@ const todoCategoryMeta: Record<
|
|||||||
> = {
|
> = {
|
||||||
task: { label: '任务', tone: 'emerald', icon: 'mdi:checkbox-marked-circle-outline' },
|
task: { label: '任务', tone: 'emerald', icon: 'mdi:checkbox-marked-circle-outline' },
|
||||||
ticket: { label: '工单', tone: 'amber', icon: 'mdi:ticket-confirmation-outline' },
|
ticket: { label: '工单', tone: 'amber', icon: 'mdi:ticket-confirmation-outline' },
|
||||||
personal: { label: '个人事项', tone: 'violet', icon: 'mdi:notebook-edit-outline' },
|
personal: { label: '我的事项', tone: 'violet', icon: 'mdi:notebook-edit-outline' },
|
||||||
approval: { label: '待审批', tone: 'sky', icon: 'mdi:checkbox-multiple-marked-outline' }
|
approval: { label: '待审批', tone: 'sky', icon: 'mdi:checkbox-multiple-marked-outline' }
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -239,7 +239,7 @@ export function buildWorkbenchOwnedProjects(
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 工时分布行项:项目 / 个人事项 / 其他(杂项) */
|
/** 工时分布行项:项目 / 我的事项 / 其他(杂项) */
|
||||||
export interface WorkbenchWorklogDistributionItem {
|
export interface WorkbenchWorklogDistributionItem {
|
||||||
/** 唯一 key;project 用 projectId,personal/other 用固定字面量 */
|
/** 唯一 key;project 用 projectId,personal/other 用固定字面量 */
|
||||||
key: string;
|
key: string;
|
||||||
@@ -280,7 +280,7 @@ function toWorklogDistributionItem(item: Api.Project.WorklogDistributionItem): W
|
|||||||
const isProject = item.kind === 'project' && Boolean(item.projectId);
|
const isProject = item.kind === 'project' && Boolean(item.projectId);
|
||||||
return {
|
return {
|
||||||
key: isProject ? (item.projectId as string) : item.kind,
|
key: isProject ? (item.projectId as string) : item.kind,
|
||||||
label: item.projectName ?? (item.kind === 'personal' ? '个人事项' : '其他'),
|
label: item.projectName ?? (item.kind === 'personal' ? '我的事项' : '其他'),
|
||||||
hours: roundHours(item.hours),
|
hours: roundHours(item.hours),
|
||||||
kind: item.kind,
|
kind: item.kind,
|
||||||
projectId: isProject ? (item.projectId as string) : undefined
|
projectId: isProject ? (item.projectId as string) : undefined
|
||||||
@@ -438,14 +438,14 @@ export interface WorkbenchTeamLoadItemSource {
|
|||||||
key: string;
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
kind: WorkbenchTeamLoadItemKind;
|
kind: WorkbenchTeamLoadItemKind;
|
||||||
/** 该项目/个人事项下,该成员未完成的任务/事项数(含待开始/已暂停;任务按"负责人/协办人"口径,单任务多人各算 1 条) */
|
/** 该项目/我的事项下,该成员未完成的任务/事项数(含待开始/已暂停;任务按"负责人/协办人"口径,单任务多人各算 1 条) */
|
||||||
count: number;
|
count: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorkbenchTeamLoadMemberSource {
|
export interface WorkbenchTeamLoadMemberSource {
|
||||||
memberId: string;
|
memberId: string;
|
||||||
memberName: string;
|
memberName: string;
|
||||||
/** 未完成数按项目 + 个人事项的拆分(合计即"未完成总数") */
|
/** 未完成数按项目 + 我的事项的拆分(合计即"未完成总数") */
|
||||||
items: WorkbenchTeamLoadItemSource[];
|
items: WorkbenchTeamLoadItemSource[];
|
||||||
/** 今天 ≤ 计划结束 ≤ 今天+3 天 且未完成(与逾期互斥) */
|
/** 今天 ≤ 计划结束 ≤ 今天+3 天 且未完成(与逾期互斥) */
|
||||||
dueSoon: number;
|
dueSoon: number;
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ function toTeamLoadSource(member: Api.Project.TeamLoadMember, index: number): Wo
|
|||||||
memberName: index === 0 ? `${member.userNickname}(我)` : member.userNickname,
|
memberName: index === 0 ? `${member.userNickname}(我)` : member.userNickname,
|
||||||
items: member.items.map(item => ({
|
items: member.items.map(item => ({
|
||||||
key: item.kind === 'project' && item.projectId ? item.projectId : item.kind,
|
key: item.kind === 'project' && item.projectId ? item.projectId : item.kind,
|
||||||
label: item.projectName ?? (item.kind === 'personal' ? '个人事项' : '其他'),
|
label: item.projectName ?? (item.kind === 'personal' ? '我的事项' : '其他'),
|
||||||
kind: item.kind,
|
kind: item.kind,
|
||||||
count: item.count
|
count: item.count
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { useRouterPush } from '@/hooks/common/router';
|
|||||||
import { useDict } from '@/hooks/business/dict';
|
import { useDict } from '@/hooks/business/dict';
|
||||||
import type { WorklogChangedPayload } from '@/views/project/project/execution/shared';
|
import type { WorklogChangedPayload } from '@/views/project/project/execution/shared';
|
||||||
import TaskStatusActionDialog from '@/views/project/project/execution/modules/status-action-dialog.vue';
|
import TaskStatusActionDialog from '@/views/project/project/execution/modules/status-action-dialog.vue';
|
||||||
|
import TaskInfoReadonly from '@/views/project/project/execution/modules/task-info-readonly.vue';
|
||||||
import TaskWorklogDialog from '@/views/project/project/execution/modules/task-worklog-dialog.vue';
|
import TaskWorklogDialog from '@/views/project/project/execution/modules/task-worklog-dialog.vue';
|
||||||
import PersonalItemDetailDialog from '@/views/personal-center/my-item/modules/personal-item-detail-dialog.vue';
|
import PersonalItemDetailDialog from '@/views/personal-center/my-item/modules/personal-item-detail-dialog.vue';
|
||||||
import PersonalItemOperateDialog from '@/views/personal-center/my-item/modules/personal-item-operate-dialog.vue';
|
import PersonalItemOperateDialog from '@/views/personal-center/my-item/modules/personal-item-operate-dialog.vue';
|
||||||
@@ -57,6 +58,7 @@ import { useWorkbenchRefresh } from '../composables/use-workbench-refresh';
|
|||||||
import { useWorkbenchWorklogSignal } from '../composables/use-workbench-worklog-signal';
|
import { useWorkbenchWorklogSignal } from '../composables/use-workbench-worklog-signal';
|
||||||
import WorkbenchModuleCard from './workbench-module-card.vue';
|
import WorkbenchModuleCard from './workbench-module-card.vue';
|
||||||
import IconMdiCheckCircleOutline from '~icons/mdi/check-circle-outline';
|
import IconMdiCheckCircleOutline from '~icons/mdi/check-circle-outline';
|
||||||
|
import IconMdiClipboardCheckOutline from '~icons/mdi/clipboard-check-outline';
|
||||||
import IconMdiClipboardEditOutline from '~icons/mdi/clipboard-edit-outline';
|
import IconMdiClipboardEditOutline from '~icons/mdi/clipboard-edit-outline';
|
||||||
import IconMdiCloseCircleOutline from '~icons/mdi/close-circle-outline';
|
import IconMdiCloseCircleOutline from '~icons/mdi/close-circle-outline';
|
||||||
import IconMdiEyeOutline from '~icons/mdi/eye-outline';
|
import IconMdiEyeOutline from '~icons/mdi/eye-outline';
|
||||||
@@ -127,7 +129,7 @@ const mainTabs: Array<{ key: WorkbenchTodoMainTab; label: string }> = [
|
|||||||
{ key: 'all', label: '全部' },
|
{ key: 'all', label: '全部' },
|
||||||
{ key: 'task', label: '任务' },
|
{ key: 'task', label: '任务' },
|
||||||
{ key: 'ticket', label: '工单' },
|
{ key: 'ticket', label: '工单' },
|
||||||
{ key: 'personal', label: '个人事项' },
|
{ key: 'personal', label: '我的事项' },
|
||||||
{ key: 'approval', label: '待审批' }
|
{ key: 'approval', label: '待审批' }
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -169,7 +171,7 @@ const myTaskItems = ref<WorkbenchTodoItem[]>([]);
|
|||||||
// 保留任务原始行,供操作图标按 availableActions 渲染并取 projectId / executionId 调状态变更接口
|
// 保留任务原始行,供操作图标按 availableActions 渲染并取 projectId / executionId 调状态变更接口
|
||||||
const myTaskRows = ref<Api.Project.MyTaskItem[]>([]);
|
const myTaskRows = ref<Api.Project.MyTaskItem[]>([]);
|
||||||
const personalTodoItems = ref<WorkbenchTodoItem[]>([]);
|
const personalTodoItems = ref<WorkbenchTodoItem[]>([]);
|
||||||
// 保留个人事项原始行,供操作图标(详情/填报/完成)按 availableActions 渲染并回传给弹层
|
// 保留我的事项原始行,供操作图标(详情/填报/完成)按 availableActions 渲染并回传给弹层
|
||||||
const personalItemRows = ref<Api.PersonalItem.PersonalItem[]>([]);
|
const personalItemRows = ref<Api.PersonalItem.PersonalItem[]>([]);
|
||||||
const overtimeApprovalItems = ref<WorkbenchTodoItem[]>([]);
|
const overtimeApprovalItems = ref<WorkbenchTodoItem[]>([]);
|
||||||
const overtimeApprovalRows = ref<Api.OvertimeApplication.OvertimeApplication[]>([]);
|
const overtimeApprovalRows = ref<Api.OvertimeApplication.OvertimeApplication[]>([]);
|
||||||
@@ -188,14 +190,14 @@ const mergedItems = computed(() => [
|
|||||||
...workReportApprovalItems.value
|
...workReportApprovalItems.value
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 个人事项操作弹层:详情用 view 模式、新增用 add 模式,共用同一实例
|
// 我的事项操作弹层:详情用 view 模式、新增用 add 模式,共用同一实例
|
||||||
const personalOperateVisible = ref(false);
|
const personalOperateVisible = ref(false);
|
||||||
const personalOperateType = ref<'add' | 'view'>('add');
|
const personalOperateType = ref<'add' | 'view'>('add');
|
||||||
const personalOperateRow = ref<Api.PersonalItem.PersonalItem | null>(null);
|
const personalOperateRow = ref<Api.PersonalItem.PersonalItem | null>(null);
|
||||||
// 个人事项填报:复用详情弹层的 worklog tab
|
// 我的事项填报:复用详情弹层的 worklog tab
|
||||||
const personalDetailVisible = ref(false);
|
const personalDetailVisible = ref(false);
|
||||||
const personalDetailRow = ref<Api.PersonalItem.PersonalItem | null>(null);
|
const personalDetailRow = ref<Api.PersonalItem.PersonalItem | null>(null);
|
||||||
// 个人事项完成:复用状态动作弹层收集原因 + 二次确认
|
// 我的事项完成:复用状态动作弹层收集原因 + 二次确认
|
||||||
const personalStatusVisible = ref(false);
|
const personalStatusVisible = ref(false);
|
||||||
const personalStatusRow = ref<Api.PersonalItem.PersonalItem | null>(null);
|
const personalStatusRow = ref<Api.PersonalItem.PersonalItem | null>(null);
|
||||||
const personalStatusAction = ref<Api.PersonalItem.PersonalItemLifecycleAction | null>(null);
|
const personalStatusAction = ref<Api.PersonalItem.PersonalItemLifecycleAction | null>(null);
|
||||||
@@ -203,6 +205,8 @@ const personalStatusAction = ref<Api.PersonalItem.PersonalItemLifecycleAction |
|
|||||||
const taskStatusVisible = ref(false);
|
const taskStatusVisible = ref(false);
|
||||||
const taskStatusRow = ref<Api.Project.MyTaskItem | null>(null);
|
const taskStatusRow = ref<Api.Project.MyTaskItem | null>(null);
|
||||||
const taskStatusAction = ref<Api.Project.LifecycleAction<Api.Project.ProjectTaskActionCode> | null>(null);
|
const taskStatusAction = ref<Api.Project.LifecycleAction<Api.Project.ProjectTaskActionCode> | null>(null);
|
||||||
|
const taskPreviewMap = ref<Record<string, Api.Project.ProjectTask>>({});
|
||||||
|
const taskPreviewLoadingIds = ref<string[]>([]);
|
||||||
// 任务填报工时:复用任务工作区的工时弹层;弹层需要完整任务详情,点击时按需拉取
|
// 任务填报工时:复用任务工作区的工时弹层;弹层需要完整任务详情,点击时按需拉取
|
||||||
const taskWorklogVisible = ref(false);
|
const taskWorklogVisible = ref(false);
|
||||||
const taskWorklogTask = ref<Api.Project.ProjectTask | null>(null);
|
const taskWorklogTask = ref<Api.Project.ProjectTask | null>(null);
|
||||||
@@ -222,9 +226,7 @@ const currentPerformanceActionType = ref<PerformanceApprovalActionType>('confirm
|
|||||||
// 批量审批选中状态(存原始加班申请 id,避免映射转换)
|
// 批量审批选中状态(存原始加班申请 id,避免映射转换)
|
||||||
const selectedOvertimeIds = ref<Set<string>>(new Set());
|
const selectedOvertimeIds = ref<Set<string>>(new Set());
|
||||||
|
|
||||||
const OVERTIME_APPROVAL_ACTION_ICONS = {
|
const APPROVAL_ENTRY_ICON = markRaw(IconMdiClipboardCheckOutline);
|
||||||
detail: markRaw(IconMdiEyeOutline)
|
|
||||||
};
|
|
||||||
|
|
||||||
function getApprovalCategoryLabel(bizType: ApprovalBizType) {
|
function getApprovalCategoryLabel(bizType: ApprovalBizType) {
|
||||||
if (bizType === 'weekly') return '周报';
|
if (bizType === 'weekly') return '周报';
|
||||||
@@ -287,13 +289,13 @@ function getPersonalCompleteAction(row: Api.PersonalItem.PersonalItem) {
|
|||||||
return row.availableActions?.find(action => action.actionCode === 'complete') || null;
|
return row.availableActions?.find(action => action.actionCode === 'complete') || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 仅当个人事项当前可完成(availableActions 含 complete)时才渲染完成图标
|
// 仅当我的事项当前可完成(availableActions 含 complete)时才渲染完成图标
|
||||||
function canCompletePersonalItem(item: WorkbenchTodoItem) {
|
function canCompletePersonalItem(item: WorkbenchTodoItem) {
|
||||||
const row = findPersonalItemRow(item);
|
const row = findPersonalItemRow(item);
|
||||||
return Boolean(row && getPersonalCompleteAction(row));
|
return Boolean(row && getPersonalCompleteAction(row));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 详情:复用个人事项操作弹层的 view 只读模式
|
// 详情:复用我的事项操作弹层的 view 只读模式
|
||||||
function openPersonalDetail(item: WorkbenchTodoItem) {
|
function openPersonalDetail(item: WorkbenchTodoItem) {
|
||||||
const row = findPersonalItemRow(item);
|
const row = findPersonalItemRow(item);
|
||||||
if (!row) return;
|
if (!row) return;
|
||||||
@@ -350,6 +352,52 @@ function findMyTaskRow(item: WorkbenchTodoItem) {
|
|||||||
return myTaskRows.value.find(row => `task-${row.id}` === item.id) || null;
|
return myTaskRows.value.find(row => `task-${row.id}` === item.id) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function canPreviewTaskInfo(item: WorkbenchTodoItem) {
|
||||||
|
const row = findMyTaskRow(item);
|
||||||
|
return Boolean(item.category === 'task' && row?.projectId && row.executionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTaskPreviewTask(item: WorkbenchTodoItem) {
|
||||||
|
const row = findMyTaskRow(item);
|
||||||
|
if (!row) return null;
|
||||||
|
return taskPreviewMap.value[row.id] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTaskPreviewTaskOptions(item: WorkbenchTodoItem) {
|
||||||
|
const task = getTaskPreviewTask(item);
|
||||||
|
return task ? [task] : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function isTaskPreviewLoading(item: WorkbenchTodoItem) {
|
||||||
|
const row = findMyTaskRow(item);
|
||||||
|
if (!row) return false;
|
||||||
|
return taskPreviewLoadingIds.value.includes(row.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleTaskPreviewShow(item: WorkbenchTodoItem) {
|
||||||
|
const row = findMyTaskRow(item);
|
||||||
|
if (!row?.executionId) return;
|
||||||
|
|
||||||
|
if (taskPreviewMap.value[row.id] || taskPreviewLoadingIds.value.includes(row.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
taskPreviewLoadingIds.value = [...taskPreviewLoadingIds.value, row.id];
|
||||||
|
|
||||||
|
const { error, data } = await fetchGetProjectTask(row.projectId, row.executionId, row.id);
|
||||||
|
|
||||||
|
taskPreviewLoadingIds.value = taskPreviewLoadingIds.value.filter(id => id !== row.id);
|
||||||
|
|
||||||
|
if (error || !data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
taskPreviewMap.value = {
|
||||||
|
...taskPreviewMap.value,
|
||||||
|
[row.id]: data
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// 状态变更接口挂在执行路径下,未挂执行的任务不渲染动作按钮
|
// 状态变更接口挂在执行路径下,未挂执行的任务不渲染动作按钮
|
||||||
function getTaskActions(item: WorkbenchTodoItem) {
|
function getTaskActions(item: WorkbenchTodoItem) {
|
||||||
const row = findMyTaskRow(item);
|
const row = findMyTaskRow(item);
|
||||||
@@ -362,6 +410,23 @@ function canReportTaskWorklog(item: WorkbenchTodoItem) {
|
|||||||
return Boolean(findMyTaskRow(item)?.executionId);
|
return Boolean(findMyTaskRow(item)?.executionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function refreshTaskWorklogTaskDetail(
|
||||||
|
target?:
|
||||||
|
| Pick<Api.Project.ProjectTask, 'id' | 'projectId' | 'executionId'>
|
||||||
|
| Pick<Api.Project.MyTaskItem, 'id' | 'projectId' | 'executionId'>
|
||||||
|
| null
|
||||||
|
) {
|
||||||
|
const task = target ?? taskWorklogTask.value;
|
||||||
|
if (!task?.executionId) return;
|
||||||
|
|
||||||
|
const { error, data } = await fetchGetProjectTask(task.projectId, task.executionId, task.id);
|
||||||
|
if (error || !data) return;
|
||||||
|
|
||||||
|
if (taskWorklogTask.value?.id === data.id) {
|
||||||
|
taskWorklogTask.value = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 填报:工时弹层需要完整任务详情(负责人/状态/日期),按需拉一次详情再打开
|
// 填报:工时弹层需要完整任务详情(负责人/状态/日期),按需拉一次详情再打开
|
||||||
async function openTaskWorklog(item: WorkbenchTodoItem) {
|
async function openTaskWorklog(item: WorkbenchTodoItem) {
|
||||||
const row = findMyTaskRow(item);
|
const row = findMyTaskRow(item);
|
||||||
@@ -378,6 +443,9 @@ async function openTaskWorklog(item: WorkbenchTodoItem) {
|
|||||||
async function handleTaskWorklogChanged(payload: WorklogChangedPayload) {
|
async function handleTaskWorklogChanged(payload: WorklogChangedPayload) {
|
||||||
notifyWorklogChanged();
|
notifyWorklogChanged();
|
||||||
await loadMyTaskItems();
|
await loadMyTaskItems();
|
||||||
|
if (taskWorklogTask.value?.id === payload.taskId) {
|
||||||
|
await refreshTaskWorklogTaskDetail();
|
||||||
|
}
|
||||||
|
|
||||||
// 与任务工作区联动一致:进度填到 100 且我是任务负责人时提示完成(仅单任务,不做级联)
|
// 与任务工作区联动一致:进度填到 100 且我是任务负责人时提示完成(仅单任务,不做级联)
|
||||||
if (payload.mode === 'delete' || payload.progressRate !== 100) return;
|
if (payload.mode === 'delete' || payload.progressRate !== 100) return;
|
||||||
@@ -459,6 +527,9 @@ async function submitTaskStatusChange(reason: string | null) {
|
|||||||
taskStatusVisible.value = false;
|
taskStatusVisible.value = false;
|
||||||
window.$message?.success(`${action.actionName}成功`);
|
window.$message?.success(`${action.actionName}成功`);
|
||||||
await loadMyTaskItems();
|
await loadMyTaskItems();
|
||||||
|
if (taskWorklogTask.value?.id === row.id) {
|
||||||
|
await refreshTaskWorklogTaskDetail(row);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabCounts = computed(() => {
|
const tabCounts = computed(() => {
|
||||||
@@ -733,6 +804,9 @@ function mapTaskTodoPriority(priority: string) {
|
|||||||
async function loadMyTaskItems() {
|
async function loadMyTaskItems() {
|
||||||
const { error, data } = await fetchGetMyTaskPage({ pageNo: 1, pageSize: -1 });
|
const { error, data } = await fetchGetMyTaskPage({ pageNo: 1, pageSize: -1 });
|
||||||
|
|
||||||
|
taskPreviewMap.value = {};
|
||||||
|
taskPreviewLoadingIds.value = [];
|
||||||
|
|
||||||
if (error || !data) {
|
if (error || !data) {
|
||||||
myTaskRows.value = [];
|
myTaskRows.value = [];
|
||||||
myTaskItems.value = [];
|
myTaskItems.value = [];
|
||||||
@@ -756,7 +830,7 @@ async function loadMyTaskItems() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 待办口径:未到终态的个人事项(pending / active / paused),terminal 态(completed / cancelled)不进待办
|
// 待办口径:未到终态的我的事项(pending / active / paused),terminal 态(completed / cancelled)不进待办
|
||||||
const PERSONAL_TODO_STATUSES: Api.PersonalItem.PersonalItemStatusCode[] = ['pending', 'active', 'paused'];
|
const PERSONAL_TODO_STATUSES: Api.PersonalItem.PersonalItemStatusCode[] = ['pending', 'active', 'paused'];
|
||||||
|
|
||||||
async function loadPersonalTodoItems() {
|
async function loadPersonalTodoItems() {
|
||||||
@@ -777,7 +851,7 @@ async function loadPersonalTodoItems() {
|
|||||||
title: item.taskTitle,
|
title: item.taskTitle,
|
||||||
createdTime: item.createTime,
|
createdTime: item.createTime,
|
||||||
deadline: item.plannedEndDate,
|
deadline: item.plannedEndDate,
|
||||||
source: '个人事项',
|
source: '我的事项',
|
||||||
progressRate: item.progressRate,
|
progressRate: item.progressRate,
|
||||||
routeKey: 'personal-center_my-item'
|
routeKey: 'personal-center_my-item'
|
||||||
}))
|
}))
|
||||||
@@ -1061,7 +1135,7 @@ onActivated(refresh);
|
|||||||
|
|
||||||
<button type="button" class="workbench-todo__add" @click="handleOpenAdd">
|
<button type="button" class="workbench-todo__add" @click="handleOpenAdd">
|
||||||
<SvgIcon icon="mdi:plus" class="workbench-todo__add-icon" />
|
<SvgIcon icon="mdi:plus" class="workbench-todo__add-icon" />
|
||||||
<span>个人事项</span>
|
<span>我的事项</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1179,6 +1253,15 @@ onActivated(refresh);
|
|||||||
|
|
||||||
<div class="workbench-todo__body">
|
<div class="workbench-todo__body">
|
||||||
<h4 class="workbench-todo__item-title">
|
<h4 class="workbench-todo__item-title">
|
||||||
|
<ElPopover
|
||||||
|
v-if="canPreviewTaskInfo(item)"
|
||||||
|
placement="right-start"
|
||||||
|
trigger="hover"
|
||||||
|
:width="960"
|
||||||
|
popper-class="workbench-task-preview-popover"
|
||||||
|
@show="handleTaskPreviewShow(item)"
|
||||||
|
>
|
||||||
|
<template #reference>
|
||||||
<span
|
<span
|
||||||
class="workbench-todo__item-title-text"
|
class="workbench-todo__item-title-text"
|
||||||
:class="{
|
:class="{
|
||||||
@@ -1190,6 +1273,32 @@ onActivated(refresh);
|
|||||||
>
|
>
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
</span>
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="workbench-task-preview">
|
||||||
|
<div class="workbench-task-preview__header">任务信息</div>
|
||||||
|
<div v-loading="isTaskPreviewLoading(item)" class="workbench-task-preview__body">
|
||||||
|
<TaskInfoReadonly
|
||||||
|
v-if="getTaskPreviewTask(item)"
|
||||||
|
:task="getTaskPreviewTask(item)"
|
||||||
|
:task-options="getTaskPreviewTaskOptions(item)"
|
||||||
|
/>
|
||||||
|
<ElEmpty v-else description="暂无任务信息" :image-size="56" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ElPopover>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="workbench-todo__item-title-text"
|
||||||
|
:class="{
|
||||||
|
'workbench-todo__item-title-text--clickable': Boolean(
|
||||||
|
item.routeKey || item.approvalBizType || item.projectId
|
||||||
|
)
|
||||||
|
}"
|
||||||
|
@click="handleClickItem(item)"
|
||||||
|
>
|
||||||
|
{{ item.title }}
|
||||||
|
</span>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="workbench-todo__meta">
|
<div class="workbench-todo__meta">
|
||||||
<span class="workbench-todo__source">{{ item.source }}</span>
|
<span class="workbench-todo__source">{{ item.source }}</span>
|
||||||
@@ -1212,16 +1321,16 @@ onActivated(refresh);
|
|||||||
|
|
||||||
<div class="workbench-todo__trailing">
|
<div class="workbench-todo__trailing">
|
||||||
<div v-if="item.approvalBizType === 'overtime_application'" class="workbench-todo__actions" @click.stop>
|
<div v-if="item.approvalBizType === 'overtime_application'" class="workbench-todo__actions" @click.stop>
|
||||||
<ElTooltip content="详情">
|
<ElTooltip content="审批">
|
||||||
<ElButton link type="primary" class="workbench-todo__action-btn" @click="openOvertimeDetail(item)">
|
<ElButton link type="primary" class="workbench-todo__action-btn" @click="openOvertimeDetail(item)">
|
||||||
<component :is="OVERTIME_APPROVAL_ACTION_ICONS.detail" class="text-15px" />
|
<component :is="APPROVAL_ENTRY_ICON" class="text-15px" />
|
||||||
</ElButton>
|
</ElButton>
|
||||||
</ElTooltip>
|
</ElTooltip>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="item.approvalBizType === 'performance'" class="workbench-todo__actions" @click.stop>
|
<div v-else-if="item.approvalBizType === 'performance'" class="workbench-todo__actions" @click.stop>
|
||||||
<ElTooltip content="查看绩效">
|
<ElTooltip content="审批">
|
||||||
<ElButton link type="primary" class="workbench-todo__action-btn" @click="openPerformanceDetail(item)">
|
<ElButton link type="primary" class="workbench-todo__action-btn" @click="openPerformanceDetail(item)">
|
||||||
<IconMdiEyeOutline class="text-15px" />
|
<component :is="APPROVAL_ENTRY_ICON" class="text-15px" />
|
||||||
</ElButton>
|
</ElButton>
|
||||||
</ElTooltip>
|
</ElTooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -1230,9 +1339,9 @@ onActivated(refresh);
|
|||||||
class="workbench-todo__actions"
|
class="workbench-todo__actions"
|
||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
<ElTooltip content="详情">
|
<ElTooltip content="审批">
|
||||||
<ElButton link type="primary" class="workbench-todo__action-btn" @click="openWorkReportDetail(item)">
|
<ElButton link type="primary" class="workbench-todo__action-btn" @click="openWorkReportDetail(item)">
|
||||||
<component :is="OVERTIME_APPROVAL_ACTION_ICONS.detail" class="text-15px" />
|
<component :is="APPROVAL_ENTRY_ICON" class="text-15px" />
|
||||||
</ElButton>
|
</ElButton>
|
||||||
</ElTooltip>
|
</ElTooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -1241,7 +1350,7 @@ onActivated(refresh);
|
|||||||
class="workbench-todo__actions"
|
class="workbench-todo__actions"
|
||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
<ElTooltip content="填报工时">
|
<ElTooltip content="填报">
|
||||||
<ElButton link type="primary" class="workbench-todo__action-btn" @click="openTaskWorklog(item)">
|
<ElButton link type="primary" class="workbench-todo__action-btn" @click="openTaskWorklog(item)">
|
||||||
<component :is="PERSONAL_ACTION_ICONS.worklog" class="text-15px" />
|
<component :is="PERSONAL_ACTION_ICONS.worklog" class="text-15px" />
|
||||||
</ElButton>
|
</ElButton>
|
||||||
@@ -1723,6 +1832,32 @@ onActivated(refresh);
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.workbench-task-preview-popover) {
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workbench-task-preview {
|
||||||
|
background: var(--el-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.workbench-task-preview__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 48px;
|
||||||
|
padding: 0 18px;
|
||||||
|
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.workbench-task-preview__body {
|
||||||
|
max-height: min(72vh, 760px);
|
||||||
|
padding: 18px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.workbench-todo__meta {
|
.workbench-todo__meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
Reference in New Issue
Block a user