clientList.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  1. <template>
  2. <div class="list-wrap">
  3. <div class="list-content" v-if="groupList && groupList.length">
  4. <el-collapse v-model="activeNames" expand-icon-position="left">
  5. <el-collapse-item
  6. title="Consistency"
  7. v-for="(group, groupIndex) in list"
  8. :key="groupIndex"
  9. :name="groupIndex"
  10. >
  11. <template #title="{ isActive }">
  12. <div :class="['title-wrapper', { 'is-active': isActive }]">
  13. <div class="title-l">
  14. <el-icon class="arrow-icon"><CaretTop /></el-icon>
  15. {{ group.name }}
  16. <span class="group-length">({{ group.list.length }})</span>
  17. </div>
  18. <div class="title-r" @click.stop="handleRightClick">
  19. <span class="title-manage" @click.stop="hadnleManage(group,groupIndex)">{{isManage?'管理':'取消管理'}}</span>
  20. <el-checkbox
  21. v-show="toSelectClient"
  22. v-model="groupCheckStates[groupIndex].checkAll"
  23. :indeterminate="groupCheckStates[groupIndex].isIndeterminate"
  24. @change="(val) => handleCheckAllChange(val, groupIndex)"
  25. >
  26. 全选
  27. </el-checkbox>
  28. </div>
  29. </div>
  30. </template>
  31. <template #icon> </template>
  32. <!-- 遍历当前分组中的每一项 -->
  33. <div v-show="!group.list.length" class="no-data">暂无数据</div>
  34. <div v-show="group.list.length">
  35. <el-checkbox-group v-model="checkedCities" @change="handleCheckedCitiesChange">
  36. <div v-for="(ele, index) in group.list" :key="index" class="list-item">
  37. <el-checkbox v-show="toSelectClient" label="" :value="ele.tel"> </el-checkbox>
  38. <el-checkbox class="checkbox" v-show="setingShow" @change="changeCheck($event,ele)" label="" :value="ele.energy"></el-checkbox>
  39. <div class="item-flex">
  40. <img
  41. class="photo"
  42. :src="
  43. ele.icon || 'https://birdseye-img.sysuimars.com/dinggou-mini/defalut-icon.png'
  44. "
  45. alt=""
  46. />
  47. <div class="item-text">
  48. <div class="name">
  49. <span>{{ ele.name }}</span>
  50. <el-icon
  51. class="icon"
  52. @click.stop="handlePerson('edit', ele)"
  53. color="#2199F8"
  54. size="16"
  55. ><Edit
  56. /></el-icon>
  57. </div>
  58. <div><span class="item-title">电话:</span>{{ ele.tel }}</div>
  59. <div><span class="item-title">地址:</span>{{ ele.address || "--" }}</div>
  60. </div>
  61. <!-- <div class="blue-btn" @click="toCustomOneTree(ele)">去分配</div> -->
  62. </div>
  63. </div>
  64. </el-checkbox-group>
  65. </div>
  66. </el-collapse-item>
  67. </el-collapse>
  68. </div>
  69. <div class="no-data" v-show="!groupList || !groupList.length">暂无数据</div>
  70. <div class="list-footer">
  71. <!-- <div class="settings" v-show="!isSetting && isManage" @click="handleGroup">新增分组</div> -->
  72. <div class="settings" v-show="!isSetting && !isManage" @click="handleSetting">批量设置</div>
  73. <div class="operation flex" v-show="isSetting && !setingShow">
  74. <div class="delete" @click="handleDelete"><el-button size="large" type="primary">删除分组</el-button></div>
  75. <div class="btn-group flex">
  76. <div class="add" @click="handleAdd">添加</div>
  77. <div class="add" @click="handleRemove">移除</div>
  78. </div>
  79. </div>
  80. <div class="controls flex" v-show="isSetting && setingShow">
  81. <div class="personnel flex">
  82. <div class="circle flex" v-show="filterList.length"><el-icon color="#000" size="11"><ArrowUpBold /></el-icon></div>
  83. <div class="text van-ellipsis">
  84. <template v-for="(item,index) in filterList" :key="index">
  85. {{item.name}}<span v-show="filterList.length - 1 !== index">/</span>
  86. </template>
  87. </div>
  88. </div>
  89. <div class="delete" @click="handleGroup" v-show="listType==='other'">设置分组</div>
  90. <div class="delete" @click="handleOperation" v-show="listType!=='other'">{{operationType==='add'?'添加':'移出'}}({{filterList.length}})</div>
  91. </div>
  92. </div>
  93. <!-- 渐变主色按钮 -->
  94. <div class="center-btn" v-show="!toSelectClient && !isSetting" @click="toCustomPage">一键分配</div>
  95. <div class="global-wrap" v-show="toSelectClient">
  96. <div class="global-btn" @click="handleGlobalCheckAllChange">全选</div>
  97. </div>
  98. </div>
  99. <!-- 新增客户、编辑客户 -->
  100. <edit-client-popup ref="editClientRef" @updateGroupList="getUserList"></edit-client-popup>
  101. </template>
  102. <script setup>
  103. import { ref, reactive, onMounted, watch } from "vue";
  104. import { Collapse, CollapseItem, Checkbox, Popup } from "vant";
  105. import EditClientPopup from "@/components/editClientPopup.vue";
  106. import { useRouter } from "vue-router";
  107. const router = useRouter();
  108. import { useStore } from "vuex";
  109. import { deepClone } from "@/common/commonFun";
  110. import { ElMessage } from "element-plus";
  111. const store = useStore();
  112. const currentFarmId = store.getters.userinfo.curFarmId
  113. const props = defineProps({
  114. // 是否显示选择客户
  115. checkDistributeShow: {
  116. type: Boolean,
  117. default: false,
  118. },
  119. // 是否开始重新加载列表
  120. startReloadList: {
  121. type: Boolean,
  122. default: false,
  123. },
  124. });
  125. // const curIndex = ref(0)
  126. const handlePerson = (type, data) => {
  127. editClientRef.value.openClientPopup(type, data);
  128. };
  129. const editClientRef = ref(null);
  130. const activeNames = ref([0]);
  131. const groupList = ref([]);
  132. const toSelectClient = ref(false);
  133. function toCustomPage() {
  134. toSelectClient.value = true;
  135. // toSelectClient.value = !toSelectClient.value;
  136. // eventBus.emit("startBoxSelect", toSelectClient.value);
  137. emit("update:checkDistributeShow", toSelectClient.value);
  138. // router.push("/layout/customTree");
  139. }
  140. const handleGlobalCheckAllChange = (val) => {
  141. if (val) {
  142. // 全选所有项目
  143. checkedCities.value = groupList.value.flatMap((group) => group.list.map((item) => item.tel));
  144. } else {
  145. // 清空所有选择
  146. checkedCities.value = [];
  147. }
  148. // 更新所有分组的状态
  149. groupList.value.forEach((_, index) => {
  150. updateGroupCheckStatus(index);
  151. });
  152. };
  153. onMounted(() => {
  154. getUserList();
  155. });
  156. function getUserList() {
  157. VE_API.manage_interface.fetchGroupList({ farmId: currentFarmId, isAllot: 0 }).then(({ data, extData }) => {
  158. // defalutList.value = data
  159. groupList.value = Object.keys(data)
  160. .filter(key => data[key] && data[key].length > 0) // 先过滤掉空的 key
  161. .map(key => ({
  162. name: key,
  163. list: data[key]
  164. }));
  165. list.value = groupList.value
  166. emit("update:listCount", extData?.count)
  167. initGroupCheckStates(); // 初始化分组选择状态
  168. });
  169. }
  170. const checkedCities = ref([]);
  171. // 使用一个对象来存储每个分组的全选状态
  172. const groupCheckStates = ref({});
  173. // 初始化分组选择状态
  174. const initGroupCheckStates = () => {
  175. groupList.value.forEach((group, index) => {
  176. groupCheckStates.value[index] = {
  177. checkAll: false,
  178. isIndeterminate: false,
  179. };
  180. });
  181. };
  182. // 修改后的全选处理方法
  183. const handleCheckAllChange = (val, groupIndex) => {
  184. const currentGroup = groupList.value[groupIndex];
  185. if (val) {
  186. // 添加当前分组的所有项到选中列表
  187. currentGroup.list.forEach((item) => {
  188. if (!checkedCities.value.includes(item.tel)) {
  189. checkedCities.value.push(item.tel);
  190. }
  191. });
  192. } else {
  193. // 移除当前分组的所有项
  194. checkedCities.value = checkedCities.value.filter(
  195. (name) => !currentGroup.list.some((item) => item.tel === name)
  196. );
  197. }
  198. updateGroupCheckStatus(groupIndex);
  199. };
  200. // 更新分组的选择状态
  201. const updateGroupCheckStatus = (groupIndex) => {
  202. const currentGroup = groupList.value[groupIndex];
  203. const checkedCount = currentGroup.list.filter((item) => checkedCities.value.includes(item.tel)).length;
  204. groupCheckStates.value[groupIndex].checkAll = checkedCount === currentGroup.list.length;
  205. groupCheckStates.value[groupIndex].isIndeterminate = checkedCount > 0 && checkedCount < currentGroup.list.length;
  206. emit("update:checkData", checkedCities.value);
  207. };
  208. // 修改 handleCheckedCitiesChange 方法
  209. const handleCheckedCitiesChange = (value) => {
  210. if (setingShow.value) return
  211. // 遍历所有分组,更新每个分组的选中状态
  212. groupList.value.forEach((group, groupIndex) => {
  213. updateGroupCheckStatus(groupIndex);
  214. });
  215. list.value = groupList.value
  216. };
  217. function handleRightClick() {
  218. // 点击事件处理
  219. }
  220. const isSetting = ref(false)
  221. const list = ref([])
  222. const listType = ref('')
  223. const isManage = ref(true)
  224. //设置
  225. const handleSetting = () =>{
  226. isSetting.value = true
  227. if(listType.value==='other'){
  228. setingShow.value = true
  229. }
  230. }
  231. // 管理
  232. const hadnleManage = (value,index) => {
  233. // console.log('val',value);
  234. listType.value = 'often'
  235. isSetting.value = true
  236. if(value.name==='未分组列表'){
  237. listType.value = 'other'
  238. isSetting.value = false
  239. }
  240. // curIndex.value = index
  241. isManage.value = !isManage.value
  242. if(!isManage.value){
  243. list.value = deepClone([groupList.value[index]])
  244. }else{
  245. resetList()
  246. }
  247. };
  248. const resetList = () =>{
  249. list.value = deepClone(groupList.value)
  250. setingShow.value = false
  251. isSetting.value = false
  252. isManage.value = true
  253. filterList.value = []
  254. }
  255. const operationType = ref('')
  256. //添加
  257. const handleAdd = () =>{
  258. operationType.value = 'add'
  259. setingShow.value = true
  260. }
  261. //移除
  262. const setingShow = ref(false)
  263. const filterList = ref([]);
  264. const changeCheck = (v, d) =>{
  265. if (v) {
  266. filterList.value.push(d)
  267. } else {
  268. filterList.value.pop()
  269. }
  270. console.log('list.value', d, v);
  271. // filterList.value = list.value[0].list.filter((item) => item.energy);
  272. }
  273. const handleRemove = () =>{
  274. operationType.value = 'remove'
  275. setingShow.value = true
  276. }
  277. //移出、添加
  278. const handleOperation = () =>{
  279. if(filterList.value.length<1) return ElMessage.warning('请选择用户')
  280. if(operationType.value === 'add'){
  281. ElMessage.success('添加成功')
  282. }else{
  283. ElMessage.success('移出成功')
  284. }
  285. resetList()
  286. }
  287. function handleDelete() {
  288. }
  289. watch(
  290. () => props.checkDistributeShow,
  291. (newVal) => {
  292. if (newVal === false) {
  293. toSelectClient.value = false; // 如果显示选择客户,则切换到选择客户模式
  294. // 清空所有选择
  295. checkedCities.value = [];
  296. initGroupCheckStates(); // 初始化分组选择状态
  297. }
  298. }
  299. );
  300. watch(
  301. () => props.startReloadList,
  302. (newVal) => {
  303. getUserList(); // 重新加载用户列表
  304. }
  305. );
  306. const emit = defineEmits(["update:checkDistributeShow", "update:checkData", "update:listCount"]);
  307. </script>
  308. <style lang="scss" scoped>
  309. .list-wrap {
  310. width: 100%;
  311. height: 100%;
  312. position: relative;
  313. .list-header {
  314. display: flex;
  315. align-items: center;
  316. justify-content: space-between;
  317. .search {
  318. width: 90px;
  319. margin-right: 10px;
  320. --el-input-placeholder-color: rgba(255, 212, 137, 0.6);
  321. ::v-deep {
  322. .el-input__wrapper {
  323. box-shadow: none;
  324. border: 1px solid rgba(255, 212, 137, 0.6);
  325. background: transparent;
  326. }
  327. .el-input__prefix,
  328. .el-input__inner {
  329. color: rgba(255, 212, 137, 0.6);
  330. }
  331. }
  332. }
  333. .button {
  334. width: calc(100% - 100px);
  335. color: #ffd489;
  336. background: rgba(255, 212, 137, 0.06);
  337. border: 1px solid rgba(255, 212, 137, 0.3);
  338. display: flex;
  339. align-items: center;
  340. justify-content: center;
  341. padding: 8px;
  342. border-radius: 5px;
  343. cursor: pointer;
  344. img {
  345. width: 20px;
  346. height: 17px;
  347. margin-right: 6px;
  348. }
  349. }
  350. }
  351. .no-data {
  352. text-align: center;
  353. color: rgba(0, 0, 0, 0.6);
  354. }
  355. .list-content {
  356. width: 100%;
  357. height: calc(100% - 56px);
  358. overflow: auto;
  359. padding: 0 8px;
  360. box-sizing: border-box;
  361. &.max-height {
  362. height: calc(100% - 50px - 55px);
  363. }
  364. .text {
  365. color: #ffd489;
  366. cursor: pointer;
  367. }
  368. ::v-deep {
  369. .el-collapse-item__arrow {
  370. display: none;
  371. }
  372. .el-collapse-item__header {
  373. &.is-active {
  374. .arrow-icon {
  375. transform: rotate(180deg);
  376. }
  377. }
  378. }
  379. .el-collapse {
  380. border-color: transparent;
  381. }
  382. .van-cell:after,
  383. .van-collapse-item--border:after,
  384. .van-hairline--top-bottom:after {
  385. border: none !important;
  386. }
  387. .van-cell {
  388. border-radius: 5px 5px 0 0;
  389. justify-content: space-between;
  390. background: rgba(255, 255, 255, 0.08);
  391. color: #fff;
  392. .van-cell__value {
  393. flex: none;
  394. }
  395. .van-cell__title {
  396. display: flex;
  397. align-items: center;
  398. .icon {
  399. margin-right: 3px;
  400. color: #bfbfbf;
  401. font-size: 16px;
  402. }
  403. .span {
  404. color: rgba(255, 255, 255, 0.4);
  405. margin-left: 10px;
  406. }
  407. }
  408. }
  409. .van-collapse-item__content {
  410. background: transparent;
  411. padding: 10px 0 0 0;
  412. }
  413. .van-collapse-item__title--expanded {
  414. .van-cell__title {
  415. .icon {
  416. transform: rotate(90deg);
  417. }
  418. }
  419. }
  420. .van-collapse-item + .van-collapse-item {
  421. margin-top: 12px;
  422. }
  423. .van-checkbox__icon--checked .van-icon {
  424. background-color: #f7be5a;
  425. border-color: #f7be5a;
  426. color: #000;
  427. }
  428. }
  429. .title-wrapper {
  430. width: 100%;
  431. display: flex;
  432. align-items: center;
  433. justify-content: space-between;
  434. .arrow-icon {
  435. transition: transform 0.3s ease;
  436. }
  437. &.is-active {
  438. .arrow-icon {
  439. transform: rotate(90deg);
  440. }
  441. }
  442. .title-r {
  443. color: #2199f8;
  444. font-size: 14px;
  445. display: flex;
  446. align-items: center;
  447. .title-manage {
  448. margin-right: 10px;
  449. }
  450. ::v-deep {
  451. .el-checkbox {
  452. color: #2199f8;
  453. }
  454. }
  455. }
  456. }
  457. .group-length {
  458. color: rgba(0, 0, 0, 0.4);
  459. font-size: 12px;
  460. }
  461. .list-item {
  462. border-radius: 6px;
  463. position: relative;
  464. display: flex;
  465. align-items: center;
  466. background: rgba(120, 120, 120, 0.05);
  467. border: 1px solid transparent;
  468. padding: 8px 10px;
  469. &.active {
  470. background: rgba(243, 193, 29, 0.1);
  471. border-color: #ffd489;
  472. }
  473. .checkbox {
  474. margin-right: 12px;
  475. }
  476. .item-flex {
  477. display: flex;
  478. align-items: center;
  479. justify-content: space-between;
  480. width: 100%;
  481. .blue-btn {
  482. cursor: pointer;
  483. color: #ffffff;
  484. font-size: 12px;
  485. padding: 5px 15px;
  486. border-radius: 20px;
  487. background: #2199f8;
  488. }
  489. }
  490. .photo {
  491. width: 63px;
  492. height: 63px;
  493. border-radius: 8px;
  494. margin-right: 12px;
  495. }
  496. .item-text {
  497. color: #999999;
  498. font-size: 12px;
  499. line-height: 1.6;
  500. flex: 1;
  501. .name {
  502. display: flex;
  503. align-items: center;
  504. span {
  505. font-size: 14px;
  506. color: #000000;
  507. font-weight: 500;
  508. margin-right: 5px;
  509. }
  510. .icon {
  511. cursor: pointer;
  512. }
  513. }
  514. .item-title {
  515. color: #666666;
  516. }
  517. }
  518. }
  519. .list-item + .list-item {
  520. margin-top: 12px;
  521. }
  522. }
  523. .center-btn {
  524. position: absolute;
  525. bottom: 9px;
  526. left: 50%;
  527. transform: translateX(-50%);
  528. color: #ffffff;
  529. border-radius: 20px;
  530. font-size: 14px;
  531. padding: 7px 10px;
  532. cursor: pointer;
  533. border: 1px solid #fff;
  534. background: linear-gradient(180deg, #84c9ff, #2199f8);
  535. width: 194px;
  536. box-sizing: border-box;
  537. text-align: center;
  538. }
  539. .global-wrap {
  540. width: 100%;
  541. padding-top: 17px;
  542. box-shadow: 4px 0 4px rgba(0, 0, 0, 0.2);
  543. padding-top: 12px;
  544. box-shadow: 4px 0 4px rgba(0, 0, 0, 0.2);
  545. text-align: center;
  546. margin: 0 auto;
  547. padding-bottom: 11px;
  548. display: flex;
  549. align-items: center;
  550. justify-content: center;
  551. .global-btn {
  552. height: 38px;
  553. width: 168px;
  554. display: flex;
  555. align-items: center;
  556. justify-content: center;
  557. background: #2199f8;
  558. color: #fff;
  559. border-radius: 4px;
  560. }
  561. }
  562. .list-footer {
  563. position: absolute;
  564. bottom: 0;
  565. left: 0;
  566. width: 100%;
  567. background: rgba(255, 255, 255, 0.08);
  568. display: flex;
  569. justify-content: center;
  570. box-sizing: border-box;
  571. padding: 11px;
  572. .settings {
  573. background: rgba(255, 255, 255, 0.1);
  574. border-radius: 4px;
  575. padding: 8px 36px;
  576. color: #fff;
  577. cursor: pointer;
  578. }
  579. .flex {
  580. display: flex;
  581. align-items: center;
  582. }
  583. .delete {
  584. // border: 1px solid #ffd489;
  585. // border-radius: 4px;
  586. // padding: 8px 19px;
  587. // color: #ffd489;
  588. // cursor: pointer;
  589. }
  590. .operation {
  591. width: 100%;
  592. justify-content: space-between;
  593. .btn-group {
  594. div {
  595. background: rgba(255, 255, 255, 0.1);
  596. padding: 8px 19px;
  597. border-radius: 4px;
  598. cursor: pointer;
  599. }
  600. .add {
  601. border: 1px solid #2199f8;
  602. margin-right: 10px;
  603. }
  604. }
  605. }
  606. .controls {
  607. width: 100%;
  608. justify-content: space-between;
  609. .personnel {
  610. width: calc(100% - 100px);
  611. .circle {
  612. width: 16px;
  613. height: 16px;
  614. background: #ffd489;
  615. border-radius: 50%;
  616. justify-content: center;
  617. margin-right: 8px;
  618. }
  619. .text {
  620. width: 90%;
  621. }
  622. }
  623. .delete {
  624. background: #ffd489;
  625. color: #000;
  626. width: 85px;
  627. box-sizing: border-box;
  628. text-align: center;
  629. padding: 8px;
  630. }
  631. }
  632. }
  633. }
  634. </style>