!28 czh-20260610-右上角显示用户名称
Merge pull request !28 from 陈志浩/feature/tasklistHelp
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 605 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 504 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 595 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 553 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 499 KiB |
@@ -11,9 +11,12 @@
|
||||
/>
|
||||
<!-- <LayoutBreadcrumb v-if="getShowContent && getShowBread" :theme="getHeaderTheme" /> -->
|
||||
<!-- 欢迎语 -->
|
||||
<span v-if="getShowContent && getShowBreadTitle && !getIsMobile" :class="[prefixCls, `${prefixCls}--${getHeaderTheme}`,'headerIntroductionClass']">
|
||||
<span
|
||||
v-if="getShowContent && getShowBreadTitle && !getIsMobile"
|
||||
:class="[prefixCls, `${prefixCls}--${getHeaderTheme}`, 'headerIntroductionClass']"
|
||||
>
|
||||
<img src="../../../assets/images/header-brand.png" class="header-logo" />
|
||||
{{t('layout.header.welcomeIn')}} {{ title }}
|
||||
{{ t('layout.header.welcomeIn') }} {{ title }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- left end -->
|
||||
@@ -42,6 +45,7 @@
|
||||
<!-- ai助手 -->
|
||||
<Aide v-if="false"></Aide>
|
||||
|
||||
<span v-if="realname" :class="`${prefixCls}-action__item header-username`">{{ realname }}</span>
|
||||
<UserDropDown :theme="getHeaderTheme" />
|
||||
</div>
|
||||
</Header>
|
||||
@@ -108,7 +112,7 @@
|
||||
const { prefixCls } = useDesign('layout-header');
|
||||
const userStore = useUserStore();
|
||||
const { getShowTopMenu, getShowHeaderTrigger, getSplit, getIsMixMode, getMenuWidth, getIsMixSidebar } = useMenuSetting();
|
||||
const { getUseErrorHandle, /* getShowSettingButton, getSettingButtonPosition, getAiIconShow */ } = useRootSetting();
|
||||
const { getUseErrorHandle /* getShowSettingButton, getSettingButtonPosition, getAiIconShow */ } = useRootSetting();
|
||||
const { title } = useGlobSetting();
|
||||
|
||||
const {
|
||||
@@ -167,6 +171,10 @@
|
||||
return unref(getSplit) ? MenuModeEnum.HORIZONTAL : null;
|
||||
});
|
||||
|
||||
const realname = computed(() => {
|
||||
return userStore.getUserInfo?.realname || '';
|
||||
});
|
||||
|
||||
/**
|
||||
* 首页多租户部门弹窗逻辑
|
||||
*/
|
||||
@@ -210,6 +218,7 @@
|
||||
getUseErrorHandle,
|
||||
getLogoWidth,
|
||||
getIsMixSidebar,
|
||||
realname,
|
||||
// getShowSettingButton,
|
||||
// getShowSetting,
|
||||
// getShowSearch,
|
||||
@@ -252,6 +261,25 @@
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.header-username {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&--light {
|
||||
.header-username {
|
||||
color: #1677ff;
|
||||
}
|
||||
}
|
||||
|
||||
&--dark {
|
||||
.header-username {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
}
|
||||
|
||||
&--light {
|
||||
.headerIntroductionClass {
|
||||
color: #000;
|
||||
@@ -262,7 +290,8 @@
|
||||
.headerIntroductionClass {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
.anticon, .truncate {
|
||||
.anticon,
|
||||
.truncate {
|
||||
color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,418 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:open="visible"
|
||||
title="事项计划 — 使用说明"
|
||||
width="1100px"
|
||||
:footer="null"
|
||||
:body-style="{ padding: 0 }"
|
||||
wrap-class-name="help-doc-modal"
|
||||
@cancel="handleClose"
|
||||
>
|
||||
<div class="help-doc">
|
||||
<div class="help-doc-nav">
|
||||
<div
|
||||
v-for="chapter in chapters"
|
||||
:key="chapter.id"
|
||||
class="nav-item"
|
||||
:class="{ active: activeChapter === chapter.id }"
|
||||
@click="scrollToChapter(chapter.id)"
|
||||
>
|
||||
<Icon :icon="chapter.icon" class="nav-icon" />
|
||||
<span>{{ chapter.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ref="contentRef" class="help-doc-content">
|
||||
<div v-for="chapter in chapters" :key="chapter.id" :id="chapter.id" class="chapter">
|
||||
<h3 class="chapter-title">
|
||||
<Icon :icon="chapter.icon" class="chapter-icon" />
|
||||
{{ chapter.title }}
|
||||
</h3>
|
||||
|
||||
<div v-for="(section, si) in chapter.sections" :key="si" class="section">
|
||||
<h4 v-if="section.heading" class="section-heading">{{ section.heading }}</h4>
|
||||
<div v-if="section.text" class="section-text" v-html="section.text"></div>
|
||||
|
||||
<div v-if="section.image" class="doc-image">
|
||||
<img :src="section.image" :alt="section.heading" class="doc-image-img" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, nextTick } from 'vue';
|
||||
import Icon from '/@/components/Icon';
|
||||
import createPlanImg from '/@/assets/images/create-plan.png';
|
||||
import createTaskQuickImg from '/@/assets/images/create-task-quick.png';
|
||||
import createTaskDetailImg from '/@/assets/images/create-task-detail.png';
|
||||
import manageCollaboratorImg from '/@/assets/images/manage-collaborator.png';
|
||||
import subtaskViewImg from '/@/assets/images/subtask-view.png';
|
||||
|
||||
const props = defineProps<{ open: boolean }>();
|
||||
const emit = defineEmits<{ 'update:open': [value: boolean] }>();
|
||||
|
||||
const visible = ref(props.open);
|
||||
|
||||
watch(
|
||||
() => props.open,
|
||||
(val) => {
|
||||
visible.value = val;
|
||||
}
|
||||
);
|
||||
|
||||
watch(visible, (val) => {
|
||||
emit('update:open', val);
|
||||
});
|
||||
|
||||
function handleClose() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
const contentRef = ref<HTMLElement | null>(null);
|
||||
const activeChapter = ref('quickstart');
|
||||
|
||||
function scrollToChapter(id: string) {
|
||||
activeChapter.value = id;
|
||||
nextTick(() => {
|
||||
const el = document.getElementById(id);
|
||||
if (el && contentRef.value) {
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const chapters = [
|
||||
// ==================== 1. 快速入门 ====================
|
||||
{
|
||||
id: 'quickstart',
|
||||
title: '快速入门',
|
||||
icon: 'ant-design:rocket-outlined',
|
||||
sections: [
|
||||
{
|
||||
heading: '创建计划',
|
||||
text: `
|
||||
<ol>
|
||||
<li>左侧边栏 → <strong>"计划总览"</strong> → 点击<strong>所有计划 / 我创建的 / 我参与的</strong></li>
|
||||
<li>右侧出现"计划列表",点击标题栏 <strong>+</strong> 按钮</li>
|
||||
<li>填写<strong>计划名称</strong>,选择<strong>密级</strong></li>
|
||||
<li>点击确定完成创建</li>
|
||||
</ol>
|
||||
<p>也可以在左侧边栏的"常用计划"旁点击 <strong>+</strong> 快速创建。</p>
|
||||
`,
|
||||
image: createPlanImg,
|
||||
},
|
||||
{
|
||||
heading: '创建事项',
|
||||
text: `
|
||||
<p><strong>快速创建:</strong>进入计划 → 事项分组标题栏点击 <strong>+</strong> → 输入名称 → 回车</p>
|
||||
`,
|
||||
image: createTaskQuickImg,
|
||||
},
|
||||
{
|
||||
text: `
|
||||
<p><strong>详细创建:</strong>点击 + 后不输入名称直接确认打开弹窗,可设置名称、描述、负责人、参与人、关注人、优先级、起止时间。</p>
|
||||
<p>需要权限:<strong>所有者 或可编辑</strong>。</p>
|
||||
`,
|
||||
image: createTaskDetailImg,
|
||||
},
|
||||
{
|
||||
heading: '收藏与取消收藏',
|
||||
text: `
|
||||
<p><strong>收藏:</strong>进入"所有计划"视图 → 点击计划上的<strong>星标图标 ☆</strong> → 收藏后计划出现在左侧边栏。</p>
|
||||
<p><strong>取消收藏:</strong>在左侧边栏找到该计划,点击星标(⭐ → ☆),或点击计划旁的 <strong>⋯</strong> → "移除"。</p>
|
||||
`,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ==================== 2. 权限说明 ====================
|
||||
{
|
||||
id: 'permissions',
|
||||
title: '权限说明',
|
||||
icon: 'ant-design:safety-certificate-outlined',
|
||||
sections: [
|
||||
{
|
||||
heading: '三级权限对比',
|
||||
text: `
|
||||
<table class="faq-table">
|
||||
<tr><th></th><th>所有者</th><th>可编辑</th><th>可阅读</th></tr>
|
||||
<tr><td>创建事项</td><td class="td-yes">✓</td><td class="td-yes">✓</td><td class="td-no">✗</td></tr>
|
||||
<tr><td>编辑事项</td><td class="td-yes">✓</td><td class="td-yes">✓</td><td class="td-no">✗</td></tr>
|
||||
<tr><td>完成事项</td><td class="td-yes">✓</td><td class="td-yes">✓</td><td class="td-no">✗</td></tr>
|
||||
<tr><td>删除事项</td><td class="td-yes">✓</td><td class="td-yes">✓</td><td class="td-no">✗</td></tr>
|
||||
<tr><td>拖拽排序</td><td class="td-yes">✓</td><td class="td-yes">✓</td><td class="td-no">✗</td></tr>
|
||||
<tr><td>管理协作者</td><td class="td-yes">✓</td><td class="td-no">✗</td><td class="td-no">✗</td></tr>
|
||||
<tr><td>删除计划</td><td class="td-yes">✓</td><td class="td-no">✗</td><td class="td-no">✗</td></tr>
|
||||
</table>
|
||||
<ul>
|
||||
<li><strong>所有者</strong>:计划创建者,拥有全部权限</li>
|
||||
<li><strong>可编辑</strong>:可操作事项,但不能管理协作者和删除计划</li>
|
||||
<li><strong>可阅读</strong>:只能查看,无法编辑</li>
|
||||
</ul>
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: '密级说明',
|
||||
text: `
|
||||
<p>每个计划创建时需指定<strong>密级</strong>。用户只能看到密级 ≤ 自己密级的计划。</p>
|
||||
<p><strong>密级创建后不可修改</strong>,如需调整请重新创建计划。</p>
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: '管理协作者',
|
||||
text: `
|
||||
<ol>
|
||||
<li>进入计划 → 右上角点击<strong>"协作人"</strong>按钮</li>
|
||||
<li>点击"添加协作人" → 搜索并选择用户 → 设置权限为"可编辑"或"可阅读"</li>
|
||||
<li>确认添加</li>
|
||||
</ol>
|
||||
<p>只有计划的 <strong>所有者</strong> 才能管理协作者。如果看不到此按钮,说明你没有管理权限。</p>
|
||||
`,
|
||||
image: manageCollaboratorImg,
|
||||
},
|
||||
{
|
||||
heading: '计划收藏权限说明',
|
||||
text: `
|
||||
<p>满足以下任一条件,即可在"所有计划"中看到并收藏该计划:</p>
|
||||
<ul>
|
||||
<li>你是该计划的<strong>所有者</strong></li>
|
||||
<li>被设为该计划的<strong>协作者</strong></li>
|
||||
<li>被设为计划中某个事项的<strong>负责人</strong>或<strong>参与者</strong></li>
|
||||
</ul>
|
||||
`,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ==================== 3. 事项管理 ====================
|
||||
{
|
||||
id: 'task-mgmt',
|
||||
title: '事项管理',
|
||||
icon: 'ant-design:check-square-outlined',
|
||||
sections: [
|
||||
{
|
||||
heading: '子事项',
|
||||
text: `
|
||||
<ul>
|
||||
<li>点击事项名 → 详情抽屉 → 底部<strong>"添加子事项"</strong></li>
|
||||
<li>最多支持 <strong>5 层</strong>嵌套</li>
|
||||
<li>每个子事项可独立设置负责人、截止时间、优先级</li>
|
||||
<li>列表中"子事项进度"列显示完成情况(如 2/5)</li>
|
||||
</ul>
|
||||
`,
|
||||
image: subtaskViewImg,
|
||||
},
|
||||
{
|
||||
heading: '我的事项与关注事项',
|
||||
text: `
|
||||
<p>左侧边栏顶部有两个全局视图:</p>
|
||||
<ul>
|
||||
<li><strong>"我的事项":</strong>跨所有计划汇总你被设为"负责人"的事项,扁平列表,可直接编辑、完成、展开子事项</li>
|
||||
<li><strong>"关注事项":</strong>汇总你关注的所有事项,适合跟踪他人负责但你需要了解进展的事项</li>
|
||||
</ul>
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: '关注事项',
|
||||
text: `
|
||||
<p>点击事项名 → 详情抽屉 → 顶部点击<strong>眼睛图标</strong>即可关注。不需要是负责人或协作者,任何你可见的事项都能关注。关注后事项出现在"关注事项"视图中。</p>
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: '删除事项与计划',
|
||||
text: `
|
||||
<ul>
|
||||
<li><strong>删除事项:</strong>详情抽屉 → 顶部删除图标(需 所有者 或可编辑)</li>
|
||||
<li><strong>删除计划:</strong>标题栏 → ⋯ → 删除(需 所有者)</li>
|
||||
</ul>
|
||||
<p style="color:#f5222d">⚠ 删除后无法恢复,请谨慎操作。</p>
|
||||
`,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ==================== 4. 常见问题 ====================
|
||||
{
|
||||
id: 'faq',
|
||||
title: '常见问题',
|
||||
icon: 'ant-design:question-circle-outlined',
|
||||
sections: [
|
||||
{
|
||||
heading: '计划的密级创建后能改吗?',
|
||||
text: `
|
||||
<p><strong>不能直接修改。</strong>密级创建后不可更改。如需调整,只能删除原计划后重新创建并选择新的密级。</p>
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: '事项的截止时间有什么用?',
|
||||
text: `
|
||||
<p>设置截止时间后,到期未完成的事项日期会<strong>自动标红</strong>提醒。在"我的事项"视图中可快速识别过期事项。</p>
|
||||
`,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.help-doc {
|
||||
display: flex;
|
||||
height: 80vh;
|
||||
max-height: 750px;
|
||||
}
|
||||
|
||||
.help-doc-nav {
|
||||
width: 180px;
|
||||
min-width: 180px;
|
||||
padding: 12px 0;
|
||||
border-right: 1px solid #f0f0f0;
|
||||
background: #fafafa;
|
||||
overflow-y: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 16px;
|
||||
font-size: 13px;
|
||||
color: #595959;
|
||||
cursor: pointer;
|
||||
border-left: 3px solid transparent;
|
||||
transition: all 0.15s;
|
||||
|
||||
&:hover {
|
||||
color: #1a1a1a;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: #1677ff;
|
||||
background: #e6f4ff;
|
||||
border-left-color: #1677ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
font-size: 14px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.help-doc-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 24px 28px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.chapter {
|
||||
margin-bottom: 32px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.chapter-title {
|
||||
margin: 0 0 16px;
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.chapter-icon {
|
||||
font-size: 18px;
|
||||
color: #1677ff;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
margin: 0 0 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1f2329;
|
||||
}
|
||||
|
||||
.section-text {
|
||||
font-size: 13px;
|
||||
line-height: 1.8;
|
||||
color: #555;
|
||||
|
||||
:deep(p) {
|
||||
margin: 0 0 6px;
|
||||
}
|
||||
|
||||
:deep(ul),
|
||||
:deep(ol) {
|
||||
margin: 0 0 6px;
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
:deep(li) {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
:deep(strong) {
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
:deep(.faq-table) {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 8px 0 12px;
|
||||
font-size: 13px;
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #d9d9d9;
|
||||
padding: 8px 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #f5f5f5;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
td:first-child {
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.td-yes {
|
||||
color: #52c41a;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.td-no {
|
||||
color: #d9d9d9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.doc-image {
|
||||
margin-top: 10px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.doc-image-img {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
@@ -1,8 +1,23 @@
|
||||
<template>
|
||||
<div class="task-content">
|
||||
<a-spin :spinning="props.loading" tip="加载中...">
|
||||
<div class="content-header">
|
||||
<template v-if="showListGroupView">
|
||||
<div class="content-header-row">
|
||||
<div class="content-header-row-left">
|
||||
<h2 class="content-title">计划列表</h2>
|
||||
<a-tooltip title="新建清单">
|
||||
<a-button type="text" size="small" class="add-list-btn" @click="onCreateListClick">
|
||||
<Icon icon="ant-design:plus-outlined" />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<a-tooltip title="使用帮助">
|
||||
<a-button type="text" size="small" class="help-btn" @click="helpModalOpen = true">
|
||||
<Icon icon="ant-design:question-circle-outlined" />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="content-header-left">
|
||||
@@ -226,7 +241,7 @@
|
||||
<template #content>
|
||||
<div class="field-config-panel">
|
||||
<div class="field-config-title">字段配置</div>
|
||||
<div v-for="field in FIELD_CONFIG" :key="field.key" class="field-config-item" @click="toggleField(field.key)">
|
||||
<div v-for="field in taskContentFieldConfigs" :key="field.key" class="field-config-item" @click="toggleField(field.key)">
|
||||
<span>{{ field.label }}</span>
|
||||
<Icon
|
||||
:icon="visibleFields.includes(field.key) ? 'ant-design:eye-outlined' : 'ant-design:eye-invisible-outlined'"
|
||||
@@ -807,11 +822,14 @@
|
||||
:secret-level="currentList?.secretLevel || 1"
|
||||
@confirm="onUserSelectConfirm"
|
||||
/>
|
||||
<HelpModal v-model:open="helpModalOpen" />
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch, nextTick } from 'vue';
|
||||
import HelpModal from './HelpModal.vue';
|
||||
import dayjs from 'dayjs';
|
||||
import Icon from '/@/components/Icon/index';
|
||||
import AvatarDisplay from './AvatarDisplay.vue';
|
||||
@@ -847,6 +865,7 @@
|
||||
visibleFields: FieldKey[];
|
||||
showListGroupView?: boolean;
|
||||
favoriteIdMap?: Map<string, string>;
|
||||
loading?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -873,11 +892,18 @@
|
||||
'delete-list': [listId: string];
|
||||
'rename-group': [groupId: string, newName: string];
|
||||
'delete-group': [groupId: string];
|
||||
'create-list': [];
|
||||
}>();
|
||||
|
||||
const userStore = useUserStore();
|
||||
const { createConfirm } = useMessage();
|
||||
|
||||
const helpModalOpen = ref(false);
|
||||
|
||||
function onCreateListClick() {
|
||||
emit('create-list');
|
||||
}
|
||||
|
||||
const showNewTaskInput = ref<string | null>(null);
|
||||
const newTaskTitle = ref('');
|
||||
const showNewGroupInput = ref(false);
|
||||
@@ -1202,8 +1228,12 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
const taskContentFieldConfigs = computed(() => {
|
||||
return FIELD_CONFIG.filter((f) => f.key !== 'listName');
|
||||
});
|
||||
|
||||
const visibleFieldConfigs = computed(() => {
|
||||
return FIELD_CONFIG.filter((f) => props.visibleFields.includes(f.key));
|
||||
return taskContentFieldConfigs.value.filter((f) => props.visibleFields.includes(f.key));
|
||||
});
|
||||
|
||||
const totalFieldsWidth = computed(() => {
|
||||
@@ -1871,6 +1901,36 @@
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.content-header-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.content-header-row-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.add-list-btn {
|
||||
color: #888;
|
||||
font-size: 16px;
|
||||
padding: 0 4px;
|
||||
&:hover {
|
||||
color: #1677ff;
|
||||
}
|
||||
}
|
||||
|
||||
.help-btn {
|
||||
color: #888;
|
||||
font-size: 18px;
|
||||
&:hover {
|
||||
color: #1677ff;
|
||||
}
|
||||
}
|
||||
|
||||
.content-header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="task-flat-view">
|
||||
<a-spin :spinning="props.loading" tip="加载中...">
|
||||
<div class="flat-view-header">
|
||||
<h2 class="flat-view-title">{{ title }}</h2>
|
||||
<span class="task-count">({{ flattenedTasks.length }})</span>
|
||||
@@ -343,6 +344,7 @@
|
||||
:multiple="true"
|
||||
@confirm="onUserSelectConfirm"
|
||||
/>
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -365,6 +367,7 @@
|
||||
visibleFields: FieldKey[];
|
||||
editable?: boolean;
|
||||
taskEditMap?: Map<string, boolean>;
|
||||
loading?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
:visible-fields="visibleFields"
|
||||
:show-list-group-view="showListGroupView"
|
||||
:favorite-id-map="favoriteIdMap"
|
||||
:loading="pageLoading"
|
||||
@create-task="onCreateTask"
|
||||
@toggle-task="onToggleTask"
|
||||
@task-click="onTaskClick"
|
||||
@@ -53,6 +54,7 @@
|
||||
@delete-list="onDeleteList"
|
||||
@rename-group="onRenameTaskGroup"
|
||||
@delete-group="onDeleteTaskGroup"
|
||||
@create-list="onCreateListModal('')"
|
||||
/>
|
||||
<TaskFlatView
|
||||
v-else
|
||||
@@ -64,6 +66,7 @@
|
||||
:visible-fields="visibleFields"
|
||||
:editable="flatViewEditable"
|
||||
:task-edit-map="flatTaskEditMap"
|
||||
:loading="pageLoading"
|
||||
@toggle-task="onToggleTask"
|
||||
@task-click="onTaskClick"
|
||||
@update-task-field="onUpdateTaskField"
|
||||
@@ -164,6 +167,7 @@
|
||||
const showListGroupView = ref(false);
|
||||
const visibleFields = ref<FieldKey[]>([...DEFAULT_VISIBLE_FIELDS]);
|
||||
const currentDrawerTaskId = ref<string>('');
|
||||
const pageLoading = ref(false);
|
||||
|
||||
const [registerTaskModal, { openModal: openTaskModal }] = useModal();
|
||||
const [registerListModal, { openModal: openListModal }] = useModal();
|
||||
@@ -446,19 +450,16 @@
|
||||
groupDimension.value = 'none';
|
||||
sortField.value = 'createTime';
|
||||
sortDirection.value = 'asc';
|
||||
pageLoading.value = true;
|
||||
|
||||
if (view === 'my-tasks') {
|
||||
try {
|
||||
if (view === 'my-tasks') {
|
||||
flatViewTitle.value = '我的事项';
|
||||
flatViewEditable.value = true;
|
||||
flatTaskEditMap.value = new Map();
|
||||
const data: any[] = await myResponsibleTasks();
|
||||
flatTasks.value = (data || []).map(buildFlatTaskItem);
|
||||
} catch (e) {
|
||||
console.error('[TaskList] 加载我负责的任务失败', e);
|
||||
}
|
||||
} else if (view === 'followed') {
|
||||
try {
|
||||
flatViewTitle.value = '关注事项';
|
||||
flatViewEditable.value = false;
|
||||
const data: any[] = await myFollowedTasks();
|
||||
@@ -472,19 +473,26 @@
|
||||
}
|
||||
flatTaskEditMap.value = editMap;
|
||||
flatTasks.value = items;
|
||||
} catch (e) {
|
||||
console.error('[TaskList] 加载我关注的任务失败', e);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[TaskList] 加载视图失败', e);
|
||||
} finally {
|
||||
pageLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onListSelect(listId: string) {
|
||||
async function onListSelect(listId: string) {
|
||||
currentView.value = '';
|
||||
currentListId.value = listId;
|
||||
showListGroupView.value = false;
|
||||
groupDimension.value = 'custom';
|
||||
pageLoading.value = true;
|
||||
try {
|
||||
loadPermissionForList(listId);
|
||||
loadTasks(listId);
|
||||
await loadTasks(listId);
|
||||
} finally {
|
||||
pageLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onOpenCollaborator() {
|
||||
@@ -602,6 +610,7 @@
|
||||
currentListId.value = '';
|
||||
groupDimension.value = 'custom';
|
||||
flatTasks.value = [];
|
||||
pageLoading.value = true;
|
||||
|
||||
try {
|
||||
if (tabKey === 'all') {
|
||||
@@ -616,6 +625,8 @@
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[TaskList] 加载清单失败', e);
|
||||
} finally {
|
||||
pageLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -682,6 +693,26 @@
|
||||
await addToFavorites({ taskListId: listId });
|
||||
}
|
||||
await loadFavorites();
|
||||
if (showListGroupView.value) {
|
||||
refreshQuickAccessView();
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshQuickAccessView() {
|
||||
try {
|
||||
const viewMap: Record<string, () => Promise<TaskList[]>> = {
|
||||
'all-tasks': getAllLists,
|
||||
created: getMyOwnLists,
|
||||
assigned: getMyCollabLists,
|
||||
};
|
||||
const fetcher = viewMap[currentView.value];
|
||||
if (fetcher) {
|
||||
const data = await fetcher();
|
||||
quickAccessGroups.value = wrapAsVirtualGroupsWithDetails(data);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[TaskList] 刷新计划列表失败', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function onTaskCreated() {
|
||||
|
||||
Reference in New Issue
Block a user