feat(admin): 选人组件接入——UserCheckModal 加单选模式+UserSelectInput 单选封装;映射办理人/审批节点指定用户弃手输 ID 改弹窗选人 (/admin/user/tree);删休眠 UserCheckInput 死代码
build / build (push) Successful in 1m56s

This commit is contained in:
chenxing
2026-09-17 11:09:53 +08:00
parent 07275c6e3d
commit 2e591e7a54
6 changed files with 237 additions and 237 deletions
-165
View File
@@ -1,165 +0,0 @@
<template>
<!-- 通知对象选择:全部用户 / 指定用户(自旧前端 user_check_input.vue 适配) -->
<div class="check-user-wrap">
<div class="check-user-box">
<RadioGroup v-model="isCheckUserAll" class="radio" @on-change="userAllCheck">
<Radio :disabled="editDisabled" :label="0">{{ isEdit ? "现有" : "全部" }}用户</Radio>
<Radio :label="1">指定用户</Radio>
</RadioGroup>
<Input
type="text"
v-model="userNameStr"
readonly
:disabled="!isCheckUserAll"
placeholder="选择指定的用户"
class="input"
></Input>
<Button icon="md-add" @click="checkUserShow" :disabled="!isCheckUserAll"></Button>
<Button icon="md-trash" @click="resetUser" :disabled="!isCheckUserAll"></Button>
</div>
<UserCheckModal
ref="userCheckModal"
:sources="sources"
:userids="tempUserIds"
@on-submit="submitUser"
/>
</div>
</template>
<script lang="ts">
/*
* 通知对象选择输入(自旧前端 oldfront/src/components/user_check_input.vue 适配 Vue3)。
* v-model 值为 "all" 或用户 id 逗号串;切换到「指定用户」直接弹出选择用户弹窗。
*/
import UserCheckModal from "@/components/UserCheckModal.vue";
import { baseApi, settingApi } from "@/api/module/notice";
export default {
name: "UserCheckInput",
components: { UserCheckModal },
props: {
modelValue: {
type: String,
default: "all",
},
/** 编辑态:全部用户选项禁用为「现有用户」 */
isEdit: {
type: Boolean,
default: false,
},
},
emits: ["update:modelValue"],
data() {
return {
isCheckUserAll: 0 as number,
tempUserIds: [] as any[],
userNameStr: "",
editDisabled: false,
sources: [
{
name: "基础平台",
loader: () => baseApi.getSchoolAll().send() as Promise<any[]>,
},
{
name: "自定义组",
loader: () => settingApi.getGroupTree().send() as Promise<any[]>,
},
],
};
},
created() {
if (this.modelValue === "all") {
this.isCheckUserAll = 0;
this.userNameStr = this.isEdit ? "现有用户" : "全部用户";
} else {
this.isCheckUserAll = 1;
this.tempUserIds = this.modelValue.split(",").map((x) => parseInt(x)).filter((x) => !isNaN(x));
}
},
methods: {
checkUserShow() {
(this.$refs.userCheckModal as any).show();
},
resetUser() {
//清除用户
this.tempUserIds = [];
this.userNameStr = "";
this.$emit("update:modelValue", "");
},
userAllCheck(type: number) {
let tempIds: any = String(this.modelValue || "")
.split(",")
.map((x) => parseInt(x))
.filter((x) => !isNaN(x));
if (type == 0) {
if (this.isEdit) {
this.tempUserIds = tempIds;
this.userNameStr = "现有用户";
this.$emit("update:modelValue", String(tempIds));
} else {
this.tempUserIds = [];
this.userNameStr = "全部用户";
this.$emit("update:modelValue", "all");
}
} else {
if (this.isEdit) {
this.editDisabled = true;
}
this.tempUserIds = tempIds;
this.userNameStr = "";
this.checkUserShow();
}
},
submitUser(arr: any[]) {
//确认用户
this.userNameStr = arr.map((x) => x.name).join("、");
const ids = arr.map((x) => x.id).join(",");
this.$emit("update:modelValue", ids || "all");
if (!arr.length) {
// 清空后回到全部用户
this.isCheckUserAll = 0;
this.userNameStr = "全部用户";
this.$emit("update:modelValue", "all");
}
},
},
watch: {
modelValue(val: string) {
if (val === "all") {
this.isCheckUserAll = 0;
this.userNameStr = this.isEdit ? "现有用户" : "全部用户";
} else if (val === "") {
this.userNameStr = "";
} else {
// 编辑回显:指定用户 id 串
this.isCheckUserAll = 1;
this.tempUserIds = val
.split(",")
.map((x) => parseInt(x))
.filter((x) => !isNaN(x));
}
},
},
};
</script>
<style lang="scss" scoped>
.check-user-wrap {
width: 100%;
.check-user-box {
display: flex;
align-items: center;
gap: 8px;
.radio {
width: 180px;
flex: none;
font-size: 12px;
vertical-align: middle;
line-height: 32px;
}
.input {
flex: 1;
}
}
}
</style>
+95 -26
View File
@@ -51,27 +51,37 @@
</svg>
</div>
<div class="user-panel fl">
<div class="title-box fix">
<Checkbox @on-change="checkAllUser" v-model="isCheckUserAll">全选</Checkbox>
<div class="fr">{{ tempUserList.length }}</div>
<a
href="javascript:;"
@click="delCheckUser"
v-if="tempUserList.findIndex((x: any) => x.isCheck) > -1"
>删除选中</a
>
</div>
<ul class="user-box">
<li
v-for="item in tempUserList"
:key="item.id"
:class="{ active: item.isCheck }"
@click="checkUserOne(item)"
>
{{ item.name }}
<span class="info">{{ item.group }}</span>
</li>
</ul>
<template v-if="multiple">
<div class="title-box fix">
<Checkbox @on-change="checkAllUser" v-model="isCheckUserAll">全选</Checkbox>
<div class="fr">{{ tempUserList.length }}</div>
<a
href="javascript:;"
@click="delCheckUser"
v-if="tempUserList.findIndex((x: any) => x.isCheck) > -1"
>删除选中</a
>
</div>
<ul class="user-box">
<li
v-for="item in tempUserList"
:key="item.id"
:class="{ active: item.isCheck }"
@click="checkUserOne(item)"
>
{{ item.name }}
<span class="info">{{ item.group }}</span>
</li>
</ul>
</template>
<template v-else>
<div class="title-box fix"><b>已选择</b></div>
<div class="single-box" v-if="selectedUser">
{{ selectedUser.name }}
<span class="info">{{ selectedUser.group }}</span>
</div>
<div class="single-empty" v-else>在左侧树中点击选中一名用户</div>
</template>
</div>
</div>
<template #footer>
@@ -112,6 +122,11 @@ export default {
type: Boolean,
default: false,
},
/** 单选模式:树上点击叶子即选定(flow-center 办理人映射等单选场景);默认多选与 Notice 口径一致 */
multiple: {
type: Boolean,
default: true,
},
},
emits: ["update:userids", "on-submit"],
data() {
@@ -119,6 +134,9 @@ export default {
ztreeDomId: "user_check_tree_" + Math.floor(Math.random() * 1e8),
modal: false,
tabIndex: 0,
/** 单选模式当前选中(每次打开重置) */
selectedUser: null as any,
tabIndex: 0,
/** 每次打开弹窗自增,强制重建 Tabs,避免上次 tab 高亮残留与数据错位 */
tabKey: 0,
/** 树加载序号:tab 快速切换时只渲染最后一次请求的结果 */
@@ -146,6 +164,7 @@ export default {
methods: {
show() {
this.modal = true;
this.selectedUser = null;
// 重置到第一个 tab:显式赋值 + key 重建 Tabs,保证高亮与数据一致
this.tabIndex = 0;
this.tabKey++;
@@ -175,9 +194,19 @@ export default {
},
handleSelect(query: string) {
if (query.indexOf(",") != -1) query = query.split(",")[0];
var treeObj = ($.fn as any).zTree.getZTreeObj(this.ztreeDomId);
var node = treeObj.getNodeByParam("name", query, null);
if (!!node) treeObj.checkNode(node, true, true, true);
const findObj = this.currentTreeFlatten.find(
(x: any) => x.type == 2 && x.name == query.split(",")[0]
);
if (!findObj) return;
if (this.multiple) {
var treeObj = ($.fn as any).zTree.getZTreeObj(this.ztreeDomId);
var node = treeObj.getNodeByParam("name", query, null);
if (!!node) treeObj.checkNode(node, true, true, true);
} else {
// 单选:搜索命中即选定并直接提交(提交后可再清空)
this.selectedUser = { ...findObj };
this.submit();
}
},
/**
* ztree 数据预处理:拼接 pid 保证同人多组时 id 可区分;叶子标记 type=2。
@@ -240,7 +269,7 @@ export default {
showIcon: true,
},
check: {
enable: true,
enable: this.multiple,
chkboxType: { Y: "ps", N: "ps" },
},
callback: {},
@@ -283,8 +312,22 @@ export default {
}
}
treeSetting.callback.onClick = (event: any, treeId: string, treeNode: any) => {
let check = treeNode.checked;
let treeObj = ($.fn as any).zTree.getZTreeObj(treeId);
if (!_this.multiple) {
// 单选:叶子节点点击即选定;组节点仅展开(关闭复选,无勾选语义)
if (treeNode.type == 2) {
treeObj.selectNode(treeNode, false, false);
_this.selectedUser = {
id: _this.getSplitId(treeNode.id),
name: treeNode.name,
group: treeNode.group || "",
};
} else {
treeObj.expandNode(treeNode, !treeNode.open, false, false);
}
return;
}
let check = treeNode.checked;
treeObj.checkNode(treeNode, !check, true);
formatUser(treeNode, treeNode.checked);
};
@@ -333,6 +376,17 @@ export default {
},
submit() {
const _this = this;
if (!_this.multiple) {
if (!_this.selectedUser) {
Message.warning("请先选择用户");
return;
}
const u: any = { id: _this.selectedUser.id, name: _this.selectedUser.name, group: _this.selectedUser.group };
this.$emit("update:userids", [u.id]);
this.$emit("on-submit", [u]);
this.modal = false;
return;
}
let idsArr = this.tempUserList.map((x: any) => _this.getSplitId(x.id));
idsArr = Array.from(new Set(idsArr));
this.$emit("update:userids", idsArr);
@@ -412,6 +466,21 @@ export default {
background: #e4edf6;
}
}
.single-box {
padding: 12px;
line-height: 32px;
.info {
float: right;
margin-right: 12px;
font-size: 12px;
color: gray;
}
}
.single-empty {
padding: 12px;
font-size: 12px;
color: #c5c8ce;
}
}
.arrow-box {
width: 128px;
+95
View File
@@ -0,0 +1,95 @@
<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.adminUserTree().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>