fix(ui): 用户验收反馈 R2——单卡片骨架+实测高度对齐 Notice、应用管理启用/停用、Tag 真组件、科室行 deptKey、成员多选、顶栏去房子图标钮
build / build (push) Successful in 4m4s

This commit is contained in:
chenxing
2026-10-10 22:42:58 +08:00
parent affb1bdeb7
commit a998e455d2
11 changed files with 257 additions and 273 deletions
+16 -11
View File
@@ -1,13 +1,15 @@
<template> <template>
<Table :columns="columns" :data="list" :height="tableH" :loading="loading" no-data-text="暂无数据" <div class="table-region" ref="regionRef">
class="link-table table-bottom-box" @on-selection-change="selectChange"> <Table :columns="columns" :data="list" :height="tableH" :loading="loading" no-data-text="暂无数据"
<template #loading> class="link-table table-bottom-box" @on-selection-change="selectChange">
<div class="table-spin-load">加载中</div> <template #loading>
</template> <div class="table-spin-load">加载中</div>
<template v-for="slotName in tableSlotNames" :key="slotName" v-slot:[slotName]="slotProps"> </template>
<slot :name="slotName" v-bind="slotProps" /> <template v-for="slotName in tableSlotNames" :key="slotName" v-slot:[slotName]="slotProps">
</template> <slot :name="slotName" v-bind="slotProps" />
</Table> </template>
</Table>
</div>
<div class="page-panel"> <div class="page-panel">
<div class="fl ml-15 align-middle"> <div class="fl ml-15 align-middle">
@@ -22,7 +24,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, useSlots, watch } from 'vue' import { computed, ref, useSlots, watch } from 'vue'
import Utils from '@/utils' import { useElementHeight } from '@/utils/hook/useElementHeight'
import { OperatorEnum } from '@/types' import { OperatorEnum } from '@/types'
interface Props { interface Props {
@@ -44,7 +46,10 @@ const emit = defineEmits<{
}>() }>()
const slots = useSlots() const slots = useSlots()
const tableH = ref(props.tableHeight ?? Utils.useTableHeight()) const regionRef = ref<HTMLElement | null>(null)
const measuredH = useElementHeight(regionRef)
// 显式传 tableHeight 则尊重之;否则实测 .table-region(flex:1)高度喂固定表头,首帧未测出前用 undefined(auto)
const tableH = computed(() => props.tableHeight ?? (measuredH.value || undefined))
const tableSlotNames = computed(() => Object.keys(slots).filter(name => !['default', 'loading'].includes(name))) const tableSlotNames = computed(() => Object.keys(slots).filter(name => !['default', 'loading'].includes(name)))
const selectList = ref<number[]>([]) const selectList = ref<number[]>([])
-95
View File
@@ -1,95 +0,0 @@
<template>
<!-- 单选用户输入(参照 Notice 自定义组选人范式):readonly Input + 选择按钮 + 清空;
v-model=用户 id;display-name 回显姓名快照(调用方已知姓名时随值下传) -->
<div class="sel-user-box">
<Input
type="text"
v-model="nameText"
readonly
placeholder="点击「选择」指定用户"
class="input"
></Input>
<Button icon="md-add" @click="openModal">选择</Button>
<Button icon="md-trash" v-if="modelValue" @click="reset"></Button>
<UserCheckModal
ref="userCheckModal"
:sources="sources"
:multiple="false"
@on-submit="onSubmit"
/>
</div>
</template>
<script lang="ts">
import UserCheckModal from "@/components/UserCheckModal.vue";
import { flowApi } from "@/api/module/flow";
export default {
name: "UserSelectInput",
components: { UserCheckModal },
props: {
modelValue: {
type: Number,
default: null,
},
/** 当前 id 的姓名快照(编辑回显/选中即得,避免 #id 兜底) */
displayName: {
type: String,
default: "",
},
},
emits: ["update:modelValue", "update:displayName", "on-change"],
data() {
return {
nameText: this.displayName,
sources: [
{
name: "基础平台",
loader: () => flowApi.adminTeacherTree().send() as Promise<any[]>,
},
],
};
},
methods: {
openModal() {
(this.$refs.userCheckModal as any).show();
},
/** 单选弹窗提交:数组恒为 1 条(多选复用同组件) */
onSubmit(arr: any[]) {
if (!arr || !arr.length) return;
const u = arr[0];
this.nameText = u.name + "(" + u.id + ")";
this.$emit("update:modelValue", u.id);
this.$emit("update:displayName", u.name);
this.$emit("on-change", u);
},
reset() {
this.nameText = "";
this.$emit("update:modelValue", null);
this.$emit("update:displayName", "");
this.$emit("on-change", null);
},
},
watch: {
modelValue(val: number) {
// 外部清空/重置时同步清姓名
if (!val) this.nameText = "";
else if (!this.nameText) this.nameText = "#" + val;
},
displayName(val: string) {
this.nameText = val;
},
},
};
</script>
<style lang="scss" scoped>
.sel-user-box {
display: flex;
align-items: center;
gap: 8px;
.input {
flex: 1;
}
}
</style>
-5
View File
@@ -191,11 +191,6 @@ function linkHomePage() {
<header class="header-container fix"> <header class="header-container fix">
<!-- 云海 logo 徽标(结构与 Notice layout 对齐,皮肤见通道 base.layout.css .logo-badge) --> <!-- 云海 logo 徽标(结构与 Notice layout 对齐,皮肤见通道 base.layout.css .logo-badge) -->
<span class="logo-badge fl"><img :src="logoUrl" alt="云海" @error="onLogoError" /></span> <span class="logo-badge fl"><img :src="logoUrl" alt="云海" @error="onLogoError" /></span>
<div class="fl align-middle icon-box">
<div title="首页" class="icon-btn" @click="linkHomePage">
<i class="iconfont icon-shouye"></i>
</div>
</div>
<div class="delimiter fl align-middle"></div> <div class="delimiter fl align-middle"></div>
<h4 class="fl align-middle">{{ appName }}</h4> <h4 class="fl align-middle">{{ appName }}</h4>
<div class="school-bg"></div> <div class="school-bg"></div>
+36
View File
@@ -0,0 +1,36 @@
import { ref, onMounted, onBeforeUnmount, watch, type Ref } from "vue";
/**
* 实测某容器元素的可用高度(px),随尺寸变化实时更新。
*
* 用途:表格页把 <Table> 放进 flex:1 的 .table-region 里,用本组合式量出该区域
* 的真实高度喂给 view-ui 的 `:height`(固定表头需要一个数值高度),从而彻底替代
* 旧的 useTableHeight()「视口高 - 一堆写死常量」的算法——加/删工具条、改顶栏高
* 都不再需要同步改任何魔法数字。
*
* @param el 目标容器 ref(模板里 ref 绑定的那个元素)
* @returns 响应式高度(px);挂载测量前为 0,调用方可 `:height="h || undefined"` 兜底
*/
export function useElementHeight(el: Ref<HTMLElement | null>): Ref<number> {
const height = ref(0);
let ro: ResizeObserver | null = null;
const measure = () => {
if (el.value) height.value = Math.floor(el.value.clientHeight);
};
const observe = () => {
ro?.disconnect();
if (!el.value || typeof ResizeObserver === "undefined") return;
measure();
ro = new ResizeObserver(measure);
ro.observe(el.value);
};
onMounted(observe);
// v-if / v-show 切换导致容器重建时,重新挂观察
watch(el, observe);
onBeforeUnmount(() => ro?.disconnect());
return height;
}
+4 -6
View File
@@ -2,17 +2,15 @@
// 我的办件(提交人视角):列表 + 撤回/催办/详情 // 我的办件(提交人视角):列表 + 撤回/催办/详情
import { ref } from "vue"; import { ref } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { Message } from "view-ui-plus"; import { Message, Tag } from "view-ui-plus";
import MyConfirm from "@/components/MyConfirm"; import MyConfirm from "@/components/MyConfirm";
import TablePageAlovaNew from "@/components/TablePageAlovaNew.vue"; import TablePageAlovaNew from "@/components/TablePageAlovaNew.vue";
import { OperatorEnum } from "@/types"; import { OperatorEnum } from "@/types";
import { flowApi, INSTANCE_STATUS, INSTANCE_STATUS_COLOR } from "@/api/module/flow"; import { flowApi, INSTANCE_STATUS, INSTANCE_STATUS_COLOR } from "@/api/module/flow";
import { actionColumn } from "@/utils/tableActions"; import { actionColumn } from "@/utils/tableActions";
import Utils from "@/utils";
const router = useRouter(); const router = useRouter();
const tableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null); const tableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null);
const tableHeight = ref(Utils.useTableHeight({ isSearch: true, isPage: true }));
const dataParams = ref<{ status?: number }>({ status: undefined }); const dataParams = ref<{ status?: number }>({ status: undefined });
const columns = [ const columns = [
@@ -20,7 +18,7 @@ const columns = [
{ title: "办件标题", key: "bizTitle", minWidth: 220 }, { title: "办件标题", key: "bizTitle", minWidth: 220 },
{ title: "业务类型", key: "bizType", width: 110 }, { title: "业务类型", key: "bizType", width: 110 },
{ 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: 170, 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: 170, render: (h: any, p: any) => h("span", (p.row.finishTime || "").replace("T", " ") || "-") },
@@ -70,7 +68,7 @@ async function onUrge(row: any) {
</script> </script>
<template> <template>
<div class="page-wrapper"> <div class="page-wrapper yh-fill">
<div class="main-container"> <div class="main-container">
<div class="search-wrap fix"> <div class="search-wrap fix">
<Select v-model="dataParams.status" class="search-wrap_sel" clearable placeholder="状态" @on-change="onSearch"> <Select v-model="dataParams.status" class="search-wrap_sel" clearable placeholder="状态" @on-change="onSearch">
@@ -80,7 +78,7 @@ async function onUrge(row: any) {
<Option :value="40" label="已撤回" /> <Option :value="40" label="已撤回" />
</Select> </Select>
</div> </div>
<TablePageAlovaNew ref="tableRef" :dataApi="tableApi" :columns="columns" :tableHeight="tableHeight" /> <TablePageAlovaNew ref="tableRef" :dataApi="tableApi" :columns="columns" />
</div> </div>
</div> </div>
</template> </template>
+19 -19
View File
@@ -3,19 +3,17 @@
// 规范骨架(bsch应用页规范 §1):tabs-wrapper + Tabs type=card,Tab 切换只 v-show 内容区,分页状态各自独立 // 规范骨架(bsch应用页规范 §1):tabs-wrapper + Tabs type=card,Tab 切换只 v-show 内容区,分页状态各自独立
import { ref } from "vue"; import { ref } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { Tag } from "view-ui-plus";
import TablePageAlovaNew from "@/components/TablePageAlovaNew.vue"; import TablePageAlovaNew from "@/components/TablePageAlovaNew.vue";
import { OperatorEnum } from "@/types"; import { OperatorEnum } from "@/types";
import { flowApi, TASK_STATUS, TASK_STATUS_COLOR, INSTANCE_STATUS } from "@/api/module/flow"; import { flowApi, TASK_STATUS, TASK_STATUS_COLOR, INSTANCE_STATUS } from "@/api/module/flow";
import { actionColumn } from "@/utils/tableActions"; import { actionColumn } from "@/utils/tableActions";
import Utils from "@/utils";
const router = useRouter(); const router = useRouter();
const tab = ref<"todo" | "done" | "cc">("todo"); const tab = ref<"todo" | "done" | "cc">("todo");
const todoTableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null); const todoTableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null);
const doneTableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null); const doneTableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null);
const ccTableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null); const ccTableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null);
// tabs 页:表格高度在常规骨架上再扣 Tab 条
const tableHeight = ref(Utils.useTableHeight({ isTabs: true, isOperat: true, isPage: true }));
const todoColumns = [ const todoColumns = [
{ title: "序号", type: "index", width: 70, align: "center" }, { title: "序号", type: "index", width: 70, align: "center" },
@@ -35,13 +33,13 @@ const doneColumns = [
title: "节点", minWidth: 150, render: (h: any, p: any) => { title: "节点", minWidth: 150, render: (h: any, p: any) => {
const kids: any[] = [h("span", p.row.nodeName || p.row.nodeId || "-")]; const kids: any[] = [h("span", p.row.nodeName || p.row.nodeId || "-")];
if (p.row.doneCount > 1) { if (p.row.doneCount > 1) {
kids.push(h("Tag", { color: "default", size: "small", style: "margin-left:6px" }, kids.push(h(Tag, { color: "default", size: "small", style: "margin-left:6px" },
() => `共办理 ${p.row.doneCount} 次`)); () => `共办理 ${p.row.doneCount} 次`));
} }
return h("div", kids); return h("div", kids);
}, },
}, },
{ 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 }, { title: "意见", key: "opinion", minWidth: 120 },
{ title: "办理时间", width: 170, render: (h: any, p: any) => h("span", (p.row.handleTime || "").replace("T", " ")) }, { title: "办理时间", width: 170, 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) },
@@ -84,21 +82,23 @@ function switchTab(name: string) {
</script> </script>
<template> <template>
<div class="tabs-wrapper"> <div class="tabs-wrapper yh-fill">
<Tabs class="tabs-panel" :value="tab" @on-click="switchTab"> <div class="main-container yh-tabs-card">
<TabPane label="我的待办" name="todo" /> <Tabs class="tabs-panel" :value="tab" @on-click="switchTab">
<TabPane label="我的已办" name="done" /> <TabPane label="我的待办" name="todo" />
<TabPane label="抄送我的" name="cc" /> <TabPane label="我的已办" name="done" />
</Tabs> <TabPane label="抄送我的" name="cc" />
</Tabs>
<div class="main-container" v-show="tab === 'todo'"> <div class="tab-body" v-show="tab === 'todo'">
<TablePageAlovaNew ref="todoTableRef" :dataApi="todoApi" :columns="todoColumns" :tableHeight="tableHeight" /> <TablePageAlovaNew ref="todoTableRef" :dataApi="todoApi" :columns="todoColumns" />
</div> </div>
<div class="main-container" v-show="tab === 'done'"> <div class="tab-body" v-show="tab === 'done'">
<TablePageAlovaNew ref="doneTableRef" :dataApi="doneApi" :columns="doneColumns" :tableHeight="tableHeight" :autoLoad="false" /> <TablePageAlovaNew ref="doneTableRef" :dataApi="doneApi" :columns="doneColumns" :autoLoad="false" />
</div> </div>
<div class="main-container" v-show="tab === 'cc'"> <div class="tab-body" v-show="tab === 'cc'">
<TablePageAlovaNew ref="ccTableRef" :dataApi="ccApi" :columns="ccColumns" :tableHeight="tableHeight" :autoLoad="false" /> <TablePageAlovaNew ref="ccTableRef" :dataApi="ccApi" :columns="ccColumns" :autoLoad="false" />
</div>
</div> </div>
</div> </div>
</template> </template>
+86 -68
View File
@@ -3,42 +3,40 @@
// tab2 科室管理(flw_dept 组+成员模型,取代旧「办理人映射」页——deptKey=本地自增 id, // tab2 科室管理(flw_dept 组+成员模型,取代旧「办理人映射」页——deptKey=本地自增 id,
// 切源后旧流程定义引用的 UserBase 组 id 需重建科室后重新配置)。 // 切源后旧流程定义引用的 UserBase 组 id 需重建科室后重新配置)。
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import { Message } from "view-ui-plus"; import { Message, Tag } from "view-ui-plus";
import MyConfirm from "@/components/MyConfirm"; import MyConfirm from "@/components/MyConfirm";
import UserSelectInput from "@/components/UserSelectInput.vue"; import UserCheckModal from "@/components/UserCheckModal.vue";
import { flowApi } from "@/api/module/flow"; import { flowApi } from "@/api/module/flow";
import { actionColumn } from "@/utils/tableActions"; import { actionColumn } from "@/utils/tableActions";
import Utils from "@/utils"; import { useElementHeight } from "@/utils/hook/useElementHeight";
const tab = ref("apps"); const tab = ref("apps");
// ==================== tab1 应用管理 ==================== // ==================== tab1 应用管理 ====================
const loading = ref(false); const loading = ref(false);
const rows = ref<any[]>([]); const rows = ref<any[]>([]);
const tableHeight = ref(Utils.useTableHeight({ isOperat: true })); const appRegionRef = ref<HTMLElement | null>(null);
const appH = useElementHeight(appRegionRef);
const columns = [ const columns = [
{ title: "序号", type: "index", width: 70, align: "center" }, { title: "序号", type: "index", width: 70, align: "center" },
{ title: "应用", key: "appName", minWidth: 200 }, { title: "应用", key: "appName", minWidth: 200 },
{ title: "appKey", key: "appKey", width: 120, align: "center" }, { title: "appKey", key: "appKey", width: 120, align: "center" },
{
title: "对接流程",
width: 120,
align: "center",
render: (h: any, p: any) =>
h("i-switch", {
props: { value: p.row.enabled === 1, size: "large", loading: p.row._switching },
on: { "on-change": (v: boolean) => onToggle(p.row, v) },
}),
},
{ {
title: "状态", title: "状态",
width: 90, width: 90,
align: "center", align: "center",
render: (h: any, p: any) => render: (h: any, p: any) =>
h("Tag", { props: { color: p.row.enabled === 1 ? "green" : "gray" } }, h(Tag, { color: p.row.enabled === 1 ? "green" : "gray" },
() => (p.row.enabled === 1 ? "已开启" : "已停用")), () => (p.row.enabled === 1 ? "已开启" : "已停用")),
}, },
actionColumn(
[
{ key: "disable", label: "停用", icon: "icon-stop", on: (r: any) => onToggle(r, false), disabledWhen: (r: any) => r.enabled !== 1, danger: true },
{ key: "enable", label: "启用", icon: "icon-play", on: (r: any) => onToggle(r, true), disabledWhen: (r: any) => r.enabled === 1 },
],
{ mode: "text", width: 120 },
),
]; ];
async function load() { async function load() {
@@ -67,6 +65,8 @@ async function onToggle(row: any, enabled: boolean) {
// ==================== tab2 科室管理 ==================== // ==================== tab2 科室管理 ====================
const deptLoading = ref(false); const deptLoading = ref(false);
const depts = ref<any[]>([]); const depts = ref<any[]>([]);
const deptRegionRef = ref<HTMLElement | null>(null);
const deptH = useElementHeight(deptRegionRef);
const deptColumns = [ const deptColumns = [
{ title: "序号", type: "index", width: 60, align: "center" }, { title: "序号", type: "index", width: 60, align: "center" },
{ title: "科室名", key: "deptName", minWidth: 160 }, { title: "科室名", key: "deptName", minWidth: 160 },
@@ -78,18 +78,12 @@ const deptColumns = [
align: "center", align: "center",
render: (h: any, p: any) => h("span", String(p.row.count)), render: (h: any, p: any) => h("span", String(p.row.count)),
}, },
{
title: "来源",
width: 90,
align: "center",
render: (h: any, p: any) => h("Tag", () => (p.row._pulled ? "拉取" : "手工")),
},
{ {
title: "状态", title: "状态",
width: 80, width: 80,
align: "center", align: "center",
render: (h: any, p: any) => render: (h: any, p: any) =>
h("Tag", { props: { color: p.row.status === 0 ? "green" : "gray" } }, h(Tag, { color: p.row.status === 0 ? "green" : "gray" },
() => (p.row.status === 0 ? "启用" : "停用")), () => (p.row.status === 0 ? "启用" : "停用")),
}, },
actionColumn( actionColumn(
@@ -160,7 +154,7 @@ async function doRename() {
} }
renameSaving.value = true; renameSaving.value = true;
try { try {
await flowApi.deptRename(renameRow.value.id, renameName.value.trim()).send(); await flowApi.deptRename(renameRow.value.deptKey, renameName.value.trim()).send();
Message.success("已改名"); Message.success("已改名");
renameVisible.value = false; renameVisible.value = false;
loadDepts(); loadDepts();
@@ -180,7 +174,7 @@ function onDeptStatus(row: any, enabled: boolean) {
? "引用它的流程节点恢复解析到科室成员" ? "引用它的流程节点恢复解析到科室成员"
: "停用后引用它的流程定义不能发布;在途办件推进到该节点会报无可用办理人", : "停用后引用它的流程定义不能发布;在途办件推进到该节点会报无可用办理人",
async onOk() { async onOk() {
await flowApi.deptStatus(row.id, enabled).send(); await flowApi.deptStatus(row.deptKey, enabled).send();
Message.success("操作成功"); Message.success("操作成功");
loadDepts(); loadDepts();
}, },
@@ -193,7 +187,7 @@ function onDelete(row: any) {
msg: `确定删除「${row.deptName}」及其全部成员吗?`, msg: `确定删除「${row.deptName}」及其全部成员吗?`,
info: "删除后引用该 deptKey 的流程定义节点将解析失败(发布校验拦截/办理报错),请先确认无流程引用", info: "删除后引用该 deptKey 的流程定义节点将解析失败(发布校验拦截/办理报错),请先确认无流程引用",
async onOk() { async onOk() {
await flowApi.deptDelete(row.id).send(); await flowApi.deptDelete(row.deptKey).send();
Message.success("已删除"); Message.success("已删除");
loadDepts(); loadDepts();
}, },
@@ -205,8 +199,6 @@ const memberVisible = ref(false);
const memberRow = ref<any>(null); const memberRow = ref<any>(null);
const members = ref<any[]>([]); const members = ref<any[]>([]);
const memberLoading = ref(false); const memberLoading = ref(false);
const addUserId = ref<number | null>(null);
const addUserName = ref("");
function onMembers(row: any) { function onMembers(row: any) {
memberRow.value = row; memberRow.value = row;
@@ -217,22 +209,43 @@ function onMembers(row: any) {
async function loadMembers(row: any) { async function loadMembers(row: any) {
memberLoading.value = true; memberLoading.value = true;
try { try {
members.value = ((await flowApi.deptMembers(row.id).send()) as any[]) || []; members.value = ((await flowApi.deptMembers(row.deptKey).send()) as any[]) || [];
} catch (e: any) {
Message.error(e?.message || "成员加载失败");
members.value = [];
} finally { } finally {
memberLoading.value = false; memberLoading.value = false;
} }
} }
async function onMemberAdd() { // 添加成员:规范选人组件(多选),提交即批量加入当前科室
if (!addUserId.value) { const addMemberCheckRef = ref();
const memberSources = [
{
name: "基础平台",
loader: () => flowApi.adminTeacherTree().send() as Promise<any[]>,
},
];
function onAddMemberShow() {
addMemberCheckRef.value?.show();
}
async function onMemberAddSubmit(users: any[]) {
if (!users || !users.length) {
Message.warning("请先选择用户");
return;
}
const ids = users
.map((u: any) => Number(typeof u === "object" && u != null ? u.id : u))
.filter((n: number) => Number.isFinite(n) && n > 0);
if (!ids.length) {
Message.warning("请先选择用户"); Message.warning("请先选择用户");
return; return;
} }
try { try {
const added: any = await flowApi.deptMemberAdd(memberRow.value.id, [addUserId.value]).send(); const added: any = await flowApi.deptMemberAdd(memberRow.value.deptKey, ids).send();
Message.success(added > 0 ? `已添加 ${added} 人` : "该成员已存在"); Message.success(added > 0 ? `已添加 ${added} 人` : "所选成员均已存在");
addUserId.value = null;
addUserName.value = "";
loadMembers(memberRow.value); loadMembers(memberRow.value);
loadDepts(); loadDepts();
} catch (e: any) { } catch (e: any) {
@@ -298,33 +311,43 @@ onMounted(() => {
</script> </script>
<template> <template>
<div class="page-wrapper"> <div class="page-wrapper yh-fill">
<div class="main-container"> <div class="main-container yh-tabs-card">
<Tabs v-model="tab"> <Tabs class="tabs-panel" v-model="tab">
<TabPane label="应用管理" name="apps"> <TabPane label="应用管理" name="apps"></TabPane>
<div class="fix mb-12"> <TabPane label="科室管理" name="depts"></TabPane>
<Button class="fl" type="primary" size="large" @click="load">刷新(同步学校池)</Button>
<span class="fl" style="color: #808695; font-size: 12px; line-height: 40px; margin-left: 10px">
应用清单来自本校已开通应用(UserBase 学校组开通,含学校别名);开启后可用于流程归属与筛选。
新开通/改名在 UserBase 管理端操作后回本页刷新即可
</span>
</div>
<Table :columns="columns" :data="rows" :loading="loading" class="link-table table-bottom-box"
:height="tableHeight" no-data-text="本校暂无已开通应用(请先在用户中心开通)" />
</TabPane>
<TabPane label="科室管理" name="depts">
<div class="fix mb-12">
<Button class="fl" type="primary" size="large" @click="onCreate">新建科室</Button>
<Button class="fl" size="large" style="margin-left: 8px" @click="onGroupPreview">一键拉取教师组</Button>
<span class="fl" style="color: #808695; font-size: 12px; line-height: 40px; margin-left: 10px">
流程节点「按科室」办理的解析源;重复拉取=刷新组名与成员集(不重复建组)
</span>
</div>
<Table :columns="deptColumns" :data="depts" :loading="deptLoading" class="link-table table-bottom-box"
:height="tableHeight" no-data-text="暂无科室(可一键拉取教师组或手工新建)" />
</TabPane>
</Tabs> </Tabs>
<!-- 应用管理 -->
<div class="tab-body" v-show="tab === 'apps'">
<div class="fix mb-12">
<Button class="fl" type="primary" size="large" @click="load">刷新(同步学校池)</Button>
<span class="fl" style="color: #808695; font-size: 12px; line-height: 40px; margin-left: 10px">
应用清单来自本校已开通应用(UserBase 学校组开通,含学校别名);开启后可用于流程归属与筛选。
新开通/改名在 UserBase 管理端操作后回本页刷新即可
</span>
</div>
<div class="table-region" ref="appRegionRef">
<Table :columns="columns" :data="rows" :loading="loading" class="link-table table-bottom-box"
:height="appH || undefined" no-data-text="本校暂无已开通应用(请先在用户中心开通)" />
</div>
</div>
<!-- 科室管理 -->
<div class="tab-body" v-show="tab === 'depts'">
<div class="fix mb-12">
<Button class="fl" type="primary" size="large" @click="onCreate">新建科室</Button>
<Button class="fl" size="large" style="margin-left: 8px" @click="onGroupPreview">一键拉取教师组</Button>
<span class="fl" style="color: #808695; font-size: 12px; line-height: 40px; margin-left: 10px">
流程节点「按科室」办理的解析源;重复拉取=刷新组名与成员集(不重复建组)
</span>
</div>
<div class="table-region" ref="deptRegionRef">
<Table :columns="deptColumns" :data="depts" :loading="deptLoading" class="link-table table-bottom-box"
:height="deptH || undefined" no-data-text="暂无科室(可一键拉取教师组或手工新建)" />
</div>
</div>
<Modal v-model="createVisible" title="新建科室" :mask-closable="false"> <Modal v-model="createVisible" title="新建科室" :mask-closable="false">
<Input v-model="createName" placeholder="科室名称,如 人事处" :maxlength="30" /> <Input v-model="createName" placeholder="科室名称,如 人事处" :maxlength="30" />
<template #footer> <template #footer>
@@ -343,15 +366,7 @@ onMounted(() => {
<Modal v-model="memberVisible" :title="`成员维护 - ${memberRow?.deptName || ''}`" :mask-closable="false" width="640"> <Modal v-model="memberVisible" :title="`成员维护 - ${memberRow?.deptName || ''}`" :mask-closable="false" width="640">
<div class="fix" style="margin-bottom: 10px"> <div class="fix" style="margin-bottom: 10px">
<UserSelectInput <Button class="fl" type="primary" icon="md-add" @click="onAddMemberShow">添加成员</Button>
class="fl"
style="width: 260px"
:model-value="addUserId"
:display-name="addUserName"
@update:model-value="addUserId = $event"
@update:display-name="addUserName = $event"
/>
<Button class="fl" type="primary" style="margin-left: 8px" @click="onMemberAdd">添加</Button>
</div> </div>
<Table :columns="[ <Table :columns="[
{ title: '用户ID', key: 'userId', width: 100 }, { title: '用户ID', key: 'userId', width: 100 },
@@ -374,6 +389,9 @@ onMounted(() => {
<Button type="primary" :loading="groupImporting" :disabled="!groupRows.length" @click="onGroupImport">导入</Button> <Button type="primary" :loading="groupImporting" :disabled="!groupRows.length" @click="onGroupImport">导入</Button>
</template> </template>
</Modal> </Modal>
<!-- 添加成员选人弹窗(多选,与 Notice 设置中心同款组件) -->
<UserCheckModal ref="addMemberCheckRef" :sources="memberSources" @on-submit="onMemberAddSubmit" />
</div> </div>
</div> </div>
</template> </template>
+53 -47
View File
@@ -2,13 +2,13 @@
// 回调管理(概览 §9.2):注册列表/启停/secret 重置 + 推送日志 + 挂起(持续重试可视/标记死信)+ 死信重推。 // 回调管理(概览 §9.2):注册列表/启停/secret 重置 + 推送日志 + 挂起(持续重试可视/标记死信)+ 死信重推。
// 规范骨架(bsch应用页规范 §1):tabs-wrapper + Tabs type=card,Tab 切换只 v-show 内容区;「重推选中」落在分页栏左侧批量操作位 // 规范骨架(bsch应用页规范 §1):tabs-wrapper + Tabs type=card,Tab 切换只 v-show 内容区;「重推选中」落在分页栏左侧批量操作位
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import { Message } from "view-ui-plus"; import { Message, Tag } from "view-ui-plus";
import MyConfirm from "@/components/MyConfirm"; import MyConfirm from "@/components/MyConfirm";
import TablePageAlovaNew from "@/components/TablePageAlovaNew.vue"; import TablePageAlovaNew from "@/components/TablePageAlovaNew.vue";
import { OperatorEnum } from "@/types"; import { OperatorEnum } from "@/types";
import { flowApi } from "@/api/module/flow"; import { flowApi } from "@/api/module/flow";
import { actionColumn } from "@/utils/tableActions"; import { actionColumn } from "@/utils/tableActions";
import Utils from "@/utils"; import { useElementHeight } from "@/utils/hook/useElementHeight";
const LOG_STATUS: Record<number, string> = { 0: "待推/重试中", 1: "成功", 2: "死信" }; const LOG_STATUS: Record<number, string> = { 0: "待推/重试中", 1: "成功", 2: "死信" };
const EVENT_OPTIONS = ["flow.submitted", "flow.task.created", "flow.task.handled", "flow.completed", "flow.rejected", "flow.withdrawn"]; const EVENT_OPTIONS = ["flow.submitted", "flow.task.created", "flow.task.handled", "flow.completed", "flow.rejected", "flow.withdrawn"];
@@ -16,10 +16,11 @@ const EVENT_OPTIONS = ["flow.submitted", "flow.task.created", "flow.task.handled
const tab = ref("regs"); const tab = ref("regs");
const loading = ref(false); const loading = ref(false);
// tabs 页高度:在常规骨架上再扣 Tab 条;日志页含搜索区+分页,挂起页含提示条 // 无分页 tab 的表格高度:实测各自 .table-region(flex:1),替代 useTableHeight 常量减法
const regsTableHeight = ref(Utils.useTableHeight({ isTabs: true })); const regsRegionRef = ref<HTMLElement | null>(null);
const logTableHeight = ref(Utils.useTableHeight({ isTabs: true, isSearch: true, isPage: true })); const hungRegionRef = ref<HTMLElement | null>(null);
const hungTableHeight = ref(Utils.useTableHeight({ isTabs: true, otherH: 46 })); const regsH = useElementHeight(regsRegionRef);
const hungH = useElementHeight(hungRegionRef);
// ---- 注册列表 ---- // ---- 注册列表 ----
const regs = ref<any[]>([]); const regs = ref<any[]>([]);
@@ -29,7 +30,7 @@ const regColumns = [
{ title: "流程 GUID", key: "flowGuid", width: 210 }, { title: "流程 GUID", key: "flowGuid", width: 210 },
{ title: "回调地址", key: "callbackUrl", minWidth: 240 }, { title: "回调地址", key: "callbackUrl", minWidth: 240 },
{ title: "订阅事件", key: "eventTypes", width: 200, render: (h: any, p: any) => h("span", p.row.eventTypes || "全部") }, { title: "订阅事件", key: "eventTypes", width: 200, render: (h: any, p: any) => h("span", p.row.eventTypes || "全部") },
{ title: "状态", width: 80, align: "center", render: (h: any, p: any) => h("Tag", { color: p.row.status === 0 ? "green" : "gray" }, () => (p.row.status === 0 ? "启用" : "停用")) }, { title: "状态", width: 80, align: "center", render: (h: any, p: any) => h(Tag, { color: p.row.status === 0 ? "green" : "gray" }, () => (p.row.status === 0 ? "启用" : "停用")) },
actionColumn( actionColumn(
[ [
{ key: "disable", label: "停用", icon: "icon-stop", on: (r: any) => onRegStatus(r, true), disabledWhen: (r: any) => r.status !== 0, danger: true }, { key: "disable", label: "停用", icon: "icon-stop", on: (r: any) => onRegStatus(r, true), disabledWhen: (r: any) => r.status !== 0, danger: true },
@@ -51,7 +52,7 @@ const logColumns = [
{ title: "biz", width: 160, render: (h: any, p: any) => h("span", `${p.row.bizType}/${p.row.bizId}`) }, { title: "biz", width: 160, render: (h: any, p: any) => h("span", `${p.row.bizType}/${p.row.bizId}`) },
{ title: "状态", width: 110, align: "center", render: (h: any, p: any) => { { title: "状态", width: 110, align: "center", render: (h: any, p: any) => {
const c = p.row.status === 1 ? "green" : p.row.status === 2 ? "red" : "orange"; const c = p.row.status === 1 ? "green" : p.row.status === 2 ? "red" : "orange";
return h("Tag", { color: c }, () => LOG_STATUS[p.row.status]); return h(Tag, { color: c }, () => LOG_STATUS[p.row.status]);
} }, } },
{ title: "重试", key: "retryCount", width: 60, align: "center" }, { title: "重试", key: "retryCount", width: 60, align: "center" },
{ title: "末次结果", key: "lastError", minWidth: 200, render: (h: any, p: any) => h("span", p.row.lastError || p.row.lastHttpStatus || "-") }, { title: "末次结果", key: "lastError", minWidth: 200, render: (h: any, p: any) => h("span", p.row.lastError || p.row.lastHttpStatus || "-") },
@@ -66,7 +67,7 @@ const hungColumns = [
{ title: "事件", key: "event", width: 150 }, { title: "事件", key: "event", width: 150 },
{ title: "办件", key: "instanceId", width: 80 }, { title: "办件", key: "instanceId", width: 80 },
{ title: "biz", width: 160, render: (h: any, p: any) => h("span", `${p.row.bizType}/${p.row.bizId}`) }, { title: "biz", width: 160, render: (h: any, p: any) => h("span", `${p.row.bizType}/${p.row.bizId}`) },
{ title: "重试", key: "retryCount", width: 70, align: "center", render: (h: any, p: any) => h("Tag", { color: "orange" }, () => `${p.row.retryCount} 次`) }, { title: "重试", key: "retryCount", width: 70, align: "center", render: (h: any, p: any) => h(Tag, { color: "orange" }, () => `${p.row.retryCount} 次`) },
{ title: "末次结果", key: "lastError", minWidth: 220, render: (h: any, p: any) => h("span", p.row.lastError || p.row.lastHttpStatus || "-") }, { title: "末次结果", key: "lastError", minWidth: 220, render: (h: any, p: any) => h("span", p.row.lastError || p.row.lastHttpStatus || "-") },
{ title: "下次重推", width: 160, render: (h: any, p: any) => h("span", (p.row.nextRetryTime || "").replace("T", " ") || "-") }, { title: "下次重推", width: 160, render: (h: any, p: any) => h("span", (p.row.nextRetryTime || "").replace("T", " ") || "-") },
actionColumn([{ key: "markDead", label: "标记死信", icon: "icon-ashbin", on: (r: any) => onMarkDead(r), danger: true }], { mode: "text", width: 120, fixed: "right" }), actionColumn([{ key: "markDead", label: "标记死信", icon: "icon-ashbin", on: (r: any) => onMarkDead(r), danger: true }], { mode: "text", width: 120, fixed: "right" }),
@@ -175,48 +176,53 @@ onMounted(loadRegs);
</script> </script>
<template> <template>
<div class="tabs-wrapper"> <div class="tabs-wrapper yh-fill">
<Tabs class="tabs-panel" :value="tab" @on-click="switchTab"> <div class="main-container yh-tabs-card">
<TabPane label="回调注册" name="regs" /> <Tabs class="tabs-panel" :value="tab" @on-click="switchTab">
<TabPane label="推送日志" name="logs" /> <TabPane label="回调注册" name="regs" />
<TabPane label="挂起中" name="hung" /> <TabPane label="推送日志" name="logs" />
</Tabs> <TabPane label="挂起中" name="hung" />
</Tabs>
<!-- 回调注册:无分页 --> <!-- 回调注册:无分页 -->
<div class="main-container" v-show="tab === 'regs'"> <div class="tab-body" v-show="tab === 'regs'">
<Table :columns="regColumns" :data="regs" :loading="loading" class="link-table table-bottom-box" <div class="table-region" ref="regsRegionRef">
:height="regsTableHeight" no-data-text="暂无数据" /> <Table :columns="regColumns" :data="regs" :loading="loading" class="link-table table-bottom-box"
</div> :height="regsH || undefined" no-data-text="暂无数据" />
</div>
<!-- 推送日志:搜索区 + 分页,重推选中落分页栏左侧批量操作位 -->
<div class="main-container" v-show="tab === 'logs'">
<div class="search-wrap fix">
<Select v-model="logFilter.status" class="search-wrap_sel" clearable placeholder="状态" @on-change="onLogSearch">
<Option :value="0" label="待推/重试中" />
<Option :value="1" label="成功" />
<Option :value="2" label="死信" />
</Select>
<Select v-model="logFilter.event" class="search-wrap_sel ml-12" clearable placeholder="事件" @on-change="onLogSearch">
<Option v-for="e in EVENT_OPTIONS" :key="e" :value="e" :label="e" />
</Select>
<Input class="fr" v-model="logFilter.instanceId" placeholder="办件ID" search suffix="ios-search"
style="width: 200px" @on-search="onLogSearch" @on-clear="onLogSearch" />
</div> </div>
<TablePageAlovaNew ref="logTableRef" :dataApi="logApi" :columns="logColumns"
:tableHeight="logTableHeight" :autoLoad="false">
<template #default="{ checkList }">
<Button size="small" type="primary" :disabled="!checkList.length" @click="onRepush(checkList)">重推选中</Button>
</template>
</TablePageAlovaNew>
</div>
<!-- 挂起中:无分页 --> <!-- 推送日志:搜索区 + 分页,重推选中落分页栏左侧批量操作位 -->
<div class="main-container" v-show="tab === 'hung'"> <div class="tab-body" v-show="tab === 'logs'">
<div class="tip"> <div class="search-wrap fix">
持续重试中的事件(消费方疑似停机;消费方恢复后自动追平,无需干预)。仅当人工判定事件已无意义时才标记死信。 <Select v-model="logFilter.status" class="search-wrap_sel" clearable placeholder="状态" @on-change="onLogSearch">
<Option :value="0" label="待推/重试中" />
<Option :value="1" label="成功" />
<Option :value="2" label="死信" />
</Select>
<Select v-model="logFilter.event" class="search-wrap_sel ml-12" clearable placeholder="事件" @on-change="onLogSearch">
<Option v-for="e in EVENT_OPTIONS" :key="e" :value="e" :label="e" />
</Select>
<Input class="fr" v-model="logFilter.instanceId" placeholder="办件ID" search suffix="ios-search"
style="width: 200px" @on-search="onLogSearch" @on-clear="onLogSearch" />
</div>
<TablePageAlovaNew ref="logTableRef" :dataApi="logApi" :columns="logColumns" :autoLoad="false">
<template #default="{ checkList }">
<Button size="small" type="primary" :disabled="!checkList.length" @click="onRepush(checkList)">重推选中</Button>
</template>
</TablePageAlovaNew>
</div>
<!-- 挂起中:无分页 -->
<div class="tab-body" v-show="tab === 'hung'">
<div class="tip">
持续重试中的事件(消费方疑似停机;消费方恢复后自动追平,无需干预)。仅当人工判定事件已无意义时才标记死信。
</div>
<div class="table-region" ref="hungRegionRef">
<Table :columns="hungColumns" :data="hungs" :loading="hungLoading" class="link-table table-bottom-box"
:height="hungH || undefined" no-data-text="暂无数据" />
</div>
</div> </div>
<Table :columns="hungColumns" :data="hungs" :loading="hungLoading" class="link-table table-bottom-box"
:height="hungTableHeight" no-data-text="暂无数据" />
</div> </div>
</div> </div>
</template> </template>
+20 -9
View File
@@ -4,12 +4,12 @@
// 本页不再直接编辑 JSON 文本(编辑器内置"源码"高级模式兜底);行内「源码」为只读查看+复制。 // 本页不再直接编辑 JSON 文本(编辑器内置"源码"高级模式兜底);行内「源码」为只读查看+复制。
import { ref, computed, onMounted } from "vue"; import { ref, computed, onMounted } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { Message } from "view-ui-plus"; import { Message, Tag } from "view-ui-plus";
import MyConfirm from "@/components/MyConfirm"; import MyConfirm from "@/components/MyConfirm";
import SourceDrawer from "@/views/flow/components/SourceDrawer.vue"; import SourceDrawer from "@/views/flow/components/SourceDrawer.vue";
import { flowApi } from "@/api/module/flow"; import { flowApi } from "@/api/module/flow";
import { actionColumn } from "@/utils/tableActions"; import { actionColumn } from "@/utils/tableActions";
import Utils from "@/utils"; import { useElementHeight } from "@/utils/hook/useElementHeight";
const router = useRouter(); const router = useRouter();
const DEF_STATUS: Record<number, string> = { 0: "草稿", 1: "已发布", 2: "停用" }; const DEF_STATUS: Record<number, string> = { 0: "草稿", 1: "已发布", 2: "停用" };
@@ -22,8 +22,9 @@ const versions = ref<any[]>([]);
const sourceVisible = ref(false); const sourceVisible = ref(false);
const sourceText = ref(""); const sourceText = ref("");
const sourceTitle = ref(""); const sourceTitle = ref("");
// 操作按钮区 + 表格,无搜索、无分页 // 操作按钮区 + 表格,无搜索、无分页:表格高度实测 .table-region(flex:1)
const tableHeight = ref(Utils.useTableHeight({ isOperat: true })); const defsRegionRef = ref<HTMLElement | null>(null);
const defsH = useElementHeight(defsRegionRef);
// 按应用筛选(flw_def.app_key,2026-10-10):下拉数据源来自应用设置白名单(flw_app enabled=1) // 按应用筛选(flw_def.app_key,2026-10-10):下拉数据源来自应用设置白名单(flw_app enabled=1)
const apps = ref<{ appKey: string; appName: string }[]>([]); const apps = ref<{ appKey: string; appName: string }[]>([]);
const appMap = ref<Record<string, string>>({}); const appMap = ref<Record<string, string>>({});
@@ -37,7 +38,7 @@ const columns = [
{ title: "版本", key: "version", width: 70, align: "center" }, { title: "版本", key: "version", width: 70, align: "center" },
{ title: "状态", width: 90, align: "center", render: (h: any, p: any) => { { title: "状态", width: 90, align: "center", render: (h: any, p: any) => {
const c = p.row.status === 1 ? "green" : p.row.status === 0 ? "orange" : "gray"; const c = p.row.status === 1 ? "green" : p.row.status === 0 ? "orange" : "gray";
return h("Tag", { color: c }, () => DEF_STATUS[p.row.status] || p.row.status); return h(Tag, { color: c }, () => DEF_STATUS[p.row.status] || p.row.status);
} }, } },
{ title: "GUID", key: "flowGuid", width: 200 }, { title: "GUID", key: "flowGuid", width: 200 },
{ title: "发布时间", width: 160, render: (h: any, p: any) => h("span", (p.row.publishTime || "").replace("T", " ") || "-") }, { title: "发布时间", width: 160, render: (h: any, p: any) => h("span", (p.row.publishTime || "").replace("T", " ") || "-") },
@@ -220,7 +221,7 @@ onMounted(() => {
</script> </script>
<template> <template>
<div class="page-wrapper"> <div class="page-wrapper yh-fill">
<div class="main-container"> <div class="main-container">
<!-- 操作按钮区:主操作靠左(bsch应用页规范 §1) --> <!-- 操作按钮区:主操作靠左(bsch应用页规范 §1) -->
<div class="fix mb-12"> <div class="fix mb-12">
@@ -231,14 +232,16 @@ onMounted(() => {
</Select> </Select>
</div> </div>
<Table :columns="columns" :data="rows" :loading="loading" class="link-table table-bottom-box" <div class="table-region" ref="defsRegionRef">
:height="tableHeight" no-data-text="暂无数据" /> <Table :columns="columns" :data="rows" :loading="loading" class="link-table table-bottom-box"
:height="defsH || undefined" no-data-text="暂无数据" />
</div>
<Modal v-model="versionVisible" title="版本历史" width="640"> <Modal v-model="versionVisible" title="版本历史" width="640">
<Table :columns="[ <Table :columns="[
{ title: '版本', key: 'version', width: 80 }, { title: '版本', key: 'version', width: 80 },
{ title: '名称', key: 'name' }, { title: '名称', key: 'name' },
{ title: '状态', width: 90, render: (h, p) => h('Tag', () => DEF_STATUS[p.row.status]) }, { title: '状态', width: 90, render: (h, p) => h(Tag, () => DEF_STATUS[p.row.status]) },
{ title: '发布时间', width: 160, render: (h, p) => (p.row.publishTime || '').replace('T', ' ') || '-' }, { title: '发布时间', width: 160, render: (h, p) => (p.row.publishTime || '').replace('T', ' ') || '-' },
]" :data="versions" size="small" no-data-text="暂无数据" /> ]" :data="versions" size="small" no-data-text="暂无数据" />
</Modal> </Modal>
@@ -269,3 +272,11 @@ onMounted(() => {
</div> </div>
</div> </div>
</template> </template>
<style scoped>
/* 无分页脚:通道只对含 .page-panel 的卡片自动转纵向分栏,本页自声明保证 table-region 撑满 */
.main-container {
display: flex;
flex-direction: column;
}
</style>
+4 -6
View File
@@ -2,17 +2,15 @@
// 办件记录(管理端):全校办件查询;CONDITION 判断失败挂起的办件在此"重跑路由"恢复 // 办件记录(管理端):全校办件查询;CONDITION 判断失败挂起的办件在此"重跑路由"恢复
import { ref } from "vue"; import { ref } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { Message } from "view-ui-plus"; import { Message, Tag } from "view-ui-plus";
import MyConfirm from "@/components/MyConfirm"; import MyConfirm from "@/components/MyConfirm";
import { OperatorEnum } from "@/types"; import { OperatorEnum } from "@/types";
import TablePageAlovaNew from "@/components/TablePageAlovaNew.vue"; import TablePageAlovaNew from "@/components/TablePageAlovaNew.vue";
import { flowApi, INSTANCE_STATUS, INSTANCE_STATUS_COLOR } from "@/api/module/flow"; import { flowApi, INSTANCE_STATUS, INSTANCE_STATUS_COLOR } from "@/api/module/flow";
import { actionColumn } from "@/utils/tableActions"; import { actionColumn } from "@/utils/tableActions";
import Utils from "@/utils";
const router = useRouter(); const router = useRouter();
const tableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null); const tableRef = ref<InstanceType<typeof TablePageAlovaNew> | null>(null);
const tableHeight = ref(Utils.useTableHeight({ isSearch: true, isPage: true }));
const dataParams = ref<{ status?: number; keyword?: string }>({ status: undefined, keyword: "" }); const dataParams = ref<{ status?: number; keyword?: string }>({ status: undefined, keyword: "" });
const columns = [ const columns = [
@@ -20,7 +18,7 @@ const columns = [
{ title: "办件标题", key: "bizTitle", minWidth: 220 }, { title: "办件标题", key: "bizTitle", minWidth: 220 },
{ title: "业务", width: 170, render: (h: any, p: any) => h("span", `${p.row.bizType || "-"}/${p.row.bizId || "-"}`) }, { title: "业务", width: 170, render: (h: any, p: any) => h("span", `${p.row.bizType || "-"}/${p.row.bizId || "-"}`) },
{ 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: "submitterName", width: 100 }, { title: "提交人", key: "submitterName", width: 100 },
{ title: "提交时间", width: 160, render: (h: any, p: any) => h("span", (p.row.submitTime || "").replace("T", " ")) }, { title: "提交时间", width: 160, render: (h: any, p: any) => h("span", (p.row.submitTime || "").replace("T", " ")) },
actionColumn( actionColumn(
@@ -61,7 +59,7 @@ function onSearch() {
</script> </script>
<template> <template>
<div class="page-wrapper"> <div class="page-wrapper yh-fill">
<div class="main-container"> <div class="main-container">
<div class="search-wrap fix"> <div class="search-wrap fix">
<Select v-model="dataParams.status" class="search-wrap_sel" clearable placeholder="状态" @on-change="onSearch"> <Select v-model="dataParams.status" class="search-wrap_sel" clearable placeholder="状态" @on-change="onSearch">
@@ -73,7 +71,7 @@ function onSearch() {
<Input class="fr" v-model="dataParams.keyword" placeholder="标题关键字" search suffix="ios-search" <Input class="fr" v-model="dataParams.keyword" placeholder="标题关键字" search suffix="ios-search"
style="width: 200px" @on-search="onSearch" @on-clear="onSearch" /> style="width: 200px" @on-search="onSearch" @on-clear="onSearch" />
</div> </div>
<TablePageAlovaNew ref="tableRef" :dataApi="tableApi" :columns="columns" :tableHeight="tableHeight" /> <TablePageAlovaNew ref="tableRef" :dataApi="tableApi" :columns="columns" />
</div> </div>
</div> </div>
</template> </template>
+19 -7
View File
@@ -3,15 +3,17 @@
// CONDITION 节点的 routerKey 必须命中启用中的注册(发布校验口径);basePath 是对端 context-path, // CONDITION 节点的 routerKey 必须命中启用中的注册(发布校验口径);basePath 是对端 context-path,
// 漏填必 401(规范 §4.2 同款坑)——生产 notice 为 /notice,本地为空。 // 漏填必 401(规范 §4.2 同款坑)——生产 notice 为 /notice,本地为空。
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import { Message } from "view-ui-plus"; import { Message, Tag } from "view-ui-plus";
import MyConfirm from "@/components/MyConfirm"; import MyConfirm from "@/components/MyConfirm";
import { flowApi } from "@/api/module/flow"; import { flowApi } from "@/api/module/flow";
import { actionColumn } from "@/utils/tableActions"; import { actionColumn } from "@/utils/tableActions";
import Utils from "@/utils"; import { useElementHeight } from "@/utils/hook/useElementHeight";
const loading = ref(false); const loading = ref(false);
const rows = ref<any[]>([]); const rows = ref<any[]>([]);
const tableHeight = ref(Utils.useTableHeight({ isSearch: true, isOperat: true })); // 无搜索分页脚:表格高度实测 .table-region(flex:1)
const routerRegionRef = ref<HTMLElement | null>(null);
const routersH = useElementHeight(routerRegionRef);
const columns = [ const columns = [
{ title: "序号", type: "index", width: 70, align: "center" }, { title: "序号", type: "index", width: 70, align: "center" },
@@ -29,7 +31,7 @@ const columns = [
width: 90, width: 90,
align: "center", align: "center",
render: (h: any, p: any) => render: (h: any, p: any) =>
h("Tag", { color: p.row.status === 1 ? "green" : "gray" }, () => (p.row.status === 1 ? "启用" : "停用")), h(Tag, { color: p.row.status === 1 ? "green" : "gray" }, () => (p.row.status === 1 ? "启用" : "停用")),
}, },
actionColumn( actionColumn(
[ [
@@ -160,7 +162,7 @@ onMounted(load);
</script> </script>
<template> <template>
<div class="page-wrapper"> <div class="page-wrapper yh-fill">
<div class="main-container"> <div class="main-container">
<div class="fix mb-12"> <div class="fix mb-12">
<Button class="fl" type="primary" size="large" @click="onAdd">新增注册</Button> <Button class="fl" type="primary" size="large" @click="onAdd">新增注册</Button>
@@ -174,8 +176,10 @@ onMounted(load);
</span> </span>
</div> </div>
<Table :columns="columns" :data="rows" :loading="loading" class="link-table table-bottom-box" <div class="table-region" ref="routerRegionRef">
:height="tableHeight" no-data-text="暂无数据" /> <Table :columns="columns" :data="rows" :loading="loading" class="link-table table-bottom-box"
:height="routersH || undefined" no-data-text="暂无数据" />
</div>
<Modal v-model="saveVisible" title="新增/编辑判断服务" :mask-closable="false"> <Modal v-model="saveVisible" title="新增/编辑判断服务" :mask-closable="false">
<Form ref="formRef" :model="form" :rules="saveRules" :label-width="100"> <Form ref="formRef" :model="form" :rules="saveRules" :label-width="100">
@@ -235,3 +239,11 @@ onMounted(load);
</div> </div>
</div> </div>
</template> </template>
<style scoped>
/* 无分页脚:通道只对含 .page-panel 的卡片自动转纵向分栏,本页自声明保证 table-region 撑满 */
.main-container {
display: flex;
flex-direction: column;
}
</style>