列宽/展示:待办办件标题 minWidth 220→160、四页签时间列 170→190、已办意见列 ellipsis+tooltip(长意见单行截断 hover 看全文);应用管理停用态 Tag 改用 iView 默认样式(原 gray 非预设色)。应用筛选下拉数据源同流程定义页(flw_app enabled=1)。 门禁:vite build ✓;vue-tsc 本机不可用(TS 5.9.3 与 vue-tsc 1.8.27 不兼容)
This commit is contained in:
@@ -59,20 +59,20 @@ export const TASK_STATUS_COLOR: Record<number, string> = {
|
|||||||
|
|
||||||
class FlowApi {
|
class FlowApi {
|
||||||
// ==================== 用户面 ====================
|
// ==================== 用户面 ====================
|
||||||
/** 我的待办 */
|
/** 我的待办(appKey=按应用筛选,可空) */
|
||||||
todo(params: { page?: number; size?: number }) {
|
todo(params: { page?: number; size?: number; appKey?: string }) {
|
||||||
return get(`${ROOT}/todo`, params);
|
return get(`${ROOT}/todo`, params);
|
||||||
}
|
}
|
||||||
/** 我的已办 */
|
/** 我的已办(appKey=按应用筛选,可空) */
|
||||||
done(params: { page?: number; size?: number }) {
|
done(params: { page?: number; size?: number; appKey?: string }) {
|
||||||
return get(`${ROOT}/done`, params);
|
return get(`${ROOT}/done`, params);
|
||||||
}
|
}
|
||||||
/** 我的抄送(NOTIFY 节点落库;知会不占待办,独立页签) */
|
/** 我的抄送(NOTIFY 节点落库;知会不占待办,独立页签) */
|
||||||
ccMy(params: { page?: number; size?: number }) {
|
ccMy(params: { page?: number; size?: number; appKey?: string }) {
|
||||||
return get(`${ROOT}/cc/my`, params);
|
return get(`${ROOT}/cc/my`, params);
|
||||||
}
|
}
|
||||||
/** 我提交的办件 */
|
/** 我提交的办件 */
|
||||||
my(params: { status?: number; page?: number; size?: number }) {
|
my(params: { status?: number; page?: number; size?: number; appKey?: string }) {
|
||||||
return get(`${ROOT}/my`, params);
|
return get(`${ROOT}/my`, params);
|
||||||
}
|
}
|
||||||
/** 办件详情(提交人/办理人/本校管理员可见) */
|
/** 办件详情(提交人/办理人/本校管理员可见) */
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// 工作台:我的待办 / 我的已办 / 抄送我的 / 我提交的(概览 §9.4 主路径查询,走 assignee 索引)。
|
// 工作台:我的待办 / 我的已办 / 我提交的 / 抄送我的(概览 §9.4 主路径查询,走 assignee 索引)。
|
||||||
// 规范骨架(bsch应用页规范 §1):tabs-wrapper + Tabs type=card,Tab 切换只 v-show 内容区,分页状态各自独立
|
// 四页签均有「按应用筛选」(办件定义归属 flw_def.app_key)。
|
||||||
import { ref } from "vue";
|
// 规范骨架(bsch应用页规范 §1):tabs-wrapper + Tabs type=card,Tab 切换只 v-show 内容区,分页与筛选状态各自独立
|
||||||
|
import { reactive, ref, onMounted } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { Message, Tag } from "view-ui-plus";
|
import { Message, Tag } from "view-ui-plus";
|
||||||
import MyConfirm from "@/components/MyConfirm";
|
import MyConfirm from "@/components/MyConfirm";
|
||||||
@@ -11,8 +12,10 @@ import { OperatorEnum } from "@/types";
|
|||||||
import { flowApi, TASK_STATUS, TASK_STATUS_COLOR, INSTANCE_STATUS, INSTANCE_STATUS_COLOR } from "@/api/module/flow";
|
import { flowApi, TASK_STATUS, TASK_STATUS_COLOR, INSTANCE_STATUS, INSTANCE_STATUS_COLOR } from "@/api/module/flow";
|
||||||
import { actionColumn } from "@/utils/tableActions";
|
import { actionColumn } from "@/utils/tableActions";
|
||||||
|
|
||||||
|
type TabKey = "todo" | "done" | "cc" | "my";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const tab = ref<"my" | "todo" | "done" | "cc">("todo");
|
const tab = ref<TabKey>("todo");
|
||||||
const drawerVisible = ref(false);
|
const drawerVisible = ref(false);
|
||||||
const drawerInstanceId = ref<string | number>("");
|
const drawerInstanceId = ref<string | number>("");
|
||||||
const todoTableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null);
|
const todoTableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null);
|
||||||
@@ -20,13 +23,19 @@ const doneTableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null);
|
|||||||
const ccTableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null);
|
const ccTableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null);
|
||||||
const myTableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null);
|
const myTableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null);
|
||||||
const myParams = ref<{ status?: number }>({ status: undefined });
|
const myParams = ref<{ status?: number }>({ status: undefined });
|
||||||
|
// 应用筛选(2026-10-12):后端按办件定义归属 flw_def.app_key 过滤;四页签筛选状态各自独立
|
||||||
|
const apps = ref<{ appKey: string; appName: string }[]>([]);
|
||||||
|
const filters = reactive<Record<TabKey, string>>({ todo: "", done: "", cc: "", my: "" });
|
||||||
|
const tableRefs: Record<TabKey, typeof todoTableRef> = {
|
||||||
|
todo: todoTableRef, done: doneTableRef, cc: ccTableRef, my: myTableRef,
|
||||||
|
};
|
||||||
|
|
||||||
const todoColumns = [
|
const todoColumns = [
|
||||||
{ title: "序号", type: "index", width: 70, align: "center" },
|
{ title: "序号", type: "index", width: 70, align: "center" },
|
||||||
{ title: "办件标题", key: "bizTitle", minWidth: 220 },
|
{ title: "办件标题", key: "bizTitle", minWidth: 160 },
|
||||||
{ title: "当前节点", key: "nodeName", width: 160 },
|
{ title: "当前节点", key: "nodeName", width: 160 },
|
||||||
{ title: "提交人", width: 100, render: (h: any, p: any) => h("span", p.row.submitterName || "-") },
|
{ title: "提交人", width: 100, render: (h: any, p: any) => h("span", p.row.submitterName || "-") },
|
||||||
{ title: "到达时间", width: 170, render: (h: any, p: any) => h("span", (p.row.arriveTime || "").replace("T", " ")) },
|
{ title: "到达时间", width: 190, render: (h: any, p: any) => h("span", (p.row.arriveTime || "").replace("T", " ")) },
|
||||||
actionColumn([{ key: "handle", label: "办理", icon: "icon-edit", on: (r: any) => goHandle(r) }], { mode: "text", width: 100, fixed: "right" }),
|
actionColumn([{ key: "handle", label: "办理", icon: "icon-edit", on: (r: any) => goHandle(r) }], { mode: "text", width: 100, fixed: "right" }),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -46,8 +55,9 @@ const doneColumns = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ title: "结果", width: 90, align: "center", render: (h: any, p: any) => h(Tag, { color: TASK_STATUS_COLOR[p.row.status] || "default" }, () => TASK_STATUS[p.row.status] || p.row.status) },
|
{ title: "结果", width: 90, align: "center", render: (h: any, p: any) => h(Tag, { color: TASK_STATUS_COLOR[p.row.status] || "default" }, () => TASK_STATUS[p.row.status] || p.row.status) },
|
||||||
{ title: "意见", key: "opinion", minWidth: 120 },
|
// 意见可能很长(退回理由/审批批语),单行截断 + hover 全文
|
||||||
{ title: "办理时间", width: 170, render: (h: any, p: any) => h("span", (p.row.handleTime || "").replace("T", " ")) },
|
{ title: "意见", key: "opinion", minWidth: 120, ellipsis: true, tooltip: true },
|
||||||
|
{ title: "办理时间", width: 190, render: (h: any, p: any) => h("span", (p.row.handleTime || "").replace("T", " ")) },
|
||||||
{ title: "办件状态", width: 100, render: (h: any, p: any) => h("span", INSTANCE_STATUS[p.row.instanceStatus] || p.row.instanceStatus) },
|
{ title: "办件状态", width: 100, render: (h: any, p: any) => h("span", INSTANCE_STATUS[p.row.instanceStatus] || p.row.instanceStatus) },
|
||||||
actionColumn([{ key: "detail", label: "详情", icon: "icon-browse", on: (r: any) => openDetail(r.instanceId) }], { mode: "text", width: 100, fixed: "right" }),
|
actionColumn([{ key: "detail", label: "详情", icon: "icon-browse", on: (r: any) => openDetail(r.instanceId) }], { mode: "text", width: 100, fixed: "right" }),
|
||||||
];
|
];
|
||||||
@@ -57,7 +67,7 @@ const ccColumns = [
|
|||||||
{ title: "序号", type: "index", width: 70, align: "center" },
|
{ title: "序号", type: "index", width: 70, align: "center" },
|
||||||
{ title: "办件标题", key: "bizTitle", minWidth: 220 },
|
{ title: "办件标题", key: "bizTitle", minWidth: 220 },
|
||||||
{ title: "抄送节点", key: "nodeName", width: 160 },
|
{ title: "抄送节点", key: "nodeName", width: 160 },
|
||||||
{ title: "抄送时间", width: 170, render: (h: any, p: any) => h("span", (p.row.sentTime || "").replace("T", " ")) },
|
{ title: "抄送时间", width: 190, render: (h: any, p: any) => h("span", (p.row.sentTime || "").replace("T", " ")) },
|
||||||
{ title: "办件状态", width: 100, render: (h: any, p: any) => h("span", INSTANCE_STATUS[p.row.instanceStatus] || p.row.instanceStatus || "-") },
|
{ title: "办件状态", width: 100, render: (h: any, p: any) => h("span", INSTANCE_STATUS[p.row.instanceStatus] || p.row.instanceStatus || "-") },
|
||||||
actionColumn([{ key: "detail", label: "详情", icon: "icon-browse", on: (r: any) => openDetail(r.instanceId) }], { mode: "text", width: 100, fixed: "right" }),
|
actionColumn([{ key: "detail", label: "详情", icon: "icon-browse", on: (r: any) => openDetail(r.instanceId) }], { mode: "text", width: 100, fixed: "right" }),
|
||||||
];
|
];
|
||||||
@@ -70,8 +80,8 @@ const myColumns = [
|
|||||||
{ title: "状态", width: 100, align: "center", render: (h: any, p: any) =>
|
{ title: "状态", width: 100, align: "center", render: (h: any, p: any) =>
|
||||||
h(Tag, { color: INSTANCE_STATUS_COLOR[p.row.status] || "gray" }, () => INSTANCE_STATUS[p.row.status] || p.row.status) },
|
h(Tag, { color: INSTANCE_STATUS_COLOR[p.row.status] || "gray" }, () => INSTANCE_STATUS[p.row.status] || p.row.status) },
|
||||||
{ title: "当前节点", key: "currentNodes", width: 150 },
|
{ title: "当前节点", key: "currentNodes", width: 150 },
|
||||||
{ title: "提交时间", width: 170, render: (h: any, p: any) => h("span", (p.row.submitTime || "").replace("T", " ")) },
|
{ title: "提交时间", width: 190, render: (h: any, p: any) => h("span", (p.row.submitTime || "").replace("T", " ")) },
|
||||||
{ title: "办结时间", width: 170, render: (h: any, p: any) => h("span", (p.row.finishTime || "").replace("T", " ") || "-") },
|
{ title: "办结时间", width: 190, render: (h: any, p: any) => h("span", (p.row.finishTime || "").replace("T", " ") || "-") },
|
||||||
actionColumn(
|
actionColumn(
|
||||||
[
|
[
|
||||||
{ key: "detail", label: "详情", icon: "icon-browse", on: (r: any) => openDetail(r.instanceId) },
|
{ key: "detail", label: "详情", icon: "icon-browse", on: (r: any) => openDetail(r.instanceId) },
|
||||||
@@ -83,16 +93,36 @@ const myColumns = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
// dataApi 返回 Method(alova usePagination 负责发送与解包),不能调 .send()
|
// dataApi 返回 Method(alova usePagination 负责发送与解包),不能调 .send()
|
||||||
const todoApi = (p: { pageIndex: number; pageSize: number }) => flowApi.todo({ page: p.pageIndex, size: p.pageSize });
|
const todoApi = (p: { pageIndex: number; pageSize: number }) =>
|
||||||
const doneApi = (p: { pageIndex: number; pageSize: number }) => flowApi.done({ page: p.pageIndex, size: p.pageSize });
|
flowApi.todo({ appKey: filters.todo || undefined, page: p.pageIndex, size: p.pageSize });
|
||||||
const ccApi = (p: { pageIndex: number; pageSize: number }) => flowApi.ccMy({ page: p.pageIndex, size: p.pageSize });
|
const doneApi = (p: { pageIndex: number; pageSize: number }) =>
|
||||||
|
flowApi.done({ appKey: filters.done || undefined, page: p.pageIndex, size: p.pageSize });
|
||||||
|
const ccApi = (p: { pageIndex: number; pageSize: number }) =>
|
||||||
|
flowApi.ccMy({ appKey: filters.cc || undefined, page: p.pageIndex, size: p.pageSize });
|
||||||
const myApi = (p: { pageIndex: number; pageSize: number }) =>
|
const myApi = (p: { pageIndex: number; pageSize: number }) =>
|
||||||
flowApi.my({ status: myParams.value.status || undefined, page: p.pageIndex, size: p.pageSize });
|
flowApi.my({ status: myParams.value.status || undefined, appKey: filters.my || undefined, page: p.pageIndex, size: p.pageSize });
|
||||||
|
|
||||||
function onMySearch() {
|
function onMySearch() {
|
||||||
myTableRef.value?.refreshData();
|
myTableRef.value?.refreshData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 应用下拉数据源:应用设置白名单(flw_app enabled=1),失败降级为空下拉 */
|
||||||
|
async function loadApps() {
|
||||||
|
try {
|
||||||
|
const rows: any[] = (await flowApi.appList().send()) || [];
|
||||||
|
apps.value = rows.filter((r) => r.enabled === 1)
|
||||||
|
.map((r) => ({ appKey: String(r.appKey), appName: r.appName || String(r.appKey) }));
|
||||||
|
} catch {
|
||||||
|
apps.value = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 筛选变更:回到第一页重查;未加载过的页签标记为已加载,避免切页签时重复请求 */
|
||||||
|
function onFilterChange(name: TabKey) {
|
||||||
|
loaded[name] = true;
|
||||||
|
tableRefs[name].value?.refreshData();
|
||||||
|
}
|
||||||
|
|
||||||
async function onWithdraw(row: any) {
|
async function onWithdraw(row: any) {
|
||||||
await MyConfirm({
|
await MyConfirm({
|
||||||
title: "撤回",
|
title: "撤回",
|
||||||
@@ -129,24 +159,15 @@ function openDetail(instanceId: string | number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 各内容区 v-show 共存,非首个页签首次切换才拉取(保持原惰性加载行为)
|
// 各内容区 v-show 共存,非首个页签首次切换才拉取(保持原惰性加载行为)
|
||||||
const doneLoaded = ref(false);
|
const loaded = reactive<Record<TabKey, boolean>>({ todo: true, done: false, cc: false, my: false });
|
||||||
const ccLoaded = ref(false);
|
|
||||||
const myLoaded = ref(false);
|
|
||||||
function switchTab(name: string) {
|
function switchTab(name: string) {
|
||||||
tab.value = name as any;
|
tab.value = name as TabKey;
|
||||||
if (name === "done" && !doneLoaded.value) {
|
if (loaded[name as TabKey]) return;
|
||||||
doneLoaded.value = true;
|
loaded[name as TabKey] = true;
|
||||||
doneTableRef.value?.reloadData(OperatorEnum.sel);
|
tableRefs[name as TabKey].value?.reloadData(OperatorEnum.sel);
|
||||||
}
|
|
||||||
if (name === "cc" && !ccLoaded.value) {
|
|
||||||
ccLoaded.value = true;
|
|
||||||
ccTableRef.value?.reloadData(OperatorEnum.sel);
|
|
||||||
}
|
|
||||||
if (name === "my" && !myLoaded.value) {
|
|
||||||
myLoaded.value = true;
|
|
||||||
myTableRef.value?.reloadData(OperatorEnum.sel);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(loadApps);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -155,19 +176,28 @@ function switchTab(name: string) {
|
|||||||
<Tabs class="tabs-panel" :value="tab" @on-click="switchTab">
|
<Tabs class="tabs-panel" :value="tab" @on-click="switchTab">
|
||||||
<TabPane label="我的待办" name="todo" />
|
<TabPane label="我的待办" name="todo" />
|
||||||
<TabPane label="我的已办" name="done" />
|
<TabPane label="我的已办" name="done" />
|
||||||
<TabPane label="抄送我的" name="cc" />
|
|
||||||
<TabPane label="我提交的" name="my" />
|
<TabPane label="我提交的" name="my" />
|
||||||
|
<TabPane label="抄送我的" name="cc" />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
<div class="tab-body" v-show="tab === 'todo'">
|
<div class="tab-body" v-show="tab === 'todo'">
|
||||||
|
<div class="search-wrap fix">
|
||||||
|
<Select v-model="filters.todo" class="search-wrap_sel" clearable filterable placeholder="按应用筛选"
|
||||||
|
@on-change="onFilterChange('todo')">
|
||||||
|
<Option v-for="a in apps" :key="a.appKey" :value="a.appKey">{{ a.appName }}</Option>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
<TablePageAlovaNew ref="todoTableRef" :dataApi="todoApi" :columns="todoColumns" />
|
<TablePageAlovaNew ref="todoTableRef" :dataApi="todoApi" :columns="todoColumns" />
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-body" v-show="tab === 'done'">
|
<div class="tab-body" v-show="tab === 'done'">
|
||||||
|
<div class="search-wrap fix">
|
||||||
|
<Select v-model="filters.done" class="search-wrap_sel" clearable filterable placeholder="按应用筛选"
|
||||||
|
@on-change="onFilterChange('done')">
|
||||||
|
<Option v-for="a in apps" :key="a.appKey" :value="a.appKey">{{ a.appName }}</Option>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
<TablePageAlovaNew ref="doneTableRef" :dataApi="doneApi" :columns="doneColumns" :autoLoad="false" />
|
<TablePageAlovaNew ref="doneTableRef" :dataApi="doneApi" :columns="doneColumns" :autoLoad="false" />
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-body" v-show="tab === 'cc'">
|
|
||||||
<TablePageAlovaNew ref="ccTableRef" :dataApi="ccApi" :columns="ccColumns" :autoLoad="false" />
|
|
||||||
</div>
|
|
||||||
<div class="tab-body" v-show="tab === 'my'">
|
<div class="tab-body" v-show="tab === 'my'">
|
||||||
<div class="search-wrap fix">
|
<div class="search-wrap fix">
|
||||||
<Select v-model="myParams.status" class="search-wrap_sel" clearable placeholder="状态" @on-change="onMySearch">
|
<Select v-model="myParams.status" class="search-wrap_sel" clearable placeholder="状态" @on-change="onMySearch">
|
||||||
@@ -176,9 +206,22 @@ function switchTab(name: string) {
|
|||||||
<Option :value="30" label="已拒绝" />
|
<Option :value="30" label="已拒绝" />
|
||||||
<Option :value="40" label="已撤回" />
|
<Option :value="40" label="已撤回" />
|
||||||
</Select>
|
</Select>
|
||||||
|
<Select v-model="filters.my" class="search-wrap_sel" clearable filterable placeholder="按应用筛选"
|
||||||
|
@on-change="onFilterChange('my')">
|
||||||
|
<Option v-for="a in apps" :key="a.appKey" :value="a.appKey">{{ a.appName }}</Option>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<TablePageAlovaNew ref="myTableRef" :dataApi="myApi" :columns="myColumns" :autoLoad="false" />
|
<TablePageAlovaNew ref="myTableRef" :dataApi="myApi" :columns="myColumns" :autoLoad="false" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tab-body" v-show="tab === 'cc'">
|
||||||
|
<div class="search-wrap fix">
|
||||||
|
<Select v-model="filters.cc" class="search-wrap_sel" clearable filterable placeholder="按应用筛选"
|
||||||
|
@on-change="onFilterChange('cc')">
|
||||||
|
<Option v-for="a in apps" :key="a.appKey" :value="a.appKey">{{ a.appName }}</Option>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<TablePageAlovaNew ref="ccTableRef" :dataApi="ccApi" :columns="ccColumns" :autoLoad="false" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<InstanceDrawer v-model="drawerVisible" :instance-id="drawerInstanceId" />
|
<InstanceDrawer v-model="drawerVisible" :instance-id="drawerInstanceId" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -42,9 +42,9 @@ const columns = [
|
|||||||
title: "状态",
|
title: "状态",
|
||||||
width: 90,
|
width: 90,
|
||||||
align: "center",
|
align: "center",
|
||||||
|
// 停用态走 iView 默认 Tag(不指定 color),避免自定义灰底与默认样式不一致
|
||||||
render: (h: any, p: any) =>
|
render: (h: any, p: any) =>
|
||||||
h(Tag, { color: p.row.enabled === 1 ? "green" : "gray" },
|
p.row.enabled === 1 ? h(Tag, { color: "green" }, () => "已开启") : h(Tag, () => "已停用"),
|
||||||
() => (p.row.enabled === 1 ? "已开启" : "已停用")),
|
|
||||||
},
|
},
|
||||||
actionColumn(
|
actionColumn(
|
||||||
[
|
[
|
||||||
|
|||||||
Reference in New Issue
Block a user