- AppSettings.vue 应用设置页(基础/字典两 tab);修复 P86:Tabs 写 :value 而受控 prop 是 modelValue,致 ?tab=dicts 深链静默失效,改后深链实拍生效 - UserCheckModal.vue 自 notice 移植(view-ui-plus+ztree,sources 注入式数据源);public/libs 收录 jquery+ztree 本地件(index.html 引用) - api/module/admin.ts + mock/adminapi.ts + types/admin.ts;AdminListPanel/FormAdminModal 组件 - routes/menuNav/menuRoute/modulePerms 适配管理员菜单与权限位;tree-icon 图标资源
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 管理员名单面板(T10 方案 §6.3「图二形态」:序号 / 姓名 / 操作-删除 + 添加按钮 → 选人弹窗右栏穿梭确认)。
|
||||
*
|
||||
* 一处实现两处用:
|
||||
* - `mode="school"`:应用设置 · 角色设置 tab → `school_admin`(校级表单管理员,守 /admin/app);
|
||||
* - `mode="form"`:表单列表 / 设计器的「管理员」弹窗 → `form_admin`(单表单管理员,守 /admin/forms)。
|
||||
*
|
||||
* 三条与直觉不同的地方:
|
||||
* 1. **写操作回的是操作后的全量列表**(后端不分页,每校/每表单个位数),所以这里不再自己拼增量;
|
||||
* 2. **防呆与跨校拒绝在服务端**(最后一员不可删、名单里的人必须属于本次域)——前端不做"只剩一个就禁用删除",
|
||||
* 那会把后端的规则复制成两份并且必然漂移;失败就按信封 msg 提示;
|
||||
* 3. **选人数据源=本校组织树**(`/api/base/GetSchoolAll`,无 schId 入参),跨校的人根本选不出来;
|
||||
* 即便有人手搓请求体,落库前的归属复核仍会拒。
|
||||
*/
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { Message, Modal } from "view-ui-plus";
|
||||
import { adminApi } from "@/api/module/admin";
|
||||
import { baseApi } from "@/api/module/base";
|
||||
import UserCheckModal from "@/components/UserCheckModal.vue";
|
||||
import type { AdminRow } from "@/types/admin";
|
||||
|
||||
const props = defineProps<{
|
||||
/** school=校级表单管理员;form=单表单管理员 */
|
||||
mode: "school" | "form";
|
||||
/** mode=form 时必填:授权落到哪张表单(对外身份是 uuid,契约 §1) */
|
||||
formUuid?: string;
|
||||
}>();
|
||||
|
||||
const rows = ref<AdminRow[]>([]);
|
||||
const loading = ref(false);
|
||||
const busy = ref(false);
|
||||
const checkRef = ref<InstanceType<typeof UserCheckModal> | null>(null);
|
||||
|
||||
/** 弹窗里已勾的人(重开时回显,避免"加了三个再点进去只剩空树") */
|
||||
const pickedIds = computed(() => rows.value.map((r) => r.userId));
|
||||
|
||||
const sources = [{ name: "本校组织", loader: () => baseApi.getSchoolAll().send() }];
|
||||
|
||||
const columns = computed(() => [
|
||||
{ title: "序号", key: "index", width: 70, render: (h: any, p: any) => h("span", String(p.index + 1)) },
|
||||
{ title: "姓名", key: "userName", minWidth: 160 },
|
||||
{
|
||||
title: "操作",
|
||||
key: "ops",
|
||||
width: 90,
|
||||
render: (h: any, p: any) =>
|
||||
h("a", { class: "action-del", onClick: () => confirmRemove(p.row as AdminRow) }, "删除")
|
||||
}
|
||||
]);
|
||||
|
||||
async function load() {
|
||||
loading.value = true;
|
||||
try {
|
||||
rows.value = (await listCall().send()) || [];
|
||||
} catch (e: any) {
|
||||
Message.error(e?.message || "管理员名单加载失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function listCall() {
|
||||
return props.mode === "form" && props.formUuid
|
||||
? adminApi.formAdmins(props.formUuid)
|
||||
: adminApi.schoolAdmins();
|
||||
}
|
||||
|
||||
function writeCall(users: { userId: number; userName: string }[]) {
|
||||
return props.mode === "form" && props.formUuid
|
||||
? adminApi.addFormAdmins(props.formUuid, { users })
|
||||
: adminApi.addSchoolAdmins({ users });
|
||||
}
|
||||
|
||||
function removeCall(id: number) {
|
||||
return props.mode === "form" && props.formUuid
|
||||
? adminApi.removeFormAdmin(props.formUuid, id)
|
||||
: adminApi.removeSchoolAdmin(id);
|
||||
}
|
||||
|
||||
function openPicker() {
|
||||
checkRef.value?.show();
|
||||
}
|
||||
|
||||
/** UserCheckModal 的 on-submit:[{ id, name, group... }],id 即 UserBase 用户主键 */
|
||||
async function onPicked(users: any[]) {
|
||||
const items = (users || [])
|
||||
.filter((u) => u && u.id != null)
|
||||
.map((u) => ({ userId: Number(u.id), userName: String(u.name || "").trim() }));
|
||||
if (!items.length) return;
|
||||
if (items.some((i) => !i.userName)) {
|
||||
Message.error("选人结果里有没有姓名的行,请重选");
|
||||
return;
|
||||
}
|
||||
busy.value = true;
|
||||
try {
|
||||
rows.value = (await writeCall(items).send()) || [];
|
||||
Message.success("已添加管理员");
|
||||
} catch (e: any) {
|
||||
Message.error(e?.message || "添加失败");
|
||||
} finally {
|
||||
busy.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function confirmRemove(row: AdminRow) {
|
||||
Modal.confirm({
|
||||
title: "删除管理员",
|
||||
content: `确定移除「${row.userName}」?该操作只撤销数据范围升级,不影响他已经拿到的模块行权限。`,
|
||||
okText: "删除",
|
||||
cancelText: "取消",
|
||||
onOk: async () => {
|
||||
busy.value = true;
|
||||
try {
|
||||
rows.value = (await removeCall(row.id).send()) || [];
|
||||
} catch (e: any) {
|
||||
// 防呆(最后一名不可删)与跨校拒绝都在这支:按后端 msg 原样提示
|
||||
Message.error(e?.message || "删除失败");
|
||||
} finally {
|
||||
busy.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(load);
|
||||
defineExpose({ load });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="admin-panel">
|
||||
<div class="toolbar">
|
||||
<Button type="primary" icon="md-person-add" :disabled="busy" @click="openPicker">添加管理员</Button>
|
||||
<span class="tip">
|
||||
{{
|
||||
mode === "school"
|
||||
? "校级表单管理员:本校表单数据范围=全部,基础表单=本校提交"
|
||||
: "单表单管理员:这张表单的数据查看与导出(编辑权仍走表单设计权限)"
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<Table :columns="columns" :data="rows" :loading="loading" size="small" :border="false" />
|
||||
<UserCheckModal ref="checkRef" :sources="sources" :userids="pickedIds" @on-submit="onPicked" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.admin-panel .toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.admin-panel .tip {
|
||||
color: #808695;
|
||||
font-size: 12px;
|
||||
}
|
||||
.admin-panel :deep(.action-del) {
|
||||
color: #ed4014;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 单表单管理员弹窗(T10 方案 §6.4):表单列表的行操作与设计器的配置面板都挂它,
|
||||
* 保证"同一弹窗"只有一份实现。每次打开都重新挂载面板,名单永远是进弹窗时拉的。
|
||||
*/
|
||||
import { ref } from "vue";
|
||||
import AdminListPanel from "./AdminListPanel.vue";
|
||||
|
||||
const visible = ref(false);
|
||||
const formUuid = ref("");
|
||||
const formName = ref("");
|
||||
|
||||
function open(uuid: string, name?: string) {
|
||||
if (!uuid) return;
|
||||
formUuid.value = uuid;
|
||||
formName.value = name || "";
|
||||
visible.value = true;
|
||||
}
|
||||
|
||||
defineExpose({ open });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal v-model="visible" :title="formName ? `表单管理员|${formName}` : '表单管理员'" width="680" footer-hide>
|
||||
<AdminListPanel v-if="visible" mode="form" :form-uuid="formUuid" />
|
||||
</Modal>
|
||||
</template>
|
||||
Reference in New Issue
Block a user