Commit 1ef65f87 by 周海峰

no message

parent 169a1987
<template>
<div style="padding:1vh;">
<div v-show="getTreeData.length > 0">
<div class="search">
<q-input v-model="searchText" dense outlined @input="onChange" clearable label="搜索(支持两字以上的名字搜索)"/>
<div class="search-container">
<div class="search-wrapper">
<q-input
v-model="searchText"
clearable
class="search-input"
label="搜索(支持两字以上的名字搜索)"
placeholder="输入搜索并选择"
@input="onSearchInput"
@focus="showSearchMenu = true"
@blur="hideSearchMenu"
>
<template v-slot:prepend>
<q-icon name="search" class="search-icon" />
</template>
<template v-slot:append>
<q-btn
flat
dense
round
icon="search"
class="search-btn"
@click="manualSearch"
/>
</template>
</q-input>
<!-- 搜索菜单 -->
<div
v-show="showSearchMenu"
class="search-menu"
>
<div class="search-results" v-if="searchOptions.length > 0">
<div
v-for="option in searchOptions"
:key="option.key"
class="search-result-item"
@click="selectSearchOption(option)"
>
<div class="search-result-avatar">
<q-icon
:name="option.type === 'department' ? 'business' : 'person'"
:color="option.type === 'department' ? 'primary' : 'positive'"
size="sm"
/>
</div>
<div class="search-result-content">
<div class="search-result-label">
{{ option.label }}
</div>
<div class="search-result-caption">
{{ option.type === 'department' ? '部门' : '人员' }}
<span v-if="option.key < 1000000 && option.key" class="search-result-id">
ID: {{ option.key }}
</span>
</div>
</div>
<div class="search-result-side">
<q-icon name="chevron_right" size="sm" color="grey-5" />
</div>
</div>
</div>
<!-- 无搜索结果 -->
<div v-else-if="searchText && searchText.length > 0" class="no-results">
<div class="no-result-item">
<div class="no-result-avatar">
<q-icon name="info" color="grey-5" size="sm" />
</div>
<div class="no-result-content">
<div class="no-results-text">
没有找到匹配的人员或部门
</div>
<div class="no-results-hint">
请尝试其他关键词
</div>
</div>
</div>
</div>
</div>
<div style="padding-top: 5vh">
</div>
</div>
<div style="padding-top: 6vh">
<Tree
v-if="getTreeData.length > 0"
ref="tree"
v-model="selectedKeys"
checkable
:checkedKeys="checkedKeys"
@expand="onExpand"
:expandedKeys="expandedKeys"
:autoExpandParent="autoExpandParent"
:treeData="getTreeData"
:defaultExpandedKeys="defaultExpandedKeys"
@select="onSelect"
@check="onCheck"
@select="onSelect"
>
<template slot="title" slot-scope="node">
<span style="color: rgb(33, 186, 69);" v-if="getselectKeysStr.indexOf(node.key)>-1" ></span>
<span style="color: rgb(33, 186, 69);" v-if="checkedKeys.indexOf(node.key)>-1" ></span>
<span v-if="node.title.indexOf(searchValue) > -1">
{{node.title.substr(0, node.title.indexOf(searchValue))}}
<span style="color: #f50">{{searchValue }}</span>
......@@ -96,18 +176,17 @@
expandedKeys: [1000001],
defaultExpandedKeys: [1000001],
autoExpandParent: true,
// v-model
// 复选模式下的选中key(包含部门和人员)
checkedKeys: [],
// 选择的key
selectedKeys: [],
selectedParent: [],
// 准备提交的选中的部门
checkedDepartment: [],
// 准备提交的选中的人类对象
checkedPeople: [],
checkNodes: [],
// 选中部门下的所有人
checkedPeopleWithDepartment: []
// 节点缓存,用于快速查找
nodeCache: null,
// 搜索相关
searchOptions: [],
showSearchMenu: false
};
},
props: {
......@@ -127,8 +206,7 @@
return this.treeData;
},
getselectKeysStr(){
// alert(this.selectedKeys.map(item=>item).join(","))
return this.selectedKeys.map(item=>item).join(",")
return this.checkedKeys.map(item=>item).join(",")
}
},
created() {
......@@ -143,6 +221,10 @@
let resdata = Res.data.data;
this.treeData.push(resdata)
generateList(this.treeData);
// 构建节点缓存
this.nodeCache = new Map();
this.buildNodeCache(this.treeData);
},
onExpand(expandedKeys) {
// console.log('onExpand', expandedKeys)
......@@ -151,87 +233,194 @@
this.expandedKeys = expandedKeys
this.autoExpandParent = false
},
onCheck(checkedKeys) {
// console.log('onCheck', checkedKeys)
// this.checkedKeys = checkedKeys
onCheck(checkedKeys, e) {
console.log('onCheck', checkedKeys, e);
let finalCheckedKeys = [...checkedKeys];
// 当勾选父节点时,自动勾选所有叶子节点
if (e.checked && e.node.children && e.node.children.length > 0) {
const leafKeys = this.getAllLeafKeys(e.node.children);
finalCheckedKeys = [...new Set([...finalCheckedKeys, ...leafKeys])];
}
// 当取消勾选父节点时,取消勾选所有叶子节点
if (!e.checked && e.node.children && e.node.children.length > 0) {
const leafKeys = this.getAllLeafKeys(e.node.children);
finalCheckedKeys = finalCheckedKeys.filter(key => !leafKeys.includes(key));
}
// 只在最终结果不同时才更新,避免不必要的触发
if (JSON.stringify(finalCheckedKeys) !== JSON.stringify(this.checkedKeys)) {
this.checkedKeys = finalCheckedKeys;
// 更新选中的人员列表
this.updateSelectedStaff();
}
},
onSelect(selectedKeys, info) {
if(selectedKeys.length==0 || selectedKeys[0]>1000000){return}
let index = this.selectedKeys.indexOf(selectedKeys[0])
if(index == -1 && selectedKeys[0]<1000000){
this.selectedKeys.push(selectedKeys[0]);
// 在复选模式下,onSelect 可以用来处理一些辅助功能,比如展开/折叠
// console.log('onSelect', selectedKeys, info);
},
// 搜索输入处理
onSearchInput(value) {
this.searchValue = value || '';
if (value && value.length > 0) {
// 过滤搜索选项
this.searchOptions = dataList
.filter(item => item.title == value || item.no == value)
.map(item => ({
key: item.key,
label: item.title,
displayLabel: `${item.title}${item.no ? ' (' + item.no + ')' : ''}${item.key >= 1000000 ? ' [部门]' : ' [人员]'}`,
type: item.key >= 1000000 ? 'department' : 'person'
}))
.slice(0, 10); // 限制最多显示10个结果
this.showSearchMenu = true;
} else {
this.searchOptions = [];
this.showSearchMenu = false;
}
// console.log(this.selectedKeys)
// 调用原有的搜索高亮逻辑
// this.onChange(value);
},
onChange(e) {
if (!e) {
// 手动搜索
manualSearch() {
if (this.searchText && this.searchText.length > 0) {
this.onSearchInput(this.searchText);
}
},
// 隐藏搜索菜单
hideSearchMenu() {
// 延迟隐藏,以便点击选项时能正常触发
setTimeout(() => {
this.showSearchMenu = false;
}, 500);
},
// 选择搜索选项
selectSearchOption(option) {
console.log(option,'selectSearchOption=======')
// 勾选选中的节点
if (!this.checkedKeys.includes(option.key)) {
this.checkedKeys = [...this.checkedKeys, option.key];
this.updateSelectedStaff();
}
// 展开到选中节点的路径
const parentKeys = this.getParentKeys(option.key, this.treeData);
this.expandedKeys = [...new Set([...this.expandedKeys, ...parentKeys])];
this.autoExpandParent = false;
// 清空搜索状态
this.searchText = '';
this.searchOptions = [];
this.showSearchMenu = false;
},
// 获取节点到根节点的路径
getParentKeys(key, treeData, parents = []) {
for (let node of treeData) {
if (node.key === key) {
return parents;
}
if (node.children) {
const found = this.getParentKeys(key, node.children, [...parents, node.key]);
if (found.length > parents.length) {
return found;
}
}
}
return parents;
},
// 原有的搜索方法,现在主要用于高亮显示
onChange(value) {
if (!value) {
this.searchValue = '';
return;
}
const value = e;
// const value = e.target.value.toString();
console.log('正在搜索...' + value);
console.log('查找中'.value)
this.searchValue = value;
// 展开包含搜索结果的节点
const expandedKeys = dataList.map((item) => {
// console.log("item",item)
if (item.title.indexOf(value) > -1) {
return getParentKey(item.key, this.getTreeData)
}
return null
}).filter((item, i, self) => item && self.indexOf(item) === i)
Object.assign(this, {
expandedKeys,
searchValue: value,
autoExpandParent: true,
})
},
readyToDeepin() {
this.checkedDepartment = [];
this.checkedPeople = [];
this.checkedPeopleWithDepartment = []
},
// 一些递归方法
deepinTreeData(tree) {
tree.forEach(node => {
// 判断是否选中当前节点
if (this.selectedKeys.indexOf(node.key) > -1) {
// 判断是否是部门,是部门
if (node.key > 1000000) {
this.checkedDepartment.push(node);
} else {
// 是人
this.checkedPeople.push(node);
if (expandedKeys.length > 0) {
this.expandedKeys = [...new Set([...this.expandedKeys, ...expandedKeys])];
this.autoExpandParent = true;
}
},
// 获取节点下的所有叶子节点key
getAllLeafKeys(nodes) {
let leafKeys = [];
nodes.forEach(node => {
if (node.children && node.children.length > 0) {
// 如果有子节点,递归获取
leafKeys = leafKeys.concat(this.getAllLeafKeys(node.children));
} else {
// 未选中当前节点
if (node.children) {
// 执行递归
this.deepinTreeData(node.children);
// 如果是叶子节点(人员),添加到结果中
if (node.key < 1000000) { // 小于1000000的是人员
leafKeys.push(node.key);
}
}
});
return leafKeys;
},
// 递归部门所有人
deepinChildren(tree) {
tree.forEach(node => {
// 判断是否是人
if (node.key > 1000000) {
// 是部门
if (node.children) {
this.deepinChildren(node.children);
// 更新选中的人员列表
updateSelectedStaff() {
this.checkedPeople = [];
this.checkedDepartment = [];
// 使用 Map 缓存节点查找结果,避免重复遍历
if (!this.nodeCache) {
this.nodeCache = new Map();
this.buildNodeCache(this.treeData);
}
// 遍历所有选中的key,区分人员和部门
this.checkedKeys.forEach(key => {
const node = this.nodeCache.get(key);
if (node) {
if (key < 1000000) { // 人员
this.checkedPeople.push(node);
} else { // 部门
this.checkedDepartment.push(node);
}
} else {
// 是人
this.checkedPeopleWithDepartment.push(node);
}
});
// 向父组件发送选中的人员列表
this.$emit('staffNode', this.checkedPeople);
},
// 构建节点缓存,避免每次查找都遍历树
buildNodeCache(nodes) {
nodes.forEach(node => {
this.nodeCache.set(node.key, node);
if (node.children && node.children.length > 0) {
this.buildNodeCache(node.children);
}
});
},
orderStaffNode(){
//对选择的人根据选择顺序排序
let staffNode = []
for(var i=0;i<this.selectedKeys.length;i++){
let key = this.selectedKeys[i]
for(var i=0;i<this.checkedKeys.length;i++){
let key = this.checkedKeys[i]
if(key<1000000){//小于百万的为人员。大于为机构
let node =this.checkedPeople.filter(item =>item.key==key)[0]
if(node){
......@@ -243,37 +432,32 @@
}
},
watch: {
selectedKeys(val) {
// 深度遍历组织机构树
this.readyToDeepin();//清空check的部门和人员
this.deepinTreeData(this.treeData);
// 执行递归后,向父组件附件筛选出的数据
this.$emit('staffNode', this.checkedPeople);
// this.$emit('departmentNode', this.checkedDepartment);
checkedKeys: {
handler(val) {
// 当 checkedKeys 改变时,同步更新 value,但避免循环
const filteredKeys = val.filter(key => key < 1000000); // 只传递人员key给父组件
// console.log("你到底提交了吗", this)
// this.deepinChildren(this.checkedDepartment);
// this.$emit('checkedPeopleWithDepartment', this.checkedPeopleWithDepartment);
// this.value = this.checkedKeys;
// console.log("你到底提交了吗?????")
// this.$emit('input', val);
// 只有当过滤后的key与当前value不同时才emit,避免循环
if (JSON.stringify(filteredKeys) !== JSON.stringify(this.value || [])) {
this.$emit('input', filteredKeys);
}
},
deep: true
},
// checkedKeys(val){
// this.readyToDeepin();//清空check的部门和人员
// this.deepinTreeData(this.treeData);//先把选择的节点,分为人员checkedPeople和部门checkedDepartment
// this.deepinChildren(this.checkedDepartment);//遍历部门,获取部门下的人checkedPeopleWithDepartment
// this.$emit('checkStaffNode', this.checkedPeople.concat(this.checkedPeopleWithDepartment));
//单纯的提交选择的key给父组件
// let keys = val.filter(item=>item<1000000)
// this.$emit('checkStaffKey', keys);
// },
value(newValue) {
// console.log('this.value', newValue);
this.selectedKeys = newValue
// this.checkedKeys = newValue
value: {
handler(newValue) {
// 只有当树已经加载完成且新值与当前checkedKeys中的人员key不同时才更新,避免循环
if (this.treeData.length > 0) {
const currentStaffKeys = this.checkedKeys.filter(key => key < 1000000);
if (JSON.stringify(newValue || []) !== JSON.stringify(currentStaffKeys)) {
this.checkedKeys = newValue || [];
if (this.checkedKeys.length > 0) {
this.updateSelectedStaff();
}
}
}
},
immediate: true
}
},
}
......@@ -282,12 +466,222 @@
</script>
<style scoped>
.search{
border: 1px solid rgb(208, 208, 220);
position: fixed;width: 16vw;
background-color: #fff;
.search-container {
position: fixed;
width: 17vw;
z-index: 9999;
padding-left: 1vh;
padding-top: 1vh;
padding: 1vh;
}
.search-wrapper {
position: relative;
}
.search-input {
border-radius: 4px !important;
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
/* box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); */
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.search-input:hover {
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
transform: translateY(-1px);
}
.search-input:focus-within {
box-shadow: 0 8px 25px rgba(33, 150, 243, 0.15);
transform: translateY(-2px);
}
.search-input >>> .q-field__control {
border-radius: 12px;
padding: 8px 16px;
}
.search-input >>> .q-field__label {
font-weight: 500;
color: #666;
font-size: 12px;
}
.search-input >>> .q-field__native {
font-size: 5px;
font-weight: 200;
}
.search-icon {
color: #999;
transition: color 0.3s ease;
}
.search-input:hover .search-icon {
color: #1976d2;
}
.search-btn {
margin-right: 4px;
transition: all 0.3s ease;
}
.search-btn:hover {
background-color: rgba(25, 118, 210, 0.1);
transform: scale(1.05);
}
.search-menu {
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
border: 1px solid rgba(255, 255, 255, 0.2);
backdrop-filter: blur(8px);
background: rgba(255, 255, 255, 0.95);
min-width: 16vw;
max-width: 16vw;
max-height: 300px;
overflow-y: auto;
position: absolute;
top: 100%;
left: 0;
right: 0;
z-index: 1000;
margin-top: 8px;
}
.search-results {
padding: 8px 0;
}
.search-result-item {
display: flex;
align-items: center;
padding: 12px 16px;
border-radius: 8px;
margin: 2px 8px;
cursor: pointer;
transition: all 0.2s ease;
border: 1px solid transparent;
}
.search-result-item:hover {
background-color: rgba(25, 118, 210, 0.08);
transform: translateX(2px);
border-color: rgba(25, 118, 210, 0.1);
}
.search-result-avatar {
flex-shrink: 0;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.04);
margin-right: 12px;
}
.search-result-content {
flex: 1;
min-width: 0;
}
.search-result-side {
flex-shrink: 0;
margin-left: 8px;
opacity: 0.6;
}
.search-result-label {
font-weight: 500;
color: #333;
font-size: 14px;
}
.search-result-caption {
font-size: 12px;
color: #666;
display: flex;
align-items: center;
gap: 8px;
}
.search-result-id {
background-color: rgba(102, 102, 102, 0.1);
padding: 2px 6px;
border-radius: 4px;
font-size: 11px;
font-weight: 500;
}
.no-results {
padding: 16px;
text-align: center;
}
.no-result-item {
display: flex;
align-items: center;
padding: 12px 16px;
justify-content: center;
}
.no-result-avatar {
flex-shrink: 0;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.04);
margin-right: 12px;
}
.no-result-content {
flex: 1;
text-align: left;
}
.no-results-text {
color: #666;
font-weight: 500;
font-size: 14px;
}
.no-results-hint {
color: #999;
font-size: 12px;
margin-top: 4px;
}
/* 滚动条美化 */
.search-menu::-webkit-scrollbar {
width: 6px;
}
.search-menu::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.05);
border-radius: 3px;
}
.search-menu::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.2);
border-radius: 3px;
}
.search-menu::-webkit-scrollbar-thumb:hover {
background: rgba(0, 0, 0, 0.3);
}
/* 响应式设计 */
@media (max-width: 768px) {
.search-container {
width: 90vw;
}
.search-menu {
min-width: 90vw;
max-width: 90vw;
}
}
</style>
......@@ -2,7 +2,6 @@
<div style="padding:10px">
<q-card inline class="fit shadow-6">
<div>
<!-- <a-button type="primary">hello world</a-button> -->
</div>
<q-card-main style="height:80%">
<q-table ref="table" color="primary" :data="serverData" :columns="columns" separator="cell" selection="none" row-key="id" :rows-per-page-options="[5,10,20,30,40,50,60,200,500]" :pagination.sync="serverPagination"
......@@ -36,24 +35,6 @@
<q-btn round color="red" @click="modifyRoleUser">{{$t('Save')}}</q-btn>
<q-btn round @click="closeEditModal">{{$t('Cancel')}}</q-btn>
</q-toolbar>
<!-- <div style="padding:20px">
<q-table ref="usertable" color="primary" :data="roleUser.serverData" :columns="roleUser.columns" separator="cell" row-key="id" :pagination.sync="roleUser.serverPagination"
@request="roleUserRequest" :loading="roleUser.loading" :rows-per-page-label="$t('Rows per page')" :no-data-label="$t('No data')">
<template slot="top-left" slot-scope="props">
<q-input v-model="roleUser.filter.account" type="text" :prefix="$t('Account') + ':'" />&nbsp;&nbsp;
<q-input v-model="roleUser.filter.username" type="text" :prefix="$t('Name') + ':'" />&nbsp;&nbsp;
<q-btn push dense color="primary" icon="search" @click="searchRoleUser">{{$t('Search')}}</q-btn>&nbsp;&nbsp;
</template>
<template slot="top-right" slot-scope="props">
<q-btn flat round dense :icon="props.inFullscreen ? 'fullscreen_exit' : 'fullscreen'" @click="props.toggleFullscreen" />
</template>
<q-td slot="body-cell-id" slot-scope="props" :props="props" style="width:100px">
<q-btn v-if="props.row.isAdd==2" glossy color="secondary" label='新增' @click="modifyRoleUser(props.value,1)"></q-btn>
<q-btn v-if="props.row.isAdd==1" glossy color="secondary" label='删除' @click="modifyRoleUser(props.value,0)"></q-btn>
</q-td>
</q-table>
</div>-->
<!-- 选择个人 -->
<div class=" row q-mb-lg q-ml-lg q-mr-lg" >
<div class="col-2 q-ml-lg" style="height:80vh;overflow:auto;">
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论