widget.qw 1.0.81 → 1.0.82
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.development +9 -9
- package/.env.production +8 -8
- package/README.md +75 -75
- package/build/style.css +471 -471
- package/build/widget.qw.es.js +55 -56
- package/build/widget.qw.umd.js +55 -56
- package/index.html +17 -17
- package/package.json +47 -47
- package/src/App.vue +26 -26
- package/src/api/index.js +165 -165
- package/src/components/AuditBar.vue +396 -396
- package/src/components/BillCard.vue +164 -164
- package/src/components/CascaderPicker.vue +132 -132
- package/src/components/CascaderPop.vue +371 -371
- package/src/components/CheckGroup.vue +113 -113
- package/src/components/DatetimePicker/DatetimePop.vue +342 -342
- package/src/components/DatetimePicker/index.vue +124 -124
- package/src/components/DayDropdown.vue +56 -56
- package/src/components/FilePicker.vue +169 -169
- package/src/components/Input.vue +80 -80
- package/src/components/MonthDropdown.vue +51 -51
- package/src/components/MultiPicker.vue +123 -123
- package/src/components/ObjsEditor.vue +334 -334
- package/src/components/SecretNotify.js +5 -5
- package/src/components/Sheet.vue +92 -92
- package/src/components/SingleApiPicker.vue +108 -108
- package/src/components/SinglePicker.vue +102 -102
- package/src/components/SingleUserSelector.vue +425 -425
- package/src/components/Switch.vue +64 -64
- package/src/components/TreePicker.vue +113 -113
- package/src/components/UserPicker.vue +106 -106
- package/src/components/UserProfile.vue +129 -129
- package/src/components/UsersPicker.vue +118 -118
- package/src/components/YearDropdown.vue +59 -59
- package/src/components/data_selector.vue +303 -303
- package/src/components/image_picker.vue +123 -123
- package/src/components/images_picker.vue +195 -195
- package/src/components/index.js +162 -162
- package/src/components/mult_list_selector.vue +155 -155
- package/src/components/subdepartment_selector.vue +481 -481
- package/src/components/user_selector.vue +639 -639
- package/src/components/widget/DataPop.vue +95 -95
- package/src/components/widget/TreePop.vue +88 -88
- package/src/components/widget/UserPop.vue +347 -347
- package/src/env.d.ts +8 -8
- package/src/main.js +117 -117
- package/src/router/index.ts +173 -173
- package/src/util/array_util.js +32 -32
- package/src/util/auth_util.js +72 -73
- package/src/util/bool_util.js +5 -5
- package/src/util/bus.js +1 -1
- package/src/util/cache_util.js +18 -18
- package/src/util/errer_code.js +6 -6
- package/src/util/eval_util.js +19 -19
- package/src/util/icon_util.js +36 -36
- package/src/util/image_util.js +27 -27
- package/src/util/index.js +57 -57
- package/src/util/num_util.js +70 -70
- package/src/util/obj_util.js +28 -28
- package/src/util/request.js +73 -73
- package/src/util/request_json.js +71 -71
- package/src/util/request_json_mute.js +65 -65
- package/src/util/request_upload.js +79 -79
- package/src/util/route_util.js +31 -31
- package/src/util/str_util.js +143 -143
- package/src/util/time_util.js +406 -406
- package/src/util/toast_util.js +24 -24
- package/src/util/tree_util.js +153 -153
- package/src/util/uuid_util.js +9 -9
- package/src/util/validate.js +182 -182
- package/src/util/vue_filter.js +223 -223
- package/src/views/auditbar/index.vue +78 -78
- package/src/views/billcard/index.vue +48 -48
- package/src/views/cascaderpop/index.vue +90 -90
- package/src/views/checkgroup/index.vue +35 -35
- package/src/views/dataSelector/index.vue +48 -48
- package/src/views/datetimepicker/index.vue +34 -34
- package/src/views/daydropdown/index.vue +31 -31
- package/src/views/filepicker/index.vue +32 -32
- package/src/views/imagepicker/index.vue +31 -31
- package/src/views/imagespicker/index.vue +37 -37
- package/src/views/input/index.vue +35 -35
- package/src/views/monthdropdown/index.vue +31 -31
- package/src/views/multListSelector/index.vue +61 -61
- package/src/views/multipicker/index.vue +36 -36
- package/src/views/objseditor/index.vue +277 -277
- package/src/views/productSelector/index.vue +35 -35
- package/src/views/projectpicker/index.vue +41 -41
- package/src/views/secretnotify/index.vue +27 -27
- package/src/views/sheet/index.vue +45 -45
- package/src/views/singlepicker/index.vue +35 -35
- package/src/views/subdepartmentSelector/index.vue +40 -40
- package/src/views/switch/index.vue +34 -34
- package/src/views/treepicker/index.vue +41 -41
- package/src/views/userSelector/index.vue +54 -54
- package/src/views/userSelectorNew/index.vue +45 -45
- package/src/views/userpicker/index.vue +43 -43
- package/src/views/userprofile/index.vue +30 -30
- package/src/views/userspicker/index.vue +43 -43
- package/src/views/yeardropdown/index.vue +32 -32
- package/src/vm/index.js +1 -1
- package/tsconfig.json +19 -19
- package/tsconfig.node.json +8 -8
- package/vite.config.ts +122 -122
|
@@ -1,425 +1,425 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<van-field v-if="!isGone" :is-link="isReadonly ? false : true" readonly :label="props.label"
|
|
3
|
-
:placeholder="props.placeholder" @click="show = !isReadonly" :model-value="selectedUserInfo" :required="isRequired"
|
|
4
|
-
:rules="props.rules" input-align="center" />
|
|
5
|
-
<van-popup v-model:show="show" round position="bottom" class="user-popup">
|
|
6
|
-
<div class="department-selector">
|
|
7
|
-
<div class="picker-header">
|
|
8
|
-
<div class="header-left">
|
|
9
|
-
<van-button @click="goBack" icon="arrow-left" size="small" v-show="currentPath !== '请选择部门'" />
|
|
10
|
-
</div>
|
|
11
|
-
|
|
12
|
-
<!-- 移除header-center包装,直接使用current-path -->
|
|
13
|
-
<div class="current-path">{{ currentPath }}</div>
|
|
14
|
-
|
|
15
|
-
<div class="header-right">
|
|
16
|
-
<van-button size="small" @click="handleClear" class="cancel-btn">清空</van-button>
|
|
17
|
-
|
|
18
|
-
<van-button type="primary" size="small" @click="confirmSelection" class="confirm-btn">确定</van-button>
|
|
19
|
-
</div>
|
|
20
|
-
</div>
|
|
21
|
-
|
|
22
|
-
<!-- 搜索框 -->
|
|
23
|
-
<van-search v-model="searchValue" placeholder="输入工号查询" @clear="clearSearch" @search="handleSearch"
|
|
24
|
-
@cancel="clearSearch" show-action>
|
|
25
|
-
<template #action>
|
|
26
|
-
<div @click="handleSearch">搜索</div>
|
|
27
|
-
</template>
|
|
28
|
-
</van-search>
|
|
29
|
-
|
|
30
|
-
<div class="scroll-container">
|
|
31
|
-
<!-- 遍历部门列表 -->
|
|
32
|
-
<van-cell-group>
|
|
33
|
-
<van-cell v-for="department in departments" :key="department.id" :title="department.name" is-link
|
|
34
|
-
@click="toggleDepartment(department)" />
|
|
35
|
-
</van-cell-group>
|
|
36
|
-
|
|
37
|
-
<!-- 遍历人员列表 -->
|
|
38
|
-
<van-radio-group v-model="tempSelectedUser">
|
|
39
|
-
<van-cell-group>
|
|
40
|
-
<van-cell v-for="user in userlist" :key="user.id" :title="user.name">
|
|
41
|
-
<template #right-icon>
|
|
42
|
-
<van-radio :name="user.userid" />
|
|
43
|
-
</template>
|
|
44
|
-
</van-cell>
|
|
45
|
-
</van-cell-group>
|
|
46
|
-
</van-radio-group>
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
</van-popup>
|
|
50
|
-
</template>
|
|
51
|
-
<script setup>
|
|
52
|
-
import { ref, onMounted, watch } from "vue";
|
|
53
|
-
import util from "../util"
|
|
54
|
-
import { useVModel } from '@vueuse/core'
|
|
55
|
-
|
|
56
|
-
// 弹出层
|
|
57
|
-
const show = ref(false);
|
|
58
|
-
|
|
59
|
-
const props = defineProps({
|
|
60
|
-
modelValue: {
|
|
61
|
-
type: String,
|
|
62
|
-
default: "",
|
|
63
|
-
},
|
|
64
|
-
label: {
|
|
65
|
-
type: String,
|
|
66
|
-
default: "选择人员",
|
|
67
|
-
},
|
|
68
|
-
placeholder: {
|
|
69
|
-
type: String,
|
|
70
|
-
default: "请选择人员",
|
|
71
|
-
},
|
|
72
|
-
required: {
|
|
73
|
-
type: Boolean,
|
|
74
|
-
default: false,
|
|
75
|
-
},
|
|
76
|
-
readonly: {
|
|
77
|
-
type: Boolean,
|
|
78
|
-
default: false,
|
|
79
|
-
},
|
|
80
|
-
rules: {
|
|
81
|
-
type: Array,
|
|
82
|
-
default: () => [],
|
|
83
|
-
},
|
|
84
|
-
// 默认查询所有组织机构
|
|
85
|
-
currentDeptId: {
|
|
86
|
-
type: String,
|
|
87
|
-
default: "1", //组织机构id
|
|
88
|
-
},
|
|
89
|
-
// 二级部门取值范围字典id
|
|
90
|
-
// 取dictionary库.dict的配置id
|
|
91
|
-
deptRangeId: {
|
|
92
|
-
type: String,
|
|
93
|
-
default: ''
|
|
94
|
-
},
|
|
95
|
-
auth: {
|
|
96
|
-
type: String,
|
|
97
|
-
default: "",
|
|
98
|
-
},
|
|
99
|
-
});
|
|
100
|
-
// 定义emit
|
|
101
|
-
const emit = defineEmits(["update:modelValue", "select"]);
|
|
102
|
-
const modelValue = useVModel(props, 'modelValue', emit)
|
|
103
|
-
const { isRequired, isReadonly, isGone, isDisabled } = util.props2auth(props);
|
|
104
|
-
|
|
105
|
-
// 部门列表
|
|
106
|
-
let departments = ref([]);
|
|
107
|
-
let departmentFulls = ref([])
|
|
108
|
-
const searchValue = ref("");
|
|
109
|
-
const currentPath = ref("请选择部门");
|
|
110
|
-
// 定义用户
|
|
111
|
-
const userlist = ref([]);
|
|
112
|
-
// 定义选择单选按钮的值
|
|
113
|
-
const tempSelectedUser = ref(null);
|
|
114
|
-
// 定义展示表单展示区的值
|
|
115
|
-
const selectedUserInfo = ref([]);
|
|
116
|
-
|
|
117
|
-
// 获得部门列表
|
|
118
|
-
const fetchAllDepartments = async () => {
|
|
119
|
-
// 查询所有部门
|
|
120
|
-
const response = await util.get_subdepartments({ departmentId: props.currentDeptId })
|
|
121
|
-
let level2Depts = response.data.subDepartments
|
|
122
|
-
|
|
123
|
-
// 取配置的二级部门取值范围
|
|
124
|
-
let level2DeptIds = []
|
|
125
|
-
if (props.deptRangeId) {
|
|
126
|
-
let res = await util.dictionary_dict({ id: props.deptRangeId })
|
|
127
|
-
if (res.data.parseValue && res.data.parseValue.length>0) {
|
|
128
|
-
level2DeptIds = res.data.parseValue
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// 当前用户所在二级部门列表
|
|
132
|
-
const user = await getUserInfoById(props.modelValue)
|
|
133
|
-
if (user && user.department.id) {
|
|
134
|
-
level2DeptIds = [...level2DeptIds, user.department.id]
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// 只保留指定二级部门列表、当前用户的二级部门
|
|
139
|
-
if (props.deptRangeId) {
|
|
140
|
-
if (level2DeptIds && level2DeptIds.length > 0) {
|
|
141
|
-
level2Depts = level2Depts.filter(item => {
|
|
142
|
-
return level2DeptIds.includes(item.id)
|
|
143
|
-
})
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
//departments
|
|
148
|
-
departments.value = getCurrentDepartments(level2Depts || [])
|
|
149
|
-
departmentFulls.value = departments.value;
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
//嵌套处理获得树形结构
|
|
153
|
-
const getCurrentDepartments = (data) => {
|
|
154
|
-
let result = data.map((item) => {
|
|
155
|
-
return {
|
|
156
|
-
id: item.id,
|
|
157
|
-
name: item.name,
|
|
158
|
-
subDepartments: getCurrentDepartments(item.subDepartments || []),
|
|
159
|
-
};
|
|
160
|
-
});
|
|
161
|
-
return result;
|
|
162
|
-
};
|
|
163
|
-
// 添加部门路径查找方法
|
|
164
|
-
function findDeptPath(deptId, tree, path = []) {
|
|
165
|
-
for (const dept of tree) {
|
|
166
|
-
if (dept.id === deptId) {
|
|
167
|
-
return [...path, dept];
|
|
168
|
-
}
|
|
169
|
-
if (dept.subDepartments?.length) {
|
|
170
|
-
const found = findDeptPath(deptId, dept.subDepartments, [...path, dept]);
|
|
171
|
-
if (found && found.length>0) return found;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
return [];
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// 获得人员列表
|
|
178
|
-
const fetchAllUsers = async (departmentId) => {
|
|
179
|
-
const response = await util.get_subdepartments_users({ departmentId });
|
|
180
|
-
return response.data.users || [];
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
// 通过id获得人员信息
|
|
184
|
-
const getUserInfoById = async (userId) => {
|
|
185
|
-
const response = await util.get_wxuser_by_id({ userId });
|
|
186
|
-
return response.data;
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
// 清空搜索
|
|
190
|
-
const clearSearch = () => {
|
|
191
|
-
searchValue.value = ""; // 清空搜索框
|
|
192
|
-
// 部门列表赋初始值
|
|
193
|
-
departments.value = departmentFulls.value;
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
// 搜索人员
|
|
197
|
-
const handleSearch = () => {
|
|
198
|
-
// 在这里可以处理搜索逻辑
|
|
199
|
-
if (searchValue.value) {
|
|
200
|
-
departments.value = []; // 清空部门列表
|
|
201
|
-
// 搜索用户
|
|
202
|
-
getUserInfoById(searchValue.value).then((user) => {
|
|
203
|
-
if (user) {
|
|
204
|
-
userlist.value = [user];
|
|
205
|
-
//更新currentPath显示用户部门
|
|
206
|
-
const path = findDeptPath(user.mainDepartment, departmentFulls.value);
|
|
207
|
-
console.log("path", path);
|
|
208
|
-
currentPath.value = "";
|
|
209
|
-
path.forEach((dept) => {
|
|
210
|
-
currentPath.value += dept.name + "/";
|
|
211
|
-
});
|
|
212
|
-
} else {
|
|
213
|
-
userlist.value = []; // 清空人员列表
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
// 切换部门
|
|
220
|
-
const toggleDepartment = (department) => {
|
|
221
|
-
console.log("选择了部门:", department.subDepartments);
|
|
222
|
-
departments.value = department.subDepartments;
|
|
223
|
-
// 在这里可以处理部门选择逻辑
|
|
224
|
-
console.log("选择的部门:", departments);
|
|
225
|
-
// 更新当前路径
|
|
226
|
-
if (currentPath.value === "请选择部门") {
|
|
227
|
-
currentPath.value = "";
|
|
228
|
-
}
|
|
229
|
-
currentPath.value += department.name + "/";
|
|
230
|
-
// 请求加载人员
|
|
231
|
-
fetchAllUsers(department.id).then((users) => {
|
|
232
|
-
// 在这里可以处理人员列表逻辑
|
|
233
|
-
userlist.value = users;
|
|
234
|
-
})
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
// 返回上一级部门
|
|
238
|
-
const goBack = () => {
|
|
239
|
-
searchValue.value = ""; // 清空搜索框
|
|
240
|
-
if (currentPath.value === "请选择部门") return;
|
|
241
|
-
currentPath.value = currentPath.value.replace(/\/+$/, "");
|
|
242
|
-
currentPath.value = currentPath.value.substring(
|
|
243
|
-
0,
|
|
244
|
-
currentPath.value.lastIndexOf("/")
|
|
245
|
-
);
|
|
246
|
-
|
|
247
|
-
if (currentPath.value !== "") {
|
|
248
|
-
// 加载当前路径的部门列表
|
|
249
|
-
const departmentlist = departmentFulls.value.find(
|
|
250
|
-
(dept) => dept.name === currentPath.value.split("/")[0]
|
|
251
|
-
);
|
|
252
|
-
departments.value = departmentlist.subDepartments;
|
|
253
|
-
// 加载当前路径的人员列表
|
|
254
|
-
fetchAllUsers(departmentlist.id).then((users) => {
|
|
255
|
-
userlist.value = users;
|
|
256
|
-
});
|
|
257
|
-
} else {
|
|
258
|
-
currentPath.value = "请选择部门";
|
|
259
|
-
departments.value = departmentFulls.value;
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
// 确认按钮
|
|
264
|
-
const confirmSelection = () => {
|
|
265
|
-
modelValue.value = tempSelectedUser.value
|
|
266
|
-
//对应的人员信息
|
|
267
|
-
const selectedUser = userlist.value.find(
|
|
268
|
-
(user) => user.userid === tempSelectedUser.value
|
|
269
|
-
);
|
|
270
|
-
|
|
271
|
-
selectedUserInfo.value = selectedUser.name;
|
|
272
|
-
|
|
273
|
-
emit('select', selectedUser)
|
|
274
|
-
|
|
275
|
-
show.value = false;
|
|
276
|
-
};
|
|
277
|
-
// 清空按钮
|
|
278
|
-
const handleClear = () => {
|
|
279
|
-
tempSelectedUser.value = null;
|
|
280
|
-
selectedUserInfo.value = null;
|
|
281
|
-
// 清空人员列表
|
|
282
|
-
userlist.value = [];
|
|
283
|
-
show.value = false;
|
|
284
|
-
currentPath.value = "请选择部门";
|
|
285
|
-
departments.value = departmentFulls.value;
|
|
286
|
-
modelValue.value = tempSelectedUser.value
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
//首页加载
|
|
290
|
-
onMounted(async () => {
|
|
291
|
-
await fetchAllDepartments();
|
|
292
|
-
await initDepartment();
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
// 人员初始化
|
|
296
|
-
const initUser = async () => {
|
|
297
|
-
console.log('initUser', props.modelValue, departmentFulls.value)
|
|
298
|
-
if (!props.modelValue || !departmentFulls.value || departmentFulls.value.length < 1)
|
|
299
|
-
return
|
|
300
|
-
|
|
301
|
-
const user = await getUserInfoById(props.modelValue);
|
|
302
|
-
if (user) {
|
|
303
|
-
//更新currentPath显示用户部门
|
|
304
|
-
const path = findDeptPath(user.mainDepartment, departmentFulls.value);
|
|
305
|
-
currentPath.value = "";
|
|
306
|
-
path.forEach((dept) => {
|
|
307
|
-
currentPath.value += dept.name + "/";
|
|
308
|
-
});
|
|
309
|
-
// 清空部门列表
|
|
310
|
-
departments.value = []; // 清空部门列表
|
|
311
|
-
// 加载当前路径的人员列表
|
|
312
|
-
fetchAllUsers(user.mainDepartment).then((users) => {
|
|
313
|
-
userlist.value = users;
|
|
314
|
-
// 选中当前用户
|
|
315
|
-
tempSelectedUser.value = user.userid;
|
|
316
|
-
selectedUserInfo.value = user.name;
|
|
317
|
-
});
|
|
318
|
-
} else {
|
|
319
|
-
userlist.value = []; // 清空人员列表
|
|
320
|
-
}
|
|
321
|
-
};
|
|
322
|
-
// 部门初始化
|
|
323
|
-
const initDepartment = async () => {
|
|
324
|
-
//更新currentPath显示用户部门
|
|
325
|
-
const path = findDeptPath(props.currentDeptId, departmentFulls.value);
|
|
326
|
-
currentPath.value = "";
|
|
327
|
-
path.forEach((dept) => {
|
|
328
|
-
currentPath.value += dept.name + "/";
|
|
329
|
-
});
|
|
330
|
-
// 更新部门列表
|
|
331
|
-
departments.value = departmentFulls.value; // 重置部门列表
|
|
332
|
-
};
|
|
333
|
-
|
|
334
|
-
//监听searchValue变化
|
|
335
|
-
watch(searchValue, (newValue, oldValue) => {
|
|
336
|
-
if (newValue.length === 0) {
|
|
337
|
-
// 重新加载部门列表
|
|
338
|
-
departments.value = departmentFulls.value;
|
|
339
|
-
// 清空人员列表
|
|
340
|
-
userlist.value = [];
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
watch(() => modelValue.value, (n, o) => {
|
|
344
|
-
initUser()
|
|
345
|
-
}, {
|
|
346
|
-
immediate: true
|
|
347
|
-
})
|
|
348
|
-
watch(() => departmentFulls.value, (n, o) => {
|
|
349
|
-
initUser()
|
|
350
|
-
}, {
|
|
351
|
-
immediate: true
|
|
352
|
-
})
|
|
353
|
-
</script>
|
|
354
|
-
|
|
355
|
-
<style scoped>
|
|
356
|
-
.user-popup {
|
|
357
|
-
width: 100%;
|
|
358
|
-
overflow: hidden;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
.department-selector {
|
|
362
|
-
padding: 10px;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
.search-buttons {
|
|
366
|
-
display: flex;
|
|
367
|
-
gap: 10px;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
.clear-btn {
|
|
371
|
-
background-color: #f2f3f5;
|
|
372
|
-
border: none;
|
|
373
|
-
border-radius: 4px;
|
|
374
|
-
padding: 4px 8px;
|
|
375
|
-
font-size: 14px;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
.confirm-btn {
|
|
379
|
-
background-color: #1989fa;
|
|
380
|
-
color: white;
|
|
381
|
-
border: none;
|
|
382
|
-
border-radius: 4px;
|
|
383
|
-
padding: 4px 8px;
|
|
384
|
-
font-size: 14px;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
.no-results {
|
|
388
|
-
text-align: center;
|
|
389
|
-
padding: 20px;
|
|
390
|
-
color: #969799;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
.picker-header {
|
|
394
|
-
padding: 10px;
|
|
395
|
-
border-bottom: 1px solid #ebedf0;
|
|
396
|
-
display: flex;
|
|
397
|
-
justify-content: space-between;
|
|
398
|
-
align-items: center;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
.current-path {
|
|
402
|
-
flex: 1;
|
|
403
|
-
font-size: 14px;
|
|
404
|
-
color: #323233;
|
|
405
|
-
white-space: nowrap;
|
|
406
|
-
overflow: hidden;
|
|
407
|
-
text-overflow: ellipsis;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
.header-right {
|
|
411
|
-
display: flex;
|
|
412
|
-
gap: 8px;
|
|
413
|
-
margin-left: auto;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
.cancel-btn {
|
|
417
|
-
color: #969799;
|
|
418
|
-
margin-right: 5px;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
.scroll-container {
|
|
422
|
-
height: calc(80vh - 150px);
|
|
423
|
-
overflow-y: auto;
|
|
424
|
-
}
|
|
425
|
-
</style>
|
|
1
|
+
<template>
|
|
2
|
+
<van-field v-if="!isGone" :is-link="isReadonly ? false : true" readonly :label="props.label"
|
|
3
|
+
:placeholder="props.placeholder" @click="show = !isReadonly" :model-value="selectedUserInfo" :required="isRequired"
|
|
4
|
+
:rules="props.rules" input-align="center" />
|
|
5
|
+
<van-popup v-model:show="show" round position="bottom" class="user-popup">
|
|
6
|
+
<div class="department-selector">
|
|
7
|
+
<div class="picker-header">
|
|
8
|
+
<div class="header-left">
|
|
9
|
+
<van-button @click="goBack" icon="arrow-left" size="small" v-show="currentPath !== '请选择部门'" />
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<!-- 移除header-center包装,直接使用current-path -->
|
|
13
|
+
<div class="current-path">{{ currentPath }}</div>
|
|
14
|
+
|
|
15
|
+
<div class="header-right">
|
|
16
|
+
<van-button size="small" @click="handleClear" class="cancel-btn">清空</van-button>
|
|
17
|
+
|
|
18
|
+
<van-button type="primary" size="small" @click="confirmSelection" class="confirm-btn">确定</van-button>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<!-- 搜索框 -->
|
|
23
|
+
<van-search v-model="searchValue" placeholder="输入工号查询" @clear="clearSearch" @search="handleSearch"
|
|
24
|
+
@cancel="clearSearch" show-action>
|
|
25
|
+
<template #action>
|
|
26
|
+
<div @click="handleSearch">搜索</div>
|
|
27
|
+
</template>
|
|
28
|
+
</van-search>
|
|
29
|
+
|
|
30
|
+
<div class="scroll-container">
|
|
31
|
+
<!-- 遍历部门列表 -->
|
|
32
|
+
<van-cell-group>
|
|
33
|
+
<van-cell v-for="department in departments" :key="department.id" :title="department.name" is-link
|
|
34
|
+
@click="toggleDepartment(department)" />
|
|
35
|
+
</van-cell-group>
|
|
36
|
+
|
|
37
|
+
<!-- 遍历人员列表 -->
|
|
38
|
+
<van-radio-group v-model="tempSelectedUser">
|
|
39
|
+
<van-cell-group>
|
|
40
|
+
<van-cell v-for="user in userlist" :key="user.id" :title="user.name">
|
|
41
|
+
<template #right-icon>
|
|
42
|
+
<van-radio :name="user.userid" />
|
|
43
|
+
</template>
|
|
44
|
+
</van-cell>
|
|
45
|
+
</van-cell-group>
|
|
46
|
+
</van-radio-group>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</van-popup>
|
|
50
|
+
</template>
|
|
51
|
+
<script setup>
|
|
52
|
+
import { ref, onMounted, watch } from "vue";
|
|
53
|
+
import util from "../util"
|
|
54
|
+
import { useVModel } from '@vueuse/core'
|
|
55
|
+
|
|
56
|
+
// 弹出层
|
|
57
|
+
const show = ref(false);
|
|
58
|
+
|
|
59
|
+
const props = defineProps({
|
|
60
|
+
modelValue: {
|
|
61
|
+
type: String,
|
|
62
|
+
default: "",
|
|
63
|
+
},
|
|
64
|
+
label: {
|
|
65
|
+
type: String,
|
|
66
|
+
default: "选择人员",
|
|
67
|
+
},
|
|
68
|
+
placeholder: {
|
|
69
|
+
type: String,
|
|
70
|
+
default: "请选择人员",
|
|
71
|
+
},
|
|
72
|
+
required: {
|
|
73
|
+
type: Boolean,
|
|
74
|
+
default: false,
|
|
75
|
+
},
|
|
76
|
+
readonly: {
|
|
77
|
+
type: Boolean,
|
|
78
|
+
default: false,
|
|
79
|
+
},
|
|
80
|
+
rules: {
|
|
81
|
+
type: Array,
|
|
82
|
+
default: () => [],
|
|
83
|
+
},
|
|
84
|
+
// 默认查询所有组织机构
|
|
85
|
+
currentDeptId: {
|
|
86
|
+
type: String,
|
|
87
|
+
default: "1", //组织机构id
|
|
88
|
+
},
|
|
89
|
+
// 二级部门取值范围字典id
|
|
90
|
+
// 取dictionary库.dict的配置id
|
|
91
|
+
deptRangeId: {
|
|
92
|
+
type: String,
|
|
93
|
+
default: ''
|
|
94
|
+
},
|
|
95
|
+
auth: {
|
|
96
|
+
type: String,
|
|
97
|
+
default: "",
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
// 定义emit
|
|
101
|
+
const emit = defineEmits(["update:modelValue", "select"]);
|
|
102
|
+
const modelValue = useVModel(props, 'modelValue', emit)
|
|
103
|
+
const { isRequired, isReadonly, isGone, isDisabled } = util.props2auth(props);
|
|
104
|
+
|
|
105
|
+
// 部门列表
|
|
106
|
+
let departments = ref([]);
|
|
107
|
+
let departmentFulls = ref([])
|
|
108
|
+
const searchValue = ref("");
|
|
109
|
+
const currentPath = ref("请选择部门");
|
|
110
|
+
// 定义用户
|
|
111
|
+
const userlist = ref([]);
|
|
112
|
+
// 定义选择单选按钮的值
|
|
113
|
+
const tempSelectedUser = ref(null);
|
|
114
|
+
// 定义展示表单展示区的值
|
|
115
|
+
const selectedUserInfo = ref([]);
|
|
116
|
+
|
|
117
|
+
// 获得部门列表
|
|
118
|
+
const fetchAllDepartments = async () => {
|
|
119
|
+
// 查询所有部门
|
|
120
|
+
const response = await util.get_subdepartments({ departmentId: props.currentDeptId })
|
|
121
|
+
let level2Depts = response.data.subDepartments
|
|
122
|
+
|
|
123
|
+
// 取配置的二级部门取值范围
|
|
124
|
+
let level2DeptIds = []
|
|
125
|
+
if (props.deptRangeId) {
|
|
126
|
+
let res = await util.dictionary_dict({ id: props.deptRangeId })
|
|
127
|
+
if (res.data.parseValue && res.data.parseValue.length>0) {
|
|
128
|
+
level2DeptIds = res.data.parseValue
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 当前用户所在二级部门列表
|
|
132
|
+
const user = await getUserInfoById(props.modelValue)
|
|
133
|
+
if (user && user.department.id) {
|
|
134
|
+
level2DeptIds = [...level2DeptIds, user.department.id]
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 只保留指定二级部门列表、当前用户的二级部门
|
|
139
|
+
if (props.deptRangeId) {
|
|
140
|
+
if (level2DeptIds && level2DeptIds.length > 0) {
|
|
141
|
+
level2Depts = level2Depts.filter(item => {
|
|
142
|
+
return level2DeptIds.includes(item.id)
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
//departments
|
|
148
|
+
departments.value = getCurrentDepartments(level2Depts || [])
|
|
149
|
+
departmentFulls.value = departments.value;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
//嵌套处理获得树形结构
|
|
153
|
+
const getCurrentDepartments = (data) => {
|
|
154
|
+
let result = data.map((item) => {
|
|
155
|
+
return {
|
|
156
|
+
id: item.id,
|
|
157
|
+
name: item.name,
|
|
158
|
+
subDepartments: getCurrentDepartments(item.subDepartments || []),
|
|
159
|
+
};
|
|
160
|
+
});
|
|
161
|
+
return result;
|
|
162
|
+
};
|
|
163
|
+
// 添加部门路径查找方法
|
|
164
|
+
function findDeptPath(deptId, tree, path = []) {
|
|
165
|
+
for (const dept of tree) {
|
|
166
|
+
if (dept.id === deptId) {
|
|
167
|
+
return [...path, dept];
|
|
168
|
+
}
|
|
169
|
+
if (dept.subDepartments?.length) {
|
|
170
|
+
const found = findDeptPath(deptId, dept.subDepartments, [...path, dept]);
|
|
171
|
+
if (found && found.length>0) return found;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 获得人员列表
|
|
178
|
+
const fetchAllUsers = async (departmentId) => {
|
|
179
|
+
const response = await util.get_subdepartments_users({ departmentId });
|
|
180
|
+
return response.data.users || [];
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// 通过id获得人员信息
|
|
184
|
+
const getUserInfoById = async (userId) => {
|
|
185
|
+
const response = await util.get_wxuser_by_id({ userId });
|
|
186
|
+
return response.data;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// 清空搜索
|
|
190
|
+
const clearSearch = () => {
|
|
191
|
+
searchValue.value = ""; // 清空搜索框
|
|
192
|
+
// 部门列表赋初始值
|
|
193
|
+
departments.value = departmentFulls.value;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// 搜索人员
|
|
197
|
+
const handleSearch = () => {
|
|
198
|
+
// 在这里可以处理搜索逻辑
|
|
199
|
+
if (searchValue.value) {
|
|
200
|
+
departments.value = []; // 清空部门列表
|
|
201
|
+
// 搜索用户
|
|
202
|
+
getUserInfoById(searchValue.value).then((user) => {
|
|
203
|
+
if (user) {
|
|
204
|
+
userlist.value = [user];
|
|
205
|
+
//更新currentPath显示用户部门
|
|
206
|
+
const path = findDeptPath(user.mainDepartment, departmentFulls.value);
|
|
207
|
+
console.log("path", path);
|
|
208
|
+
currentPath.value = "";
|
|
209
|
+
path.forEach((dept) => {
|
|
210
|
+
currentPath.value += dept.name + "/";
|
|
211
|
+
});
|
|
212
|
+
} else {
|
|
213
|
+
userlist.value = []; // 清空人员列表
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// 切换部门
|
|
220
|
+
const toggleDepartment = (department) => {
|
|
221
|
+
console.log("选择了部门:", department.subDepartments);
|
|
222
|
+
departments.value = department.subDepartments;
|
|
223
|
+
// 在这里可以处理部门选择逻辑
|
|
224
|
+
console.log("选择的部门:", departments);
|
|
225
|
+
// 更新当前路径
|
|
226
|
+
if (currentPath.value === "请选择部门") {
|
|
227
|
+
currentPath.value = "";
|
|
228
|
+
}
|
|
229
|
+
currentPath.value += department.name + "/";
|
|
230
|
+
// 请求加载人员
|
|
231
|
+
fetchAllUsers(department.id).then((users) => {
|
|
232
|
+
// 在这里可以处理人员列表逻辑
|
|
233
|
+
userlist.value = users;
|
|
234
|
+
})
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// 返回上一级部门
|
|
238
|
+
const goBack = () => {
|
|
239
|
+
searchValue.value = ""; // 清空搜索框
|
|
240
|
+
if (currentPath.value === "请选择部门") return;
|
|
241
|
+
currentPath.value = currentPath.value.replace(/\/+$/, "");
|
|
242
|
+
currentPath.value = currentPath.value.substring(
|
|
243
|
+
0,
|
|
244
|
+
currentPath.value.lastIndexOf("/")
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
if (currentPath.value !== "") {
|
|
248
|
+
// 加载当前路径的部门列表
|
|
249
|
+
const departmentlist = departmentFulls.value.find(
|
|
250
|
+
(dept) => dept.name === currentPath.value.split("/")[0]
|
|
251
|
+
);
|
|
252
|
+
departments.value = departmentlist.subDepartments;
|
|
253
|
+
// 加载当前路径的人员列表
|
|
254
|
+
fetchAllUsers(departmentlist.id).then((users) => {
|
|
255
|
+
userlist.value = users;
|
|
256
|
+
});
|
|
257
|
+
} else {
|
|
258
|
+
currentPath.value = "请选择部门";
|
|
259
|
+
departments.value = departmentFulls.value;
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// 确认按钮
|
|
264
|
+
const confirmSelection = () => {
|
|
265
|
+
modelValue.value = tempSelectedUser.value
|
|
266
|
+
//对应的人员信息
|
|
267
|
+
const selectedUser = userlist.value.find(
|
|
268
|
+
(user) => user.userid === tempSelectedUser.value
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
selectedUserInfo.value = selectedUser.name;
|
|
272
|
+
|
|
273
|
+
emit('select', selectedUser)
|
|
274
|
+
|
|
275
|
+
show.value = false;
|
|
276
|
+
};
|
|
277
|
+
// 清空按钮
|
|
278
|
+
const handleClear = () => {
|
|
279
|
+
tempSelectedUser.value = null;
|
|
280
|
+
selectedUserInfo.value = null;
|
|
281
|
+
// 清空人员列表
|
|
282
|
+
userlist.value = [];
|
|
283
|
+
show.value = false;
|
|
284
|
+
currentPath.value = "请选择部门";
|
|
285
|
+
departments.value = departmentFulls.value;
|
|
286
|
+
modelValue.value = tempSelectedUser.value
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
//首页加载
|
|
290
|
+
onMounted(async () => {
|
|
291
|
+
await fetchAllDepartments();
|
|
292
|
+
await initDepartment();
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// 人员初始化
|
|
296
|
+
const initUser = async () => {
|
|
297
|
+
console.log('initUser', props.modelValue, departmentFulls.value)
|
|
298
|
+
if (!props.modelValue || !departmentFulls.value || departmentFulls.value.length < 1)
|
|
299
|
+
return
|
|
300
|
+
|
|
301
|
+
const user = await getUserInfoById(props.modelValue);
|
|
302
|
+
if (user) {
|
|
303
|
+
//更新currentPath显示用户部门
|
|
304
|
+
const path = findDeptPath(user.mainDepartment, departmentFulls.value);
|
|
305
|
+
currentPath.value = "";
|
|
306
|
+
path.forEach((dept) => {
|
|
307
|
+
currentPath.value += dept.name + "/";
|
|
308
|
+
});
|
|
309
|
+
// 清空部门列表
|
|
310
|
+
departments.value = []; // 清空部门列表
|
|
311
|
+
// 加载当前路径的人员列表
|
|
312
|
+
fetchAllUsers(user.mainDepartment).then((users) => {
|
|
313
|
+
userlist.value = users;
|
|
314
|
+
// 选中当前用户
|
|
315
|
+
tempSelectedUser.value = user.userid;
|
|
316
|
+
selectedUserInfo.value = user.name;
|
|
317
|
+
});
|
|
318
|
+
} else {
|
|
319
|
+
userlist.value = []; // 清空人员列表
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
// 部门初始化
|
|
323
|
+
const initDepartment = async () => {
|
|
324
|
+
//更新currentPath显示用户部门
|
|
325
|
+
const path = findDeptPath(props.currentDeptId, departmentFulls.value);
|
|
326
|
+
currentPath.value = "";
|
|
327
|
+
path.forEach((dept) => {
|
|
328
|
+
currentPath.value += dept.name + "/";
|
|
329
|
+
});
|
|
330
|
+
// 更新部门列表
|
|
331
|
+
departments.value = departmentFulls.value; // 重置部门列表
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
//监听searchValue变化
|
|
335
|
+
watch(searchValue, (newValue, oldValue) => {
|
|
336
|
+
if (newValue.length === 0) {
|
|
337
|
+
// 重新加载部门列表
|
|
338
|
+
departments.value = departmentFulls.value;
|
|
339
|
+
// 清空人员列表
|
|
340
|
+
userlist.value = [];
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
watch(() => modelValue.value, (n, o) => {
|
|
344
|
+
initUser()
|
|
345
|
+
}, {
|
|
346
|
+
immediate: true
|
|
347
|
+
})
|
|
348
|
+
watch(() => departmentFulls.value, (n, o) => {
|
|
349
|
+
initUser()
|
|
350
|
+
}, {
|
|
351
|
+
immediate: true
|
|
352
|
+
})
|
|
353
|
+
</script>
|
|
354
|
+
|
|
355
|
+
<style scoped>
|
|
356
|
+
.user-popup {
|
|
357
|
+
width: 100%;
|
|
358
|
+
overflow: hidden;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.department-selector {
|
|
362
|
+
padding: 10px;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.search-buttons {
|
|
366
|
+
display: flex;
|
|
367
|
+
gap: 10px;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.clear-btn {
|
|
371
|
+
background-color: #f2f3f5;
|
|
372
|
+
border: none;
|
|
373
|
+
border-radius: 4px;
|
|
374
|
+
padding: 4px 8px;
|
|
375
|
+
font-size: 14px;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.confirm-btn {
|
|
379
|
+
background-color: #1989fa;
|
|
380
|
+
color: white;
|
|
381
|
+
border: none;
|
|
382
|
+
border-radius: 4px;
|
|
383
|
+
padding: 4px 8px;
|
|
384
|
+
font-size: 14px;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.no-results {
|
|
388
|
+
text-align: center;
|
|
389
|
+
padding: 20px;
|
|
390
|
+
color: #969799;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.picker-header {
|
|
394
|
+
padding: 10px;
|
|
395
|
+
border-bottom: 1px solid #ebedf0;
|
|
396
|
+
display: flex;
|
|
397
|
+
justify-content: space-between;
|
|
398
|
+
align-items: center;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.current-path {
|
|
402
|
+
flex: 1;
|
|
403
|
+
font-size: 14px;
|
|
404
|
+
color: #323233;
|
|
405
|
+
white-space: nowrap;
|
|
406
|
+
overflow: hidden;
|
|
407
|
+
text-overflow: ellipsis;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.header-right {
|
|
411
|
+
display: flex;
|
|
412
|
+
gap: 8px;
|
|
413
|
+
margin-left: auto;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.cancel-btn {
|
|
417
|
+
color: #969799;
|
|
418
|
+
margin-right: 5px;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.scroll-container {
|
|
422
|
+
height: calc(80vh - 150px);
|
|
423
|
+
overflow-y: auto;
|
|
424
|
+
}
|
|
425
|
+
</style>
|