ykt-wxapp/pages/work/department-select.vue
2026-02-05 17:12:52 +08:00

395 lines
9.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<full-page :customScroll="true">
<template #header>
<view class="flex items-center px-15 py-12 bg-white border-b">
<view class="w-0 flex-grow mr-10 border py-5 px-15 rounded-full">
<input v-model="keyword" class="w-full text-dark text-base" type="text" placeholder="搜索科室"
placeholder-class="text-base text-gray" />
</view>
<view class="text-primary text-base" @click="keyword = ''">
清空
</view>
</view>
</template>
<scroll-view v-if="filteredList.length" class="h-full bg-white" scroll-y="true">
<view v-for="i in filteredList" :key="`filter-${i.deptId}`" class="px-15 py-12 text-base text-dark border-b"
@click="select(i)">
{{ i.deptName }}
</view>
</scroll-view>
<view v-else-if="level1List.length" class="flex w-full h-full">
<view class="flex-shrink-0 sidebar h-full bg-white">
<scroll-view scroll-y="true" class="h-full" :scroll-into-view="anchor">
<view v-for="item in level1List" :key="item.deptId" :id="item.deptId"
class="relative p-10 text-center text-base border-b"
:class="item.deptId === selectedParentId ? 'text-primary active' : 'text-dark'" @click="selectParent(item)">
{{ item.hlwDeptName || item.deptName }}
</view>
</scroll-view>
</view>
<view class="flex-grow h-full bg-white">
<scroll-view v-if="contentList.length" scroll-y="true" class="h-full" :scroll-into-view="anchor2">
<view v-for="child in contentList" :key="child.deptId" :id="child.deptId"
class="flex justify-between items-center p-10 border-b"
:class="selectedMap[child.deptId] ? 'text-primary ' : 'text-dark'" @click="selectDept(child)">
<view class="flex-shrink-0 mr-10 text-base">
{{ child.deptName }}
</view>
<image v-if="selectedMap[child.deptId]" class="flex-shrink-0 icon-checked" src="/static/form/checked.svg" />
</view>
</scroll-view>
<empty-data v-else fullCenter />
</view>
</view>
<empty-data v-else fullCenter />
<template #footer>
<view class="relative z-2 shadow-up bg-white">
<view class="pt-12 px-15 text-center" @click="toService()">
<text class="mr-5 text-base text-gray">如没有符合的内容</text>
<text class="text-base text-primary">联系客服</text>
</view>
<button-footer :showCancel="false" @confirm="save()" />
</view>
</template>
</full-page>
</template>
<script setup>
import { computed, ref, nextTick } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import useGuard from "@/hooks/useGuard.js";
import api from "@/utils/api";
import { toast } from "@/utils/widget";
import buttonFooter from "@/components/button-footer.vue";
import EmptyData from "@/components/empty-data.vue";
import fullPage from "@/components/full-page.vue";
const { useLoad } = useGuard();
const deptList = ref([]);
const selectedParentId = ref("");
const selectedDeptIds = ref([]);
const keyword = ref("");
const anchor = ref('');
const anchor2 = ref('');
const eventName = ref('')
const selectedMap = computed(() => selectedDeptIds.value.reduce((acc, cur) => {
acc[cur] = true;
return acc
}, {}))
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)
);
const level2List = computed(() =>
deptList.value.filter((i) => i.level === 2).sort(sorter)
);
const filteredList = computed(() => {
const target = keyword.value.trim();
if (target.trim()) {
const list1 = level1List.value.filter(i => i.deptName.includes(target));
const list2 = level2List.value.filter(i => i.deptName.includes(target));
return [...list1, ...list2];
}
return []
});
const contentList = computed(() => {
const children = deptList.value
.filter(
(i) =>
i.level === 2 && i.parentId && i.parentId === selectedParentId.value
)
.sort(sorter);
return children;
});
function select(i) {
if (i.level === 1) {
selectedParentId.value = i.deptId;
scrollTo(i.deptId);
} else if (i.level === 2) {
selectedParentId.value = i.parentId;
scrollTo(i.parentId, i.deptId);
}
keyword.value = '';
}
function scrollTo(parentId, deptId) {
if (parentId) anchor.value = '';
if (deptId) anchor2.value = '';
setTimeout(() => {
if (parentId) anchor.value = parentId;
if (deptId) anchor2.value = deptId;
}, 600);
}
async function fetchDeptList() {
uni.showLoading({ title: "加载中..." });
try {
const res = await api("getAllHlwDeptList");
if (res && res.success && Array.isArray(res.data)) {
deptList.value = res.data.map(i=>({
deptId:i.hlwDeptId,
deptName:i.hlwDeptName,
level:i.level,
parentId:i.parentId,
sort:i.sort,
createTime:i.createTime
}));
} else {
toast(res?.message || "获取科室失败");
}
} catch (error) {
toast("获取科室失败");
} finally {
uni.hideLoading();
}
}
function selectParent(item) {
selectedParentId.value = item.deptId;
}
function selectDept(item) {
if (selectedDeptIds.value.includes(item.deptId)) {
selectedDeptIds.value = selectedDeptIds.value.filter(i => i !== item.deptId);
} else {
selectedDeptIds.value.push(item.deptId)
}
}
function toService() {
uni.navigateTo({
url: '/pages/work/service/contact-service'
})
}
function save() {
const depts = selectedDeptIds.value.map(i => {
const dept = level2List.value.find(j => j.deptId === i);
return dept ? { deptId: dept.deptId, deptName: dept.deptName } : null;
}).filter(Boolean);
if (depts.length) {
uni.$emit(eventName.value, depts);
uni.navigateBack();
} else {
toast('请选择科室');
}
}
useLoad((opt) => {
eventName.value = opt.eventName;
const deptIds = typeof opt.deptIds === 'string' ? opt.deptIds.split(',').filter(Boolean) : [];
selectedDeptIds.value = deptIds;
fetchDeptList();
});
</script>
<style lang="scss" scoped>
.sidebar {
width: 280rpx;
border-right: 1px solid #eee;
}
.active::before {
content: "";
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 8rpx;
background: #0074ff;
}
.pt-12 {
padding-top: 24rpx;
}
.icon-checked {
width: 40rpx;
height: 40rpx;
}
.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;
}
.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;
}
.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;
}
.sidebar-empty {
padding: 40rpx 16rpx;
font-size: 24rpx;
color: #8c94a6;
text-align: center;
}
.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>