- routes.ts 增 /my/fillable(子页面路由不占模块行不进导航,手册 §2.2 注记)。
- My.vue 三视图化:Tab 增「可填写」(居首);数据源=store.modules(App.vue
已拉的 GetUserMod 原始树)经 buildNavTree+flattenNav 同一套解析(与左侧
导航同口径,含 menuRoute 的 /f/{code} 修复),按 page 形如 /f/ 挑填报行并
去重;**不按角色过滤**——树里有什么就是什么,授权语义在后端(T8 红线同源)。
- 卡片列表:菜单名+寻址 path+「进入填报」router.push(page);空态文案引导
找管理员配 /f/{code} 模块行并授权。
- 该视图纯前端零请求(答卷两视图行为不变)。
验证:vue-tsc --noEmit 干净;vite build 通过。
This commit is contained in:
@@ -129,6 +129,14 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import("@/views/My.vue"),
|
||||
meta: { title: "关于我的", parentPath: "/my" },
|
||||
},
|
||||
{
|
||||
// P91 可填表单列表:数据源=会话菜单树里的 /f/ 行(授权语义在后端,前端不自行过滤)。
|
||||
// 子页面路由不占模块行、不进导航(手册 §2.2 注记),入口在 My 容器顶部 Tab。
|
||||
path: "/my/fillable",
|
||||
name: "fc_my_fillable",
|
||||
component: () => import("@/views/My.vue"),
|
||||
meta: { title: "可填写", parentPath: "/my" },
|
||||
},
|
||||
// 兜底:外部深链/残留配置落到未登记路由时不白屏,回入口由壳决定首个可用页
|
||||
{ path: "/:pathMatch(.*)*", redirect: "/" },
|
||||
];
|
||||
|
||||
+105
-8
@@ -9,6 +9,11 @@
|
||||
* 两者是**两个端点、两个谓词**,页面按路由分派;顶部 Tab 只是 UI 导航(切路由),不是接口参数。
|
||||
* 管理员代填后:这条答卷在**填表人的"我提交的"**里出现,同时在**被代填人的"关于我的"**里出现(V18)。
|
||||
*
|
||||
* P91 起第三视图「可填写」(/my/fillable):这不是答卷视图,是**填报入口列表**——
|
||||
* 数据源=会话菜单树里 page 形如 /f/{code} 的行(App.vue 已拉过、store.modules 存原件),
|
||||
* 复用 buildNavTree+flattenNav 同一套解析(与左侧导航同口径,天然吃到 menuRoute 的修复)。
|
||||
* **不得在此按角色过滤**:树里有什么就是什么,授权语义在后端(任务书 T8 红线同源)。
|
||||
*
|
||||
* 三条交互口径沿 T7 的"我的提交"段(不另造轮子):
|
||||
* 1. **编辑入口只在"我提交的"**:编辑=填表人的动作;"关于我的"整列只读
|
||||
* (被代填人能看不能改——改是填表人或管理员的动作,后端 myDetail 同口径)。
|
||||
@@ -29,6 +34,8 @@ import { deleteMyAnswer, getMyAnswerDetail, listAnswersAboutMe, listMySubmitted
|
||||
import AnswerEditModal from "@/components/answer/AnswerEditModal.vue";
|
||||
import AnswerValueView from "@/components/answer/AnswerValueView.vue";
|
||||
import { statusDisplay } from "@/utils/dataValue";
|
||||
import { buildNavTree, flattenNav, type FlatNav } from "@/utils/menuNav";
|
||||
import { resolveMenuPage } from "@/utils/menuRoute";
|
||||
import { useDefaultStore } from "@/store";
|
||||
import type { AnswerDetail, MyAnswerViewItem } from "@/types/data";
|
||||
|
||||
@@ -36,8 +43,13 @@ const route = useRoute();
|
||||
const router = useRouter();
|
||||
const store = useDefaultStore();
|
||||
|
||||
/** 当前视图:只由路由路径决定(两个独立入口,不看 query) */
|
||||
const view = computed<"submit" | "about">(() => (String(route.path).endsWith("/about") ? "about" : "submit"));
|
||||
/** 当前视图:只由路由路径决定(三个独立入口,不看 query) */
|
||||
const view = computed<"submit" | "about" | "fillable">(() => {
|
||||
const p = String(route.path);
|
||||
if (p.endsWith("/about")) return "about";
|
||||
if (p.endsWith("/fillable")) return "fillable";
|
||||
return "submit";
|
||||
});
|
||||
|
||||
const VIEW_META = {
|
||||
submit: {
|
||||
@@ -51,6 +63,12 @@ const VIEW_META = {
|
||||
endpoint: "GET /api/form/my/about-list",
|
||||
predicate: "谓词:subject_id = 会话用户",
|
||||
note: "这些数据是关于我的(老师/管理员代填后指定的主体是我)。整列只读——我不是提交人,改/删是提交人或管理员的动作。"
|
||||
},
|
||||
fillable: {
|
||||
title: "可填写",
|
||||
endpoint: "数据源:会话菜单树(GetUserMod)",
|
||||
predicate: "谓词:模块行 path 形如 /f/{code}(授权在后端)",
|
||||
note: "当前账号被授权的填报入口。列表由会话菜单树直接推导——树里没有的行这里不出现,也不做本地角色过滤。"
|
||||
}
|
||||
} as const;
|
||||
|
||||
@@ -72,6 +90,7 @@ function isMine(row: MyAnswerViewItem): boolean {
|
||||
}
|
||||
|
||||
async function load() {
|
||||
if (view.value === "fillable") return; // 数据源=本地菜单树,无需请求
|
||||
loading.value = true;
|
||||
errorText.value = "";
|
||||
try {
|
||||
@@ -86,9 +105,30 @@ async function load() {
|
||||
}
|
||||
}
|
||||
|
||||
function switchView(target: "submit" | "about") {
|
||||
function switchView(target: "submit" | "about" | "fillable") {
|
||||
if (target === view.value) return;
|
||||
router.push({ path: target === "about" ? "/my/about" : "/my/submit" });
|
||||
router.push({ path: `/${target === "submit" ? "my/submit" : "my/" + target}` });
|
||||
}
|
||||
|
||||
// ==================== P91 可填写视图:菜单树 → 填报入口卡片 ====================
|
||||
|
||||
/**
|
||||
* 与左侧导航同一套解析:buildNavTree 吃 menuRoute(P87 起 /f/{code} 行能解析),
|
||||
* flattenNav 展平后按 page 形态挑填报行;按 page 去重(同名/多行指向同一表单只出一张卡)。
|
||||
*/
|
||||
const fillableItems = computed<FlatNav[]>(() => {
|
||||
if (view.value !== "fillable") return [];
|
||||
const nav = flattenNav(buildNavTree(store.modules, resolveMenuPage));
|
||||
const seen = new Set<string>();
|
||||
return nav.filter((n) => {
|
||||
if (!/^\/f\//.test(n.page) || seen.has(n.page)) return false;
|
||||
seen.add(n.page);
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
function goFill(page: string) {
|
||||
router.push({ path: page });
|
||||
}
|
||||
|
||||
// ==================== 详情 / 编辑 / 删除 ====================
|
||||
@@ -196,13 +236,16 @@ onMounted(load);
|
||||
<div class="page-wrapper yh-fill">
|
||||
<div class="main-container">
|
||||
<div class="view-head">
|
||||
<RadioGroup :model-value="view" type="button" @on-change="(v: string | number) => switchView(String(v) as 'submit' | 'about')">
|
||||
<RadioGroup :model-value="view" type="button"
|
||||
@on-change="(v: string | number) => switchView(String(v) as 'submit' | 'about' | 'fillable')">
|
||||
<Radio label="fillable">可填写</Radio>
|
||||
<Radio label="submit">我提交的</Radio>
|
||||
<Radio label="about">关于我的</Radio>
|
||||
</RadioGroup>
|
||||
<Button icon="md-refresh" @click="load">刷新</Button>
|
||||
<Button v-if="view !== 'fillable'" icon="md-refresh" @click="load">刷新</Button>
|
||||
<span class="toolbar-note">
|
||||
{{ meta.endpoint }} | {{ meta.predicate }}(两个端点两个谓词,不做参数分支)
|
||||
{{ meta.endpoint }} | {{ meta.predicate }}
|
||||
<template v-if="view !== 'fillable'">(两个端点两个谓词,不做参数分支)</template>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -211,9 +254,32 @@ onMounted(load);
|
||||
<template v-if="view === 'about'">
|
||||
<br />本视图不渲染编辑/删除入口(填表人才能改自己提交的那条);也不渲染改状态(填表人在接口层即被拒)。
|
||||
</template>
|
||||
<template v-else-if="view === 'fillable'">
|
||||
<br />入口来自基础平台的「填报」模块行(path=/f/{表单code},授权给组后这里才会出现)。
|
||||
</template>
|
||||
</Alert>
|
||||
|
||||
<div class="table-region" ref="regionRef">
|
||||
<!-- P91 可填写:卡片列表(菜单名 + 进入填报),数据源=会话菜单树 -->
|
||||
<template v-if="view === 'fillable'">
|
||||
<div v-if="fillableItems.length" class="fill-cards">
|
||||
<div v-for="item in fillableItems" :key="item.id" class="fill-card">
|
||||
<div class="fill-card-main">
|
||||
<div class="fill-card-name">
|
||||
<i class="iconfont icon-edit"></i>
|
||||
{{ item.name }}
|
||||
</div>
|
||||
<code class="fill-card-path">{{ item.page }}</code>
|
||||
</div>
|
||||
<Button type="primary" @click="goFill(item.page)">进入填报</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Alert v-else show-icon type="warning">
|
||||
当前账号没有可填写的表单入口。请找管理员在基础平台为本应用配置「填报」模块行
|
||||
(path 填 /f/{表单code}),并授权给你所在的组织组。
|
||||
</Alert>
|
||||
</template>
|
||||
|
||||
<div v-else class="table-region" ref="regionRef">
|
||||
<Table :columns="tableColumns" :data="rows" :height="regionH || undefined" :loading="loading"
|
||||
no-data-text="这里还没有数据" class="link-table" />
|
||||
</div>
|
||||
@@ -278,4 +344,35 @@ onMounted(load);
|
||||
margin-top: 12px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
/* P91 可填写卡片列表 */
|
||||
.fill-cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.fill-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 14px 18px;
|
||||
border: 1px solid #e8eaec;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
}
|
||||
.fill-card-name {
|
||||
font-size: 14px;
|
||||
color: #17233d;
|
||||
font-weight: 600;
|
||||
}
|
||||
.fill-card-name .iconfont {
|
||||
margin-right: 8px;
|
||||
color: var(--brand, #2d8cf0);
|
||||
}
|
||||
.fill-card-path {
|
||||
color: #808695;
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user