feat:完善转移、共享、分组功能
This commit is contained in:
parent
7cb83882b8
commit
da7397ce7e
@ -76,8 +76,8 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="block-content">
|
||||
<view v-if="archive.groups && archive.groups.length" class="tags-wrap">
|
||||
<view v-for="tag in archive.groups" :key="tag" class="tag-item">
|
||||
<view v-if="archiveGroupNames.length" class="tags-wrap">
|
||||
<view v-for="tag in archiveGroupNames" :key="tag" class="tag-item">
|
||||
{{ tag }}
|
||||
</view>
|
||||
</view>
|
||||
@ -184,13 +184,13 @@
|
||||
</view>
|
||||
|
||||
<view class="group-list">
|
||||
<view v-for="g in groupOptions" :key="g" class="group-item" @click="toggleGroup(g)">
|
||||
<view v-for="g in teamGroups" :key="g._id" class="group-item" @click="toggleGroup(String(g._id))">
|
||||
<uni-icons
|
||||
:type="selectedGroupMap[g] ? 'checkbox-filled' : 'checkbox'"
|
||||
:type="selectedGroupMap[String(g._id)] ? 'checkbox-filled' : 'checkbox'"
|
||||
size="20"
|
||||
:color="selectedGroupMap[g] ? '#4f6ef7' : '#c7c7c7'"
|
||||
:color="selectedGroupMap[String(g._id)] ? '#4f6ef7' : '#c7c7c7'"
|
||||
/>
|
||||
<text class="group-name">{{ g }}</text>
|
||||
<text class="group-name">{{ g.groupName }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@ -259,8 +259,7 @@ const archive = ref({
|
||||
createdByDoctor: true,
|
||||
hasBindWechat: false,
|
||||
notes: '',
|
||||
groups: [],
|
||||
groupOptions: ['高血压', '糖尿病', '高血脂']
|
||||
groupIds: []
|
||||
});
|
||||
|
||||
const accountStore = useAccountStore();
|
||||
@ -295,6 +294,7 @@ function normalizeArchiveFromApi(raw) {
|
||||
createTime: r.createTime || '',
|
||||
creator: r.creator || '',
|
||||
notes: r.notes || r.remark || '',
|
||||
groupIds: Array.isArray(r.groupIds) ? r.groupIds : [],
|
||||
};
|
||||
return next;
|
||||
}
|
||||
@ -321,6 +321,7 @@ async function fetchArchive() {
|
||||
}
|
||||
archive.value = { ...archive.value, ...normalizeArchiveFromApi(res.data) };
|
||||
saveToStorage();
|
||||
await fetchTeamGroups(true);
|
||||
} catch (e) {
|
||||
toast('获取档案失败');
|
||||
} finally {
|
||||
@ -328,6 +329,56 @@ async function fetchArchive() {
|
||||
}
|
||||
}
|
||||
|
||||
const teamGroups = ref([]);
|
||||
const groupNameMap = computed(() => {
|
||||
const map = new Map();
|
||||
(Array.isArray(teamGroups.value) ? teamGroups.value : []).forEach((g) => {
|
||||
if (g && g._id && g.groupName) map.set(String(g._id), String(g.groupName));
|
||||
});
|
||||
return map;
|
||||
});
|
||||
|
||||
const archiveGroupNames = computed(() => {
|
||||
const ids = Array.isArray(archive.value.groupIds) ? archive.value.groupIds : [];
|
||||
const map = groupNameMap.value;
|
||||
return ids.map((id) => map.get(String(id))).filter(Boolean);
|
||||
});
|
||||
|
||||
function sortGroupList(list) {
|
||||
const { orderList, corpList, restList } = (Array.isArray(list) ? list : []).reduce(
|
||||
(p, c) => {
|
||||
if (typeof c?.sortOrder === 'number') p.orderList.push(c);
|
||||
else if (c?.parentGroupId) p.corpList.push(c);
|
||||
else p.restList.push(c);
|
||||
return p;
|
||||
},
|
||||
{ orderList: [], corpList: [], restList: [] }
|
||||
);
|
||||
orderList.sort((a, b) => a.sortOrder - b.sortOrder);
|
||||
return [...orderList, ...corpList, ...restList];
|
||||
}
|
||||
|
||||
async function fetchTeamGroups(silent = false) {
|
||||
const corpId = getCorpId();
|
||||
const team = uni.getStorageSync(CURRENT_TEAM_STORAGE_KEY) || {};
|
||||
const teamId = team?.teamId ? String(team.teamId) : '';
|
||||
if (!corpId || !teamId) return;
|
||||
try {
|
||||
const projection = { _id: 1, groupName: 1, parentGroupId: 1, sortOrder: 1 };
|
||||
const res = await api('getGroups', { corpId, teamId, page: 1, pageSize: 1000, projection, countGroupMember: false });
|
||||
if (!res?.success) {
|
||||
if (!silent) toast(res?.message || '获取分组失败');
|
||||
teamGroups.value = [];
|
||||
return;
|
||||
}
|
||||
const list = Array.isArray(res.data) ? res.data : Array.isArray(res.data?.data) ? res.data.data : [];
|
||||
teamGroups.value = sortGroupList(list);
|
||||
} catch (e) {
|
||||
if (!silent) toast('获取分组失败');
|
||||
teamGroups.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
async function updateArchive(patch) {
|
||||
const id = String(archiveId.value || '');
|
||||
if (!id) return false;
|
||||
@ -375,8 +426,7 @@ onLoad((options) => {
|
||||
archive.value = {
|
||||
...archive.value,
|
||||
...cached,
|
||||
groups: Array.isArray(cached.groups) ? cached.groups : archive.value.groups,
|
||||
groupOptions: Array.isArray(cached.groupOptions) ? cached.groupOptions : archive.value.groupOptions
|
||||
groupIds: Array.isArray(cached.groupIds) ? cached.groupIds : archive.value.groupIds
|
||||
};
|
||||
}
|
||||
|
||||
@ -416,8 +466,7 @@ onShow(() => {
|
||||
archive.value = {
|
||||
...archive.value,
|
||||
...cached,
|
||||
groups: Array.isArray(cached.groups) ? cached.groups : archive.value.groups,
|
||||
groupOptions: Array.isArray(cached.groupOptions) ? cached.groupOptions : archive.value.groupOptions,
|
||||
groupIds: Array.isArray(cached.groupIds) ? cached.groupIds : archive.value.groupIds,
|
||||
};
|
||||
}
|
||||
setTimeout(measureTabsTop, 30);
|
||||
@ -528,19 +577,18 @@ const saveNotes = () => {
|
||||
const groupPopup = ref(null);
|
||||
const addGroupPopup = ref(null);
|
||||
|
||||
const groupOptions = computed(() => (Array.isArray(archive.value.groupOptions) ? archive.value.groupOptions : []));
|
||||
|
||||
const selectedGroupMap = ref({});
|
||||
|
||||
const syncSelectedMapFromArchive = () => {
|
||||
const current = Array.isArray(archive.value.groups) ? archive.value.groups : [];
|
||||
selectedGroupMap.value = current.reduce((acc, name) => {
|
||||
acc[name] = true;
|
||||
const current = Array.isArray(archive.value.groupIds) ? archive.value.groupIds.map(String) : [];
|
||||
selectedGroupMap.value = current.reduce((acc, id) => {
|
||||
acc[String(id)] = true;
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
|
||||
const openGroups = () => {
|
||||
const openGroups = async () => {
|
||||
await fetchTeamGroups(false);
|
||||
syncSelectedMapFromArchive();
|
||||
groupPopup.value?.open();
|
||||
};
|
||||
@ -549,14 +597,54 @@ const closeGroups = () => {
|
||||
groupPopup.value?.close();
|
||||
};
|
||||
|
||||
const toggleGroup = (name) => {
|
||||
selectedGroupMap.value[name] = !selectedGroupMap.value[name];
|
||||
const toggleGroup = (groupId) => {
|
||||
const key = String(groupId || '');
|
||||
if (!key) return;
|
||||
selectedGroupMap.value[key] = !selectedGroupMap.value[key];
|
||||
};
|
||||
|
||||
const saveGroups = () => {
|
||||
archive.value.groups = Object.keys(selectedGroupMap.value).filter(k => selectedGroupMap.value[k]);
|
||||
saveToStorage();
|
||||
closeGroups();
|
||||
async function applyGroupChange(nextIds) {
|
||||
const memberId = String(archiveId.value || '');
|
||||
if (!memberId) return false;
|
||||
const prevIds = Array.isArray(archive.value.groupIds) ? archive.value.groupIds.map(String) : [];
|
||||
const next = Array.isArray(nextIds) ? nextIds.map(String).filter(Boolean) : [];
|
||||
|
||||
const toAdd = next.filter((id) => !prevIds.includes(id));
|
||||
const toRemove = prevIds.filter((id) => !next.includes(id));
|
||||
|
||||
if (toAdd.length === 0 && toRemove.length === 0) return true;
|
||||
|
||||
loading('保存中...');
|
||||
try {
|
||||
for (const id of toAdd) {
|
||||
const res = await api('addGroupIdForMember', { memberId, toGroupId: id });
|
||||
if (!res?.success) {
|
||||
toast(res?.message || '分组保存失败');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const id of toRemove) {
|
||||
const res = await api('addGroupIdForMember', { memberId, fromGroupId: id });
|
||||
if (!res?.success) {
|
||||
toast(res?.message || '分组保存失败');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
uni.setStorageSync(NEED_RELOAD_STORAGE_KEY, 1);
|
||||
await fetchArchive();
|
||||
return true;
|
||||
} catch (e) {
|
||||
toast('分组保存失败');
|
||||
return false;
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
const saveGroups = async () => {
|
||||
const nextIds = Object.keys(selectedGroupMap.value).filter((k) => selectedGroupMap.value[k]);
|
||||
const ok = await applyGroupChange(nextIds);
|
||||
if (ok) closeGroups();
|
||||
};
|
||||
|
||||
// 添加新分组
|
||||
@ -571,22 +659,46 @@ const closeAddGroup = () => {
|
||||
addGroupPopup.value?.close();
|
||||
};
|
||||
|
||||
const saveAddGroup = () => {
|
||||
const saveAddGroup = async () => {
|
||||
const name = String(newGroupName.value || '').trim();
|
||||
if (!name) {
|
||||
uni.showToast({ title: '请输入分组名称', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
const exists = groupOptions.value.some(g => String(g).trim() === name);
|
||||
const exists = (Array.isArray(teamGroups.value) ? teamGroups.value : []).some((g) => String(g?.groupName || '').trim() === name);
|
||||
if (exists) {
|
||||
uni.showToast({ title: '该分组已存在', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
archive.value.groupOptions = [...groupOptions.value, name];
|
||||
selectedGroupMap.value[name] = true;
|
||||
saveToStorage();
|
||||
closeAddGroup();
|
||||
const corpId = getCorpId();
|
||||
const team = uni.getStorageSync(CURRENT_TEAM_STORAGE_KEY) || {};
|
||||
const teamId = team?.teamId ? String(team.teamId) : '';
|
||||
const creator = getUserId();
|
||||
if (!corpId || !teamId || !creator) {
|
||||
toast('缺少用户/团队信息');
|
||||
return;
|
||||
}
|
||||
|
||||
loading('保存中...');
|
||||
try {
|
||||
const params = { groupName: name, teamId, groupType: 'team', creator };
|
||||
const res = await api('createGroup', { corpId, teamId, params });
|
||||
if (!res?.success) {
|
||||
toast(res?.message || '新增分组失败');
|
||||
return;
|
||||
}
|
||||
await fetchTeamGroups(true);
|
||||
// 自动选中新建分组(后续在“保存”时调用 addGroupIdForMember)
|
||||
const newId = res.data ? String(res.data) : '';
|
||||
if (newId) selectedGroupMap.value[newId] = true;
|
||||
uni.setStorageSync('ykt_case_groups_need_reload', 1);
|
||||
closeAddGroup();
|
||||
} catch (e) {
|
||||
toast('新增分组失败');
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
<view class="content">
|
||||
<view class="section-title">选择共享团队</view>
|
||||
<view class="selector-item" @click="selectTeam">
|
||||
<text :class="team ? '' : 'placeholder'">{{ team ? team.name : "请选择团队" }}</text>
|
||||
<uni-icons type="arrowdown" size="16" color="#999"></uni-icons>
|
||||
<text :class="team ? '' : 'placeholder'">{{ team ? team.name : '请选择团队' }}</text>
|
||||
<uni-icons type="arrowdown" size="16" color="#999" />
|
||||
</view>
|
||||
|
||||
<view class="tips">共享客户:表示客户档案共享多个团队可见,多个团队可同时为该客户服务。</view>
|
||||
@ -12,8 +12,8 @@
|
||||
<template v-if="team">
|
||||
<view class="section-title">选择责任人</view>
|
||||
<view class="selector-item" @click="selectUser">
|
||||
<text :class="user ? '' : 'placeholder'">{{ user ? user.name : "请选择责任人" }}</text>
|
||||
<uni-icons type="arrowdown" size="16" color="#999"></uni-icons>
|
||||
<text :class="userId ? '' : 'placeholder'">{{ userLabel || '请选择责任人' }}</text>
|
||||
<uni-icons type="arrowdown" size="16" color="#999" />
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
@ -26,39 +26,129 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import api from '@/utils/api';
|
||||
import useAccountStore from '@/store/account';
|
||||
import { hideLoading, loading, toast } from '@/utils/widget';
|
||||
|
||||
const CURRENT_TEAM_STORAGE_KEY = 'ykt_case_current_team';
|
||||
const NEED_RELOAD_STORAGE_KEY = 'ykt_case_need_reload';
|
||||
const BATCH_CUSTOMER_IDS_KEY = 'ykt_case_batch_customer_ids';
|
||||
|
||||
const accountStore = useAccountStore();
|
||||
const { account, doctorInfo } = storeToRefs(accountStore);
|
||||
const { getDoctorInfo } = accountStore;
|
||||
|
||||
const customerIds = ref([]);
|
||||
const currentTeam = ref(null);
|
||||
const teams = ref([]);
|
||||
const team = ref(null);
|
||||
const user = ref(null);
|
||||
const teamMembers = ref([]);
|
||||
const userId = ref('');
|
||||
|
||||
// Mock Data
|
||||
const teams = [
|
||||
{ id: 1, name: '李医生团队' },
|
||||
{ id: 2, name: '王医生团队' }
|
||||
];
|
||||
const userLabel = computed(() => {
|
||||
const list = Array.isArray(teamMembers.value) ? teamMembers.value : [];
|
||||
const found = list.find((m) => String(m?.userid || '') === String(userId.value));
|
||||
return found ? String(found.anotherName || found.name || found.userid || '') : '';
|
||||
});
|
||||
|
||||
const users = [
|
||||
{ id: 101, name: '张医生' },
|
||||
{ id: 102, name: '李医生' },
|
||||
{ id: 103, name: '王医生' }
|
||||
];
|
||||
function getUserId() {
|
||||
const d = doctorInfo.value || {};
|
||||
const a = account.value || {};
|
||||
return String(d.userid || d.userId || d.corpUserId || a.userid || a.userId || '') || '';
|
||||
}
|
||||
|
||||
const selectTeam = () => {
|
||||
function getCorpId() {
|
||||
const t = currentTeam.value || {};
|
||||
const a = account.value || {};
|
||||
const d = doctorInfo.value || {};
|
||||
return String(t.corpId || d.corpId || a.corpId || '') || '';
|
||||
}
|
||||
|
||||
function getCurrentTeamId() {
|
||||
return String(currentTeam.value?.teamId || '') || '';
|
||||
}
|
||||
|
||||
function normalizeTeam(raw) {
|
||||
if (!raw || typeof raw !== 'object') return null;
|
||||
const teamId = raw.teamId || raw.id || raw._id || '';
|
||||
const name = raw.name || raw.teamName || raw.team || '';
|
||||
const corpId = raw.corpId || raw.corpID || '';
|
||||
if (!teamId || !name) return null;
|
||||
return { teamId: String(teamId), name: String(name), corpId: corpId ? String(corpId) : '' };
|
||||
}
|
||||
|
||||
async function ensureDoctor() {
|
||||
if (doctorInfo.value) return;
|
||||
if (!account.value?.openid) return;
|
||||
try {
|
||||
await getDoctorInfo();
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTeams() {
|
||||
await ensureDoctor();
|
||||
const corpId = getCorpId();
|
||||
const userId = getUserId();
|
||||
if (!corpId || !userId) return;
|
||||
const res = await api('getTeamBymember', { corpId, corpUserId: userId });
|
||||
if (!res?.success) {
|
||||
toast(res?.message || '获取团队失败');
|
||||
return;
|
||||
}
|
||||
const list = Array.isArray(res?.data) ? res.data : Array.isArray(res?.data?.data) ? res.data.data : [];
|
||||
teams.value = list.map(normalizeTeam).filter(Boolean);
|
||||
}
|
||||
|
||||
async function loadTeamMembers(teamId) {
|
||||
const corpId = getCorpId();
|
||||
if (!teamId) return;
|
||||
const res = await api('getTeamData', { corpId, teamId });
|
||||
if (!res?.success) {
|
||||
toast(res?.message || '获取团队成员失败');
|
||||
teamMembers.value = [];
|
||||
return;
|
||||
}
|
||||
const t = res?.data && typeof res.data === 'object' ? res.data : {};
|
||||
teamMembers.value = Array.isArray(t.memberList) ? t.memberList : [];
|
||||
}
|
||||
|
||||
const selectTeam = async () => {
|
||||
if (!teams.value.length) await loadTeams();
|
||||
const currentId = getCurrentTeamId();
|
||||
const candidates = teams.value.filter((t) => t.teamId !== currentId);
|
||||
if (!candidates.length) {
|
||||
toast('暂无可选团队');
|
||||
return;
|
||||
}
|
||||
uni.showActionSheet({
|
||||
itemList: teams.map(t => t.name),
|
||||
success: (res) => {
|
||||
team.value = teams[res.tapIndex];
|
||||
user.value = null;
|
||||
}
|
||||
itemList: candidates.map((t) => t.name),
|
||||
success: async (res) => {
|
||||
team.value = candidates[res.tapIndex] || null;
|
||||
userId.value = '';
|
||||
teamMembers.value = [];
|
||||
if (team.value) await loadTeamMembers(team.value.teamId);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const selectUser = () => {
|
||||
const list = Array.isArray(teamMembers.value) ? teamMembers.value : [];
|
||||
if (!list.length) {
|
||||
toast('当前团队暂无可选成员');
|
||||
return;
|
||||
}
|
||||
const labels = list.map((m) => String(m?.anotherName || m?.name || m?.userid || ''));
|
||||
uni.showActionSheet({
|
||||
itemList: users.map(u => u.name),
|
||||
itemList: labels,
|
||||
success: (res) => {
|
||||
user.value = users[res.tapIndex];
|
||||
}
|
||||
const picked = list[res.tapIndex];
|
||||
userId.value = picked?.userid ? String(picked.userid) : '';
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -66,25 +156,52 @@ const cancel = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
const save = () => {
|
||||
if (!team.value) {
|
||||
uni.showToast({ title: '请选择团队', icon: 'none' });
|
||||
return;
|
||||
const save = async () => {
|
||||
if (!team.value) return toast('请选择团队');
|
||||
if (!userId.value) return toast('请选择负责人');
|
||||
const corpId = getCorpId();
|
||||
const currentTeamId = getCurrentTeamId();
|
||||
const creatorUserId = getUserId();
|
||||
if (!corpId || !currentTeamId || !creatorUserId) return toast('缺少用户/团队信息');
|
||||
|
||||
loading('保存中...');
|
||||
try {
|
||||
const res = await api('transferCustomers', {
|
||||
corpId,
|
||||
customerIds: customerIds.value,
|
||||
currentTeamId,
|
||||
targetTeamId: team.value.teamId,
|
||||
targetUserId: userId.value,
|
||||
operationType: 'share',
|
||||
creatorUserId,
|
||||
});
|
||||
if (!res?.success) {
|
||||
toast(res?.message || '操作失败');
|
||||
return;
|
||||
}
|
||||
toast('操作成功');
|
||||
uni.removeStorageSync(BATCH_CUSTOMER_IDS_KEY);
|
||||
uni.setStorageSync(NEED_RELOAD_STORAGE_KEY, 1);
|
||||
uni.navigateBack();
|
||||
} catch (e) {
|
||||
toast('操作失败');
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
if (!user.value) {
|
||||
uni.showToast({ title: '请选择负责人', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showLoading({ title: '保存中' });
|
||||
setTimeout(() => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: '操作成功' });
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
onLoad(async () => {
|
||||
customerIds.value = Array.isArray(uni.getStorageSync(BATCH_CUSTOMER_IDS_KEY))
|
||||
? uni.getStorageSync(BATCH_CUSTOMER_IDS_KEY).map(String).filter(Boolean)
|
||||
: [];
|
||||
currentTeam.value = uni.getStorageSync(CURRENT_TEAM_STORAGE_KEY) || null;
|
||||
if (!customerIds.value.length) {
|
||||
toast('未选择客户');
|
||||
setTimeout(() => uni.navigateBack(), 200);
|
||||
return;
|
||||
}
|
||||
await loadTeams();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -105,7 +222,7 @@ const save = () => {
|
||||
color: #333;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
@ -119,11 +236,11 @@ const save = () => {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 15px;
|
||||
|
||||
|
||||
text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
|
||||
|
||||
&.placeholder {
|
||||
color: #999;
|
||||
}
|
||||
@ -143,7 +260,7 @@ const save = () => {
|
||||
padding: 15px 20px 30px;
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
|
||||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
@ -164,10 +281,11 @@ const save = () => {
|
||||
color: #fff;
|
||||
border: none;
|
||||
}
|
||||
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@ -3,15 +3,15 @@
|
||||
<view class="content">
|
||||
<view class="section-title">选择新负责团队</view>
|
||||
<view class="selector-item" @click="selectTeam">
|
||||
<text :class="team ? '' : 'placeholder'">{{ team ? team.name : "请选择选择团队" }}</text>
|
||||
<uni-icons type="arrowdown" size="16" color="#999"></uni-icons>
|
||||
<text :class="team ? '' : 'placeholder'">{{ team ? team.name : '请选择团队' }}</text>
|
||||
<uni-icons type="arrowdown" size="16" color="#999" />
|
||||
</view>
|
||||
|
||||
<template v-if="team">
|
||||
<view class="section-title">选择责任人</view>
|
||||
<view class="selector-item" @click="selectUser">
|
||||
<text :class="user ? '' : 'placeholder'">{{ user ? user.name : "请选择责任人" }}</text>
|
||||
<uni-icons type="arrowdown" size="16" color="#999"></uni-icons>
|
||||
<text :class="userId ? '' : 'placeholder'">{{ userLabel || '请选择责任人' }}</text>
|
||||
<uni-icons type="arrowdown" size="16" color="#999" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@ -26,40 +26,129 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import api from '@/utils/api';
|
||||
import useAccountStore from '@/store/account';
|
||||
import { hideLoading, loading, toast } from '@/utils/widget';
|
||||
|
||||
const CURRENT_TEAM_STORAGE_KEY = 'ykt_case_current_team';
|
||||
const NEED_RELOAD_STORAGE_KEY = 'ykt_case_need_reload';
|
||||
const BATCH_CUSTOMER_IDS_KEY = 'ykt_case_batch_customer_ids';
|
||||
|
||||
const accountStore = useAccountStore();
|
||||
const { account, doctorInfo } = storeToRefs(accountStore);
|
||||
const { getDoctorInfo } = accountStore;
|
||||
|
||||
const customerIds = ref([]);
|
||||
const currentTeam = ref(null);
|
||||
const teams = ref([]);
|
||||
const team = ref(null);
|
||||
const user = ref(null);
|
||||
const teamMembers = ref([]);
|
||||
const userId = ref('');
|
||||
|
||||
// Mock Data
|
||||
const teams = [
|
||||
{ id: 1, name: '张敏西服务团队' },
|
||||
{ id: 2, name: '李医生团队' },
|
||||
{ id: 3, name: '王医生团队' }
|
||||
];
|
||||
const userLabel = computed(() => {
|
||||
const list = Array.isArray(teamMembers.value) ? teamMembers.value : [];
|
||||
const found = list.find((m) => String(m?.userid || '') === String(userId.value));
|
||||
return found ? String(found.anotherName || found.name || found.userid || '') : '';
|
||||
});
|
||||
|
||||
const users = [
|
||||
{ id: 101, name: '张医生' },
|
||||
{ id: 102, name: '李医生' },
|
||||
{ id: 103, name: '王医生' }
|
||||
];
|
||||
function getUserId() {
|
||||
const d = doctorInfo.value || {};
|
||||
const a = account.value || {};
|
||||
return String(d.userid || d.userId || d.corpUserId || a.userid || a.userId || '') || '';
|
||||
}
|
||||
|
||||
const selectTeam = () => {
|
||||
function getCorpId() {
|
||||
const t = currentTeam.value || {};
|
||||
const a = account.value || {};
|
||||
const d = doctorInfo.value || {};
|
||||
return String(t.corpId || d.corpId || a.corpId || '') || '';
|
||||
}
|
||||
|
||||
function getCurrentTeamId() {
|
||||
return String(currentTeam.value?.teamId || '') || '';
|
||||
}
|
||||
|
||||
function normalizeTeam(raw) {
|
||||
if (!raw || typeof raw !== 'object') return null;
|
||||
const teamId = raw.teamId || raw.id || raw._id || '';
|
||||
const name = raw.name || raw.teamName || raw.team || '';
|
||||
const corpId = raw.corpId || raw.corpID || '';
|
||||
if (!teamId || !name) return null;
|
||||
return { teamId: String(teamId), name: String(name), corpId: corpId ? String(corpId) : '' };
|
||||
}
|
||||
|
||||
async function ensureDoctor() {
|
||||
if (doctorInfo.value) return;
|
||||
if (!account.value?.openid) return;
|
||||
try {
|
||||
await getDoctorInfo();
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
async function loadTeams() {
|
||||
await ensureDoctor();
|
||||
const corpId = getCorpId();
|
||||
const userId = getUserId();
|
||||
if (!corpId || !userId) return;
|
||||
const res = await api('getTeamBymember', { corpId, corpUserId: userId });
|
||||
if (!res?.success) {
|
||||
toast(res?.message || '获取团队失败');
|
||||
return;
|
||||
}
|
||||
const list = Array.isArray(res?.data) ? res.data : Array.isArray(res?.data?.data) ? res.data.data : [];
|
||||
teams.value = list.map(normalizeTeam).filter(Boolean);
|
||||
}
|
||||
|
||||
async function loadTeamMembers(teamId) {
|
||||
const corpId = getCorpId();
|
||||
if (!teamId) return;
|
||||
const res = await api('getTeamData', { corpId, teamId });
|
||||
if (!res?.success) {
|
||||
toast(res?.message || '获取团队成员失败');
|
||||
teamMembers.value = [];
|
||||
return;
|
||||
}
|
||||
const t = res?.data && typeof res.data === 'object' ? res.data : {};
|
||||
teamMembers.value = Array.isArray(t.memberList) ? t.memberList : [];
|
||||
}
|
||||
|
||||
const selectTeam = async () => {
|
||||
if (!teams.value.length) await loadTeams();
|
||||
const currentId = getCurrentTeamId();
|
||||
const candidates = teams.value.filter((t) => t.teamId !== currentId);
|
||||
if (!candidates.length) {
|
||||
toast('暂无可选团队');
|
||||
return;
|
||||
}
|
||||
uni.showActionSheet({
|
||||
itemList: teams.map(t => t.name),
|
||||
success: (res) => {
|
||||
team.value = teams[res.tapIndex];
|
||||
user.value = null; // Reset user when team changes
|
||||
}
|
||||
itemList: candidates.map((t) => t.name),
|
||||
success: async (res) => {
|
||||
team.value = candidates[res.tapIndex] || null;
|
||||
userId.value = '';
|
||||
teamMembers.value = [];
|
||||
if (team.value) await loadTeamMembers(team.value.teamId);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const selectUser = () => {
|
||||
const list = Array.isArray(teamMembers.value) ? teamMembers.value : [];
|
||||
if (!list.length) {
|
||||
toast('当前团队暂无可选成员');
|
||||
return;
|
||||
}
|
||||
const labels = list.map((m) => String(m?.anotherName || m?.name || m?.userid || ''));
|
||||
uni.showActionSheet({
|
||||
itemList: users.map(u => u.name),
|
||||
itemList: labels,
|
||||
success: (res) => {
|
||||
user.value = users[res.tapIndex];
|
||||
}
|
||||
const picked = list[res.tapIndex];
|
||||
userId.value = picked?.userid ? String(picked.userid) : '';
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -67,25 +156,53 @@ const cancel = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
const save = () => {
|
||||
if (!team.value) {
|
||||
uni.showToast({ title: '请选择团队', icon: 'none' });
|
||||
return;
|
||||
const save = async () => {
|
||||
if (!team.value) return toast('请选择团队');
|
||||
if (!userId.value) return toast('请选择负责人');
|
||||
|
||||
const corpId = getCorpId();
|
||||
const currentTeamId = getCurrentTeamId();
|
||||
const creatorUserId = getUserId();
|
||||
if (!corpId || !currentTeamId || !creatorUserId) return toast('缺少用户/团队信息');
|
||||
|
||||
loading('保存中...');
|
||||
try {
|
||||
const res = await api('transferCustomers', {
|
||||
corpId,
|
||||
customerIds: customerIds.value,
|
||||
currentTeamId,
|
||||
targetTeamId: team.value.teamId,
|
||||
targetUserId: userId.value,
|
||||
operationType: 'transferToOtherTeam',
|
||||
creatorUserId,
|
||||
});
|
||||
if (!res?.success) {
|
||||
toast(res?.message || '操作失败');
|
||||
return;
|
||||
}
|
||||
toast('操作成功');
|
||||
uni.removeStorageSync(BATCH_CUSTOMER_IDS_KEY);
|
||||
uni.setStorageSync(NEED_RELOAD_STORAGE_KEY, 1);
|
||||
uni.navigateBack();
|
||||
} catch (e) {
|
||||
toast('操作失败');
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
if (!user.value) {
|
||||
uni.showToast({ title: '请选择责任人', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showLoading({ title: '保存中' });
|
||||
setTimeout(() => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: '操作成功' });
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
onLoad(async () => {
|
||||
customerIds.value = Array.isArray(uni.getStorageSync(BATCH_CUSTOMER_IDS_KEY))
|
||||
? uni.getStorageSync(BATCH_CUSTOMER_IDS_KEY).map(String).filter(Boolean)
|
||||
: [];
|
||||
currentTeam.value = uni.getStorageSync(CURRENT_TEAM_STORAGE_KEY) || null;
|
||||
if (!customerIds.value.length) {
|
||||
toast('未选择客户');
|
||||
setTimeout(() => uni.navigateBack(), 200);
|
||||
return;
|
||||
}
|
||||
await loadTeams();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -106,7 +223,7 @@ const save = () => {
|
||||
color: #333;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
@ -120,11 +237,11 @@ const save = () => {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 15px;
|
||||
|
||||
|
||||
text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
|
||||
|
||||
&.placeholder {
|
||||
color: #999;
|
||||
}
|
||||
@ -144,7 +261,7 @@ const save = () => {
|
||||
padding: 15px 20px 30px;
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
|
||||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
@ -165,10 +282,11 @@ const save = () => {
|
||||
color: #fff;
|
||||
border: none;
|
||||
}
|
||||
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@ -31,13 +31,13 @@
|
||||
<scroll-view scroll-x class="tabs-scroll" :show-scrollbar="false">
|
||||
<view class="tabs-container">
|
||||
<view
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="index"
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
class="tab-item"
|
||||
:class="{ active: currentTab === index }"
|
||||
@click="currentTab = index"
|
||||
:class="{ active: currentTabKey === tab.key }"
|
||||
@click="onTabClick(tab)"
|
||||
>
|
||||
{{ tab }}
|
||||
{{ tab.label }}
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
@ -83,7 +83,7 @@
|
||||
|
||||
<!-- Row 2 / 3 -->
|
||||
<view class="card-row-bottom">
|
||||
<template v-if="currentTab === 1"> <!-- New Patient Tab -->
|
||||
<template v-if="currentTabKey === 'new'"> <!-- New Patient Tab -->
|
||||
<text class="record-text">
|
||||
{{ patient.createTime || '-' }} / {{ patient.creator || '-' }}
|
||||
</text>
|
||||
@ -147,9 +147,19 @@ import { toast } from '@/utils/widget';
|
||||
// State
|
||||
const teams = ref([]);
|
||||
const currentTeam = ref(null);
|
||||
const currentTab = ref(0);
|
||||
const currentTabKey = ref('all');
|
||||
const scrollIntoId = ref('');
|
||||
const tabs = ['全部', '新患者', '糖尿病', '高血压', '冠心病', '慢阻肺'];
|
||||
const teamGroups = ref([]);
|
||||
const tabs = computed(() => {
|
||||
const base = [
|
||||
{ key: 'all', label: '全部', kind: 'all' },
|
||||
{ key: 'new', label: '新患者', kind: 'new' },
|
||||
];
|
||||
const groupTabs = (Array.isArray(teamGroups.value) ? teamGroups.value : [])
|
||||
.filter((g) => g && g._id && g.groupName)
|
||||
.map((g) => ({ key: `group:${g._id}`, label: String(g.groupName), kind: 'group', groupId: String(g._id) }));
|
||||
return [...base, ...groupTabs];
|
||||
});
|
||||
const isBatchMode = ref(false);
|
||||
const selectedItems = ref([]); // Stores patient phone or unique ID
|
||||
|
||||
@ -162,6 +172,8 @@ const verifyFailedReason = ref('资料不完整,请补充营业执照/资质
|
||||
const DETAIL_STORAGE_KEY = 'ykt_case_archive_detail';
|
||||
const CURRENT_TEAM_STORAGE_KEY = 'ykt_case_current_team';
|
||||
const NEED_RELOAD_STORAGE_KEY = 'ykt_case_need_reload';
|
||||
const GROUPS_RELOAD_KEY = 'ykt_case_groups_need_reload';
|
||||
const BATCH_CUSTOMER_IDS_KEY = 'ykt_case_batch_customer_ids';
|
||||
|
||||
const page = ref(1);
|
||||
const pages = ref(0);
|
||||
@ -170,6 +182,7 @@ const totalFromApi = ref(0);
|
||||
const loading = ref(false);
|
||||
const rawPatients = ref([]);
|
||||
const more = computed(() => page.value < pages.value);
|
||||
const currentTab = computed(() => tabs.value.find((t) => t.key === currentTabKey.value) || tabs.value[0]);
|
||||
|
||||
const accountStore = useAccountStore();
|
||||
const { account, doctorInfo } = storeToRefs(accountStore);
|
||||
@ -207,6 +220,41 @@ function getTeamId() {
|
||||
return String(currentTeam.value?.teamId || '') || '';
|
||||
}
|
||||
|
||||
function sortGroupList(list) {
|
||||
const { orderList, corpList, restList } = (Array.isArray(list) ? list : []).reduce(
|
||||
(p, c) => {
|
||||
if (typeof c?.sortOrder === 'number') p.orderList.push(c);
|
||||
else if (c?.parentGroupId) p.corpList.push(c);
|
||||
else p.restList.push(c);
|
||||
return p;
|
||||
},
|
||||
{ orderList: [], corpList: [], restList: [] }
|
||||
);
|
||||
orderList.sort((a, b) => a.sortOrder - b.sortOrder);
|
||||
return [...orderList, ...corpList, ...restList];
|
||||
}
|
||||
|
||||
async function loadGroups() {
|
||||
if (!currentTeam.value) return;
|
||||
const corpId = getCorpId();
|
||||
const teamId = getTeamId();
|
||||
if (!corpId || !teamId) return;
|
||||
const projection = { _id: 1, groupName: 1, parentGroupId: 1, sortOrder: 1 };
|
||||
const res = await api('getGroups', { corpId, teamId, page: 1, pageSize: 1000, projection, countGroupMember: false });
|
||||
if (!res?.success) {
|
||||
teamGroups.value = [];
|
||||
return;
|
||||
}
|
||||
const list = Array.isArray(res.data) ? res.data : Array.isArray(res.data?.data) ? res.data.data : [];
|
||||
teamGroups.value = sortGroupList(list);
|
||||
|
||||
// 当前 tab 如果是分组,但分组已不存在,则回退到“全部”
|
||||
if (currentTabKey.value.startsWith('group:')) {
|
||||
const gid = currentTabKey.value.slice('group:'.length);
|
||||
if (!teamGroups.value.some((g) => String(g._id) === String(gid))) currentTabKey.value = 'all';
|
||||
}
|
||||
}
|
||||
|
||||
function getLetter(patient) {
|
||||
const raw = patient?.firstLetter || patient?.nameFirstLetter || patient?.pinyinFirstLetter || patient?.letter || '';
|
||||
const candidate = String(raw || '').trim();
|
||||
@ -342,14 +390,25 @@ async function reload(reset = true) {
|
||||
totalFromApi.value = 0;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
const res = await api('searchCorpCustomerWithFollowTime', {
|
||||
const query = {
|
||||
corpId,
|
||||
userId,
|
||||
teamId,
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
});
|
||||
};
|
||||
|
||||
if (currentTab.value.kind === 'group' && currentTab.value.groupId) {
|
||||
query.groupIds = [currentTab.value.groupId];
|
||||
} else if (currentTab.value.kind === 'new') {
|
||||
const start = dayjs().subtract(7, 'day').startOf('day').valueOf();
|
||||
const end = dayjs().endOf('day').valueOf();
|
||||
query.startCreateTime = start;
|
||||
query.endCreateTime = end;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
const res = await api('searchCorpCustomerWithFollowTime', query);
|
||||
loading.value = false;
|
||||
|
||||
if (!res?.success) {
|
||||
@ -409,7 +468,7 @@ const patientList = computed(() => {
|
||||
const all = rawPatients.value || [];
|
||||
|
||||
// New Patient Filter (Last 7 days)
|
||||
if (currentTab.value === 1) {
|
||||
if (currentTab.value.kind === 'new') {
|
||||
const now = dayjs();
|
||||
const sevenDaysAgo = now.subtract(7, 'day').valueOf();
|
||||
const flatList = all
|
||||
@ -423,25 +482,18 @@ const patientList = computed(() => {
|
||||
return [{ letter: '最近新增', data: flatList }];
|
||||
}
|
||||
|
||||
// Tab Filtering (Mock logic for other tabs)
|
||||
let filtered = all;
|
||||
if (currentTab.value > 1) {
|
||||
const tabName = tabs[currentTab.value];
|
||||
filtered = filtered.filter((p) => Array.isArray(p.tags) && p.tags.includes(tabName));
|
||||
}
|
||||
return groupByLetter(filtered);
|
||||
return groupByLetter(all);
|
||||
});
|
||||
|
||||
const indexList = computed(() => {
|
||||
if (currentTab.value === 1) return []; // No index bar for new patient
|
||||
if (currentTab.value.kind === 'new') return []; // No index bar for new patient
|
||||
return 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').filter(l => patientList.value.some(g => g.letter === l));
|
||||
});
|
||||
|
||||
const totalPatients = computed(() => {
|
||||
let count = 0;
|
||||
patientList.value.forEach(g => count += g.data.length);
|
||||
if (currentTab.value === 0 && totalFromApi.value) return totalFromApi.value;
|
||||
return count;
|
||||
return totalFromApi.value || count;
|
||||
});
|
||||
|
||||
// Methods
|
||||
@ -454,7 +506,7 @@ const checkBatchMode = () => {
|
||||
};
|
||||
|
||||
const scrollToLetter = (letter) => {
|
||||
if (currentTab.value === 1) return;
|
||||
if (currentTab.value.kind === 'new') return;
|
||||
scrollIntoId.value = letterToDomId(letter);
|
||||
};
|
||||
|
||||
@ -469,6 +521,8 @@ const toggleTeamPopup = () => {
|
||||
success: function (res) {
|
||||
currentTeam.value = teams.value[res.tapIndex] || teams.value[0] || null;
|
||||
if (currentTeam.value) uni.setStorageSync(CURRENT_TEAM_STORAGE_KEY, currentTeam.value);
|
||||
currentTabKey.value = 'all';
|
||||
loadGroups();
|
||||
reload(true);
|
||||
}
|
||||
});
|
||||
@ -619,6 +673,7 @@ const handleTransfer = () => {
|
||||
uni.showToast({ title: '请选择患者', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
uni.setStorageSync(BATCH_CUSTOMER_IDS_KEY, selectedItems.value.slice());
|
||||
// Navigate to Transfer Page
|
||||
uni.navigateTo({ url: '/pages/case/batch-transfer' });
|
||||
};
|
||||
@ -628,12 +683,12 @@ const handleShare = () => {
|
||||
uni.showToast({ title: '请选择患者', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
uni.setStorageSync(BATCH_CUSTOMER_IDS_KEY, selectedItems.value.slice());
|
||||
// Navigate to Share Page
|
||||
uni.navigateTo({ url: '/pages/case/batch-share' });
|
||||
};
|
||||
|
||||
function loadMore() {
|
||||
if (currentTab.value === 1) return;
|
||||
if (!more.value || loading.value) return;
|
||||
page.value += 1;
|
||||
reload(false);
|
||||
@ -644,9 +699,24 @@ watch(currentTeam, (t) => {
|
||||
uni.setStorageSync(CURRENT_TEAM_STORAGE_KEY, t);
|
||||
});
|
||||
|
||||
watch(currentTabKey, () => {
|
||||
if (!currentTeam.value) return;
|
||||
reload(true);
|
||||
});
|
||||
|
||||
function onTabClick(tab) {
|
||||
if (checkBatchMode()) return;
|
||||
if (!tab || !tab.key) return;
|
||||
if (currentTabKey.value === tab.key) return;
|
||||
currentTabKey.value = tab.key;
|
||||
}
|
||||
|
||||
onLoad(async () => {
|
||||
await loadTeams();
|
||||
if (currentTeam.value) await reload(true);
|
||||
if (currentTeam.value) {
|
||||
await loadGroups();
|
||||
await reload(true);
|
||||
}
|
||||
});
|
||||
|
||||
onShow(async () => {
|
||||
@ -654,6 +724,18 @@ onShow(async () => {
|
||||
if (need) {
|
||||
uni.removeStorageSync(NEED_RELOAD_STORAGE_KEY);
|
||||
await reload(true);
|
||||
// 批量操作完成后回到列表,默认退出批量态
|
||||
isBatchMode.value = false;
|
||||
selectedItems.value = [];
|
||||
}
|
||||
|
||||
const needGroups = uni.getStorageSync(GROUPS_RELOAD_KEY);
|
||||
if (needGroups) {
|
||||
uni.removeStorageSync(GROUPS_RELOAD_KEY);
|
||||
await loadGroups();
|
||||
await reload(true);
|
||||
} else {
|
||||
await loadGroups();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -1,21 +1,35 @@
|
||||
<template>
|
||||
<view class="manage-container">
|
||||
<view class="group-list">
|
||||
<view v-for="(item, index) in groups" :key="index" class="group-item">
|
||||
<view class="left-action" @click="handleDelete(index)">
|
||||
<uni-icons type="minus-filled" size="24" color="#ff4d4f"></uni-icons>
|
||||
<view v-for="(item, index) in groups" :key="item._id" class="group-item">
|
||||
<view class="left-action" :class="{ disabled: Boolean(item.parentGroupId) || isSort }" @click="handleDelete(item, index)">
|
||||
<uni-icons type="minus-filled" size="24" :color="Boolean(item.parentGroupId) || isSort ? '#ddd' : '#ff4d4f'"></uni-icons>
|
||||
</view>
|
||||
<view class="group-name">
|
||||
<view class="name-row">
|
||||
<text class="name-text">{{ item.groupName }}</text>
|
||||
<text v-if="item.parentGroupId" class="corp-tag">机构</text>
|
||||
</view>
|
||||
<text v-if="item.description" class="desc">{{ item.description }}</text>
|
||||
</view>
|
||||
<view class="group-name">{{ item.name }}</view>
|
||||
<view class="right-actions">
|
||||
<uni-icons type="compose" size="24" color="#5d8aff" class="icon-edit" @click="handleEdit(item, index)"></uni-icons>
|
||||
<uni-icons type="bars" size="24" color="#5d8aff" class="icon-drag"></uni-icons>
|
||||
<uni-icons type="compose" size="24" :color="isSort ? '#ddd' : '#5d8aff'" class="icon-edit" @click="handleEdit(item, index)"></uni-icons>
|
||||
<template v-if="isSort">
|
||||
<uni-icons type="arrowup" size="20" color="#5d8aff" class="icon-sort" @click="moveUp(index)"></uni-icons>
|
||||
<uni-icons type="arrowdown" size="20" color="#5d8aff" class="icon-sort" @click="moveDown(index)"></uni-icons>
|
||||
</template>
|
||||
<uni-icons v-else type="bars" size="24" color="#5d8aff" class="icon-drag" @click="enterSort"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Bottom Button -->
|
||||
<view class="footer">
|
||||
<button class="add-btn" @click="handleAdd">添加新分组</button>
|
||||
<template v-if="isSort">
|
||||
<button class="add-btn plain" @click="cancelSort">取消</button>
|
||||
<button class="add-btn" @click="saveSort">保存</button>
|
||||
</template>
|
||||
<button v-else class="add-btn" @click="handleAdd">添加新分组</button>
|
||||
</view>
|
||||
|
||||
<!-- Dialog -->
|
||||
@ -36,13 +50,90 @@
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import api from '@/utils/api';
|
||||
import useAccountStore from '@/store/account';
|
||||
import { hideLoading, loading, toast } from '@/utils/widget';
|
||||
|
||||
// State
|
||||
const groups = ref([
|
||||
{ id: 1, name: '糖尿病' },
|
||||
{ id: 2, name: '高血压' },
|
||||
{ id: 3, name: '高血脂' }
|
||||
]);
|
||||
const groups = ref([]);
|
||||
const originalGroups = ref([]);
|
||||
const isSort = ref(false);
|
||||
|
||||
const GROUPS_RELOAD_KEY = 'ykt_case_groups_need_reload';
|
||||
const CURRENT_TEAM_STORAGE_KEY = 'ykt_case_current_team';
|
||||
|
||||
const accountStore = useAccountStore();
|
||||
const { account, doctorInfo } = storeToRefs(accountStore);
|
||||
const { getDoctorInfo } = accountStore;
|
||||
|
||||
function getUserId() {
|
||||
const d = doctorInfo.value || {};
|
||||
const a = account.value || {};
|
||||
return String(d.userid || d.userId || d.corpUserId || a.userid || a.userId || '') || '';
|
||||
}
|
||||
|
||||
function getCorpId() {
|
||||
const d = doctorInfo.value || {};
|
||||
const a = account.value || {};
|
||||
const team = uni.getStorageSync(CURRENT_TEAM_STORAGE_KEY) || {};
|
||||
return String(d.corpId || a.corpId || team.corpId || '') || '';
|
||||
}
|
||||
|
||||
function getTeamId() {
|
||||
const team = uni.getStorageSync(CURRENT_TEAM_STORAGE_KEY) || {};
|
||||
return String(team.teamId || '') || '';
|
||||
}
|
||||
|
||||
function sortGroupList(list) {
|
||||
const { orderList, corpList, restList } = (Array.isArray(list) ? list : []).reduce(
|
||||
(p, c) => {
|
||||
if (typeof c?.sortOrder === 'number') p.orderList.push(c);
|
||||
else if (c?.parentGroupId) p.corpList.push(c);
|
||||
else p.restList.push(c);
|
||||
return p;
|
||||
},
|
||||
{ orderList: [], corpList: [], restList: [] }
|
||||
);
|
||||
orderList.sort((a, b) => a.sortOrder - b.sortOrder);
|
||||
return [...orderList, ...corpList, ...restList];
|
||||
}
|
||||
|
||||
async function ensureDoctor() {
|
||||
if (doctorInfo.value) return;
|
||||
if (!account.value?.openid) return;
|
||||
try {
|
||||
await getDoctorInfo();
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
async function loadGroups() {
|
||||
await ensureDoctor();
|
||||
const corpId = getCorpId();
|
||||
const teamId = getTeamId();
|
||||
if (!corpId || !teamId) return;
|
||||
|
||||
loading('加载中...');
|
||||
try {
|
||||
const projection = { _id: 1, groupName: 1, parentGroupId: 1, description: 1, sortOrder: 1 };
|
||||
const res = await api('getGroups', { corpId, teamId, page: 1, pageSize: 1000, projection, countGroupMember: false });
|
||||
if (!res?.success) {
|
||||
toast(res?.message || '获取分组失败');
|
||||
return;
|
||||
}
|
||||
const list = Array.isArray(res.data) ? res.data : Array.isArray(res.data?.data) ? res.data.data : [];
|
||||
const sorted = sortGroupList(list);
|
||||
groups.value = sorted.map((i) => ({ ...i }));
|
||||
originalGroups.value = sorted.map((i) => ({ ...i }));
|
||||
} catch (e) {
|
||||
toast('获取分组失败');
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
const showDialog = ref(false);
|
||||
const dialogMode = ref('add'); // 'add' or 'edit'
|
||||
@ -53,6 +144,7 @@ const dialogTitle = ref('添加新分组');
|
||||
|
||||
// Methods
|
||||
const handleAdd = () => {
|
||||
if (isSort.value) return;
|
||||
dialogMode.value = 'add';
|
||||
dialogTitle.value = '添加新分组';
|
||||
inputValue.value = '';
|
||||
@ -60,22 +152,42 @@ const handleAdd = () => {
|
||||
};
|
||||
|
||||
const handleEdit = (item, index) => {
|
||||
if (isSort.value) return;
|
||||
dialogMode.value = 'edit';
|
||||
dialogTitle.value = '编辑分组名称';
|
||||
inputValue.value = item.name;
|
||||
inputValue.value = item.groupName || '';
|
||||
editingIndex.value = index;
|
||||
showDialog.value = true;
|
||||
};
|
||||
|
||||
const handleDelete = (index) => {
|
||||
const handleDelete = (item, index) => {
|
||||
if (isSort.value) return;
|
||||
if (item?.parentGroupId) return;
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要删除该分组吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
groups.value.splice(index, 1);
|
||||
success: async (res) => {
|
||||
if (!res.confirm) return;
|
||||
const corpId = getCorpId();
|
||||
const teamId = getTeamId();
|
||||
if (!corpId || !teamId) {
|
||||
toast('缺少团队信息');
|
||||
return;
|
||||
}
|
||||
}
|
||||
loading('');
|
||||
try {
|
||||
const delRes = await api('removeGroup', { corpId, id: item._id, teamId, groupType: 'team' });
|
||||
if (!delRes?.success) {
|
||||
toast(delRes?.message || '删除失败');
|
||||
return;
|
||||
}
|
||||
toast('删除成功');
|
||||
uni.setStorageSync(GROUPS_RELOAD_KEY, 1);
|
||||
await loadGroups();
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -83,23 +195,107 @@ const closeDialog = () => {
|
||||
showDialog.value = false;
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
const handleSave = async () => {
|
||||
if (!inputValue.value.trim()) {
|
||||
uni.showToast({ title: '请输入分组名称', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (dialogMode.value === 'add') {
|
||||
groups.value.push({
|
||||
id: Date.now(),
|
||||
name: inputValue.value
|
||||
});
|
||||
} else {
|
||||
groups.value[editingIndex.value].name = inputValue.value;
|
||||
await ensureDoctor();
|
||||
const corpId = getCorpId();
|
||||
const teamId = getTeamId();
|
||||
const userId = getUserId();
|
||||
if (!corpId || !teamId || !userId) {
|
||||
toast('缺少用户/团队信息');
|
||||
return;
|
||||
}
|
||||
|
||||
closeDialog();
|
||||
loading('保存中...');
|
||||
try {
|
||||
if (dialogMode.value === 'add') {
|
||||
const params = {
|
||||
groupName: inputValue.value.trim(),
|
||||
teamId,
|
||||
groupType: 'team',
|
||||
creator: userId,
|
||||
};
|
||||
const res = await api('createGroup', { corpId, teamId, params });
|
||||
if (!res?.success) {
|
||||
toast(res?.message || '新增失败');
|
||||
return;
|
||||
}
|
||||
toast('新增成功');
|
||||
} else {
|
||||
const item = groups.value[editingIndex.value];
|
||||
if (!item?._id) return;
|
||||
const res = await api('updateGroup', { corpId, id: item._id, params: { groupName: inputValue.value.trim() } });
|
||||
if (!res?.success) {
|
||||
toast(res?.message || '修改失败');
|
||||
return;
|
||||
}
|
||||
toast('修改成功');
|
||||
}
|
||||
uni.setStorageSync(GROUPS_RELOAD_KEY, 1);
|
||||
closeDialog();
|
||||
await loadGroups();
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
};
|
||||
|
||||
function enterSort() {
|
||||
if (!groups.value.length) return;
|
||||
isSort.value = true;
|
||||
}
|
||||
|
||||
function cancelSort() {
|
||||
isSort.value = false;
|
||||
groups.value = originalGroups.value.map((i) => ({ ...i }));
|
||||
}
|
||||
|
||||
function moveUp(index) {
|
||||
if (!isSort.value) return;
|
||||
if (index <= 0) return;
|
||||
const next = groups.value.slice();
|
||||
const tmp = next[index - 1];
|
||||
next[index - 1] = next[index];
|
||||
next[index] = tmp;
|
||||
groups.value = next;
|
||||
}
|
||||
|
||||
function moveDown(index) {
|
||||
if (!isSort.value) return;
|
||||
if (index >= groups.value.length - 1) return;
|
||||
const next = groups.value.slice();
|
||||
const tmp = next[index + 1];
|
||||
next[index + 1] = next[index];
|
||||
next[index] = tmp;
|
||||
groups.value = next;
|
||||
}
|
||||
|
||||
async function saveSort() {
|
||||
const teamId = getTeamId();
|
||||
if (!teamId) return;
|
||||
loading('');
|
||||
try {
|
||||
const data = groups.value.map((i, idx) => ({ _id: i._id, sortOrder: idx }));
|
||||
const res = await api('sortGroups', { teamId, data });
|
||||
if (!res?.success) {
|
||||
toast(res?.message || '保存失败');
|
||||
return;
|
||||
}
|
||||
toast('保存成功');
|
||||
uni.setStorageSync(GROUPS_RELOAD_KEY, 1);
|
||||
isSort.value = false;
|
||||
await loadGroups();
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
loadGroups();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -121,12 +317,33 @@ const handleSave = () => {
|
||||
|
||||
.left-action {
|
||||
margin-right: 15px;
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.group-name {
|
||||
flex: 1;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
.name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.corp-tag {
|
||||
font-size: 12px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 10px;
|
||||
background: #f0f0f0;
|
||||
color: #666;
|
||||
}
|
||||
.desc {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.right-actions {
|
||||
@ -137,6 +354,9 @@ const handleSave = () => {
|
||||
.icon-edit, .icon-drag {
|
||||
padding: 5px; // Increase tap area
|
||||
}
|
||||
.icon-sort {
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,6 +381,12 @@ const handleSave = () => {
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.plain {
|
||||
background-color: #fff;
|
||||
color: #666;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -21,6 +21,13 @@ const urlsConfig = {
|
||||
member: {
|
||||
addCustomer: 'add',
|
||||
updateCustomer: 'update',
|
||||
transferCustomers: 'transferCustomers',
|
||||
getGroups: 'getGroups',
|
||||
createGroup: 'createGroup',
|
||||
updateGroup: 'updateGroup',
|
||||
removeGroup: 'removeGroup',
|
||||
sortGroups: 'sortGroups',
|
||||
addGroupIdForMember: 'addGroupIdForMember',
|
||||
bindMiniAppArchive: "bindMiniAppArchive",
|
||||
getCustomerByCustomerId: 'getCustomerByCustomerId',
|
||||
getMiniAppCustomers: 'getMiniAppCustomers',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user