ykt-wxapp/pages/work/department-select.vue

403 lines
8.5 KiB
Vue
Raw Normal View History

2026-01-20 16:30:03 +08:00
<template>
<view class="dept-page">
<view class="dept-container bg-white">
2026-01-21 13:37:54 +08:00
<view class="sidebar">
<view class="sidebar-search">
<input
v-model="keyword"
class="search-input"
type="text"
confirm-type="search"
placeholder="搜索一级科室"
placeholder-class="search-placeholder"
/>
<view v-if="keyword" class="clear-btn" @click="keyword = ''"
>清空</view
>
2026-01-20 16:30:03 +08:00
</view>
2026-01-21 13:37:54 +08:00
<scroll-view class="sidebar-list" scroll-y>
<view
v-for="item in filteredLevel1List"
:key="item.deptId"
:class="[
'sidebar-item',
item.deptId === selectedParentId ? 'active' : '',
]"
@click="selectParent(item)"
>
<text class="sidebar-text">{{
item.hlwDeptName || item.deptName
}}</text>
</view>
</scroll-view>
</view>
2026-01-20 16:30:03 +08:00
<scroll-view class="content" scroll-y>
<view v-if="contentList.length" class="content-list">
<view
v-for="child in contentList"
2026-01-21 13:37:54 +08:00
:key="child.deptId"
2026-01-20 16:30:03 +08:00
:class="['dept-item', isSelected(child) ? 'selected' : '']"
2026-01-21 13:37:54 +08:00
@click="selectDept(child)"
2026-01-20 16:30:03 +08:00
>
2026-01-21 13:37:54 +08:00
<view class="dept-name-row">
2026-01-20 16:30:03 +08:00
<view class="dept-name">{{ child.deptName }}</view>
<text v-if="isSelected(child)" class="check-tag">已选</text>
</view>
</view>
</view>
<view v-else class="empty-wrapper">
<empty-data text="暂无科室" />
</view>
</scroll-view>
</view>
2026-01-21 13:37:54 +08:00
2026-01-20 16:30:03 +08:00
<view class="footer-tip">
如没有符合的内容请联系客服
<text class="link" @click="contactService">点击添加客服</text>
</view>
</view>
</template>
2026-01-21 13:37:54 +08:00
2026-01-20 16:30:03 +08:00
<script setup>
2026-01-21 13:37:54 +08:00
import { computed, ref, watch } from "vue";
2026-01-20 16:30:03 +08:00
import { onLoad } from "@dcloudio/uni-app";
2026-01-21 13:37:54 +08:00
import { storeToRefs } from "pinia";
2026-01-20 16:30:03 +08:00
import useGuard from "@/hooks/useGuard.js";
2026-01-21 13:37:54 +08:00
import useAccountStore from "@/store/account.js";
2026-01-20 16:30:03 +08:00
import EmptyData from "@/components/empty-data.vue";
import api from "@/utils/api";
import { toast } from "@/utils/widget";
const { useLoad } = useGuard();
const deptList = ref([]);
const selectedParentId = ref("");
const selectedDeptId = ref("");
2026-01-21 13:37:54 +08:00
const keyword = ref("");
2026-01-20 16:30:03 +08:00
const sorter = (a, b) => {
const sortA = typeof a.sort === "number" ? a.sort : 0;
const sortB = typeof b.sort === "number" ? b.sort : 0;
if (sortA !== sortB) return sortA - sortB;
const timeA = a.createTime || 0;
const timeB = b.createTime || 0;
return timeA - timeB;
};
const level1List = computed(() =>
deptList.value.filter((i) => i.level === 1).sort(sorter)
);
2026-01-21 13:37:54 +08:00
const filteredLevel1List = computed(() => {
const key = keyword.value.trim().toLowerCase();
if (!key) return level1List.value;
return level1List.value.filter((i) =>
i.deptName.toString().toLowerCase().includes(key)
);
});
2026-01-20 16:30:03 +08:00
const contentList = computed(() => {
const children = deptList.value
.filter(
(i) =>
i.level === 2 && i.parentId && i.parentId === selectedParentId.value
)
.sort(sorter);
2026-01-21 13:37:54 +08:00
return children;
2026-01-20 16:30:03 +08:00
});
function isSelected(item) {
const targetId = selectedDeptId.value;
2026-01-21 13:37:54 +08:00
return !!targetId && (item.deptId === targetId || item.deptId === targetId);
2026-01-20 16:30:03 +08:00
}
async function fetchDeptList() {
uni.showLoading({ title: "加载中..." });
try {
const res = await api("getDeptList");
2026-01-21 13:37:54 +08:00
if (res && res.success && Array.isArray(res.data)) {
deptList.value = res.data;
2026-01-20 16:30:03 +08:00
hydrateSelectedParent();
} else {
toast(res?.message || "获取科室失败");
}
} catch (error) {
console.error("获取科室失败", error);
toast("获取科室失败");
} finally {
uni.hideLoading();
}
}
function hydrateSelectedParent() {
// 优先根据已选科室找到对应父级
if (selectedDeptId.value) {
const match =
deptList.value.find(
(i) =>
2026-01-21 13:37:54 +08:00
i.deptId === selectedDeptId.value || i.deptId === selectedDeptId.value
2026-01-20 16:30:03 +08:00
) || null;
if (match?.parentId) {
selectedParentId.value = match.parentId;
return;
}
}
// 再次兜底使用入参的 parentId
if (selectedParentId.value) return;
// 最后兜底选第一个一级科室
const first = level1List.value[0];
if (first) {
2026-01-21 13:37:54 +08:00
selectedParentId.value = first.deptId;
2026-01-20 16:30:03 +08:00
}
}
2026-01-21 13:37:54 +08:00
watch(
filteredLevel1List,
(list) => {
if (!list.length) return;
const exists = list.some((i) => i.deptId === selectedParentId.value);
if (!exists) {
selectedParentId.value = list[0].deptId;
}
},
{ immediate: false }
);
2026-01-20 16:30:03 +08:00
function selectParent(item) {
2026-01-21 13:37:54 +08:00
selectedParentId.value = item.deptId;
console.log("selectedParentId", selectedParentId.value);
2026-01-20 16:30:03 +08:00
}
function emitSelection(item) {
const eventChannel =
typeof getOpenerEventChannel === "function"
? getOpenerEventChannel()
: null;
eventChannel?.emit("deptSelected", {
2026-01-21 13:37:54 +08:00
id: item.deptId,
deptId: item.deptId || item.deptId,
2026-01-20 16:30:03 +08:00
name: item.deptName,
2026-01-21 13:37:54 +08:00
level: item.level,
parentId: item.parentId,
2026-01-20 16:30:03 +08:00
});
}
function selectDept(item) {
2026-01-21 13:37:54 +08:00
selectedDeptId.value = item.deptId || item.deptId;
2026-01-20 16:30:03 +08:00
emitSelection(item);
uni.navigateBack();
}
function contactService() {
uni.showToast({
title: "请联系客服添加科室",
icon: "none",
});
}
useLoad(() => {
fetchDeptList();
});
onLoad((opts) => {
// 支持从外部传入默认选中的一级科室
if (opts.parentId) {
selectedParentId.value = opts.parentId;
}
const passedDeptId = opts.deptId || opts.departmentId || opts.id;
if (passedDeptId) {
selectedDeptId.value = passedDeptId;
}
});
</script>
<style lang="scss" scoped>
.dept-page {
min-height: 100vh;
background: #f5f5f5;
display: flex;
flex-direction: column;
}
.dept-container {
display: flex;
border-radius: 12rpx;
overflow: hidden;
background: linear-gradient(180deg, #fdfdfd 0%, #f6f8fb 100%);
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.06);
height: 88vh;
}
.sidebar {
2026-01-21 13:37:54 +08:00
width: 230rpx;
2026-01-20 16:30:03 +08:00
background: #f8f9fb;
border-right: 1px solid #eef0f5;
2026-01-21 13:37:54 +08:00
display: flex;
flex-direction: column;
overflow-y: auto;
}
.sidebar-search {
padding: 16rpx 14rpx 12rpx;
display: flex;
align-items: center;
gap: 10rpx;
}
.search-input {
flex: 1;
height: 64rpx;
padding: 0 20rpx;
border-radius: 32rpx;
background: #fff;
border: 1px solid #e5e8ef;
font-size: 26rpx;
}
.search-placeholder {
color: #a0a8b8;
}
.clear-btn {
font-size: 24rpx;
color: #2a6ff2;
padding: 6rpx 10rpx;
}
.sidebar-list {
flex: 1;
2026-01-20 16:30:03 +08:00
}
.sidebar-item {
padding: 30rpx 18rpx;
font-size: 28rpx;
color: #333;
text-align: center;
border-bottom: 1px solid #eef0f5;
transition: all 0.2s ease;
&.active {
background: #fff;
color: #2a6ff2;
font-weight: 600;
box-shadow: inset 4rpx 0 0 #2a6ff2;
}
}
.sidebar-text {
display: inline-block;
line-height: 1.4;
}
2026-01-21 13:37:54 +08:00
.sidebar-empty {
padding: 40rpx 16rpx;
font-size: 24rpx;
color: #8c94a6;
text-align: center;
}
2026-01-20 16:30:03 +08:00
.content {
flex: 1;
background: #fff;
border-top-right-radius: 12rpx;
border-bottom-right-radius: 12rpx;
}
.content-list {
padding: 12rpx 20rpx 30rpx;
}
.dept-item {
padding: 26rpx 22rpx 14rpx;
border-bottom: 1px solid #f2f4f7;
border-radius: 12rpx;
margin-bottom: 14rpx;
background: #fff;
transition: box-shadow 0.2s ease, transform 0.15s ease;
&.selected {
box-shadow: 0 8rpx 24rpx rgba(42, 111, 242, 0.08);
border: 1px solid #e1e9ff;
transform: translateY(-2rpx);
}
}
.dept-name-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12rpx;
}
.dept-name {
font-size: 30rpx;
color: #333;
font-weight: 600;
}
.dept-sub-list {
margin-top: 12rpx;
display: flex;
flex-wrap: wrap;
gap: 12rpx;
}
.dept-sub-item {
padding: 12rpx 16rpx;
background: #f6f8fb;
border-radius: 10rpx;
font-size: 26rpx;
color: #3a3a3a;
border: 1px solid #e5e9f2;
display: flex;
align-items: center;
gap: 12rpx;
transition: all 0.15s ease;
&.selected {
border-color: #2a6ff2;
background: #eaf1ff;
color: #1f5ed6;
}
}
.dept-sub-text {
flex: 1;
}
.check-tag {
padding: 6rpx 14rpx;
font-size: 22rpx;
color: #2a6ff2;
background: #eaf1ff;
border-radius: 20rpx;
}
.check-dot {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background: #2a6ff2;
}
.empty-wrapper {
padding-top: 120rpx;
}
.footer-tip {
padding: 24rpx 20rpx 40rpx;
font-size: 26rpx;
color: #666;
text-align: center;
line-height: 1.6;
}
.link {
color: #2a6ff2;
}
</style>