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>
|
|
|
|
|
|
|