| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641 |
- <template>
- <u-popup
- :show="visible"
- mode="bottom"
- :round="10"
- :closeOnClickOverlay="false"
- @close="close"
- class="select-user-popup"
- >
- <view class="popup-content">
- <!-- 头部 -->
- <view class="popup-header">
- <text class="popup-title">选择人员</text>
- <view class="close-btn" @click="close">×</view>
- </view>
- <!-- 搜索行 -->
- <view class="search-row">
- <input
- v-model="searchVal"
- placeholder="请输入名称/工号/账号"
- class="search-input"
- @confirm="doSearch"
- />
- <view class="search-btn" @click="doSearch">
- <text>🔍</text>
- <text>搜索</text>
- </view>
- </view>
- <!-- 部门选择行 -->
- <view class="dept-row">
- <view class="dept-btn" @click="showTreePicker">
- <text>🏢</text>
- <text>{{ deptName || "全部部门" }}</text>
- <text class="arrow">▾</text>
- </view>
- <!-- 清空部门筛选 -->
- <view class="clear-dept" v-if="categoryLevelId" @click="clearDept">
- <text>✕</text>
- </view>
- </view>
- <!-- 列表 -->
- <scroll-view class="popup-body" scroll-y @scrolltolower="scrolltolower">
- <view
- v-for="(item, index) in listData"
- :key="item.id"
- class="list-item"
- :class="{ active: isSelected(item.id) }"
- @click="toggleItem(item, index)"
- >
- <!-- 自定义选中图标 -->
- <view class="item-check">
- <view class="check-box" :class="{ checked: isSelected(item.id) }">
- <text v-if="isSelected(item.id)" class="check-mark">✓</text>
- </view>
- </view>
- <view class="item-info">
- <view class="item-top">
- <text class="item-name">{{ item.name }}</text>
- <text class="item-code">{{ item.loginName }}</text>
- </view>
- <view class="item-bottom">
- <text>工号:{{ item.jobNumber || "-" }}</text>
- <text>部门:{{ item.groupName || "-" }}</text>
- </view>
- </view>
- </view>
- <view style="height: 20rpx"></view>
- <view v-if="loading" class="load-more">加载中...</view>
- <view v-if="isEnd && listData.length > 0" class="load-more"
- >没有更多了</view
- >
- <u-empty
- class="no-data"
- v-if="!loading && listData.length === 0"
- text="暂无数据"
- />
- </scroll-view>
- <!-- 底部 -->
- <view class="footer">
- <view class="bottom-left" v-if="isAll == 1" @click="toggleSelectAll">
- <!-- 自定义全选复选框 -->
- <view class="check-box" :class="{ checked: seletedAll }">
- <text v-if="seletedAll" class="check-mark">✓</text>
- </view>
- <text class="select-all-text">{{
- seletedAll ? "取消全选" : "全选"
- }}</text>
- </view>
- <view class="bottom-left" v-else>
- <text class="select-all-text">单选模式</text>
- </view>
- <view
- class="confirm-btn"
- :class="{ disabled: selectedIds.length === 0 }"
- @click="confirmSelect"
- >
- 确定 ({{ selectedIds.length }})
- </view>
- </view>
- </view>
- <!-- 部门树选择器 -->
- <ba-tree-picker
- ref="treePicker"
- key="verify"
- :multiple="false"
- @select-change="onDeptConfirm"
- title="选择部门"
- :localdata="classificationList"
- valueKey="id"
- textKey="name"
- />
- <u-toast ref="uToast" />
- </u-popup>
- </template>
- <script>
- import { listOrganizations, getUserPage } from "@/api/myTicket/index.js";
- import baTreePicker from "@/components/ba-tree-picker/ba-tree-picker.vue";
- export default {
- components: {
- baTreePicker,
- },
- data() {
- return {
- visible: false,
- // 列表数据
- listData: [],
- selectedIds: [], // 选中项的 id 数组
- page: 1,
- size: 20,
- isEnd: true,
- loading: false,
- searchVal: "",
- // 部门筛选
- classificationList: [],
- categoryLevelId: null,
- deptName: "全部部门",
- // 模式:1多选,2单选
- isAll: "1",
- seletedAll: false,
- };
- },
- watch: {
- selectedIds: {
- handler(val) {
- // 更新全选状态
- if (this.listData.length > 0) {
- this.seletedAll =
- val.length === this.listData.length && val.length > 0;
- }
- },
- deep: true,
- immediate: true,
- },
- },
- methods: {
- // ============ 外部调用 ============
- open(isAll) {
- this.isAll = isAll || "1";
- this.visible = true;
- // 重置状态
- this.listData = [];
- this.selectedIds = [];
- this.searchVal = "";
- this.categoryLevelId = null;
- this.isEnd = false;
- this.seletedAll = false;
- // 加载数据
- this.getClassify();
- },
- close() {
- this.visible = false;
- },
- // ============ 数据加载 ============
- async getClassify() {
- try {
- const res = await listOrganizations(1);
- this.classificationList = res || [];
- // ========== 关键改动:默认选中第一个部门 ==========
- if (this.classificationList.length > 0) {
- const firstDept = this.classificationList[0];
- this.categoryLevelId = firstDept.id;
- this.deptName = firstDept.name;
- } else {
- this.categoryLevelId = null;
- this.deptName = "全部部门";
- }
- this.page = 1;
- this.getList();
- } catch (e) {
- console.error("获取部门列表失败", e);
- }
- },
- async getList() {
- if (this.loading || this.isEnd) return;
- this.loading = true;
- try {
- const params = {
- pageNum: this.page,
- size: this.size,
- name: this.searchVal || undefined,
- groupId: this.categoryLevelId || undefined,
- };
- const res = await getUserPage(params);
- const list = res.list || [];
- if (this.page === 1) {
- this.listData = list;
- // 如果全选状态开启,自动选中所有
- if (this.seletedAll) {
- this.selectedIds = this.listData.map((item) => item.id);
- }
- } else {
- this.listData = this.listData.concat(list);
- // 追加数据时,如果全选开启,新数据自动选中
- if (this.seletedAll) {
- const newIds = list.map((item) => item.id);
- this.selectedIds = [...this.selectedIds, ...newIds];
- }
- }
- this.isEnd =
- list.length < this.size || this.listData.length >= res.count;
- this.page += 1;
- } catch (e) {
- console.error("获取人员列表失败", e);
- this.$refs.uToast.show({ type: "error", message: "加载失败,请重试" });
- } finally {
- this.loading = false;
- }
- },
- // ============ 搜索 ============
- doSearch() {
- this.page = 1;
- this.isEnd = false;
- this.selectedIds = [];
- this.getList();
- },
- // ============ 滚动加载更多 ============
- scrolltolower() {
- if (!this.isEnd && !this.loading) {
- this.getList();
- }
- },
- // ============ 部门树选择 ============
- showTreePicker() {
- this.$refs.treePicker._show();
- },
- onDeptConfirm(data) {
- const id = data[0];
- this.categoryLevelId = id;
- // 查找部门名称
- const findDept = (list) => {
- for (const item of list) {
- if (item.id === id) {
- return item.name;
- }
- if (item.children && item.children.length > 0) {
- const found = findDept(item.children);
- if (found) return found;
- }
- }
- return null;
- };
- this.deptName = findDept(this.classificationList) || "全部部门";
- this.page = 1;
- this.isEnd = false;
- this.selectedIds = [];
- this.getList();
- },
- // ============ 清空部门筛选 ============
- clearDept() {
- this.categoryLevelId = null;
- this.deptName = "全部部门";
- this.page = 1;
- this.isEnd = false;
- this.selectedIds = [];
- this.getList();
- },
- // ============ 判断是否选中 ============
- isSelected(id) {
- return this.selectedIds.includes(id);
- },
- // ============ 点击切换选中 ============
- toggleItem(item, index) {
- if (item.disabled) return;
- // 单选模式:只能选一个
- if (this.isAll !== "1") {
- if (this.selectedIds.includes(item.id)) {
- this.selectedIds = [];
- } else {
- this.selectedIds = [item.id];
- }
- return;
- }
- // 多选模式:切换
- const idx = this.selectedIds.indexOf(item.id);
- if (idx > -1) {
- this.selectedIds.splice(idx, 1);
- } else {
- this.selectedIds.push(item.id);
- }
- // 触发响应式更新
- this.selectedIds = [...this.selectedIds];
- },
- // ============ 全选 ============
- toggleSelectAll() {
- if (this.seletedAll) {
- this.selectedIds = [];
- } else {
- this.selectedIds = this.listData.map((item) => item.id);
- }
- this.seletedAll = !this.seletedAll;
- },
- // ============ 确认选择 ============
- confirmSelect() {
- if (this.selectedIds.length === 0) {
- this.$refs.uToast.show({ type: "warning", message: "请至少选择一人" });
- return;
- }
- const selected = this.listData.filter((item) =>
- this.selectedIds.includes(item.id),
- );
- this.$emit("confirm", selected);
- this.close();
- },
- },
- };
- </script>
- <style lang="scss" scoped>
- .select-user-popup {
- /deep/ .u-popup__content {
- border-radius: 32rpx 32rpx 0 0;
- overflow: hidden;
- }
- .popup-content {
- height: 75vh;
- display: flex;
- flex-direction: column;
- background: #f5f7fb;
- }
- // ===== 头部 =====
- .popup-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 30rpx 32rpx;
- background: #fff;
- border-bottom: 2rpx solid #eef2f6;
- flex-shrink: 0;
- .popup-title {
- font-size: 36rpx;
- font-weight: bold;
- color: #1f2b3c;
- }
- .close-btn {
- width: 60rpx;
- height: 60rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 52rpx;
- color: #8e9aae;
- line-height: 1;
- }
- }
- // ===== 搜索行 =====
- .search-row {
- display: flex;
- align-items: center;
- padding: 16rpx 24rpx 8rpx;
- background: #fff;
- flex-shrink: 0;
- gap: 16rpx;
- .search-input {
- flex: 1;
- height: 70rpx;
- background: #f5f7fb;
- border-radius: 48rpx;
- padding: 0 24rpx;
- font-size: 28rpx;
- border: 2rpx solid transparent;
- &:focus {
- border-color: #2979ff;
- }
- }
- .search-btn {
- display: flex;
- align-items: center;
- justify-content: center;
- height: 70rpx;
- padding: 0 28rpx;
- background: #2979ff;
- border-radius: 48rpx;
- color: #fff;
- font-size: 28rpx;
- gap: 8rpx;
- flex-shrink: 0;
- .text {
- font-size: 26rpx;
- }
- }
- }
- // ===== 部门选择行 =====
- .dept-row {
- display: flex;
- align-items: center;
- padding: 8rpx 24rpx 16rpx;
- background: #fff;
- border-bottom: 2rpx solid #eef2f6;
- flex-shrink: 0;
- gap: 16rpx;
- .dept-btn {
- display: inline-flex;
- align-items: center;
- height: 60rpx;
- padding: 0 24rpx;
- background: #e8edf4;
- border-radius: 48rpx;
- color: #2979ff;
- font-size: 26rpx;
- gap: 8rpx;
- .arrow {
- font-size: 20rpx;
- margin-left: 4rpx;
- }
- }
- .clear-dept {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 44rpx;
- height: 44rpx;
- border-radius: 50%;
- background: #f0f0f0;
- color: #999;
- font-size: 24rpx;
- }
- }
- // ===== 列表 =====
- .popup-body {
- flex: 1;
- overflow-y: auto;
- padding: 0 24rpx;
- background: #f5f7fb;
- .list-item {
- display: flex;
- align-items: center;
- padding: 24rpx 20rpx;
- background: #fff;
- border-radius: 24rpx;
- margin-top: 20rpx;
- box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
- transition: all 0.2s;
- border: 3rpx solid transparent;
- &.active {
- border-color: #2979ff;
- background: #f0f7ff;
- }
- &:active {
- transform: scale(0.99);
- }
- .item-check {
- margin-right: 20rpx;
- flex-shrink: 0;
- }
- // 自定义复选框
- .check-box {
- width: 40rpx;
- height: 40rpx;
- border-radius: 50%;
- border: 3rpx solid #ccc;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s;
- background: #fff;
- &.checked {
- background: #2979ff;
- border-color: #2979ff;
- }
- .check-mark {
- color: #fff;
- font-size: 24rpx;
- font-weight: bold;
- }
- }
- .item-info {
- flex: 1;
- min-width: 0;
- .item-top {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 8rpx;
- .item-name {
- font-size: 30rpx;
- font-weight: 600;
- color: #1f2b3c;
- }
- .item-code {
- font-size: 24rpx;
- color: #8e9aae;
- }
- }
- .item-bottom {
- display: flex;
- justify-content: space-between;
- font-size: 24rpx;
- color: #8e9aae;
- text {
- flex: 1;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- }
- }
- }
- .no-data {
- margin-top: 40vh;
- }
- .load-more {
- text-align: center;
- font-size: 26rpx;
- color: #aaa;
- padding: 30rpx 0 60rpx;
- }
- }
- // ===== 底部 =====
- .footer {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 16rpx 24rpx;
- background: #fff;
- border-top: 2rpx solid #eef2f6;
- flex-shrink: 0;
- min-height: 90rpx;
- .bottom-left {
- display: flex;
- align-items: center;
- gap: 12rpx;
- .check-box {
- width: 40rpx;
- height: 40rpx;
- border-radius: 4rpx;
- border: 3rpx solid #ccc;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: all 0.2s;
- background: #fff;
- &.checked {
- background: #2979ff;
- border-color: #2979ff;
- }
- .check-mark {
- color: #fff;
- font-size: 28rpx;
- font-weight: bold;
- }
- }
- .select-all-text {
- font-size: 28rpx;
- color: #333;
- }
- }
- .confirm-btn {
- height: 72rpx;
- padding: 0 40rpx;
- border-radius: 48rpx;
- background: #2979ff;
- color: #fff;
- font-size: 28rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- &.disabled {
- background: #ccc;
- color: #999;
- }
- }
- }
- }
- </style>
|