496 lines
12 KiB
Vue
496 lines
12 KiB
Vue
<template>
|
||
<full-page customScroll pageStyle="background:#f4f5f7">
|
||
<template #header>
|
||
<view class="search-bar">
|
||
<input
|
||
v-model="keyword"
|
||
class="search-input"
|
||
placeholder="搜索"
|
||
confirm-type="search"
|
||
@input="onSearchInput"
|
||
@confirm="loadList"
|
||
/>
|
||
</view>
|
||
</template>
|
||
|
||
<view class="template-page">
|
||
<scroll-view scroll-y class="category-panel">
|
||
<view
|
||
v-for="item in categoryList"
|
||
:key="item.viewType"
|
||
class="category-item"
|
||
:class="{ active: activeViewType === item.viewType }"
|
||
@click="changeCategory(item.viewType)"
|
||
>
|
||
<text class="category-text">{{ item.label }}</text>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<view class="content-panel">
|
||
<view v-if="loading" class="state-text">加载中...</view>
|
||
<empty-data v-else-if="list.length === 0" fullCenter :text="emptyText" />
|
||
<scroll-view v-else scroll-y class="list-scroll">
|
||
<view v-for="p in list" :key="p.id" class="template-card">
|
||
<view class="card-header">
|
||
<view class="plan-name">{{ p.planName || '--' }}</view>
|
||
<view
|
||
v-if="canToggleFavorite(p)"
|
||
class="favorite-btn"
|
||
:class="{ collected: p.isFavorite }"
|
||
@click.stop="toggleFavorite(p)"
|
||
>
|
||
<uni-icons
|
||
class="favorite-icon"
|
||
:type="p.isFavorite ? 'star-filled' : 'star'"
|
||
size="18"
|
||
:color="p.isFavorite ? '#1f5cff' : '#667085'"
|
||
/>
|
||
</view>
|
||
</view>
|
||
<view class="creator" v-if="getCreatorText(p)">{{ getCreatorText(p) }}</view>
|
||
<view class="card-footer">
|
||
<view class="task-link" @click.stop="preview(p)">任务详情 〉</view>
|
||
<view class="select-btn" @click="select(p)">选择</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</view>
|
||
|
||
<template #footer>
|
||
<view class="footer">
|
||
<view class="service-entry" @click="toService">
|
||
<text class="service-muted">如没有符合的内容,请</text>
|
||
<text class="service-link">联系客服</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
</full-page>
|
||
</template>
|
||
|
||
<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 { toast } from '@/utils/widget';
|
||
import { getTodoEventTypeLabel } from '@/utils/todo-const';
|
||
|
||
import EmptyData from '@/components/empty-data.vue';
|
||
import fullPage from '@/components/full-page.vue';
|
||
|
||
const archiveId = ref('');
|
||
const loading = ref(false);
|
||
const keyword = ref('');
|
||
const activeViewType = ref('my');
|
||
const emptyText = ref('暂无回访计划');
|
||
const list = ref([]);
|
||
const categoryList = ref([
|
||
{ label: '我的回访', viewType: 'my' },
|
||
{ label: '团队回访', viewType: 'team' },
|
||
{ label: '柚助手', viewType: 'doctorMiniapp' },
|
||
]);
|
||
|
||
let searchTimer = null;
|
||
|
||
const accountStore = useAccountStore();
|
||
const { account, doctorInfo } = storeToRefs(accountStore);
|
||
const { getDoctorInfo } = accountStore;
|
||
|
||
onLoad(async (options) => {
|
||
archiveId.value = options?.archiveId ? String(options.archiveId) : '';
|
||
await ensureDoctor();
|
||
buildCategoryList();
|
||
loadList();
|
||
});
|
||
|
||
async function ensureDoctor() {
|
||
if (doctorInfo.value) return;
|
||
if (!account.value?.openid) return;
|
||
try {
|
||
await getDoctorInfo();
|
||
} catch {
|
||
// ignore
|
||
}
|
||
}
|
||
|
||
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 t = uni.getStorageSync('ykt_case_current_team') || {};
|
||
const a = account.value || {};
|
||
const d = doctorInfo.value || {};
|
||
return String(t.corpId || a.corpId || d.corpId || '') || '';
|
||
}
|
||
|
||
function buildCategoryList() {
|
||
const d = doctorInfo.value || {};
|
||
const categories = [
|
||
{ label: '我的回访', viewType: 'my' },
|
||
{ label: '团队回访', viewType: 'team' },
|
||
];
|
||
if (d.hospitalId || d.hospitalName) {
|
||
categories.push({
|
||
label: String(d.hospitalName || '第一执业点'),
|
||
viewType: 'firstHospital',
|
||
});
|
||
}
|
||
if (d.secondPracticeHospitalId || d.secondPracticeHospitalName) {
|
||
categories.push({
|
||
label: String(d.secondPracticeHospitalName || '第二执业点'),
|
||
viewType: 'secondHospital',
|
||
});
|
||
}
|
||
categories.push({ label: '柚助手', viewType: 'doctorMiniapp' });
|
||
categoryList.value = categories;
|
||
if (!categories.some((item) => item.viewType === activeViewType.value)) {
|
||
activeViewType.value = categories[0]?.viewType || 'my';
|
||
}
|
||
}
|
||
|
||
function normalizePlan(raw) {
|
||
if (!raw || typeof raw !== 'object') return null;
|
||
const id = String(raw._id || raw.planId || '');
|
||
if (!id) return null;
|
||
const planId = String(raw.planId || raw._id || '');
|
||
const taskList = Array.isArray(raw.taskList) ? raw.taskList : [];
|
||
const first = taskList[0] && typeof taskList[0] === 'object' ? taskList[0] : {};
|
||
const eventType = String(first.eventType || '');
|
||
const taskContent = String(first.taskContent || '');
|
||
return {
|
||
...raw,
|
||
id,
|
||
_id: String(raw._id || id),
|
||
planId,
|
||
sourceTemplateId: String(raw.sourceTemplateId || raw._id || id),
|
||
sourceTemplatePlanId: String(raw.sourceTemplatePlanId || planId),
|
||
planName: String(raw.planName || ''),
|
||
planType: String(raw.planType || ''),
|
||
planDetail: String(raw.planDetail || ''),
|
||
firstEventType: eventType,
|
||
firstEventTypeLabel: eventType ? getTodoEventTypeLabel(eventType) : '',
|
||
taskContent,
|
||
taskCount: taskList.length,
|
||
taskList,
|
||
isFavorite: Boolean(raw.isFavorite),
|
||
};
|
||
}
|
||
|
||
function unwrapLibraryData(res) {
|
||
const data = res?.data;
|
||
if (data && typeof data === 'object' && data.data && typeof data.data === 'object') {
|
||
return data.data;
|
||
}
|
||
return data && typeof data === 'object' ? data : {};
|
||
}
|
||
|
||
async function loadList() {
|
||
if (loading.value) return;
|
||
loading.value = true;
|
||
try {
|
||
await ensureDoctor();
|
||
buildCategoryList();
|
||
const corpId = getCorpId();
|
||
const userId = getUserId();
|
||
if (!corpId || !userId) {
|
||
list.value = [];
|
||
toast('缺少用户信息');
|
||
return;
|
||
}
|
||
const res = await api('getMyManagementPlanTemplateLibrary', {
|
||
corpId,
|
||
userId,
|
||
viewType: activeViewType.value,
|
||
keyword: keyword.value.trim(),
|
||
page: 1,
|
||
pageSize: 999,
|
||
});
|
||
if (!res?.success) {
|
||
list.value = [];
|
||
toast(res?.message || '获取回访计划失败');
|
||
return;
|
||
}
|
||
const payload = unwrapLibraryData(res);
|
||
const arr = Array.isArray(payload.list) ? payload.list : [];
|
||
list.value = arr.map(normalizePlan).filter(Boolean);
|
||
emptyText.value = payload.emptyState || '暂无回访计划';
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
|
||
function changeCategory(viewType) {
|
||
if (activeViewType.value === viewType) return;
|
||
activeViewType.value = viewType;
|
||
list.value = [];
|
||
loadList();
|
||
}
|
||
|
||
function onSearchInput() {
|
||
if (searchTimer) clearTimeout(searchTimer);
|
||
searchTimer = setTimeout(() => {
|
||
loadList();
|
||
}, 400);
|
||
}
|
||
|
||
function canToggleFavorite(plan) {
|
||
if (!plan || !plan._id) return false;
|
||
if (isCreatedByCurrentUser(plan)) return false;
|
||
if (activeViewType.value !== 'my') return true;
|
||
return Boolean(plan.isFavorite);
|
||
}
|
||
|
||
function isCreatedByCurrentUser(plan = {}) {
|
||
const userId = getUserId();
|
||
if (!userId) return false;
|
||
if (String(plan.createor || plan.creator || '') === userId) return true;
|
||
const signature = plan.creatorSignature && typeof plan.creatorSignature === 'object'
|
||
? plan.creatorSignature
|
||
: null;
|
||
return signature?.type === 'personal' && String(signature.person?.userId || '') === userId;
|
||
}
|
||
|
||
async function toggleFavorite(plan) {
|
||
const corpId = getCorpId();
|
||
const userId = getUserId();
|
||
const managementPlanId = String(plan?._id || '');
|
||
if (!corpId || !userId || !managementPlanId) return toast('缺少收藏信息');
|
||
const res = await api('toggleMyFollowupFavorite', {
|
||
corpId,
|
||
userId,
|
||
managementPlanId,
|
||
});
|
||
if (!res?.success) return toast(res?.message || '操作失败');
|
||
toast(res.message || '操作成功');
|
||
loadList();
|
||
}
|
||
|
||
function getCreatorText(plan = {}) {
|
||
const signature = plan.creatorSignature && typeof plan.creatorSignature === 'object'
|
||
? plan.creatorSignature
|
||
: null;
|
||
if (signature?.type === 'institution') {
|
||
return signature.institution?.name || '';
|
||
}
|
||
if (signature?.type === 'department') {
|
||
return signature.department?.name || '';
|
||
}
|
||
if (signature?.type === 'personal') {
|
||
return signature.person?.userName || '';
|
||
}
|
||
return String(plan.createorName || plan.creatorName || plan.teamName || plan.teanName || plan.createor || '');
|
||
}
|
||
|
||
function toService() {
|
||
uni.navigateTo({
|
||
url: '/pages/work/service/contact-service',
|
||
});
|
||
}
|
||
|
||
function select(plan) {
|
||
uni.setStorageSync('select-mamagement-plan', plan);
|
||
uni.navigateTo({ url: `/pages/case/plan-execute?archiveId=${encodeURIComponent(archiveId.value)}` });
|
||
}
|
||
|
||
function preview(plan) {
|
||
uni.setStorageSync('preview-mamagement-plan', plan);
|
||
uni.navigateTo({ url: `/pages/case/plan-preview?archiveId=${encodeURIComponent(archiveId.value)}` });
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.search-bar {
|
||
padding: 14rpx 18rpx 12rpx;
|
||
background: #fff;
|
||
border-bottom: 1rpx solid #edf0f2;
|
||
}
|
||
|
||
.search-input {
|
||
height: 68rpx;
|
||
padding: 0 24rpx;
|
||
border-radius: 12rpx;
|
||
background: #f3f5f8;
|
||
color: #111827;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.template-page {
|
||
height: 100%;
|
||
display: flex;
|
||
min-height: 0;
|
||
}
|
||
|
||
.category-panel {
|
||
width: 210rpx;
|
||
height: 100%;
|
||
flex-shrink: 0;
|
||
background: #fbfcfe;
|
||
border-right: 1rpx solid #edf0f2;
|
||
}
|
||
|
||
.category-item {
|
||
position: relative;
|
||
min-height: 88rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
margin: 8rpx 10rpx;
|
||
padding: 14rpx 12rpx 14rpx 16rpx;
|
||
border-radius: 12rpx;
|
||
color: #374151;
|
||
font-size: 30rpx;
|
||
line-height: 40rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.category-item.active {
|
||
color: #1667e8;
|
||
background: #eef5ff;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.category-item.active::before {
|
||
content: "";
|
||
position: absolute;
|
||
left: 0;
|
||
top: 18rpx;
|
||
bottom: 18rpx;
|
||
width: 6rpx;
|
||
border-radius: 999rpx;
|
||
background: #1667e8;
|
||
}
|
||
|
||
.category-text {
|
||
width: 100%;
|
||
display: block;
|
||
overflow: visible;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.content-panel {
|
||
position: relative;
|
||
flex: 1;
|
||
min-width: 0;
|
||
height: 100%;
|
||
background: #f5f6f8;
|
||
}
|
||
|
||
.list-scroll {
|
||
height: 100%;
|
||
}
|
||
|
||
.state-text {
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #6b7280;
|
||
font-size: 30rpx;
|
||
}
|
||
|
||
.template-card {
|
||
position: relative;
|
||
margin: 16rpx 16rpx 0;
|
||
padding: 26rpx 24rpx 24rpx;
|
||
border: 1rpx solid #eef1f4;
|
||
border-radius: 14rpx;
|
||
background: #fff;
|
||
box-shadow: 0 4rpx 14rpx rgba(17, 24, 39, 0.04);
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.card-header {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.plan-name {
|
||
flex: 1;
|
||
min-width: 0;
|
||
color: #111827;
|
||
font-size: 34rpx;
|
||
font-weight: 700;
|
||
line-height: 46rpx;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.favorite-btn {
|
||
flex-shrink: 0;
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
margin-left: auto;
|
||
}
|
||
|
||
.favorite-icon {
|
||
font-size: 32rpx;
|
||
line-height: 1;
|
||
}
|
||
|
||
.creator {
|
||
margin-top: 12rpx;
|
||
color: #6b7280;
|
||
font-size: 30rpx;
|
||
line-height: 40rpx;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.task-link {
|
||
display: inline-flex;
|
||
color: #1667e8;
|
||
font-size: 30rpx;
|
||
line-height: 40rpx;
|
||
}
|
||
|
||
.card-footer {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 18rpx;
|
||
margin-top: 20rpx;
|
||
}
|
||
|
||
.select-btn {
|
||
flex-shrink: 0;
|
||
min-width: 76rpx;
|
||
height: 48rpx;
|
||
line-height: 46rpx;
|
||
padding: 0 18rpx;
|
||
border: 1rpx solid #1667e8;
|
||
border-radius: 999rpx;
|
||
color: #1667e8;
|
||
background: #fff;
|
||
font-size: 28rpx;
|
||
text-align: center;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.footer {
|
||
position: relative;
|
||
background: #fff;
|
||
box-shadow: 0 -6rpx 18rpx rgba(15, 23, 42, 0.06);
|
||
}
|
||
|
||
.service-entry {
|
||
padding: 24rpx 30rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.service-muted {
|
||
color: #6b7280;
|
||
font-size: 30rpx;
|
||
}
|
||
|
||
.service-link {
|
||
color: #0877f1;
|
||
font-size: 30rpx;
|
||
}
|
||
</style>
|