!10 czh-20260521-更改计划名称以及优化新增分组流程

Merge pull request !10 from 陈志浩/feature/tasklist
This commit is contained in:
陈志浩
2026-05-21 11:18:56 +00:00
committed by Gitee
16 changed files with 325 additions and 170 deletions
+8
View File
@@ -31,6 +31,14 @@ const dashboard: AppRouteModule = {
title: t('routes.dashboard.workbench'), title: t('routes.dashboard.workbench'),
}, },
}, },
{
path: 'processPortal',
name: 'ProcessPortal',
component: () => import('/@/views/dashboard/ProcessPortal/index.vue'),
meta: {
title: '流程门户',
},
},
], ],
}; };
+1 -1
View File
@@ -276,7 +276,7 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) {
// authenticationScheme: 'Bearer', // authenticationScheme: 'Bearer',
authenticationScheme: '', authenticationScheme: '',
//接口超时设置 //接口超时设置
timeout: 10 * 1000, timeout: 20 * 1000,
// 基础接口地址 // 基础接口地址
// baseURL: globSetting.apiUrl, // baseURL: globSetting.apiUrl,
headers: { 'Content-Type': ContentTypeEnum.JSON }, headers: { 'Content-Type': ContentTypeEnum.JSON },
+5 -5
View File
@@ -124,15 +124,15 @@ export const deleteTask = (params, handleSuccess) => {
export const toggleTaskStatus = (params) => defHttp.post({ url: Api.toggleTaskStatus, params }); export const toggleTaskStatus = (params) => defHttp.post({ url: Api.toggleTaskStatus, params });
export const getCollaborators = (params) => defHttp.get({ url: Api.getCollaborators, params }); export const getCollaborators = (params, options?: Record<string, any>) => defHttp.get({ url: Api.getCollaborators, params }, options);
export const addCollaborator = (params) => defHttp.post({ url: Api.addCollaborator, params }); export const addCollaborator = (params, options?) => defHttp.post({ url: Api.addCollaborator, params }, options);
export const removeCollaborator = (params) => defHttp.post({ url: Api.removeCollaborator, params }); export const removeCollaborator = (params, options?) => defHttp.post({ url: Api.removeCollaborator, params }, options);
export const updateCollaboratorPermission = (params) => defHttp.post({ url: Api.updateCollaboratorPermission, params }); export const updateCollaboratorPermission = (params, options?) => defHttp.post({ url: Api.updateCollaboratorPermission, params }, options);
export const getMyPermission = (params) => defHttp.get({ url: Api.getMyPermission, params }); export const getMyPermission = (params, options?) => defHttp.get({ url: Api.getMyPermission, params }, options);
export const moveTask = (params) => defHttp.post({ url: Api.moveTask, params }); export const moveTask = (params) => defHttp.post({ url: Api.moveTask, params });
+17 -17
View File
@@ -6,7 +6,7 @@ import { getAvailableSecretLevels } from './types';
export const columns: BasicColumn[] = [ export const columns: BasicColumn[] = [
{ {
title: '任务清单名称', title: '计划名称',
align: 'center', align: 'center',
dataIndex: 'tasklistName', dataIndex: 'tasklistName',
}, },
@@ -35,7 +35,7 @@ export const searchFormSchema: FormSchema[] = [];
export const formSchema: FormSchema[] = [ export const formSchema: FormSchema[] = [
{ {
label: '任务清单名称', label: '计划名称',
field: 'tasklistName', field: 'tasklistName',
component: 'Input', component: 'Input',
}, },
@@ -93,7 +93,7 @@ export const taskListDetialColumns: JVxeColumn[] = [
defaultValue: '', defaultValue: '',
}, },
{ {
title: '任务名称', title: '事项名称',
key: 'taskName', key: 'taskName',
type: JVxeTypes.input, type: JVxeTypes.input,
width: '200px', width: '200px',
@@ -101,7 +101,7 @@ export const taskListDetialColumns: JVxeColumn[] = [
defaultValue: '', defaultValue: '',
}, },
{ {
title: '任务描述', title: '事项描述',
key: 'taskDesc', key: 'taskDesc',
type: JVxeTypes.input, type: JVxeTypes.input,
width: '200px', width: '200px',
@@ -183,7 +183,7 @@ export const taskListDetialColumns: JVxeColumn[] = [
defaultValue: '', defaultValue: '',
}, },
{ {
title: '子任务数', title: '子事项数',
key: 'subTaskCount', key: 'subTaskCount',
type: JVxeTypes.inputNumber, type: JVxeTypes.inputNumber,
width: '200px', width: '200px',
@@ -191,7 +191,7 @@ export const taskListDetialColumns: JVxeColumn[] = [
defaultValue: '', defaultValue: '',
}, },
{ {
title: '子任务完成数', title: '子事项完成数',
key: 'completedSubTaskCount', key: 'completedSubTaskCount',
type: JVxeTypes.inputNumber, type: JVxeTypes.inputNumber,
width: '200px', width: '200px',
@@ -201,21 +201,21 @@ export const taskListDetialColumns: JVxeColumn[] = [
]; ];
export const superQuerySchema = { export const superQuerySchema = {
tasklistName: { title: '任务清单名称', order: 0, view: 'text', type: 'string' }, tasklistName: { title: '计划名称', order: 0, view: 'text', type: 'string' },
pid: { title: '父级节点', order: 1, view: 'text', type: 'string' }, pid: { title: '父级节点', order: 1, view: 'text', type: 'string' },
hasChild: { title: '是否有子节点', order: 2, view: 'text', type: 'string' }, hasChild: { title: '是否有子节点', order: 2, view: 'text', type: 'string' },
sortOrder: { title: '排序号', order: 3, view: 'number', type: 'number' }, sortOrder: { title: '排序号', order: 3, view: 'number', type: 'number' },
type: { title: '类型', order: 4, view: 'text', type: 'string' }, type: { title: '类型', order: 4, view: 'text', type: 'string' },
taskListDetial: { taskListDetial: {
title: '工作任务清单详情', title: '计划详情',
view: 'table', view: 'table',
fields: { fields: {
mainId: { title: '外键', order: 0, view: 'text', type: 'string' }, mainId: { title: '外键', order: 0, view: 'text', type: 'string' },
pid: { title: '父节点ID', order: 1, view: 'text', type: 'string' }, pid: { title: '父节点ID', order: 1, view: 'text', type: 'string' },
hasChild: { title: '是否有子节点', order: 2, view: 'text', type: 'string' }, hasChild: { title: '是否有子节点', order: 2, view: 'text', type: 'string' },
sortOrder: { title: '排序号', order: 3, view: 'number', type: 'number' }, sortOrder: { title: '排序号', order: 3, view: 'number', type: 'number' },
taskName: { title: '任务名称', order: 4, view: 'text', type: 'string' }, taskName: { title: '事项名称', order: 4, view: 'text', type: 'string' },
taskDesc: { title: '任务描述', order: 5, view: 'text', type: 'string' }, taskDesc: { title: '事项描述', order: 5, view: 'text', type: 'string' },
priority: { title: '优先级', order: 6, view: 'list', type: 'string', dictCode: 'task_priority' }, priority: { title: '优先级', order: 6, view: 'list', type: 'string', dictCode: 'task_priority' },
taskStatus: { title: '完成状态', order: 7, view: 'number', type: 'number' }, taskStatus: { title: '完成状态', order: 7, view: 'number', type: 'number' },
assigneeId: { title: '负责人ID', order: 8, view: 'text', type: 'string' }, assigneeId: { title: '负责人ID', order: 8, view: 'text', type: 'string' },
@@ -225,8 +225,8 @@ export const superQuerySchema = {
endTime: { title: '结束时间', order: 12, view: 'datetime', type: 'string' }, endTime: { title: '结束时间', order: 12, view: 'datetime', type: 'string' },
completeTime: { title: '完成时间', order: 13, view: 'datetime', type: 'string' }, completeTime: { title: '完成时间', order: 13, view: 'datetime', type: 'string' },
type: { title: '类型', order: 14, view: 'text', type: 'string' }, type: { title: '类型', order: 14, view: 'text', type: 'string' },
subTaskCount: { title: '子任务数', order: 15, view: 'number', type: 'number' }, subTaskCount: { title: '子事项数', order: 15, view: 'number', type: 'number' },
completedSubTaskCount: { title: '子任务完成数', order: 16, view: 'number', type: 'number' }, completedSubTaskCount: { title: '子事项完成数', order: 16, view: 'number', type: 'number' },
}, },
}, },
}; };
@@ -237,13 +237,13 @@ export function getBpmFormSchema(_formData): FormSchema[] {
export const createTaskFormSchema: FormSchema[] = [ export const createTaskFormSchema: FormSchema[] = [
{ {
label: '任务标题', label: '事项标题',
field: 'taskName', field: 'taskName',
component: 'Input', component: 'Input',
required: true, required: true,
}, },
{ {
label: '任务描述', label: '事项描述',
field: 'taskDesc', field: 'taskDesc',
component: 'InputTextArea', component: 'InputTextArea',
componentProps: { rows: 3 }, componentProps: { rows: 3 },
@@ -305,12 +305,12 @@ export function getCreateListFormSchema(userSecurityLevel: number): FormSchema[]
if (!userSecurityLevel) userSecurityLevel = 3; if (!userSecurityLevel) userSecurityLevel = 3;
return [ return [
{ {
label: '清单名称', label: '计划名称',
field: 'tasklistName', field: 'tasklistName',
component: 'Input', component: 'Input',
required: true, required: true,
componentProps: { componentProps: {
placeholder: '请输入清单名称', placeholder: '请输入计划名称',
}, },
}, },
{ {
@@ -331,7 +331,7 @@ export function getCreateListFormSchema(userSecurityLevel: number): FormSchema[]
export const mockTaskListGroups: TaskListGroup[] = [ export const mockTaskListGroups: TaskListGroup[] = [
{ {
id: 'tlg-1', id: 'tlg-1',
name: '任务清单', name: '计划管理',
pid: '0', pid: '0',
hasChild: '1', hasChild: '1',
type: 0, type: 0,
@@ -103,7 +103,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, reactive, computed } from 'vue'; import { ref, reactive, computed, nextTick } from 'vue';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import Icon from '/@/components/Icon/index'; import Icon from '/@/components/Icon/index';
import SzUserSelect from '/@/components/semri/userComponent/SzUserSelect.vue'; import SzUserSelect from '/@/components/semri/userComponent/SzUserSelect.vue';
@@ -135,7 +135,7 @@
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
changed: []; changed: [collaboratorNames: string];
}>(); }>();
const visible = ref(false); const visible = ref(false);
@@ -147,6 +147,8 @@
permission: '2', permission: '2',
}); });
const pendingAddList = ref<PendingAddItem[]>([]); const pendingAddList = ref<PendingAddItem[]>([]);
const pendingRemoveIds = ref<Set<string>>(new Set());
const pendingPermissionMap = ref<Map<string, string>>(new Map());
const allMembers = computed(() => { const allMembers = computed(() => {
const pendingAsCollab: CollaboratorItem[] = pendingAddList.value.map((p) => ({ const pendingAsCollab: CollaboratorItem[] = pendingAddList.value.map((p) => ({
@@ -155,7 +157,22 @@
username: p.username, username: p.username,
permission: p.permission, permission: p.permission,
})); }));
return [...collaborators.value, ...pendingAsCollab]; return [...collaborators.value, ...pendingAsCollab]
.filter((m) => (m.permission !== '1' ? !pendingRemoveIds.value.has(m.permissionId) : true))
.map((m) => {
if (m.permissionId && pendingPermissionMap.value.has(m.permissionId)) {
return { ...m, permission: pendingPermissionMap.value.get(m.permissionId)! };
}
return m;
});
});
const collaboratorNamesSnapshot = computed(() => {
return allMembers.value
.filter((m) => m.permission !== '1')
.map((m) => m.username)
.filter(Boolean)
.join(',');
}); });
function getColor(name: string): string { function getColor(name: string): string {
@@ -163,11 +180,15 @@
} }
async function open() { async function open() {
visible.value = true; collaborators.value = [];
showAddPanel.value = false; showAddPanel.value = false;
pendingAddList.value = []; pendingAddList.value = [];
pendingRemoveIds.value.clear();
pendingPermissionMap.value.clear();
addForm.userIds = ''; addForm.userIds = '';
addForm.permission = '2'; addForm.permission = '2';
visible.value = true;
await nextTick();
await loadCollaborators(); await loadCollaborators();
} }
@@ -177,11 +198,14 @@
addForm.userIds = ''; addForm.userIds = '';
addForm.permission = '2'; addForm.permission = '2';
pendingAddList.value = []; pendingAddList.value = [];
pendingRemoveIds.value.clear();
pendingPermissionMap.value.clear();
} }
async function loadCollaborators() { async function loadCollaborators() {
try { try {
const data: any[] = await getCollaborators({ taskListId: props.taskListId }); const options = { successMessageMode: 'none' as const };
const data: any[] = await getCollaborators({ taskListId: props.taskListId }, options);
collaborators.value = data; collaborators.value = data;
} catch (e) { } catch (e) {
console.error('[CollaboratorModal] load failed', e); console.error('[CollaboratorModal] load failed', e);
@@ -196,7 +220,7 @@
const secretLevel = props.secretLevel || 1; const secretLevel = props.secretLevel || 1;
const { ok, invalidNames } = await checkUserSecLevel(addForm.userIds, secretLevel); const { ok, invalidNames } = await checkUserSecLevel(addForm.userIds, secretLevel);
if (!ok) { if (!ok) {
message.warning(`以下人员的密级不满足当前清单(${props.secretText || '非密'})要求:${invalidNames.join('、')}`); message.warning(`以下人员的密级不满足当前计划(${props.secretText || '非密'})要求:${invalidNames.join('、')}`);
return; return;
} }
@@ -216,7 +240,6 @@
const existingIds = new Set(collaborators.value.map((c) => c.userId)); const existingIds = new Set(collaborators.value.map((c) => c.userId));
const pendingIds = new Set(pendingAddList.value.map((p) => p.userId)); const pendingIds = new Set(pendingAddList.value.map((p) => p.userId));
let addedCount = 0;
for (const uid of ids) { for (const uid of ids) {
if (!existingIds.has(uid) && !pendingIds.has(uid)) { if (!existingIds.has(uid) && !pendingIds.has(uid)) {
pendingAddList.value.push({ pendingAddList.value.push({
@@ -224,72 +247,82 @@
username: nameMap[uid] || uid, username: nameMap[uid] || uid,
permission: addForm.permission, permission: addForm.permission,
}); });
addedCount++;
} }
} }
if (addedCount > 0) {
createMessage.success(`已添加 ${addedCount} 位协作人`);
} else {
createMessage.warning('所选用户均已是协作人');
}
addForm.userIds = ''; addForm.userIds = '';
addForm.permission = '2'; addForm.permission = '2';
showAddPanel.value = false; showAddPanel.value = false;
} }
async function onDone() { function onDone() {
if (pendingAddList.value.length > 0) { const hasChanges =
let successCount = 0; pendingAddList.value.length > 0 || pendingRemoveIds.value.size > 0 || pendingPermissionMap.value.size > 0;
let failCount = 0;
for (const item of pendingAddList.value) {
try {
await addCollaborator({
taskListId: props.taskListId,
userId: item.userId,
permission: item.permission,
});
successCount++;
} catch {
failCount++;
}
}
if (successCount > 0) { const itemsToAdd = [...pendingAddList.value];
createMessage.success(`成功添加 ${successCount} 位协作人${failCount > 0 ? `${failCount} 位失败` : ''}`); const idsToRemove = [...pendingRemoveIds.value];
} else { const permsToChange = [...pendingPermissionMap.value];
createMessage.warning('添加协作人失败'); const listId = props.taskListId;
}
pendingAddList.value = []; pendingAddList.value = [];
emit('changed'); pendingRemoveIds.value.clear();
} pendingPermissionMap.value.clear();
visible.value = false; visible.value = false;
showAddPanel.value = false; showAddPanel.value = false;
addForm.userIds = ''; addForm.userIds = '';
addForm.permission = '2'; addForm.permission = '2';
if (!hasChanges) return;
const suppressOptions = { successMessageMode: 'none' as const };
let errorMsg = '';
(async () => {
for (const item of itemsToAdd) {
try {
await addCollaborator(
{ taskListId: listId, userId: item.userId, permission: item.permission },
suppressOptions,
);
} catch {
errorMsg = '部分协作人添加失败';
}
}
for (const permissionId of idsToRemove) {
try {
await removeCollaborator({ permissionId }, suppressOptions);
} catch {
errorMsg = '部分协作人移除失败';
}
}
for (const [permissionId, newPermission] of permsToChange) {
try {
await updateCollaboratorPermission({ permissionId, permission: newPermission }, suppressOptions);
} catch {
errorMsg = '部分权限修改失败';
}
}
await loadCollaborators();
emit('changed', collaboratorNamesSnapshot.value);
if (errorMsg) {
createMessage.warning(errorMsg);
} else {
createMessage.success('编辑成功');
}
})();
} }
async function onRemove(permissionId: string) { function onRemove(permissionId: string) {
try { pendingRemoveIds.value.add(permissionId);
await removeCollaborator({ permissionId });
await loadCollaborators();
emit('changed');
} catch (e: any) {
createMessage.error(e?.message || '移除失败');
}
} }
async function onPermissionChange(permissionId: string, newPermission: string) { function onPermissionChange(permissionId: string, newPermission: string) {
try { pendingPermissionMap.value.set(permissionId, newPermission);
await updateCollaboratorPermission({ permissionId, permission: newPermission });
await loadCollaborators();
emit('changed');
} catch (e: any) {
createMessage.error(e?.message || '修改失败');
}
} }
defineExpose({ open }); defineExpose({ open });
@@ -1,5 +1,5 @@
<template> <template>
<BasicModal v-bind="$attrs" @register="registerModal" destroy-on-close title="创建清单" :width="500" @ok="handleSubmit"> <BasicModal v-bind="$attrs" @register="registerModal" destroy-on-close title="创建计划" :width="500" @ok="handleSubmit">
<BasicForm @register="registerForm" /> <BasicForm @register="registerForm" />
</BasicModal> </BasicModal>
</template> </template>
@@ -1,5 +1,5 @@
<template> <template>
<BasicModal v-bind="$attrs" @register="registerModal" destroy-on-close title="创建任务" :width="600" @ok="handleSubmit"> <BasicModal v-bind="$attrs" @register="registerModal" destroy-on-close title="创建事项" :width="600" @ok="handleSubmit">
<BasicForm @register="registerForm" /> <BasicForm @register="registerForm" />
<div class="attachment-row"> <div class="attachment-row">
<span class="attachment-label">附件</span> <span class="attachment-label">附件</span>
@@ -58,7 +58,7 @@
if (values.assigneeId) { if (values.assigneeId) {
const { ok, invalidNames } = await checkUserSecLevel(values.assigneeId, currentSecretLevel.value); const { ok, invalidNames } = await checkUserSecLevel(values.assigneeId, currentSecretLevel.value);
if (!ok) { if (!ok) {
message.warning(`以下人员的密级不满足当前清单(${currentSecretText.value})要求:${invalidNames.join('、')}`); message.warning(`以下人员的密级不满足当前计划(${currentSecretText.value})要求:${invalidNames.join('、')}`);
return; return;
} }
} }
+63 -19
View File
@@ -2,7 +2,7 @@
<div class="task-content"> <div class="task-content">
<div class="content-header"> <div class="content-header">
<template v-if="showListGroupView"> <template v-if="showListGroupView">
<h2 class="content-title">任务清单</h2> <h2 class="content-title">计划列表</h2>
</template> </template>
<template v-else> <template v-else>
<div class="content-header-left"> <div class="content-header-left">
@@ -16,7 +16,7 @@
@blur="confirmHeaderRename" @blur="confirmHeaderRename"
/> />
</div> </div>
<h2 v-else class="content-title">{{ currentList?.name || '请选择清单' }}</h2> <h2 v-else class="content-title">{{ currentList?.name || '请选择计划' }}</h2>
<a-dropdown v-if="currentList && canShowHeaderMore" :trigger="['click']"> <a-dropdown v-if="currentList && canShowHeaderMore" :trigger="['click']">
<a-button type="text" size="small" class="header-more-btn"> <a-button type="text" size="small" class="header-more-btn">
<Icon icon="ant-design:ellipsis-outlined" /> <Icon icon="ant-design:ellipsis-outlined" />
@@ -39,7 +39,7 @@
<span v-if="currentList.secretText" class="header-secret-badge" :style="{ color: SECRET_LEVEL_COLOR[currentList.secretLevel || 1] || '#8c8c8c', borderColor: SECRET_LEVEL_COLOR[currentList.secretLevel || 1] || '#8c8c8c' }"> <span v-if="currentList.secretText" class="header-secret-badge" :style="{ color: SECRET_LEVEL_COLOR[currentList.secretLevel || 1] || '#8c8c8c', borderColor: SECRET_LEVEL_COLOR[currentList.secretLevel || 1] || '#8c8c8c' }">
{{ currentList.secretText }} {{ currentList.secretText }}
</span> </span>
<div class="collaborator-bar" @click="emit('open-collaborator')"> <div class="collaborator-bar" :class="{ disabled: !_isOwner }" @click="_isOwner && emit('open-collaborator')">
<AvatarDisplay <AvatarDisplay
v-if="collaboratorDisplayNames" v-if="collaboratorDisplayNames"
:names="collaboratorDisplayNames" :names="collaboratorDisplayNames"
@@ -60,7 +60,7 @@
<a-dropdown> <a-dropdown>
<template #overlay> <template #overlay>
<a-menu @click="onStatusFilterChange"> <a-menu @click="onStatusFilterChange">
<a-menu-item key="all" :class="{ 'ant-dropdown-menu-item-selected': statusFilter === 'all' }">全部清单</a-menu-item> <a-menu-item key="all" :class="{ 'ant-dropdown-menu-item-selected': statusFilter === 'all' }">全部</a-menu-item>
<a-menu-item key="completed" :class="{ 'ant-dropdown-menu-item-selected': statusFilter === 'completed' }">已完成</a-menu-item> <a-menu-item key="completed" :class="{ 'ant-dropdown-menu-item-selected': statusFilter === 'completed' }">已完成</a-menu-item>
<a-menu-item key="uncompleted" :class="{ 'ant-dropdown-menu-item-selected': statusFilter === 'uncompleted' }">未完成</a-menu-item> <a-menu-item key="uncompleted" :class="{ 'ant-dropdown-menu-item-selected': statusFilter === 'uncompleted' }">未完成</a-menu-item>
</a-menu> </a-menu>
@@ -269,13 +269,13 @@
</template> </template>
<span v-else class="field-placeholder">-</span> <span v-else class="field-placeholder">-</span>
</div> </div>
<div class="list-group-col list-group-col-collaborators"> <div class="list-group-col list-group-col-collaborators clickable" @click.stop="emit('open-collaborator-for-list', list.id)">
<template v-if="getAllCollaborators(list)"> <template v-if="getAllCollaborators(list)">
<a-tooltip :title="getAllCollaborators(list)" placement="top" <a-tooltip title="点击管理协作者" placement="top">
><AvatarDisplay :names="getAllCollaborators(list)" :size="24" :show-name="false" <AvatarDisplay :names="getAllCollaborators(list)" :size="24" :show-name="getCollaboratorCount(list) === 1" />
/></a-tooltip> </a-tooltip>
</template> </template>
<span v-else class="field-placeholder">-</span> <Icon v-else icon="tasklist-add-user|svg" class="collab-add-icon" />
</div> </div>
<div class="list-group-col list-group-col-secret"> <div class="list-group-col list-group-col-secret">
<span <span
@@ -293,7 +293,7 @@
</div> </div>
<div v-if="allTaskLists.length === 0" class="empty-state"> <div v-if="allTaskLists.length === 0" class="empty-state">
<Icon icon="ant-design:inbox-outlined" class="empty-icon" /> <Icon icon="ant-design:inbox-outlined" class="empty-icon" />
<p>暂无任务清单</p> <p>暂无可见计划</p>
</div> </div>
</div> </div>
</template> </template>
@@ -303,7 +303,7 @@
<div class="table-col table-col-drag-handle"></div> <div class="table-col table-col-drag-handle"></div>
<div class="table-col table-col-title"> <div class="table-col table-col-title">
<Icon icon="tasklist-title|svg" class="header-icon" /> <Icon icon="tasklist-title|svg" class="header-icon" />
<span class="header-label">任务标题</span> <span class="header-label">事项标题</span>
</div> </div>
<template v-for="field in visibleFieldConfigs" :key="field.key"> <template v-for="field in visibleFieldConfigs" :key="field.key">
<div class="table-col header-col" :style="{ width: fieldWidthMap[field.key] || '100px' }"> <div class="table-col header-col" :style="{ width: fieldWidthMap[field.key] || '100px' }">
@@ -387,7 +387,7 @@
<span <span
v-if="canEdit && groupDimension === 'custom'" v-if="canEdit && groupDimension === 'custom'"
class="group-action-btn group-action-add" class="group-action-btn group-action-add"
title="新建任务" title="新建事项"
@click.stop="showNewTaskInput = group.id" @click.stop="showNewTaskInput = group.id"
> >
<Icon icon="ant-design:plus-outlined" /> <Icon icon="ant-design:plus-outlined" />
@@ -744,7 +744,7 @@
ref="newTaskInputRef" ref="newTaskInputRef"
v-model:value="newTaskTitle" v-model:value="newTaskTitle"
size="small" size="small"
placeholder="输入任务标题,按回车确认..." placeholder="输入事项标题,按回车确认..."
autofocus autofocus
@press-enter="submitNewTask(group.id)" @press-enter="submitNewTask(group.id)"
@blur="submitNewTask(group.id)" @blur="submitNewTask(group.id)"
@@ -764,7 +764,7 @@
<span class="expand-arrow-placeholder"></span> <span class="expand-arrow-placeholder"></span>
</div> </div>
<Icon icon="ant-design:plus-outlined" class="new-task-plus" /> <Icon icon="ant-design:plus-outlined" class="new-task-plus" />
<span class="new-task-text">新建任务</span> <span class="new-task-text">新建事项</span>
</div> </div>
</div> </div>
</div> </div>
@@ -793,7 +793,7 @@
<div v-else class="empty-state"> <div v-else class="empty-state">
<Icon icon="ant-design:inbox-outlined" class="empty-icon" /> <Icon icon="ant-design:inbox-outlined" class="empty-icon" />
<p>请从左侧选择一个任务清单</p> <p>请从左侧选择一个计划</p>
</div> </div>
</template> </template>
</div> </div>
@@ -864,6 +864,7 @@
'update:list-group-tab': [value: string]; 'update:list-group-tab': [value: string];
'list-select': [listId: string]; 'list-select': [listId: string];
'open-collaborator': []; 'open-collaborator': [];
'open-collaborator-for-list': [listId: string];
'add-to-favorites': [taskListId: string]; 'add-to-favorites': [taskListId: string];
'remove-from-favorites': [favoriteId: string]; 'remove-from-favorites': [favoriteId: string];
'move-task': [taskId: string, targetPid: string, targetSortOrder: number]; 'move-task': [taskId: string, targetPid: string, targetSortOrder: number];
@@ -920,7 +921,7 @@
function showDragBlockedMessage() { function showDragBlockedMessage() {
import('ant-design-vue').then(({ message }) => { import('ant-design-vue').then(({ message }) => {
message.warning('子任务只能在当前父任务下移动'); message.warning('子事项只能在当前父事项下移动');
}); });
} }
@@ -981,6 +982,12 @@
return names.join(','); return names.join(',');
} }
function getCollaboratorCount(list: TaskList): number {
const names = getAllCollaborators(list);
if (!names) return 0;
return names.split(/[,,、]/).filter(Boolean).length;
}
function getFormattedCreateTime(list: TaskList): string { function getFormattedCreateTime(list: TaskList): string {
if (list.createTimeStr) { if (list.createTimeStr) {
return list.createTimeStr; return list.createTimeStr;
@@ -1137,6 +1144,10 @@
const canEdit = computed(() => props.currentList?.myPermission === '1' || props.currentList?.myPermission === '2'); const canEdit = computed(() => props.currentList?.myPermission === '1' || props.currentList?.myPermission === '2');
const isReadOnly = computed(() => props.currentList?.myPermission === '3'); const isReadOnly = computed(() => props.currentList?.myPermission === '3');
function isListOwner(list: TaskList): boolean {
return list.myPermission === '1';
}
const canShowHeaderMore = computed(() => { const canShowHeaderMore = computed(() => {
if (!props.currentList) return false; if (!props.currentList) return false;
const perm = props.currentList.myPermission; const perm = props.currentList.myPermission;
@@ -1162,7 +1173,7 @@
createConfirm({ createConfirm({
iconType: 'warning', iconType: 'warning',
title: '确认删除', title: '确认删除',
content: '删除任务清单后,其下所有任务也将被删除,确定要删除吗?', content: '删除计划后,其下所有事项也将被删除,确定要删除吗?',
okText: '确认', okText: '确认',
cancelText: '取消', cancelText: '取消',
onOk: () => { onOk: () => {
@@ -1238,7 +1249,7 @@
}; };
const statusFilterLabel = computed(() => { const statusFilterLabel = computed(() => {
const map: Record<StatusFilter, string> = { all: '全部清单', completed: '已完成', uncompleted: '未完成' }; const map: Record<StatusFilter, string> = { all: '全部', completed: '已完成', uncompleted: '未完成' };
return map[props.statusFilter]; return map[props.statusFilter];
}); });
@@ -1780,7 +1791,7 @@
createConfirm({ createConfirm({
iconType: 'warning', iconType: 'warning',
title: '确认删除', title: '确认删除',
content: '删除分组后,分组内的任务将合并到默认分组,确定要删除吗?', content: '删除分组后,分组内的事项将合并到默认分组,确定要删除吗?',
okText: '确认', okText: '确认',
cancelText: '取消', cancelText: '取消',
onOk: () => { onOk: () => {
@@ -1889,6 +1900,16 @@
border-color: #d0d0d0; border-color: #d0d0d0;
background: #fafafa; background: #fafafa;
} }
&.disabled {
cursor: not-allowed;
opacity: 0.5;
&:hover {
border-color: #e8e8e8;
background: transparent;
}
}
} }
.collab-btn { .collab-btn {
@@ -2676,6 +2697,29 @@
.list-group-col-collaborators { .list-group-col-collaborators {
width: 120px; width: 120px;
padding: 0 12px; padding: 0 12px;
display: flex;
align-items: center;
gap: 4px;
&.clickable {
cursor: pointer;
border-radius: 4px;
transition: background 0.15s;
&:hover {
background: #f0f5ff;
}
}
}
.collab-add-icon {
font-size: 20px;
color: #bfbfbf;
transition: color 0.15s;
.clickable:hover & {
color: #3370ff;
}
} }
.list-group-col-secret { .list-group-col-secret {
@@ -13,7 +13,7 @@
<div class="detail-toolbar"> <div class="detail-toolbar">
<a-button size="small" :type="taskData.completed ? 'primary' : 'default'" :disabled="isFullReadOnly" @click="toggleComplete"> <a-button size="small" :type="taskData.completed ? 'primary' : 'default'" :disabled="isFullReadOnly" @click="toggleComplete">
<Icon icon="ant-design:check-outlined" class="mr-1" /> <Icon icon="ant-design:check-outlined" class="mr-1" />
{{ taskData.completed ? '已完成' : '完成任务' }} {{ taskData.completed ? '已完成' : '完成事项' }}
</a-button> </a-button>
<div class="toolbar-spacer"></div> <div class="toolbar-spacer"></div>
<div class="toolbar-actions"> <div class="toolbar-actions">
@@ -38,7 +38,7 @@
<div v-if="parentTask && parentTask.completed" class="detail-parent-completed-notice"> <div v-if="parentTask && parentTask.completed" class="detail-parent-completed-notice">
<Icon icon="ant-design:info-circle-outlined" /> <Icon icon="ant-design:info-circle-outlined" />
<span>任务的上级任务已被完成</span> <span>事项的上级事项已被完成</span>
</div> </div>
<div class="detail-parent-nav" v-if="taskPath.length > 1"> <div class="detail-parent-nav" v-if="taskPath.length > 1">
@@ -59,7 +59,7 @@
size="large" size="large"
:bordered="true" :bordered="true"
class="detail-title-input" class="detail-title-input"
placeholder="请输入任务标题" placeholder="请输入事项标题"
@blur="onTitleBlur" @blur="onTitleBlur"
@press-enter="editingTitle = false" @press-enter="editingTitle = false"
/> />
@@ -254,12 +254,12 @@
<span class="feishu-subtask-icon"> <span class="feishu-subtask-icon">
<Icon icon="tasklist-subtask|svg" /> <Icon icon="tasklist-subtask|svg" />
</span> </span>
<span class="feishu-subtask-title">任务</span> <span class="feishu-subtask-title">事项</span>
<span v-if="subtaskProgress.text" class="feishu-subtask-count">{{ subtaskProgress.text }}</span> <span v-if="subtaskProgress.text" class="feishu-subtask-count">{{ subtaskProgress.text }}</span>
<div v-if="subtaskProgress.text" class="feishu-subtask-progress"> <div v-if="subtaskProgress.text" class="feishu-subtask-progress">
<div class="feishu-subtask-progress-bar" :style="{ width: subtaskProgress.percent + '%' }"></div> <div class="feishu-subtask-progress-bar" :style="{ width: subtaskProgress.percent + '%' }"></div>
</div> </div>
<span v-if="!canAddSubTask" class="feishu-subtask-limit">当前最多创建{{ MAX_TASK_DEPTH }}任务</span> <span v-if="!canAddSubTask" class="feishu-subtask-limit">当前最多创建{{ MAX_TASK_DEPTH }}事项</span>
<Icon v-if="canAddSubTask" :icon="subtaskExpanded ? 'ant-design:down-outlined' : 'ant-design:right-outlined'" class="feishu-subtask-arrow" /> <Icon v-if="canAddSubTask" :icon="subtaskExpanded ? 'ant-design:down-outlined' : 'ant-design:right-outlined'" class="feishu-subtask-arrow" />
</div> </div>
@@ -325,7 +325,7 @@
<a-input <a-input
v-model:value="newSubTaskName" v-model:value="newSubTaskName"
size="small" size="small"
placeholder="输入子任务标题按回车确认..." placeholder="输入子事项标题按回车确认..."
autofocus autofocus
class="feishu-subtask-input" class="feishu-subtask-input"
@press-enter="handleSubmitSubTask" @press-enter="handleSubmitSubTask"
@@ -334,7 +334,7 @@
</div> </div>
<div v-else-if="canAddSubTask && !isFullReadOnly" class="feishu-subtask-add" @click="showNewSubTaskInput = true"> <div v-else-if="canAddSubTask && !isFullReadOnly" class="feishu-subtask-add" @click="showNewSubTaskInput = true">
<Icon icon="ant-design:plus-outlined" class="feishu-subtask-add-icon" /> <Icon icon="ant-design:plus-outlined" class="feishu-subtask-add-icon" />
<span>添加子任务</span> <span>添加子事项</span>
</div> </div>
</template> </template>
</div> </div>
@@ -628,7 +628,7 @@
createConfirm({ createConfirm({
iconType: 'warning', iconType: 'warning',
title: '确认删除', title: '确认删除',
content: '删除后无法恢复,确定要删除这个任务吗?', content: '删除后无法恢复,确定要删除这个事项吗?',
okText: '确认', okText: '确认',
cancelText: '取消', cancelText: '取消',
onOk: () => { onOk: () => {
@@ -9,7 +9,7 @@
<a-dropdown> <a-dropdown>
<template #overlay> <template #overlay>
<a-menu @click="onStatusFilterChange"> <a-menu @click="onStatusFilterChange">
<a-menu-item key="all" :class="{ 'ant-dropdown-menu-item-selected': statusFilter === 'all' }">全部任务</a-menu-item> <a-menu-item key="all" :class="{ 'ant-dropdown-menu-item-selected': statusFilter === 'all' }">全部事项</a-menu-item>
<a-menu-item key="completed" :class="{ 'ant-dropdown-menu-item-selected': statusFilter === 'completed' }">已完成</a-menu-item> <a-menu-item key="completed" :class="{ 'ant-dropdown-menu-item-selected': statusFilter === 'completed' }">已完成</a-menu-item>
<a-menu-item key="uncompleted" :class="{ 'ant-dropdown-menu-item-selected': statusFilter === 'uncompleted' }">未完成</a-menu-item> <a-menu-item key="uncompleted" :class="{ 'ant-dropdown-menu-item-selected': statusFilter === 'uncompleted' }">未完成</a-menu-item>
</a-menu> </a-menu>
@@ -79,7 +79,7 @@
<div class="table-col table-col-drag-handle"></div> <div class="table-col table-col-drag-handle"></div>
<div class="table-col table-col-title"> <div class="table-col table-col-title">
<Icon icon="tasklist-title|svg" class="header-icon" /> <Icon icon="tasklist-title|svg" class="header-icon" />
<span class="header-label">任务标题</span> <span class="header-label">事项标题</span>
</div> </div>
<template v-for="field in visibleFieldConfigs" :key="field.key"> <template v-for="field in visibleFieldConfigs" :key="field.key">
<div class="table-col header-col" :style="{ width: fieldWidthMap[field.key] || '100px' }"> <div class="table-col header-col" :style="{ width: fieldWidthMap[field.key] || '100px' }">
@@ -331,7 +331,7 @@
<div v-if="flattenedTasks.length === 0" class="empty-state"> <div v-if="flattenedTasks.length === 0" class="empty-state">
<Icon icon="ant-design:inbox-outlined" class="empty-icon" /> <Icon icon="ant-design:inbox-outlined" class="empty-icon" />
<p>暂无任务</p> <p>暂无事项</p>
</div> </div>
</div> </div>
@@ -436,7 +436,7 @@
}); });
const statusFilterLabel = computed(() => { const statusFilterLabel = computed(() => {
const map: Record<StatusFilter, string> = { all: '全部任务', completed: '已完成', uncompleted: '未完成' }; const map: Record<StatusFilter, string> = { all: '全部事项', completed: '已完成', uncompleted: '未完成' };
return map[props.statusFilter]; return map[props.statusFilter];
}); });
@@ -3,7 +3,7 @@
<BasicForm @register="registerForm" ref="formRef" /> <BasicForm @register="registerForm" ref="formRef" />
<!-- 子表单区域 --> <!-- 子表单区域 -->
<a-tabs v-model:activeKey="activeKey" animated @change="handleChangeTabs"> <a-tabs v-model:activeKey="activeKey" animated @change="handleChangeTabs">
<a-tab-pane tab="工作任务清单详情" key="taskListDetial" :forceRender="true"> <a-tab-pane tab="计划详情" key="taskListDetial" :forceRender="true">
<JVxeTable <JVxeTable
keep-source keep-source
resizable resizable
@@ -3,7 +3,7 @@
<BasicForm @register="registerForm" ref="formRef" name="TaskListForm" /> <BasicForm @register="registerForm" ref="formRef" name="TaskListForm" />
<!-- 子表单区域 --> <!-- 子表单区域 -->
<a-tabs v-model:activeKey="activeKey" animated @change="handleChangeTabs"> <a-tabs v-model:activeKey="activeKey" animated @change="handleChangeTabs">
<a-tab-pane tab="工作任务清单详情" key="taskListDetial" :forceRender="true"> <a-tab-pane tab="计划详情" key="taskListDetial" :forceRender="true">
<JVxeTable <JVxeTable
keep-source keep-source
resizable resizable
+31 -31
View File
@@ -1,33 +1,11 @@
<template> <template>
<div class="task-sidebar"> <div class="task-sidebar">
<div class="sidebar-header">
<span class="sidebar-title">
<Icon icon="ant-design:menu-outlined" class="mr-1" />
任务
</span>
</div>
<div class="sidebar-nav">
<div
v-for="item in NAV_ITEMS"
:key="item.id"
class="sidebar-item"
:class="{ active: currentView === item.id }"
@click="emit('view-select', item.id)"
>
<Icon :icon="item.icon" class="sidebar-item-icon" />
<span>{{ item.label }}</span>
</div>
</div>
<div class="sidebar-divider"></div>
<div class="sidebar-section"> <div class="sidebar-section">
<div class="sidebar-quick-header" @click="quickAccessExpanded = !quickAccessExpanded"> <div class="sidebar-quick-header" @click="planExpanded = !planExpanded">
<Icon icon="tasklist-arrow-down|svg" class="quick-access-arrow" :class="{ expanded: quickAccessExpanded }" /> <Icon icon="tasklist-arrow-down|svg" class="quick-access-arrow" :class="{ expanded: planExpanded }" />
<span class="sidebar-quick-title">快速访问</span> <span class="sidebar-quick-title">计划总览</span>
</div> </div>
<template v-if="quickAccessExpanded"> <template v-if="planExpanded">
<div <div
v-for="item in QUICK_ACCESS_ITEMS" v-for="item in QUICK_ACCESS_ITEMS"
:key="item.id" :key="item.id"
@@ -43,17 +21,38 @@
<div class="sidebar-divider"></div> <div class="sidebar-divider"></div>
<div class="sidebar-section">
<div class="sidebar-quick-header" @click="taskExpanded = !taskExpanded">
<Icon icon="tasklist-arrow-down|svg" class="quick-access-arrow" :class="{ expanded: taskExpanded }" />
<span class="sidebar-quick-title">事项总览</span>
</div>
<template v-if="taskExpanded">
<div
v-for="item in NAV_ITEMS"
:key="item.id"
class="sidebar-item sidebar-quick-access-sub"
:class="{ active: currentView === item.id }"
@click="emit('view-select', item.id)"
>
<Icon :icon="item.icon" class="sidebar-item-icon" />
<span>{{ item.label }}</span>
</div>
</template>
</div>
<div class="sidebar-divider"></div>
<div class="sidebar-section sidebar-lists-section"> <div class="sidebar-section sidebar-lists-section">
<div class="sidebar-favorites-header"> <div class="sidebar-favorites-header">
<Icon icon="tasklist-task-list|svg" class="sidebar-favorites-icon" /> <Icon icon="tasklist-task-list|svg" class="sidebar-favorites-icon" />
<span class="sidebar-favorites-name">收藏清单</span> <span class="sidebar-favorites-name">常用计划</span>
<a-dropdown :trigger="['click']"> <a-dropdown :trigger="['click']">
<Icon icon="ant-design:plus-outlined" class="sidebar-group-add" @click.stop /> <Icon icon="ant-design:plus-outlined" class="sidebar-group-add" @click.stop />
<template #overlay> <template #overlay>
<a-menu @click="onAddMenuClick"> <a-menu @click="onAddMenuClick">
<a-menu-item key="new-list"> <a-menu-item key="new-list">
<Icon icon="ant-design:file-add-outlined" class="mr-1" /> <Icon icon="ant-design:file-add-outlined" class="mr-1" />
新建清单 新建计划
</a-menu-item> </a-menu-item>
<a-menu-item key="new-group"> <a-menu-item key="new-group">
<Icon icon="ant-design:folder-add-outlined" class="mr-1" /> <Icon icon="ant-design:folder-add-outlined" class="mr-1" />
@@ -190,7 +189,7 @@
v-if="getLocalGroupTaskLists(group).length === 0" v-if="getLocalGroupTaskLists(group).length === 0"
class="sidebar-empty-hint" class="sidebar-empty-hint"
> >
可拖拽清单加入该分组 可拖拽计划加入该分组
</div> </div>
</div> </div>
</template> </template>
@@ -258,7 +257,8 @@
} }
} }
const quickAccessExpanded = ref(true); const planExpanded = ref(true);
const taskExpanded = ref(true);
const ungroupedTaskLists = computed(() => { const ungroupedTaskLists = computed(() => {
const group = props.taskListGroups.find((g) => g.id === '__ungrouped__'); const group = props.taskListGroups.find((g) => g.id === '__ungrouped__');
@@ -424,7 +424,7 @@
createConfirm({ createConfirm({
iconType: 'warning', iconType: 'warning',
title: '确认移除', title: '确认移除',
content: '确定从收藏清单中移除吗?', content: '确定从常用计划移除吗?',
okText: '确认', okText: '确认',
cancelText: '取消', cancelText: '取消',
onOk: () => { onOk: () => {
@@ -178,7 +178,7 @@
const { ok, invalidNames } = await checkUserSecLevel(addForm.userIds, secretLevel); const { ok, invalidNames } = await checkUserSecLevel(addForm.userIds, secretLevel);
if (!ok) { if (!ok) {
const map: Record<number, string> = { 1: '非密', 2: '内部', 3: '秘密', 4: '机密' }; const map: Record<number, string> = { 1: '非密', 2: '内部', 3: '秘密', 4: '机密' };
message.warning(`以下人员的密级不满足当前清单(${map[secretLevel] || '非密'})要求:${invalidNames.join('、')}`); message.warning(`以下人员的密级不满足当前计划(${map[secretLevel] || '非密'})要求:${invalidNames.join('、')}`);
return; return;
} }
+88 -18
View File
@@ -44,6 +44,7 @@
@update:visible-fields="(v) => (visibleFields = v)" @update:visible-fields="(v) => (visibleFields = v)"
@list-select="onListSelect" @list-select="onListSelect"
@open-collaborator="onOpenCollaborator" @open-collaborator="onOpenCollaborator"
@open-collaborator-for-list="onOpenCollaboratorForList"
@add-to-favorites="onAddToFavorites" @add-to-favorites="onAddToFavorites"
@remove-from-favorites="onRemoveFromFavorites" @remove-from-favorites="onRemoveFromFavorites"
@move-task="onMoveTask" @move-task="onMoveTask"
@@ -90,7 +91,7 @@
@unfollow-task="onUnfollowTask" @unfollow-task="onUnfollowTask"
@move-task="onMoveTask" @move-task="onMoveTask"
/> />
<CollaboratorModal ref="collaboratorModalRef" :task-list-id="currentListId" :is-owner="isCurrentOwner" :secret-level="currentList?.secretLevel || 1" :secret-text="currentList?.secretText || '非密'" @changed="onCollaboratorsChanged" /> <CollaboratorModal ref="collaboratorModalRef" :task-list-id="collaboratorTargetListId || currentListId" :is-owner="collaboratorTargetListId ? isOwnerOfTargetList : isCurrentOwner" :secret-level="collaboratorTargetList ? collaboratorTargetList.secretLevel || 1 : currentList?.secretLevel || 1" :secret-text="collaboratorTargetList ? collaboratorTargetList.secretText || '非密' : currentList?.secretText || '非密'" @changed="onCollaboratorsChanged" />
</div> </div>
</template> </template>
@@ -169,6 +170,7 @@
const [registerDetailDrawer, { openDrawer: openDetailDrawer }] = useDrawer(); const [registerDetailDrawer, { openDrawer: openDetailDrawer }] = useDrawer();
const collaboratorModalRef = ref<InstanceType<typeof CollaboratorModal> | null>(null); const collaboratorModalRef = ref<InstanceType<typeof CollaboratorModal> | null>(null);
const collaboratorTargetListId = ref('');
const defaultGroupRealIdMap = ref<Record<string, string>>({}); const defaultGroupRealIdMap = ref<Record<string, string>>({});
const userStore = useUserStore(); const userStore = useUserStore();
@@ -277,7 +279,7 @@
console.error('[TaskList] 获取用户密级失败', e); console.error('[TaskList] 获取用户密级失败', e);
} }
loadFavorites(); loadFavorites();
onViewSelect('my-tasks'); onQuickAccessTabClick('all');
}); });
function findListInGroups(groups: TaskListGroup[], listId: string): TaskList | null { function findListInGroups(groups: TaskListGroup[], listId: string): TaskList | null {
@@ -297,13 +299,14 @@
const isCurrentOwner = computed(() => currentList.value?.myPermission === '1'); const isCurrentOwner = computed(() => currentList.value?.myPermission === '1');
async function loadPermissionForList(listId: string) { async function loadPermissionForList(listId: string) {
const silentOpts = { successMessageMode: 'none' as const };
try { try {
const perm = await getMyPermission({ taskListId: listId }); const perm = await getMyPermission({ taskListId: listId }, silentOpts);
const list = findListById(listId); const list = findListById(listId);
if (list) { if (list) {
list.myPermission = perm || ''; list.myPermission = perm || '';
} }
const collabs: any[] = await getCollaborators({ taskListId: listId }); const collabs: any[] = await getCollaborators({ taskListId: listId }, silentOpts);
const names = collabs.map((c: any) => c.username).filter(Boolean); const names = collabs.map((c: any) => c.username).filter(Boolean);
if (list) { if (list) {
list.collaboratorNames = names.join(','); list.collaboratorNames = names.join(',');
@@ -446,7 +449,7 @@
if (view === 'my-tasks') { if (view === 'my-tasks') {
try { try {
flatViewTitle.value = '我负责的任务'; flatViewTitle.value = '我的事项';
flatViewEditable.value = true; flatViewEditable.value = true;
flatTaskEditMap.value = new Map(); flatTaskEditMap.value = new Map();
const data: any[] = await myResponsibleTasks(); const data: any[] = await myResponsibleTasks();
@@ -456,7 +459,7 @@
} }
} else if (view === 'followed') { } else if (view === 'followed') {
try { try {
flatViewTitle.value = '关注的任务'; flatViewTitle.value = '关注事项';
flatViewEditable.value = false; flatViewEditable.value = false;
const data: any[] = await myFollowedTasks(); const data: any[] = await myFollowedTasks();
const items = (data || []).map(buildFlatTaskItem); const items = (data || []).map(buildFlatTaskItem);
@@ -485,12 +488,71 @@
} }
function onOpenCollaborator() { function onOpenCollaborator() {
collaboratorTargetListId.value = '';
collaboratorModalRef.value?.open(); collaboratorModalRef.value?.open();
} }
async function onCollaboratorsChanged() { const collaboratorTargetList = computed(() => {
if (currentListId.value) { if (!collaboratorTargetListId.value) return null;
await loadPermissionForList(currentListId.value); for (const group of quickAccessGroups.value) {
for (const list of group.taskLists) {
if (list.id === collaboratorTargetListId.value) return list;
}
}
return null;
});
const isOwnerOfTargetList = computed(() => {
return collaboratorTargetList.value?.myPermission === '1';
});
function onOpenCollaboratorForList(listId: string) {
const list = findListInQuickAccess(listId);
if (list && list.myPermission !== '1') {
message.warning('仅所有者可管理协作者');
return;
}
collaboratorTargetListId.value = listId;
collaboratorModalRef.value?.open();
}
function findListInQuickAccess(listId: string): TaskList | null {
for (const group of quickAccessGroups.value) {
for (const list of group.taskLists) {
if (list.id === listId) return list;
}
}
return null;
}
async function onCollaboratorsChanged(collaboratorNames?: string) {
const targetId = collaboratorTargetListId.value || currentListId.value;
if (targetId && collaboratorNames !== undefined) {
updateListCollaboratorNames(targetId, collaboratorNames);
}
if (targetId) {
await loadPermissionForList(targetId);
}
collaboratorTargetListId.value = '';
}
function updateListCollaboratorNames(listId: string, names: string) {
for (const group of quickAccessGroups.value) {
const idx = group.taskLists.findIndex((l) => l.id === listId);
if (idx >= 0) {
group.taskLists[idx] = { ...group.taskLists[idx], collaboratorNames: names };
return;
}
}
if (currentList.value && currentList.value.id === listId) {
currentList.value = { ...currentList.value, collaboratorNames: names };
}
for (const group of taskListGroups.value) {
const idx = group.taskLists.findIndex((l) => l.id === listId);
if (idx >= 0) {
group.taskLists[idx] = { ...group.taskLists[idx], collaboratorNames: names };
return;
}
} }
} }
@@ -581,6 +643,7 @@
createTimeStr: item.createTimeStr || '', createTimeStr: item.createTimeStr || '',
secretLevel: (item as any).secretLevel, secretLevel: (item as any).secretLevel,
secretText: (item as any).secretText, secretText: (item as any).secretText,
myPermission: item.myPermission,
}, },
], ],
})); }));
@@ -630,8 +693,19 @@
async function onCreateGroup(name: string) { async function onCreateGroup(name: string) {
const groupName = name?.trim() || '新建分组'; const groupName = name?.trim() || '新建分组';
try { try {
await addTaskListGroup({ tasklistName: groupName }); const newId = await addTaskListGroup({ tasklistName: groupName });
await loadFavorites(); const maxSort = taskListGroups.value.reduce((max, g) => Math.max(max, g.sortOrder ?? 0), 0);
const newGroup: TaskListGroup = {
id: newId,
name: groupName,
pid: '0',
hasChild: '0',
type: 0,
sortOrder: maxSort + 1,
delFlag: 0,
taskLists: [],
};
taskListGroups.value.push(newGroup);
} catch (e) { } catch (e) {
console.error('[TaskList] 创建分组失败', e); console.error('[TaskList] 创建分组失败', e);
} }
@@ -681,8 +755,6 @@
await moveTaskList({ favoriteId, targetGroupId: targetGroupId || '', sortOrder }); await moveTaskList({ favoriteId, targetGroupId: targetGroupId || '', sortOrder });
} catch (e) { } catch (e) {
console.error('[TaskList] 移动清单失败', e); console.error('[TaskList] 移动清单失败', e);
} finally {
await loadFavorites();
} }
} }
@@ -691,8 +763,6 @@
await moveGroup({ groupId, sortOrder }); await moveGroup({ groupId, sortOrder });
} catch (e) { } catch (e) {
console.error('[TaskList] 移动分组失败', e); console.error('[TaskList] 移动分组失败', e);
} finally {
await loadFavorites();
} }
} }
@@ -850,7 +920,7 @@
const secretLevel = currentList.value?.secretLevel || 1; const secretLevel = currentList.value?.secretLevel || 1;
const { ok, invalidNames } = await checkUserSecLevel(value, secretLevel); const { ok, invalidNames } = await checkUserSecLevel(value, secretLevel);
if (!ok) { if (!ok) {
message.warning(`以下人员的密级不满足当前清单(${currentList.value?.secretText || '非密'})要求:${invalidNames.join('、')}`); message.warning(`以下人员的密级不满足当前计划(${currentList.value?.secretText || '非密'})要求:${invalidNames.join('、')}`);
return; return;
} }
} }
@@ -876,7 +946,7 @@
const secretLevel = currentList.value?.secretLevel || 1; const secretLevel = currentList.value?.secretLevel || 1;
const { ok, invalidNames } = await checkUserSecLevel(fields[f], secretLevel); const { ok, invalidNames } = await checkUserSecLevel(fields[f], secretLevel);
if (!ok) { if (!ok) {
message.warning(`以下人员的密级不满足当前清单(${currentList.value?.secretText || '非密'})要求:${invalidNames.join('、')}`); message.warning(`以下人员的密级不满足当前计划(${currentList.value?.secretText || '非密'})要求:${invalidNames.join('、')}`);
return; return;
} }
} }
@@ -908,7 +978,7 @@
if (value) { if (value) {
const { ok, invalidNames } = await checkUserSecLevel(value, secretLevel); const { ok, invalidNames } = await checkUserSecLevel(value, secretLevel);
if (!ok) { if (!ok) {
message.warning(`以下人员的密级不满足当前清单(${secretText})要求:${invalidNames.join('、')}`); message.warning(`以下人员的密级不满足当前计划(${secretText})要求:${invalidNames.join('、')}`);
return; return;
} }
} }
+7 -7
View File
@@ -7,14 +7,14 @@ export interface NavItem {
} }
export const NAV_ITEMS: NavItem[] = [ export const NAV_ITEMS: NavItem[] = [
{ id: 'my-tasks', label: '我负责的', icon: 'ant-design:user-outlined' }, { id: 'my-tasks', label: '我的事项', icon: 'ant-design:user-outlined' },
{ id: 'followed', label: '关注', icon: 'ant-design:star-outlined' }, { id: 'followed', label: '关注事项', icon: 'ant-design:star-outlined' },
]; ];
export const QUICK_ACCESS_ITEMS: NavItem[] = [ export const QUICK_ACCESS_ITEMS: NavItem[] = [
{ id: 'all-tasks', label: '全部清单', icon: 'ant-design:unordered-list-outlined' }, { id: 'all-tasks', label: '所有计划', icon: 'ant-design:appstore-outlined' },
{ id: 'created', label: '我所有的', icon: 'ant-design:edit-outlined' }, { id: 'created', label: '我创建的', icon: 'ant-design:folder-outlined' },
{ id: 'assigned', label: '我协作的', icon: 'ant-design:send-outlined' }, { id: 'assigned', label: '我参与的', icon: 'ant-design:team-outlined' },
]; ];
export const QUICK_ACCESS_TAB_MAP: Record<string, string> = { export const QUICK_ACCESS_TAB_MAP: Record<string, string> = {
@@ -143,12 +143,12 @@ export interface FieldConfig {
} }
export const FIELD_CONFIG: FieldConfig[] = [ export const FIELD_CONFIG: FieldConfig[] = [
{ key: 'taskDesc', label: '任务描述', defaultVisible: false }, { key: 'taskDesc', label: '事项描述', defaultVisible: false },
{ key: 'assigneeName', label: '负责人', defaultVisible: true }, { key: 'assigneeName', label: '负责人', defaultVisible: true },
{ key: 'participantName', label: '参与人', defaultVisible: true }, { key: 'participantName', label: '参与人', defaultVisible: true },
{ key: 'startTime', label: '开始时间', defaultVisible: true }, { key: 'startTime', label: '开始时间', defaultVisible: true },
{ key: 'endTime', label: '截止时间', defaultVisible: true }, { key: 'endTime', label: '截止时间', defaultVisible: true },
{ key: 'subTaskProgress', label: '子任务进度', defaultVisible: true }, { key: 'subTaskProgress', label: '子事项进度', defaultVisible: true },
{ key: 'createBy', label: '创建人', defaultVisible: true }, { key: 'createBy', label: '创建人', defaultVisible: true },
{ key: 'assignName', label: '分配人', defaultVisible: false }, { key: 'assignName', label: '分配人', defaultVisible: false },
{ key: 'followersName', label: '关注人', defaultVisible: false }, { key: 'followersName', label: '关注人', defaultVisible: false },