bug修复
This commit is contained in:
parent
1514dc606a
commit
3e56f21e5f
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<full-page pageStyle="background:#f5f6f8" pageClass="benefit-page-shell">
|
<full-page ref="pageRef" pageStyle="background:#f5f6f8" pageClass="benefit-page-shell">
|
||||||
<view class="benefit-page">
|
<view class="benefit-page">
|
||||||
<view class="patient-card">
|
<view class="patient-card">
|
||||||
<view class="patient-card-accent"></view>
|
<view class="patient-card-accent"></view>
|
||||||
@ -21,7 +21,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="section-title">权益项目</view>
|
<view class="section-title">权益项目</view>
|
||||||
<view v-for="(entry, index) in entries" :key="entry.key" class="entry-card">
|
<view v-for="(entry, index) in entries" :id="`entry-${entry.key}`" :key="entry.key" class="entry-card">
|
||||||
<view class="field-label">项目名称</view>
|
<view class="field-label">项目名称</view>
|
||||||
<view class="project-input" @click="openProjectPicker(index)">
|
<view class="project-input" @click="openProjectPicker(index)">
|
||||||
<text :class="entry.project ? 'value-text' : 'placeholder-text'">
|
<text :class="entry.project ? 'value-text' : 'placeholder-text'">
|
||||||
@ -32,16 +32,30 @@
|
|||||||
|
|
||||||
<view v-if="showBenefitAmount" class="field-row">
|
<view v-if="showBenefitAmount" class="field-row">
|
||||||
<view class="field-block">
|
<view class="field-block">
|
||||||
<view class="field-label">价格</view>
|
<view class="field-label">单价</view>
|
||||||
<input class="number-input locked-input" type="digit" :value="entry.price" disabled />
|
<input class="number-input" type="digit" v-model="entry.price" @input="calculateDiscount(entry)" />
|
||||||
</view>
|
|
||||||
<view class="field-block">
|
|
||||||
<view class="field-label">折扣</view>
|
|
||||||
<input class="number-input" type="digit" v-model="entry.discount" />
|
|
||||||
</view>
|
</view>
|
||||||
<view class="field-block">
|
<view class="field-block">
|
||||||
<view class="field-label">数量</view>
|
<view class="field-label">数量</view>
|
||||||
<input class="number-input" type="number" v-model="entry.usageCount" />
|
<input class="number-input" type="number" v-model="entry.usageCount" @input="calculateTotalPrice(entry)" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-if="showBenefitAmount" class="field-row">
|
||||||
|
<view class="field-block">
|
||||||
|
<view class="field-label">折扣</view>
|
||||||
|
<view class="discount-input-wrap">
|
||||||
|
<input class="number-input" type="digit" v-model="entry.discount" :disabled="entry.isFree" @input="calculateTotalPrice(entry)" />
|
||||||
|
<checkbox-group class="free-check" @change="handleFreeChange($event, entry)">
|
||||||
|
<label class="free-check-label">
|
||||||
|
<checkbox value="free" :checked="entry.isFree" color="#0877f1" />
|
||||||
|
<text>赠送</text>
|
||||||
|
</label>
|
||||||
|
</checkbox-group>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="field-block">
|
||||||
|
<view class="field-label">总价</view>
|
||||||
|
<input class="number-input total-input" type="digit" v-model="entry.totalPrice" :disabled="entry.isFree" @input="calculateDiscount(entry)" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-else class="field-row">
|
<view v-else class="field-row">
|
||||||
@ -51,26 +65,44 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="field-block">
|
<view class="field-block">
|
||||||
<view class="field-label">有效期</view>
|
<view class="field-label">有效期</view>
|
||||||
<picker mode="date" :value="entry.validTime" @change="changeValidTime($event, entry)">
|
<view class="valid-time-wrap">
|
||||||
|
<view class="valid-time-picker" @tap="prepareValidTimePicker(entry)">
|
||||||
|
<picker
|
||||||
|
mode="multiSelector"
|
||||||
|
:range="validTimeRange"
|
||||||
|
:value="validTimeIndex"
|
||||||
|
@columnchange="onValidTimeColumnChange"
|
||||||
|
@change="changeValidTime($event, entry)"
|
||||||
|
>
|
||||||
<view class="picker-value" :class="entry.validTime ? 'value-text' : 'placeholder-text'">
|
<view class="picker-value" :class="entry.validTime ? 'value-text' : 'placeholder-text'">
|
||||||
{{ entry.validTime || "请选择日期" }}
|
{{ entry.validTime || "选择日期/长期" }}
|
||||||
</view>
|
</view>
|
||||||
</picker>
|
</picker>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="unlimited-button" @click="clearValidTime(entry)">长期</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="showBenefitAmount" class="field-row">
|
<view v-if="showBenefitAmount" class="field-row">
|
||||||
<view class="field-block">
|
<view class="field-block field-block-full">
|
||||||
<view class="field-label">价格总额</view>
|
|
||||||
<view class="total-price">¥{{ formatTotalPrice(entry) }}</view>
|
|
||||||
</view>
|
|
||||||
<view class="field-block">
|
|
||||||
<view class="field-label">有效期</view>
|
<view class="field-label">有效期</view>
|
||||||
<picker mode="date" :value="entry.validTime" @change="changeValidTime($event, entry)">
|
<view class="valid-time-wrap">
|
||||||
|
<view class="valid-time-picker" @tap="prepareValidTimePicker(entry)">
|
||||||
|
<picker
|
||||||
|
mode="multiSelector"
|
||||||
|
:range="validTimeRange"
|
||||||
|
:value="validTimeIndex"
|
||||||
|
@columnchange="onValidTimeColumnChange"
|
||||||
|
@change="changeValidTime($event, entry)"
|
||||||
|
>
|
||||||
<view class="picker-value" :class="entry.validTime ? 'value-text' : 'placeholder-text'">
|
<view class="picker-value" :class="entry.validTime ? 'value-text' : 'placeholder-text'">
|
||||||
{{ entry.validTime || "请选择日期" }}
|
{{ entry.validTime || "选择日期/长期" }}
|
||||||
</view>
|
</view>
|
||||||
</picker>
|
</picker>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="unlimited-button" @click="clearValidTime(entry)">长期</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="field-group">
|
<view class="field-group">
|
||||||
<view class="field-label">治疗科室</view>
|
<view class="field-label">治疗科室</view>
|
||||||
@ -123,14 +155,18 @@
|
|||||||
<uni-icons type="search" size="18" color="#999" />
|
<uni-icons type="search" size="18" color="#999" />
|
||||||
<input v-model="projectKeyword" class="search-input" placeholder="输入项目名称搜索" @input="searchProjects" />
|
<input v-model="projectKeyword" class="search-input" placeholder="输入项目名称搜索" @input="searchProjects" />
|
||||||
</view>
|
</view>
|
||||||
<scroll-view scroll-y class="project-list">
|
<scroll-view scroll-y class="project-list" @scrolltolower="loadMoreProjects">
|
||||||
<view v-for="project in projectList" :key="project._id" class="project-item"
|
<view v-for="project in projectList" :key="project._id" class="project-item"
|
||||||
@click="selectProject(project)">
|
@click="selectProject(project)">
|
||||||
|
<view class="project-info">
|
||||||
<view class="project-name">{{ project.projectName }}</view>
|
<view class="project-name">{{ project.projectName }}</view>
|
||||||
|
<view class="project-depts">{{ project.deptNames || "未配置治疗科室" }}</view>
|
||||||
|
</view>
|
||||||
<view v-if="showBenefitAmount" class="project-price">¥{{ Number(project.price || 0).toFixed(2) }}</view>
|
<view v-if="showBenefitAmount" class="project-price">¥{{ Number(project.price || 0).toFixed(2) }}</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="!projectLoading && !projectList.length" class="empty-text">暂无可用权益项目</view>
|
<view v-if="!projectLoading && !projectList.length" class="empty-text">暂无可用权益项目</view>
|
||||||
<view v-if="projectLoading" class="empty-text">加载中...</view>
|
<view v-if="projectLoading" class="empty-text">加载中...</view>
|
||||||
|
<view v-else-if="projectList.length && !projectHasMore" class="empty-text">已加载全部权益项目</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -146,13 +182,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from "vue";
|
import { nextTick, ref } from "vue";
|
||||||
import { onLoad } from "@dcloudio/uni-app";
|
import { onLoad } from "@dcloudio/uni-app";
|
||||||
import api from "@/utils/api.js";
|
import api from "@/utils/api.js";
|
||||||
import useAccountStore from "@/store/account.js";
|
import useAccountStore from "@/store/account.js";
|
||||||
import fullPage from "@/components/full-page.vue";
|
import fullPage from "@/components/full-page.vue";
|
||||||
|
|
||||||
|
const PENDING_FOLLOWUP_SEND_STORAGE_KEY = "ykt_followup_pending_send";
|
||||||
|
|
||||||
const accountStore = useAccountStore();
|
const accountStore = useAccountStore();
|
||||||
|
const pageRef = ref(null);
|
||||||
|
const groupId = ref("");
|
||||||
const patientId = ref("");
|
const patientId = ref("");
|
||||||
const patientName = ref("");
|
const patientName = ref("");
|
||||||
const teamId = ref("");
|
const teamId = ref("");
|
||||||
@ -167,15 +207,117 @@ const projectPickerIndex = ref(-1);
|
|||||||
const projectKeyword = ref("");
|
const projectKeyword = ref("");
|
||||||
const projectList = ref([]);
|
const projectList = ref([]);
|
||||||
const projectLoading = ref(false);
|
const projectLoading = ref(false);
|
||||||
|
const projectHasMore = ref(true);
|
||||||
|
const projectPage = ref(1);
|
||||||
|
const projectPageSize = 30;
|
||||||
|
const VALID_YEAR_SPAN = 50;
|
||||||
|
const validTimeRange = ref([[], [], []]);
|
||||||
|
const validTimeIndex = ref([0, 0, 0]);
|
||||||
let searchTimer = null;
|
let searchTimer = null;
|
||||||
let entryKey = 0;
|
let entryKey = 0;
|
||||||
|
|
||||||
|
function formatDate(date) {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||||
|
const day = String(date.getDate()).padStart(2, "0");
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTodayParts() {
|
||||||
|
const now = new Date();
|
||||||
|
return {
|
||||||
|
year: now.getFullYear(),
|
||||||
|
month: now.getMonth() + 1,
|
||||||
|
day: now.getDate(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDaysInMonth(year, month) {
|
||||||
|
return new Date(year, month, 0).getDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildYearOptions() {
|
||||||
|
const { year } = getTodayParts();
|
||||||
|
return Array.from({ length: VALID_YEAR_SPAN + 1 }, (_, index) => `${year + index}年`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMonthOptions(year) {
|
||||||
|
const today = getTodayParts();
|
||||||
|
const startMonth = year === today.year ? today.month : 1;
|
||||||
|
return Array.from({ length: 12 - startMonth + 1 }, (_, index) => `${startMonth + index}月`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDayOptions(year, month) {
|
||||||
|
const today = getTodayParts();
|
||||||
|
const startDay = year === today.year && month === today.month ? today.day : 1;
|
||||||
|
const maxDay = getDaysInMonth(year, month);
|
||||||
|
return Array.from({ length: maxDay - startDay + 1 }, (_, index) => `${startDay + index}日`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseValidTime(value) {
|
||||||
|
const today = getTodayParts();
|
||||||
|
if (!value) return today;
|
||||||
|
const [year, month, day] = String(value).split("-").map(Number);
|
||||||
|
if (!year || !month || !day) return today;
|
||||||
|
const selected = { year, month, day };
|
||||||
|
const selectedText = `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
||||||
|
return selectedText < formatDate(new Date()) ? today : selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncValidTimePicker(selected) {
|
||||||
|
const years = buildYearOptions();
|
||||||
|
const year = selected.year;
|
||||||
|
const months = buildMonthOptions(year);
|
||||||
|
const monthValues = months.map((item) => Number(item.replace("月", "")));
|
||||||
|
const month = monthValues.includes(selected.month) ? selected.month : monthValues[0];
|
||||||
|
const days = buildDayOptions(year, month);
|
||||||
|
const dayValues = days.map((item) => Number(item.replace("日", "")));
|
||||||
|
const day = dayValues.includes(selected.day) ? selected.day : dayValues[0];
|
||||||
|
validTimeRange.value = [years, months, days];
|
||||||
|
validTimeIndex.value = [
|
||||||
|
years.findIndex((item) => Number(item.replace("年", "")) === year),
|
||||||
|
monthValues.indexOf(month),
|
||||||
|
dayValues.indexOf(day),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareValidTimePicker(entry) {
|
||||||
|
syncValidTimePicker(parseValidTime(entry.validTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
function onValidTimeColumnChange(event) {
|
||||||
|
const { column, value } = event.detail;
|
||||||
|
const nextIndex = [...validTimeIndex.value];
|
||||||
|
nextIndex[column] = value;
|
||||||
|
|
||||||
|
const year = Number(validTimeRange.value[0][nextIndex[0]].replace("年", ""));
|
||||||
|
if (column === 0) {
|
||||||
|
const months = buildMonthOptions(year);
|
||||||
|
const month = Number(months[0].replace("月", ""));
|
||||||
|
const days = buildDayOptions(year, month);
|
||||||
|
validTimeRange.value = [validTimeRange.value[0], months, days];
|
||||||
|
nextIndex[1] = 0;
|
||||||
|
nextIndex[2] = 0;
|
||||||
|
} else if (column === 1) {
|
||||||
|
const month = Number(validTimeRange.value[1][nextIndex[1]].replace("月", ""));
|
||||||
|
const days = buildDayOptions(year, month);
|
||||||
|
validTimeRange.value = [validTimeRange.value[0], validTimeRange.value[1], days];
|
||||||
|
nextIndex[2] = Math.min(nextIndex[2], days.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
validTimeIndex.value = nextIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareValidTimePicker({ validTime: "" });
|
||||||
|
|
||||||
function createEntry() {
|
function createEntry() {
|
||||||
return {
|
return {
|
||||||
key: ++entryKey,
|
key: ++entryKey,
|
||||||
project: null,
|
project: null,
|
||||||
price: 0,
|
price: 0,
|
||||||
|
totalPrice: 0,
|
||||||
discount: 10,
|
discount: 10,
|
||||||
|
isFree: false,
|
||||||
usageCount: 1,
|
usageCount: 1,
|
||||||
validTime: "",
|
validTime: "",
|
||||||
dept: null,
|
dept: null,
|
||||||
@ -193,6 +335,7 @@ function decodeRouteParam(value = "") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onLoad((options = {}) => {
|
onLoad((options = {}) => {
|
||||||
|
groupId.value = decodeRouteParam(options.groupId);
|
||||||
patientId.value = decodeRouteParam(options.patientId);
|
patientId.value = decodeRouteParam(options.patientId);
|
||||||
patientName.value = decodeRouteParam(options.patientName);
|
patientName.value = decodeRouteParam(options.patientName);
|
||||||
teamId.value = decodeRouteParam(options.teamId);
|
teamId.value = decodeRouteParam(options.teamId);
|
||||||
@ -246,7 +389,7 @@ function openProjectPicker(index) {
|
|||||||
projectPickerIndex.value = index;
|
projectPickerIndex.value = index;
|
||||||
projectPickerVisible.value = true;
|
projectPickerVisible.value = true;
|
||||||
projectKeyword.value = "";
|
projectKeyword.value = "";
|
||||||
loadProjects();
|
loadProjects({ reset: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeProjectPicker() {
|
function closeProjectPicker() {
|
||||||
@ -256,38 +399,99 @@ function closeProjectPicker() {
|
|||||||
|
|
||||||
function searchProjects() {
|
function searchProjects() {
|
||||||
clearTimeout(searchTimer);
|
clearTimeout(searchTimer);
|
||||||
searchTimer = setTimeout(loadProjects, 300);
|
searchTimer = setTimeout(() => loadProjects({ reset: true }), 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadProjects() {
|
function normalizeProject(project = {}) {
|
||||||
|
const depts = Array.isArray(project.depts) ? project.depts : [];
|
||||||
|
return {
|
||||||
|
...project,
|
||||||
|
depts,
|
||||||
|
deptNames: depts.map((dept) => dept?.deptName).filter(Boolean).join("、"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadProjects({ reset = false } = {}) {
|
||||||
|
if (projectLoading.value) return;
|
||||||
|
if (!reset && !projectHasMore.value) return;
|
||||||
|
|
||||||
|
const page = reset ? 1 : projectPage.value;
|
||||||
projectLoading.value = true;
|
projectLoading.value = true;
|
||||||
|
if (reset) {
|
||||||
|
projectList.value = [];
|
||||||
|
projectHasMore.value = true;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const res = await api("getProjectList", {
|
const res = await api("getProjectList", {
|
||||||
corpId: corpId.value,
|
corpId: corpId.value,
|
||||||
page: 1,
|
page,
|
||||||
pageSize: 30,
|
pageSize: projectPageSize,
|
||||||
projectName: projectKeyword.value.trim(),
|
projectName: projectKeyword.value.trim(),
|
||||||
projectStatus: "enable",
|
projectStatus: "enable",
|
||||||
showDepts: true,
|
showDepts: true,
|
||||||
}, false);
|
}, false);
|
||||||
const list = res?.data?.list || res?.list || res?.data || [];
|
const list = res?.data?.list || res?.list || res?.data || [];
|
||||||
projectList.value = Array.isArray(list) ? list : [];
|
const nextList = Array.isArray(list) ? list.map(normalizeProject) : [];
|
||||||
|
projectList.value = reset ? nextList : projectList.value.concat(nextList);
|
||||||
|
projectPage.value = page + 1;
|
||||||
|
projectHasMore.value = nextList.length >= projectPageSize;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
projectList.value = [];
|
if (reset) projectList.value = [];
|
||||||
|
projectHasMore.value = false;
|
||||||
uni.showToast({ title: "项目加载失败", icon: "none" });
|
uni.showToast({ title: "项目加载失败", icon: "none" });
|
||||||
} finally {
|
} finally {
|
||||||
projectLoading.value = false;
|
projectLoading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatTotalPrice(entry) {
|
function loadMoreProjects() {
|
||||||
const price = Number(entry.price);
|
loadProjects();
|
||||||
const discount = Number(entry.discount);
|
}
|
||||||
const usageCount = Number(entry.usageCount);
|
|
||||||
const total = Number.isFinite(price) && Number.isFinite(discount) && Number.isFinite(usageCount)
|
function toNumber(value, fallback = 0) {
|
||||||
? price * (discount / 10) * usageCount
|
const number = Number(value);
|
||||||
: 0;
|
return Number.isFinite(number) ? number : fallback;
|
||||||
return Math.max(0, total).toFixed(2);
|
}
|
||||||
|
|
||||||
|
function toFixedNumber(value, precision = 2) {
|
||||||
|
return Number(toNumber(value).toFixed(precision));
|
||||||
|
}
|
||||||
|
|
||||||
|
function clampNumber(value, min, max) {
|
||||||
|
return Math.min(max, Math.max(min, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateTotalPrice(entry) {
|
||||||
|
if (entry.isFree) {
|
||||||
|
entry.discount = 0;
|
||||||
|
entry.totalPrice = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const total = toNumber(entry.price) * toNumber(entry.usageCount, 1) * (toNumber(entry.discount) / 10);
|
||||||
|
entry.totalPrice = toFixedNumber(Math.max(0, total), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateDiscount(entry) {
|
||||||
|
if (entry.isFree) {
|
||||||
|
entry.discount = 0;
|
||||||
|
entry.totalPrice = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const originalTotal = toNumber(entry.price) * toNumber(entry.usageCount);
|
||||||
|
entry.discount = originalTotal > 0
|
||||||
|
? toFixedNumber(clampNumber((toNumber(entry.totalPrice) / originalTotal) * 10, 0, 10), 1)
|
||||||
|
: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFreeChange(event, entry) {
|
||||||
|
entry.isFree = Array.isArray(event.detail.value) && event.detail.value.includes("free");
|
||||||
|
if (entry.isFree) {
|
||||||
|
entry.discount = 0;
|
||||||
|
entry.totalPrice = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
entry.discount = 10;
|
||||||
|
calculateTotalPrice(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getProjectDepts(entry) {
|
function getProjectDepts(entry) {
|
||||||
@ -309,9 +513,12 @@ function getTreatmentDoctors(entry) {
|
|||||||
function selectProject(project) {
|
function selectProject(project) {
|
||||||
const entry = entries.value[projectPickerIndex.value];
|
const entry = entries.value[projectPickerIndex.value];
|
||||||
if (!entry) return;
|
if (!entry) return;
|
||||||
entry.project = project;
|
const normalizedProject = normalizeProject(project);
|
||||||
entry.price = Number(project.price || 0);
|
entry.project = normalizedProject;
|
||||||
|
entry.price = Number(normalizedProject.price || 0);
|
||||||
entry.discount = 10;
|
entry.discount = 10;
|
||||||
|
entry.isFree = false;
|
||||||
|
calculateTotalPrice(entry);
|
||||||
entry.dept = getProjectDepts(entry).length === 1 ? getProjectDepts(entry)[0] : null;
|
entry.dept = getProjectDepts(entry).length === 1 ? getProjectDepts(entry)[0] : null;
|
||||||
entry.treatmentDoctorUserId = "";
|
entry.treatmentDoctorUserId = "";
|
||||||
entry.treatmentDoctorName = "";
|
entry.treatmentDoctorName = "";
|
||||||
@ -331,11 +538,33 @@ function changeTreatmentDoctor(event, entry) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function changeValidTime(event, entry) {
|
function changeValidTime(event, entry) {
|
||||||
entry.validTime = event.detail.value;
|
const [yearIndex, monthIndex, dayIndex] = event.detail.value || validTimeIndex.value;
|
||||||
|
const year = Number(validTimeRange.value[0][yearIndex].replace("年", ""));
|
||||||
|
const month = Number(validTimeRange.value[1][monthIndex].replace("月", ""));
|
||||||
|
const day = Number(validTimeRange.value[2][dayIndex].replace("日", ""));
|
||||||
|
const selectedDate = `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
||||||
|
if (selectedDate < formatDate(new Date())) {
|
||||||
|
uni.showToast({
|
||||||
|
title: "不能选择过去的日期",
|
||||||
|
icon: "none",
|
||||||
|
duration: 2000,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
entry.validTime = selectedDate;
|
||||||
|
validTimeIndex.value = [yearIndex, monthIndex, dayIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearValidTime(entry) {
|
||||||
|
entry.validTime = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function addEntry() {
|
function addEntry() {
|
||||||
entries.value.push(createEntry());
|
const entry = createEntry();
|
||||||
|
entries.value.push(entry);
|
||||||
|
nextTick(() => {
|
||||||
|
pageRef.value?.scrollToBottom?.();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeEntry(index) {
|
function removeEntry(index) {
|
||||||
@ -354,14 +583,55 @@ function validateEntries() {
|
|||||||
if (!entry.project?._id) return `${label}请选择项目`;
|
if (!entry.project?._id) return `${label}请选择项目`;
|
||||||
if (Number(entry.price) < 0 || Number.isNaN(Number(entry.price))) return `${label}价格不能小于0`;
|
if (Number(entry.price) < 0 || Number.isNaN(Number(entry.price))) return `${label}价格不能小于0`;
|
||||||
if (!Number.isFinite(Number(entry.discount)) || Number(entry.discount) < 0 || Number(entry.discount) > 10) return `${label}折扣需在0到10之间`;
|
if (!Number.isFinite(Number(entry.discount)) || Number(entry.discount) < 0 || Number(entry.discount) > 10) return `${label}折扣需在0到10之间`;
|
||||||
|
if (!Number.isFinite(Number(entry.totalPrice)) || Number(entry.totalPrice) < 0) return `${label}总价不能小于0`;
|
||||||
if (!Number(entry.usageCount) || Number(entry.usageCount) <= 0) return `${label}数量必须大于0`;
|
if (!Number(entry.usageCount) || Number(entry.usageCount) <= 0) return `${label}数量必须大于0`;
|
||||||
if (!entry.dept?._id) return `${label}请选择治疗科室`;
|
if (!entry.dept?._id) return `${label}请选择治疗科室`;
|
||||||
if (!entry.validTime) return `${label}请选择有效期`;
|
|
||||||
}
|
}
|
||||||
if (!patientId.value) return "当前会话缺少患者档案关联";
|
if (!patientId.value) return "当前会话缺少患者档案关联";
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildBenefitMessage(createdEntries) {
|
||||||
|
const items = createdEntries.map((entry) => ({
|
||||||
|
projectId: entry.project._id,
|
||||||
|
projectName: entry.project.projectName,
|
||||||
|
usageCount: Number(entry.usageCount || 0),
|
||||||
|
validTime: entry.validTime ? new Date(`${entry.validTime} 23:59:59`).getTime() : null,
|
||||||
|
validTimeText: entry.validTime || "无限期",
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
conversationID: groupId.value ? `GROUP${groupId.value}` : "",
|
||||||
|
groupId: groupId.value,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
type: "benefit",
|
||||||
|
content: {
|
||||||
|
title: "医生为您录入专属权益",
|
||||||
|
patientId: patientId.value,
|
||||||
|
patientName: patientName.value,
|
||||||
|
corpId: corpId.value,
|
||||||
|
teamId: teamId.value,
|
||||||
|
items,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
context: {
|
||||||
|
userId: accountStore.doctorInfo?.userid || accountStore.account?.userid || "",
|
||||||
|
customerId: patientId.value,
|
||||||
|
customerName: patientName.value,
|
||||||
|
corpId: corpId.value,
|
||||||
|
teamId: teamId.value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function queueBenefitMessage(createdEntries) {
|
||||||
|
if (!groupId.value || !createdEntries.length) return;
|
||||||
|
uni.setStorageSync(PENDING_FOLLOWUP_SEND_STORAGE_KEY, buildBenefitMessage(createdEntries));
|
||||||
|
}
|
||||||
|
|
||||||
async function submitEntries() {
|
async function submitEntries() {
|
||||||
if (saving.value) return;
|
if (saving.value) return;
|
||||||
const errorMessage = validateEntries();
|
const errorMessage = validateEntries();
|
||||||
@ -372,10 +642,12 @@ async function submitEntries() {
|
|||||||
saving.value = true;
|
saving.value = true;
|
||||||
const userId = accountStore.doctorInfo?.userid || accountStore.account?.userid || "";
|
const userId = accountStore.doctorInfo?.userid || accountStore.account?.userid || "";
|
||||||
try {
|
try {
|
||||||
|
const createdEntries = [];
|
||||||
for (const entry of entries.value) {
|
for (const entry of entries.value) {
|
||||||
const count = Number(entry.usageCount);
|
const count = Number(entry.usageCount);
|
||||||
const price = Number(entry.price);
|
const price = Number(entry.price);
|
||||||
const discount = Number(entry.discount);
|
const discount = Number(entry.discount);
|
||||||
|
const totalPrice = entry.isFree ? 0 : Number(entry.totalPrice || 0);
|
||||||
const treatmentData = {
|
const treatmentData = {
|
||||||
customerId: patientId.value,
|
customerId: patientId.value,
|
||||||
customerName: patientName.value,
|
customerName: patientName.value,
|
||||||
@ -384,9 +656,9 @@ async function submitEntries() {
|
|||||||
usageCount: count,
|
usageCount: count,
|
||||||
restUsageCount: count,
|
restUsageCount: count,
|
||||||
price,
|
price,
|
||||||
totalPrice: price * count * (discount / 10),
|
totalPrice,
|
||||||
discount,
|
discount,
|
||||||
isFree: false,
|
isFree: Boolean(entry.isFree),
|
||||||
treatmentDeptName: entry.dept.deptName,
|
treatmentDeptName: entry.dept.deptName,
|
||||||
treatmentDeptId: entry.dept._id,
|
treatmentDeptId: entry.dept._id,
|
||||||
treatmentDept_id: entry.dept._id,
|
treatmentDept_id: entry.dept._id,
|
||||||
@ -397,13 +669,15 @@ async function submitEntries() {
|
|||||||
billdCreator: userId,
|
billdCreator: userId,
|
||||||
createTreatementTime: Date.now(),
|
createTreatementTime: Date.now(),
|
||||||
billTime: Date.now(),
|
billTime: Date.now(),
|
||||||
validTime: new Date(`${entry.validTime} 23:59:59`).getTime(),
|
validTime: entry.validTime ? new Date(`${entry.validTime} 23:59:59`).getTime() : null,
|
||||||
teamId: teamId.value,
|
teamId: teamId.value,
|
||||||
corpId: corpId.value,
|
corpId: corpId.value,
|
||||||
};
|
};
|
||||||
const res = await api("addTreatmentRecord", { params: treatmentData }, false);
|
const res = await api("addTreatmentRecord", { params: treatmentData }, false);
|
||||||
if (!res?.success) throw new Error(res?.message || "权益录入失败");
|
if (!res?.success) throw new Error(res?.message || "权益录入失败");
|
||||||
|
createdEntries.push(entry);
|
||||||
}
|
}
|
||||||
|
queueBenefitMessage(createdEntries);
|
||||||
uni.showToast({ title: "权益录入成功", icon: "success" });
|
uni.showToast({ title: "权益录入成功", icon: "success" });
|
||||||
setTimeout(goBack, 500);
|
setTimeout(goBack, 500);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -421,7 +695,7 @@ async function submitEntries() {
|
|||||||
|
|
||||||
.benefit-page {
|
.benefit-page {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
padding: 20rpx 24rpx 220rpx;
|
padding: 20rpx 24rpx 32rpx;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -555,13 +829,14 @@ async function submitEntries() {
|
|||||||
.picker-value {
|
.picker-value {
|
||||||
min-height: 76rpx;
|
min-height: 76rpx;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border: 1rpx solid #e5e7eb;
|
border: 1rpx solid #d1d5db;
|
||||||
border-radius: 12rpx;
|
border-radius: 12rpx;
|
||||||
padding: 0 20rpx;
|
padding: 0 20rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
|
color: #111827;
|
||||||
}
|
}
|
||||||
|
|
||||||
.locked-input {
|
.locked-input {
|
||||||
@ -592,6 +867,57 @@ async function submitEntries() {
|
|||||||
|
|
||||||
.field-block {
|
.field-block {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-block-full {
|
||||||
|
flex-basis: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discount-input-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discount-input-wrap .number-input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.free-check {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.free-check-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4rpx;
|
||||||
|
color: #374151;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.valid-time-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.valid-time-picker {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unlimited-button {
|
||||||
|
flex-shrink: 0;
|
||||||
|
height: 76rpx;
|
||||||
|
line-height: 76rpx;
|
||||||
|
padding: 0 20rpx;
|
||||||
|
border: 1rpx solid #d1d5db;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
color: #0877f1;
|
||||||
|
font-size: 26rpx;
|
||||||
|
background: #f8fbff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.total-price {
|
.total-price {
|
||||||
@ -606,6 +932,17 @@ async function submitEntries() {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
color: #111827;
|
color: #111827;
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number-input[disabled] {
|
||||||
|
color: #4b5563;
|
||||||
|
background: #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-input {
|
||||||
|
color: #c2410c;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-entry {
|
.add-entry {
|
||||||
@ -632,7 +969,7 @@ async function submitEntries() {
|
|||||||
.footer-bar {
|
.footer-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 20rpx;
|
gap: 20rpx;
|
||||||
padding: 24rpx 24rpx calc(28rpx + env(safe-area-inset-bottom));
|
padding: 20rpx 24rpx;
|
||||||
background: rgba(245, 246, 248, 0.96);
|
background: rgba(245, 246, 248, 0.96);
|
||||||
border-top: 1rpx solid #eef2f7;
|
border-top: 1rpx solid #eef2f7;
|
||||||
backdrop-filter: blur(12px);
|
backdrop-filter: blur(12px);
|
||||||
@ -716,13 +1053,29 @@ async function submitEntries() {
|
|||||||
.project-item {
|
.project-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 20rpx;
|
||||||
padding: 24rpx 8rpx;
|
padding: 24rpx 8rpx;
|
||||||
border-bottom: 1rpx solid #f0f0f0;
|
border-bottom: 1rpx solid #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.project-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.project-name {
|
.project-name {
|
||||||
color: #333;
|
color: #333;
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-depts {
|
||||||
|
margin-top: 8rpx;
|
||||||
|
color: #8b95a1;
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 1.35;
|
||||||
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-price {
|
.project-price {
|
||||||
|
|||||||
@ -338,6 +338,18 @@ $primary-color: #0877F1;
|
|||||||
border-top: 10rpx solid transparent;
|
border-top: 10rpx solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.custom-bubble,
|
||||||
|
.benefit-bubble {
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
background: transparent;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-bubble {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
/* 医生发送的蓝色气泡(用于消息卡片) */
|
/* 医生发送的蓝色气泡(用于消息卡片) */
|
||||||
.doctor-bubble-blue {
|
.doctor-bubble-blue {
|
||||||
background-color: #0877F1;
|
background-color: #0877F1;
|
||||||
|
|||||||
@ -412,7 +412,7 @@ const goToArticleList = () => {
|
|||||||
const goToBenefitEntry = () => {
|
const goToBenefitEntry = () => {
|
||||||
showMorePanel.value = false;
|
showMorePanel.value = false;
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/message/benefit-entry?patientId=${encodeURIComponent(props.patientId)}&patientName=${encodeURIComponent(props.patientInfo.name || "")}&teamId=${encodeURIComponent(props.teamId)}&teamName=${encodeURIComponent(props.teamName)}&corpId=${encodeURIComponent(props.corpId)}`,
|
url: `/pages/message/benefit-entry?groupId=${encodeURIComponent(props.groupId)}&patientId=${encodeURIComponent(props.patientId)}&patientName=${encodeURIComponent(props.patientInfo.name || "")}&teamId=${encodeURIComponent(props.teamId)}&teamName=${encodeURIComponent(props.teamName)}&corpId=${encodeURIComponent(props.corpId)}`,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -37,7 +37,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 自定义消息卡片 -->
|
<!-- 自定义消息卡片 -->
|
||||||
<template v-else-if="message.type === 'TIMCustomElem'">
|
<block v-else-if="message.type === 'TIMCustomElem'">
|
||||||
<!-- 文章消息 -->
|
<!-- 文章消息 -->
|
||||||
<view v-if="customMessageType === 'article'" class="article-card" @click="handleArticleClick(message)">
|
<view v-if="customMessageType === 'article'" class="article-card" @click="handleArticleClick(message)">
|
||||||
<view class="article-content">
|
<view class="article-content">
|
||||||
@ -56,6 +56,28 @@
|
|||||||
<image v-if="surveyData.imgUrl" class="survey-image" :src="surveyData.imgUrl" mode="aspectFill" />
|
<image v-if="surveyData.imgUrl" class="survey-image" :src="surveyData.imgUrl" mode="aspectFill" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 权益消息 -->
|
||||||
|
<view v-else-if="customMessageType === 'benefit'" class="benefit-card">
|
||||||
|
<view class="benefit-title">{{ benefitData.title }}</view>
|
||||||
|
<view class="benefit-content">
|
||||||
|
<view v-for="(item, index) in benefitData.items" :key="`${item.projectId}-${index}`" class="benefit-item">
|
||||||
|
<view class="benefit-project-row">
|
||||||
|
<text v-if="index === 0" class="benefit-label">项目:</text>
|
||||||
|
<text v-else class="benefit-label-placeholder"></text>
|
||||||
|
<text class="benefit-project-name">{{ item.projectName }}</text>
|
||||||
|
<text class="benefit-count">X{{ item.usageCount }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="benefit-valid-row">
|
||||||
|
<text class="benefit-valid-label">有效期:</text>
|
||||||
|
<text class="benefit-valid-value">{{ item.validTimeText || '无限期' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="benefit-detail-button" @click.stop="handleBenefitClick">
|
||||||
|
<text class="benefit-detail-text">查看详情</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 其他自定义消息 -->
|
<!-- 其他自定义消息 -->
|
||||||
<!-- <view
|
<!-- <view
|
||||||
v-else
|
v-else
|
||||||
@ -69,7 +91,7 @@
|
|||||||
@viewDetail="$emit('viewDetail', $event)"
|
@viewDetail="$emit('viewDetail', $event)"
|
||||||
/>
|
/>
|
||||||
</view> -->
|
</view> -->
|
||||||
</template>
|
</block>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@ -124,6 +146,19 @@ const surveyData = computed(() => ({
|
|||||||
imgUrl: payloadData.value.imgUrl || "",
|
imgUrl: payloadData.value.imgUrl || "",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
const benefitData = computed(() => ({
|
||||||
|
title: payloadData.value.title || "医生为您录入专属权益",
|
||||||
|
patientId: payloadData.value.patientId || "",
|
||||||
|
items: Array.isArray(payloadData.value.items) ? payloadData.value.items : [],
|
||||||
|
}))
|
||||||
|
|
||||||
|
const handleBenefitClick = () => {
|
||||||
|
if (!benefitData.value.patientId) return;
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/case/archive-detail?id=${encodeURIComponent(String(benefitData.value.patientId))}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 计算图片样式
|
// 计算图片样式
|
||||||
const getImageStyle = (imageInfo) => {
|
const getImageStyle = (imageInfo) => {
|
||||||
// 如果没有尺寸信息,使用默认样式
|
// 如果没有尺寸信息,使用默认样式
|
||||||
@ -264,4 +299,95 @@ function isWechatChannels(data) {
|
|||||||
width: 480rpx;
|
width: 480rpx;
|
||||||
height: 320rpx;
|
height: 320rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.benefit-card {
|
||||||
|
width: 520rpx;
|
||||||
|
max-width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
padding: 22rpx 18rpx 18rpx;
|
||||||
|
background: linear-gradient(180deg, #0b75f2 0%, #0067df 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-title {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.3;
|
||||||
|
margin-bottom: 18rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-content {
|
||||||
|
border: 4rpx solid rgba(255, 255, 255, 0.28);
|
||||||
|
border-radius: 8rpx;
|
||||||
|
padding: 24rpx 24rpx 28rpx;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-item + .benefit-item {
|
||||||
|
margin-top: 18rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-project-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-label,
|
||||||
|
.benefit-label-placeholder {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 104rpx;
|
||||||
|
color: #8b8f96;
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-project-name {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
color: #111827;
|
||||||
|
font-size: 30rpx;
|
||||||
|
line-height: 1.4;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-count {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 16rpx;
|
||||||
|
color: #111827;
|
||||||
|
font-size: 30rpx;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-valid-row {
|
||||||
|
display: flex;
|
||||||
|
margin-left: 104rpx;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-valid-label,
|
||||||
|
.benefit-valid-value {
|
||||||
|
color: #8b8f96;
|
||||||
|
font-size: 26rpx;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-detail-button {
|
||||||
|
height: 82rpx;
|
||||||
|
margin-top: 28rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: linear-gradient(180deg, #0b75f2 0%, #0067df 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-detail-text {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -242,7 +242,7 @@ import {
|
|||||||
const timChatManager = globalTimChatManager;
|
const timChatManager = globalTimChatManager;
|
||||||
|
|
||||||
const PENDING_FOLLOWUP_SEND_STORAGE_KEY = "ykt_followup_pending_send";
|
const PENDING_FOLLOWUP_SEND_STORAGE_KEY = "ykt_followup_pending_send";
|
||||||
const pendingFollowUpSendConsumed = ref(false);
|
const pendingFollowUpSendConsumedToken = ref("");
|
||||||
const initialMessageListLoaded = ref(false);
|
const initialMessageListLoaded = ref(false);
|
||||||
const isCallbacksInitialized = ref(false); // 标记回调是否已初始化
|
const isCallbacksInitialized = ref(false); // 标记回调是否已初始化
|
||||||
|
|
||||||
@ -253,17 +253,18 @@ function normalizeGroupId(v) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function tryConsumePendingFollowUpSend() {
|
async function tryConsumePendingFollowUpSend() {
|
||||||
if (pendingFollowUpSendConsumed.value) return;
|
|
||||||
|
|
||||||
// 等待 IM 就绪与首屏消息加载完成,避免发送的本地消息被列表回调覆盖
|
|
||||||
if (!timChatManager?.isLoggedIn) return;
|
|
||||||
if (!initialMessageListLoaded.value) return;
|
|
||||||
|
|
||||||
const raw = uni.getStorageSync(PENDING_FOLLOWUP_SEND_STORAGE_KEY);
|
const raw = uni.getStorageSync(PENDING_FOLLOWUP_SEND_STORAGE_KEY);
|
||||||
const payload = raw && typeof raw === "object" ? raw : null;
|
const payload = raw && typeof raw === "object" ? raw : null;
|
||||||
if (!payload) return;
|
if (!payload) return;
|
||||||
|
|
||||||
const createdAt = Number(payload.createdAt || 0) || 0;
|
const createdAt = Number(payload.createdAt || 0) || 0;
|
||||||
|
const payloadToken = String(createdAt || payload.conversationID || "");
|
||||||
|
if (payloadToken && pendingFollowUpSendConsumedToken.value === payloadToken) return;
|
||||||
|
|
||||||
|
// 等待 IM 就绪与首屏消息加载完成,避免发送的本地消息被列表回调覆盖
|
||||||
|
if (!timChatManager?.isLoggedIn) return;
|
||||||
|
if (!initialMessageListLoaded.value) return;
|
||||||
|
|
||||||
// 过期就清理,避免误发送
|
// 过期就清理,避免误发送
|
||||||
if (createdAt && Date.now() - createdAt > 5 * 60 * 1000) {
|
if (createdAt && Date.now() - createdAt > 5 * 60 * 1000) {
|
||||||
uni.removeStorageSync(PENDING_FOLLOWUP_SEND_STORAGE_KEY);
|
uni.removeStorageSync(PENDING_FOLLOWUP_SEND_STORAGE_KEY);
|
||||||
@ -291,7 +292,7 @@ async function tryConsumePendingFollowUpSend() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pendingFollowUpSendConsumed.value = true;
|
pendingFollowUpSendConsumedToken.value = payloadToken;
|
||||||
// 先清理再发,避免页面重复初始化导致二次发送
|
// 先清理再发,避免页面重复初始化导致二次发送
|
||||||
uni.removeStorageSync(PENDING_FOLLOWUP_SEND_STORAGE_KEY);
|
uni.removeStorageSync(PENDING_FOLLOWUP_SEND_STORAGE_KEY);
|
||||||
|
|
||||||
@ -456,6 +457,17 @@ const fetchGroupOrderStatus = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getCustomMessageType(message) {
|
||||||
|
try {
|
||||||
|
const data = typeof message.payload?.data === "string"
|
||||||
|
? JSON.parse(message.payload.data)
|
||||||
|
: message.payload?.data || {};
|
||||||
|
return data?.type || "";
|
||||||
|
} catch (error) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 获取消息气泡样式类
|
// 获取消息气泡样式类
|
||||||
function getBubbleClass(message) {
|
function getBubbleClass(message) {
|
||||||
// 图片消息不需要气泡背景
|
// 图片消息不需要气泡背景
|
||||||
@ -463,7 +475,10 @@ function getBubbleClass(message) {
|
|||||||
return "image-bubble";
|
return "image-bubble";
|
||||||
}
|
}
|
||||||
if (message.type === "TIMCustomElem") {
|
if (message.type === "TIMCustomElem") {
|
||||||
return message.flow === "out" ? "" : "";
|
if (getCustomMessageType(message) === "benefit") {
|
||||||
|
return "benefit-bubble";
|
||||||
|
}
|
||||||
|
return "custom-bubble";
|
||||||
}
|
}
|
||||||
return message.flow === "out" ? "user-bubble" : "doctor-bubble";
|
return message.flow === "out" ? "user-bubble" : "doctor-bubble";
|
||||||
}
|
}
|
||||||
@ -958,6 +973,9 @@ onShow(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
startIMMonitoring(30000);
|
startIMMonitoring(30000);
|
||||||
|
nextTick(() => {
|
||||||
|
tryConsumePendingFollowUpSend();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听回访任务发送事件
|
// 监听回访任务发送事件
|
||||||
|
|||||||
@ -402,6 +402,45 @@ function buildSurveyMessage(survey, surveyLink) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function sendBenefitMessage(benefit, options = {}) {
|
||||||
|
if (!benefit || !Array.isArray(benefit.items) || !benefit.items.length) {
|
||||||
|
toast('权益信息不完整');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!globalTimChatManager?.isLoggedIn) {
|
||||||
|
toast('IM系统未就绪,请稍后重试');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const customMessageData = {
|
||||||
|
type: 'benefit',
|
||||||
|
messageType: 'benefit',
|
||||||
|
title: benefit.title || '医生为您录入专属权益',
|
||||||
|
desc: benefit.desc || '点击查看详情',
|
||||||
|
patientId: benefit.patientId || options.customerId || '',
|
||||||
|
patientName: benefit.patientName || options.customerName || '',
|
||||||
|
corpId: benefit.corpId || options.corpId || '',
|
||||||
|
teamId: benefit.teamId || options.teamId || '',
|
||||||
|
items: benefit.items.map((item) => ({
|
||||||
|
projectId: item.projectId || '',
|
||||||
|
projectName: item.projectName || '',
|
||||||
|
usageCount: Number(item.usageCount || 0),
|
||||||
|
validTime: item.validTime || null,
|
||||||
|
validTimeText: item.validTimeText || '无限期',
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await globalTimChatManager.sendCustomMessage(customMessageData);
|
||||||
|
return Boolean(result?.success);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('发送权益消息异常:', error);
|
||||||
|
toast('发送权益消息失败');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理回访任务消息发送
|
* 处理回访任务消息发送
|
||||||
* @param {Object} messages - 消息数组
|
* @param {Object} messages - 消息数组
|
||||||
@ -439,6 +478,14 @@ export async function handleFollowUpMessages(messages, context = {}) {
|
|||||||
customerName: context.customerName,
|
customerName: context.customerName,
|
||||||
corpId: context.corpId
|
corpId: context.corpId
|
||||||
});
|
});
|
||||||
|
} else if (msg.type === 'benefit') {
|
||||||
|
success = await sendBenefitMessage(msg.content, {
|
||||||
|
userId: context.userId,
|
||||||
|
customerId: context.customerId,
|
||||||
|
customerName: context.customerName,
|
||||||
|
corpId: context.corpId,
|
||||||
|
teamId: context.teamId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user