Merge branch 'dev-2.4'

This commit is contained in:
huxuejian 2026-06-01 11:03:32 +08:00
commit 6de95f5b47
59 changed files with 1764 additions and 474 deletions

View File

@ -1,4 +1,5 @@
MP_API_BASE_URL=https://patient.youcan365.com MP_API_BASE_URL=https://patient.youcan365.com
MP_CACHE_PREFIX=development MP_CACHE_PREFIX=development
MP_WX_APP_ID=wx6ee11733526b4f04 MP_WX_APP_ID=wx6ee11733526b4f04
MP_SHARE_WX_APP_VERSION=2
MP_TIM_SDK_APP_ID=1600126296 MP_TIM_SDK_APP_ID=1600126296

View File

@ -2,4 +2,5 @@ MP_API_BASE_URL=https://ykt.youcan365.com
MP_CACHE_PREFIX=production MP_CACHE_PREFIX=production
MP_WX_APP_ID=wx6ee11733526b4f04 MP_WX_APP_ID=wx6ee11733526b4f04
MP_TIM_SDK_APP_ID=1600136080 MP_TIM_SDK_APP_ID=1600136080
MP_CORP_ID=wpLgjyawAA8N0gWmXgyJq8wpjGcOT7fg MP_CORP_ID=wpLgjyawAA8N0gWmXgyJq8wpjGcOT7fg
MP_VERIFY_IM_CORP_ID=YES

16
App.vue
View File

@ -196,6 +196,10 @@ page {
padding-top: 10rpx; padding-top: 10rpx;
} }
.pt-12 {
padding-top: 24rpx;
}
.pt-15 { .pt-15 {
padding-top: 30rpx; padding-top: 30rpx;
} }
@ -251,6 +255,10 @@ page {
margin-bottom: 20rpx; margin-bottom: 20rpx;
} }
.mt-5 {
margin-top: 10rpx;
}
.mt-10 { .mt-10 {
margin-top: 20rpx; margin-top: 20rpx;
} }
@ -326,19 +334,19 @@ page {
} }
.text-sm { .text-sm {
font-size: 24rpx; font-size: 26rpx;
} }
.text-base { .text-base {
font-size: 28rpx; font-size: 30rpx;
} }
.text-lg { .text-lg {
font-size: 32rpx; font-size: 34rpx;
} }
.text-xl { .text-xl {
font-size: 36rpx; font-size: 38rpx;
} }
.leading-normal { .leading-normal {

View File

@ -16,11 +16,11 @@
display: flex; display: flex;
align-items: center; align-items: center;
text-align: right; text-align: right;
font-size: 28rpx; font-size: 30rpx;
} }
.form__placeholder { .form__placeholder {
font-size: 28rpx; font-size: 30rpx;
color: #666; color: #666;
} }
@ -44,7 +44,7 @@
align-items: center; align-items: center;
padding: 24rpx 30rpx; padding: 24rpx 30rpx;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
font-size: 28rpx; font-size: 30rpx;
} }
.form-row__label { .form-row__label {

View File

@ -61,7 +61,7 @@ function change(e) {
.form-input { .form-input {
flex-grow: 1; flex-grow: 1;
font-size: 28rpx; font-size: 30rpx;
} }
.appendText { .appendText {

View File

@ -0,0 +1,89 @@
<template>
<view class="textarea-row py-12 px-15">
<view class="flex justify-between items-center" @click="addRow()">
<view class="form-row__label text-base">
{{ name || '阳性发现' }}<text v-if="required" class="form-cell--required"></text>
</view>
<view class="pl-5">
<uni-icons type="plus" size="20" color="#0074ff"></uni-icons>
</view>
</view>
<view v-for="(i, idx) in value" :key="idx" class="pt-12" :class="idx > 0 ? 'mt-12 border-t' : ''">
<view class="flex justify-between items-center">
<view class="text-base text-dark">{{ idx + 1 }}阳性发现</view>
<view class="pl-5" @click="remove(idx)">
<uni-icons type="minus" size="20" color="#f87171"></uni-icons>
</view>
</view>
<view class="border p-10 mt-12 rounded-sm">
<textarea v-model="i.category" :auto-height="true" class="w-full h-full"></textarea>
</view>
<view class="text-base mt-12 text-dark">处理意见</view>
<view class="border p-10 mt-12 rounded-sm">
<textarea v-model="i.opinion" :auto-height="true" class="w-full h-full"></textarea>
</view>
</view>
</view>
</template>
<script setup>
import { computed } from 'vue';
const emits = defineEmits(['change']);
const props = defineProps({
form: {
type: Object,
default: () => ({})
},
name: {
default: ''
},
required: {
type: Boolean,
default: false
},
title: {
default: ''
},
disableChange: {
type: Boolean,
default: false
}
})
const value = computed(() => Array.isArray(props.form[props.title]) ? props.form[props.title] : [])
function addRow() {
if (props.disableChange) return;
const len = value.value.length;
if (len === 0) {
return change([{ category: '', opinion: '' }])
}
const last = value.value[len - 1];
const category = last.category || '';
const opinion = last.opinion || '';
if (category === '' && opinion === '') {
return;
}
change([...value.value, { category: '', opinion: '' }])
}
function remove(idx) {
if (props.disableChange) return;
change(value.value.filter((i, j) => j !== idx));
}
function change(e) {
emits('change', {
title: props.title,
value: e
})
}
</script>
<style>
@import '../cell-style.css';
.border-t {
border-top: 1px solid #eee;
}
</style>

View File

@ -66,12 +66,12 @@ function change(e) {
.textarea-row { .textarea-row {
padding: 24rpx 30rpx; padding: 24rpx 30rpx;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
font-size: 28rpx; font-size: 30rpx;
} }
.form-textarea { .form-textarea {
width: 100%; width: 100%;
font-size: 28rpx; font-size: 30rpx;
border: 1px solid #eee; border: 1px solid #eee;
padding: 20rpx; padding: 20rpx;
border-radius: 8rpx; border-radius: 8rpx;
@ -82,6 +82,6 @@ function change(e) {
padding-top: 20rpx; padding-top: 20rpx;
text-align: right; text-align: right;
color: #666; color: #666;
font-size: 24rpx; font-size: 26rpx;
} }
</style> </style>

View File

@ -6,11 +6,12 @@
<view class="flex flex-wrap"> <view class="flex flex-wrap">
<view v-for="(file, idx) in files" :key="idx" class="upload-item mt-10"> <view v-for="(file, idx) in files" :key="idx" class="upload-item mt-10">
<image v-if="file.isImage" :src="file.url" class="w-full h-full"></image> <image v-if="file.isImage" :src="file.url" class="w-full h-full"></image>
<image v-else-if="file.isPdf" src="/static/pdf.svg" class="w-full h-full"></image>
<image v-else src="/static/file.svg" class="w-full h-full"></image> <image v-else src="/static/file.svg" class="w-full h-full"></image>
<uni-icons type="close" :size="32" color="red" class="remove-icon" @click="remove(idx)"></uni-icons> <uni-icons type="close" :size="32" color="red" class="remove-icon" @click="remove(idx)"></uni-icons>
</view> </view>
<view v-if="value.length < 3" <view v-if="value.length < 10"
class="upload-item border-primary mt-10 flex items-center justify-center text-primary" @click="addImage()"> class="upload-item border-primary mt-10 flex items-center justify-center text-primary" @click="chooseType()">
<uni-icons type="camera" :size="40" color="#0074ff"></uni-icons> <uni-icons type="camera" :size="40" color="#0074ff"></uni-icons>
</view> </view>
</view> </view>
@ -48,10 +49,73 @@ const files = computed(() => value.value.map(i => {
url: i.url, url: i.url,
name: i.name, name: i.name,
type: i.type, type: i.type,
isImage: /image/i.test(i.type) isImage: /image/i.test(i.type),
isPdf: /application\/pdf/i.test(i.type),
} }
})) }))
function chooseType() {
uni.showActionSheet({
itemList: ['图片', 'PDF'],
success: (res) => {
if (res.tapIndex === 0) {
addImage()
} else if (res.tapIndex === 1) {
addPdf()
}
}
})
}
function addPdf() {
wx.chooseMessageFile({
count: 1, // 1
type: 'all', //
success: async (res) => {
const file = res.tempFiles[0];
const { path, name, size } = file;
const type = checkFileValid(name, size);
//
if (!type) return;
loading();
const result = await upload(path);
hideLoading();
if (result) {
change([...value.value, { url: result, type }])
} else {
toast('上传失败')
}
},
fail: (err) => {
if (/cancel/i.test(err.errMsg)) {
// toast('')
} else {
toast('上传失败')
}
}
})
}
function checkFileValid(fileName, fileSize) {
//
const ext = fileName.split('.').pop().toLowerCase();
// (10MB)
const maxSize = 10 * 1024 * 1024;
if (fileSize > maxSize) {
toast('文件大小不能超过10MB')
return false;
}
if (['jpg', 'jpeg', 'png'].includes(ext)) {
return 'image/png'
}
if (ext === 'pdf') {
return 'application/pdf'
}
toast('仅支持图片或PDF')
return false;
}
function addImage() { function addImage() {
uni.chooseImage({ uni.chooseImage({
count: 1, count: 1,
@ -64,6 +128,13 @@ function addImage() {
} else { } else {
toast('上传失败') toast('上传失败')
} }
},
fail: (err) => {
if (/cancel/i.test(err.errMsg)) {
// toast('')
} else {
toast('上传失败')
}
} }
}) })
} }
@ -97,7 +168,7 @@ function remove(idx) {
.textarea-row { .textarea-row {
padding: 24rpx 30rpx; padding: 24rpx 30rpx;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
font-size: 28rpx; font-size: 30rpx;
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<!-- <view class="px-10">{{ attrs.name }} {{ attrs.title }} {{ attrs.type }}</view> --> <!-- <view class="text-danger px-10">{{ attrs.name }} {{ attrs.title }} {{ attrs.type }}</view> -->
<form-datepicker v-if="attrs.type === 'date'" v-bind="attrs" :form="form" :disableChange="disableChange" <form-datepicker v-if="attrs.type === 'date'" v-bind="attrs" :form="form" :disableChange="disableChange"
@change="change" /> @change="change" />
<form-input v-else-if="attrs.type === 'input'" v-bind="attrs" :form="form" :disableChange="disableChange" <form-input v-else-if="attrs.type === 'input'" v-bind="attrs" :form="form" :disableChange="disableChange"
@ -18,6 +18,7 @@
@addRule="addRule"></form-mult-disease> @addRule="addRule"></form-mult-disease>
<form-upload v-else-if="attrs.type === 'files'" v-bind="attrs" :form="form" @change="change" /> <form-upload v-else-if="attrs.type === 'files'" v-bind="attrs" :form="form" @change="change" />
<form-mult-other v-else-if="attrs.type === 'multiSelectAndOther'" v-bind="attrs" :form="form" @change="change" /> <form-mult-other v-else-if="attrs.type === 'multiSelectAndOther'" v-bind="attrs" :form="form" @change="change" />
<form-positive-find v-else-if="attrs.type === 'positiveFind'" v-bind="attrs" :form="form" @change="change" />
<!-- <!--
<form-operation v-else-if="attrs.title === 'surgicalHistory'" v-bind="attrs" :form="form" @change="change" <form-operation v-else-if="attrs.title === 'surgicalHistory'" v-bind="attrs" :form="form" @change="change"
@addRule="addRule" /> @addRule="addRule" />
@ -39,6 +40,7 @@ import formTextarea from './form-textarea.vue';
import formMultDisease from './form-multiple-diseases.vue'; import formMultDisease from './form-multiple-diseases.vue';
import formUpload from './form-upload.vue'; import formUpload from './form-upload.vue';
import formMultOther from './form-mult-select-and-other.vue'; import formMultOther from './form-mult-select-and-other.vue';
import formPositiveFind from './form-positive-find.vue';
defineProps({ defineProps({
form: { form: {

View File

@ -32,7 +32,7 @@ const props = defineProps({
} }
}) })
const formCellType = ['input', 'select', 'date', 'radio', 'region', 'textarea', 'multiSelectAndOther', 'selfMultipleDiseases', 'files','diagnosis']; const formCellType = ['input', 'select', 'date', 'radio', 'region', 'textarea', 'multiSelectAndOther', 'selfMultipleDiseases', 'files','diagnosis','positiveFind'];
const formCellTitle = ['surgicalHistory']; const formCellTitle = ['surgicalHistory'];
const customCellType = ['BMI', 'bloodPressure']; const customCellType = ['BMI', 'bloodPressure'];
const disabledMap = computed(() => props.disableTitles.reduce((m, i) => { const disabledMap = computed(() => props.disableTitles.reduce((m, i) => {

View File

@ -468,7 +468,7 @@
} }
} }
.zh-ellipsis { .zh-ellipsis {
font-size: 30rpx; font-size: 32rpx;
color: #434343; color: #434343;
} }
.zh-overflowcont { .zh-overflowcont {

View File

@ -8,48 +8,6 @@
"disableScroll": true "disableScroll": true
} }
}, },
{
"path": "pages/article/article-list",
"style": {
"navigationBarTitleText": "我的宣教",
"disableScroll": true
}
},
{
"path": "pages/article/article-cate-list",
"style": {
"navigationBarTitleText": "健康宣教",
"disableScroll": true
}
},
{
"path": "pages/survey/survey-list",
"style": {
"navigationBarTitleText": "我的问卷",
"disableScroll": true
}
},
{
"path": "pages/survey/fill",
"style": {
"navigationBarTitleText": "问卷",
"disableScroll": true
}
},
{
"path": "pages/rate/rate-list",
"style": {
"navigationBarTitleText": "服务评价",
"disableScroll": true
}
},
{
"path": "pages/rate/rate-detail",
"style": {
"navigationBarTitleText": "服务评价",
"disableScroll": true
}
},
{ {
"path": "pages/message/message", "path": "pages/message/message",
"style": { "style": {
@ -65,6 +23,20 @@
"disableScroll": true "disableScroll": true
} }
}, },
{
"path": "pages/mine/mine",
"style": {
"navigationBarTitleText": "我的",
"disableScroll": true
}
},
{
"path": "pages/mine/contact",
"style": {
"navigationBarTitleText": "联系客服",
"disableScroll": true
}
},
{ {
"path": "pages/login/login", "path": "pages/login/login",
"style": { "style": {
@ -87,90 +59,6 @@
"disableScroll": true "disableScroll": true
} }
}, },
{
"path": "pages/archive/archive-manage",
"style": {
"navigationBarTitleText": "档案管理",
"disableScroll": true
}
},
{
"path": "pages/archive/edit-archive",
"style": {
"navigationBarTitleText": "新增档案",
"disableScroll": true
}
},
{
"path": "pages/archive/archive-result",
"style": {
"navigationBarTitleText": "团队服务",
"disableScroll": true
}
},
{
"path": "pages/health/list",
"style": {
"navigationBarTitleText": "健康信息",
"disableScroll": true
}
},
{
"path": "pages/health/record",
"style": {
"navigationBarTitleText": "健康信息",
"disableScroll": true
}
},
{
"path": "pages/library/diagnosis-list",
"style": {
"navigationBarTitleText": "选择诊断",
"disableScroll": true
}
},
{
"path": "pages/team/team-detail",
"style": {
"navigationBarTitleText": "团队介绍",
"disableScroll": true
}
},
{
"path": "pages/team/homepage",
"style": {
"navigationBarTitleText": "个人主页",
"disableScroll": true
}
},
{
"path": "pages/team/friend",
"style": {
"navigationBarTitleText": "添加好友",
"disableScroll": true
}
},
{
"path": "pages/web-view/web-view",
"style": {
"navigationBarTitleText": "",
"disableScroll": true
}
},
{
"path": "pages/mine/mine",
"style": {
"navigationBarTitleText": "我的",
"disableScroll": true
}
},
{
"path": "pages/mine/contact",
"style": {
"navigationBarTitleText": "联系客服",
"disableScroll": true
}
},
{ {
"path": "pages/common/privacy", "path": "pages/common/privacy",
"style": { "style": {
@ -186,18 +74,174 @@
} }
}, },
{ {
"path": "pages/article/article-detail", "path": "pages/web-view/web-view",
"style": { "style": {
"navigationBarTitleText": "宣教文章", "navigationBarTitleText": "",
"disableScroll": true "disableScroll": true
} }
}
],
"subPackages": [
{
"root": "pages/article",
"name": "article",
"pages": [
{
"path": "article-list",
"style": {
"navigationBarTitleText": "我的宣教",
"disableScroll": true
}
},
{
"path": "article-cate-list",
"style": {
"navigationBarTitleText": "健康宣教",
"disableScroll": true
}
},
{
"path": "article-detail",
"style": {
"navigationBarTitleText": "宣教文章",
"disableScroll": true
}
},
{
"path": "send-article",
"style": {
"navigationBarTitleText": "选择宣教文章",
"disableScroll": true
}
}
]
}, },
{ {
"path": "pages/article/send-article", "root": "pages/survey",
"style": { "name": "survey",
"navigationBarTitleText": "选择宣教文章", "pages": [
"disableScroll": true {
} "path": "survey-list",
"style": {
"navigationBarTitleText": "我的问卷",
"disableScroll": true
}
},
{
"path": "fill",
"style": {
"navigationBarTitleText": "问卷",
"disableScroll": true
}
}
]
},
{
"root": "pages/rate",
"name": "rate",
"pages": [
{
"path": "rate-list",
"style": {
"navigationBarTitleText": "服务评价",
"disableScroll": true
}
},
{
"path": "rate-detail",
"style": {
"navigationBarTitleText": "服务评价",
"disableScroll": true
}
}
]
},
{
"root": "pages/archive",
"name": "archive",
"pages": [
{
"path": "archive-manage",
"style": {
"navigationBarTitleText": "档案管理",
"disableScroll": true
}
},
{
"path": "edit-archive",
"style": {
"navigationBarTitleText": "新增档案",
"disableScroll": true
}
},
{
"path": "archive-result",
"style": {
"navigationBarTitleText": "团队服务",
"disableScroll": true
}
}
]
},
{
"root": "pages/health",
"name": "health",
"pages": [
{
"path": "list",
"style": {
"navigationBarTitleText": "健康信息",
"disableScroll": true
}
},
{
"path": "record",
"style": {
"navigationBarTitleText": "健康信息",
"disableScroll": true
}
}
]
},
{
"root": "pages/library",
"name": "library",
"pages": [
{
"path": "diagnosis-list",
"style": {
"navigationBarTitleText": "选择诊断",
"disableScroll": true
}
}
]
},
{
"root": "pages/team",
"name": "team",
"pages": [
{
"path": "team-detail",
"style": {
"navigationBarTitleText": "团队介绍",
"disableScroll": true
}
},
{
"path": "homepage",
"style": {
"navigationBarTitleText": "个人主页",
"disableScroll": true
}
},
{
"path": "friend",
"style": {
"navigationBarTitleText": "添加好友",
"disableScroll": true
}
}
]
} }
], ],
"globalStyle": { "globalStyle": {

View File

@ -58,13 +58,14 @@ const { useLoad, useShow } = useGuard();
const { account } = storeToRefs(useAccount()); const { account } = storeToRefs(useAccount());
const corpId = ref(''); const corpId = ref('');
const teamId = ref(''); const teamId = ref('');
const corpUserId = ref('') const corpUserId = ref('');
const referenceCustomerId = ref('');
const enableHis = ref(false); const enableHis = ref(false);
const customers = ref([]); const customers = ref([]);
function addArchive() { function addArchive() {
uni.navigateTo({ uni.navigateTo({
url: `/pages/archive/edit-archive?corpUserId=${corpUserId.value}&teamId=${teamId.value}&corpId=${corpId.value}` url: `/pages/archive/edit-archive?corpUserId=${corpUserId.value}&referenceCustomerId=${referenceCustomerId.value}&teamId=${teamId.value}&corpId=${corpId.value}`
}) })
} }
@ -98,6 +99,7 @@ useLoad(options => {
teamId.value = options.teamId; teamId.value = options.teamId;
corpId.value = options.corpId; corpId.value = options.corpId;
corpUserId.value = options.corpUserId; corpUserId.value = options.corpUserId;
referenceCustomerId.value = options.referenceCustomerId || '';
}) })
useShow(() => { useShow(() => {

View File

@ -51,6 +51,9 @@
</view> </view>
</template> </template>
</view> </view>
<template #footer>
<button-footer :showCancel="false" confirmText="返回首页" @confirm="backHome" />
</template>
</full-page> </full-page>
</template> </template>
<script setup> <script setup>
@ -62,6 +65,7 @@ import useAccount from '@/store/account';
import api from '@/utils/api'; import api from '@/utils/api';
import FullPage from '@/components/full-page.vue'; import FullPage from '@/components/full-page.vue';
import buttonFooter from '@/components/button-footer.vue';
const team = ref(null); const team = ref(null);
const { useLoad } = useGuard(); const { useLoad } = useGuard();
@ -69,6 +73,7 @@ const { account } = storeToRefs(useAccount());
const { memberJob, memberList: list } = useJob(); const { memberJob, memberList: list } = useJob();
const qrcode = ref(''); const qrcode = ref('');
const corpId = ref('') const corpId = ref('')
const customerId = ref('')
const friends = computed(() => { const friends = computed(() => {
const memberList = Array.isArray(team.value?.memberList) ? team.value.memberList : []; const memberList = Array.isArray(team.value?.memberList) ? team.value.memberList : [];
@ -95,19 +100,33 @@ async function getQrcode(userid) {
} }
} }
async function getTeam(corpId, teamId) { async function getTeam(corpId, teamId, customerId) {
const res = await api('getTeamData', { teamId, corpId }); const res = await api('getTeamData', { teamId, corpId });
if (res && res.data) { if (res && res.data) {
team.value = res.data; team.value = res.data;
// const qrcode = team.value && Array.isArray(team.value.qrcodes) ? team.value.qrcodes[0] : null;
// const healthTempList = qrcode && Array.isArray(qrcode.healthTempList) ? qrcode.healthTempList : [];
// const types = healthTempList.filter(i => typeof i.templateType === 'string' && i.templateType.trim() && i.archiveRecommend === true).map(i => i.templateType);
// if (types.length && customerId) {
// const nextType = types[0];
// const nextTypes = types.slice(1);
// const url = `/pages/health/record?type=${nextType}&teamId=${teamId}&corpId=${corpId}&customerId=${customerId}&nextTypes=${nextTypes.join(',')}`
// uni.navigateTo({ url });
// }
} else { } else {
toast(res?.message || '获取团队信息失败') toast(res?.message || '获取团队信息失败')
} }
} }
function backHome() {
uni.switchTab({ url: '/pages/home/home' })
}
useLoad(options => { useLoad(options => {
customerId.value = options.customerId || '';
corpId.value = options.corpId; corpId.value = options.corpId;
if (options.teamId && options.corpId) { if (options.teamId && options.corpId) {
getTeam(options.corpId, options.teamId); getTeam(options.corpId, options.teamId, options.customerId);
} }
}) })

View File

@ -6,21 +6,21 @@
<uni-icons type="closeempty" :size="24" color="#999" @click="close"></uni-icons> <uni-icons type="closeempty" :size="24" color="#999" @click="close"></uni-icons>
</view> </view>
<view class="px-15 pt-15 text-base text-dark"> <view class="px-15 pt-15 text-base text-dark">
您在{{ corpName }}医客通平台已存在档案请选择档案绑定 您在"{{ corpName }}"医客通平台已存在档案请选择档案绑定
</view> </view>
<scroll-view scroll-y="true" class="popup-content-scroll"> <scroll-view scroll-y="true" class="popup-content-scroll">
<view class="px-15 py-12"> <view class="px-15 py-12">
<view v-for="customer in customers" :key="customer._id" <view v-for="customer in list" :key="customer._id" class="flex items-center p-10 mb-10 rounded-sm bg-gray"
class="flex items-center p-10 mb-10 rounded-sm bg-gray" @click="id = customer._id"> @click="id = customer._id">
<view class="flex-grow w-0 mr-5 text-base leading-normal text-dark"> <view class="flex-grow w-0 mr-5 text-base leading-normal text-dark">
<view class="flex items-center"> <view class="flex items-center">
<view class="flex-shrink-0 min-w-60">姓名</view> <view class="flex-shrink-0 min-w-60">姓名</view>
<view>{{ customer.name }}</view> <view>{{ customer.maskName }}</view>
</view> </view>
<view class="flex items-center"> <!-- <view class="flex items-center">
<view class="flex-shrink-0 min-w-60">性别</view> <view class="flex-shrink-0 min-w-60">性别</view>
<view>{{ customer.sex || '--' }}</view> <view>{{ customer.sex || '--' }}</view>
</view> </view> -->
<view class="flex items-center"> <view class="flex items-center">
<view class="flex-shrink-0 min-w-60">手机号</view> <view class="flex-shrink-0 min-w-60">手机号</view>
<view>{{ customer.mobile }}</view> <view>{{ customer.mobile }}</view>
@ -30,22 +30,29 @@
</view> </view>
</view> </view>
</scroll-view> </scroll-view>
<view class="flex justify-end px-15 pb-10">
<view class="mr-5 text-base text-dark">以上档案都不对可以</view>
<view class="px-10 text-base leading-normal text-primary border-auto rounded-sm" @click="close()">新增档案</view>
</view>
<view class="footer-buttons"> <view class="footer-buttons">
<button-footer hideden-shadow confirmText="确定" :showCancel="false" @confirm="confirm()" /> <button-footer hideden-shadow confirmText="确定" :showCancel="false" @confirm="confirm()" />
</view> </view>
<view class="flex justify-end px-15 pb-10">
<view class="mr-5 text-base text-dark">以上档案都不对可以</view>
<view class="px-10 text-base leading-normal text-primary border-auto rounded-sm" @click="close()">新增档案</view>
</view>
</view> </view>
</uni-popup> </uni-popup>
<verify-name-popup
:visible="showVerifyPopup"
:customer="customer"
@close="onVerifyClose"
@confirm="onVerifyConfirm"
/>
</template> </template>
<script setup> <script setup>
import { ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { toast } from '@/utils/widget'; import { toast } from '@/utils/widget';
import ButtonFooter from '@/components/button-footer.vue'; import ButtonFooter from '@/components/button-footer.vue';
import VerifyNamePopup from './verify-name-popup.vue';
const emits = defineEmits(['close', 'confirm']) const emits = defineEmits(['close', 'confirm'])
const props = defineProps({ const props = defineProps({
@ -64,6 +71,20 @@ const props = defineProps({
}) })
const popup = ref() const popup = ref()
const id = ref('') const id = ref('')
const showVerifyPopup = ref(false)
const list = computed(() => props.customers.map(i => {
const name = typeof i.name === 'string' ? i.name.trim() : '';
const maskName = name.length > 1 ? name.slice(0, 1) + '*'.repeat(name.length - 1) : '*';
return {
...i,
maskName
}
}))
const customer = computed(() => {
const selected = list.value.find(i => i._id === id.value)
return selected ? selected : {}
})
function close() { function close() {
emits('close') emits('close')
@ -71,15 +92,26 @@ function close() {
function confirm() { function confirm() {
if (props.customers.some(i => i._id === id.value && id.value)) { if (props.customers.some(i => i._id === id.value && id.value)) {
emits('confirm', id.value) showVerifyPopup.value = true
} else { } else {
toast('请选择档案') toast('请选择档案')
} }
} }
function onVerifyClose() {
showVerifyPopup.value = false
}
function onVerifyConfirm() {
emits('confirm', id.value)
}
watch(() => props.visible, n => { watch(() => props.visible, n => {
if (n) { if (n) {
popup.value && popup.value.open() popup.value && popup.value.open();
if(props.customers && props.customers.length === 1 && props.customers[0] && props.customers[0]._id){
id.value = props.customers[0]._id
}
} else { } else {
popup.value && popup.value.close() popup.value && popup.value.close()
} }
@ -95,7 +127,8 @@ watch(() => props.visible, n => {
width: 48rpx; width: 48rpx;
height: 48rpx; height: 48rpx;
} }
.popup-content-scroll{
max-height: 65vh; .popup-content-scroll {
max-height: 50vh;
} }
</style> </style>

View File

@ -26,9 +26,9 @@ import dayjs from 'dayjs';
import useGuard from '@/hooks/useGuard'; import useGuard from '@/hooks/useGuard';
import useAccount from '@/store/account'; import useAccount from '@/store/account';
import api from '@/utils/api'; import api from '@/utils/api';
import { set } from "@/utils/cache";
import { toast, confirm as uniConfirm } from '@/utils/widget'; import { toast, confirm as uniConfirm } from '@/utils/widget';
import validate from '@/utils/validate'; import validate from '@/utils/validate';
import { set } from "@/utils/cache";
import ButtonFooter from '@/components/button-footer.vue'; import ButtonFooter from '@/components/button-footer.vue';
import EmptyData from '@/components/empty-data.vue'; import EmptyData from '@/components/empty-data.vue';
@ -44,6 +44,7 @@ const { getExternalUserId } = useAccount()
const corpId = ref(''); const corpId = ref('');
const corpName = ref(''); const corpName = ref('');
const corpUserId = ref(''); const corpUserId = ref('');
const referenceCustomerId = ref('');
const customer = ref({}); const customer = ref({});
const customerId = ref(''); const customerId = ref('');
const customers = ref([]); const customers = ref([]);
@ -55,6 +56,7 @@ const teamId = ref('');
const tempRef = ref(null); const tempRef = ref(null);
const verifyVisible = ref(false); const verifyVisible = ref(false);
const visible = ref(false); const visible = ref(false);
const referenceCustomer = ref(null)
const formData = computed(() => { const formData = computed(() => {
return { ...customer.value, ...form.value, mobile: account.value?.mobile } return { ...customer.value, ...form.value, mobile: account.value?.mobile }
@ -147,13 +149,22 @@ async function addArchive() {
params.personResponsibles = [{ corpUserId, teamId: teamId.value }] params.personResponsibles = [{ corpUserId, teamId: teamId.value }]
} }
} }
if (referenceCustomerId.value && !referenceCustomer.value) {
await getReferenceCustomer();
}
if (referenceCustomer.value) {
params.referenceCustomerId = referenceCustomer.value._id;
params.referenceUserId = '';
params.reference = referenceCustomer.value.name;
params.referenceType = '客户';
params.customerSource = ['客户推荐']
}
loading.value = false; loading.value = false;
const res = await api('addCustomer', { params }); const res = await api('addCustomer', { params });
set('home-invite-team-info', { teamId: teamId.value })
if (res && res.success) { if (res && res.success) {
uni.$emit('reloadTeamCustomers') uni.$emit('reloadTeamCustomers')
uni.redirectTo({ getTeam(corpId.value, teamId.value, res.data.id);
url: `/pages/archive/archive-result?corpId=${corpId.value}&teamId=${teamId.value}`
})
} else { } else {
toast(res?.message || '新增档案失败'); toast(res?.message || '新增档案失败');
} }
@ -190,7 +201,6 @@ async function init() {
await getCustomer(); await getCustomer();
} else { } else {
const res = await getArchives(); const res = await getArchives();
console.log('res:', res)
if (res.length > 0) { if (res.length > 0) {
visible.value = true; visible.value = true;
} }
@ -264,27 +274,45 @@ async function unBindArchive() {
} }
} }
// async updateCustomer() { async function getReferenceCustomer() {
// if (Object.keys(this.form).length === 0) return this.editMemberId; const res = await api('getRefrencePeople', { corpId: corpId.value, id: referenceCustomerId.value });
// const { success, message } = await updateCustomer( referenceCustomer.value = res && res.data ? res.data : null;
// this.editMemberId, }
// this.form
// ); async function getTeam(corpId, teamId, customerId) {
// if (success) return this.editMemberId; const res = await api('getTeamData', { teamId, corpId });
// this.widget.hideLoading(); if (res && res.data) {
// this.widget.toast(message); const team = res.data;
// return Promise.reject(); const qrcode = team && Array.isArray(team.qrcodes) ? team.qrcodes[0] : null;
// }, const healthTempList = qrcode && Array.isArray(qrcode.healthTempList) ? qrcode.healthTempList : [];
const types = healthTempList.filter(i => typeof i.templateType === 'string' && i.templateType.trim() && i.archiveRecommend === true).map(i => i.templateType);
if (types.length && customerId) {
const nextType = types[0];
const nextTypes = types.slice(1);
const url = `/pages/health/record?type=${nextType}&teamId=${teamId}&corpId=${corpId}&customerId=${customerId}&nextTypes=${nextTypes.join(',')}&source=afterArchive`
uni.redirectTo({ url });
return
}
}
uni.redirectTo({
url: `/pages/archive/archive-result?corpId=${corpId}&teamId=${teamId}&customerId=${customerId}`
})
}
onLoad(options => { onLoad(options => {
teamId.value = options.teamId;
corpId.value = options.corpId;
customerId.value = options.id || ''; customerId.value = options.id || '';
corpUserId.value = options.corpUserId || ''; corpUserId.value = options.corpUserId || '';
referenceCustomerId.value = options.referenceCustomerId || '';
if (referenceCustomerId.value) {
getReferenceCustomer();
}
uni.setNavigationBarTitle({ title: customerId.value ? '编辑档案' : '新增档案' }) uni.setNavigationBarTitle({ title: customerId.value ? '编辑档案' : '新增档案' })
}) })
useLoad(options => { useLoad(options => {
teamId.value = options.teamId;
corpId.value = options.corpId;
init(); init();
}) })

View File

@ -0,0 +1,140 @@
<template>
<uni-popup ref="popup" type="center" :mask-click="false">
<view class="bg-white rounded overflow-hidden" style="width: 690rpx;">
<view class="flex items-center justify-between px-15 py-12 border-b">
<view class="text-lg font-semibold text-dark">校验姓名</view>
<uni-icons type="closeempty" :size="24" color="#999" @click="close"></uni-icons>
</view>
<view class="px-15 pt-15 text-base text-dark">
请输入所选档案的姓名进行验证
</view>
<view class="px-15 py-12 flex items-center justify-center">
<view class="text-xl font-semibold text-dark">{{ nameSet.pre }}</view>
<view v-if="nameSet.middle" class="text-xl font-semibold text-dark">{{ nameSet.middle }}</view>
<!-- <view class="input-name border rounded-sm text-xl font-semibold text-dark text-center">
{{ firstInputName }} -->
<input v-model="inputName" :focus="focus" class="input-name text-xl font-semibold text-dark"
placeholder-class="text-lg font-semibold placeholder-class" />
<!-- </view> -->
</view>
<view class="pb-15">
<button-footer hideden-shadow confirmText="确定" @cancel="close" @confirm="confirm()" />
</view>
</view>
</uni-popup>
</template>
<script setup>
import { computed, ref, watch, nextTick } from 'vue';
import { toast } from '@/utils/widget';
import ButtonFooter from '@/components/button-footer.vue';
const emits = defineEmits(['close', 'confirm'])
const props = defineProps({
visible: {
type: Boolean,
default: false
},
customer: {
type: Object,
default: () => ({})
}
})
const popup = ref()
const inputName = ref('')
const focus = ref(false)
const firstInputName = computed(() => inputName.value.trim()[0] || '')
const placeholder = computed(() => '请输入姓名' + (props.customer.maskName ? `(${props.customer.maskName})` : ''));
const nameSet = computed(() => {
const name = props.customer && typeof props.customer.name === 'string' ? props.customer.name.trim() : ''
const pre = name.slice(0, -1);
const middle = pre.slice(4);
const last = name.slice(-1);
return { pre, middle: middle ? '*' : '', last: last ? '*' : '', last }
})
function close() {
emits('close')
}
function confirm() {
const trimmedName = inputName.value.trim()
if (!trimmedName) {
toast('请输入姓名最后一个字')
return
}
if (trimmedName && trimmedName === nameSet.value.last) {
emits('confirm')
close()
} else {
toast('姓名不一致,请重新输入')
}
}
watch(() => props.visible, n => {
if (n) {
inputName.value = ''
popup.value && popup.value.open();
nextTick(() => {
focus.value = true
})
} else {
popup.value && popup.value.close()
focus.value = false
}
})
</script>
<style lang="scss" scoped>
.input-name {
position: relative;
border: 1px solid #eee;
border-radius: 8rpx;
margin-left: 8rpx;
width: 64rpx;
height: 64rpx;
line-height: 60rpx;
text-align: center;
}
.input-name-enter {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
}
.placeholder-class {
color: #999;
}
.btn {
flex: 1;
height: 80rpx;
line-height: 80rpx;
text-align: center;
border-radius: 8rpx;
font-size: 30rpx;
}
.btn-cancel {
margin-right: 20rpx;
color: #666;
background-color: #f5f5f5;
}
.btn-confirm {
color: #fff;
background-color: #1890ff;
}
.leading-1 {
line-height: 1em;
}
</style>

View File

@ -95,7 +95,7 @@ watch(codes, n => {
.code-cell { .code-cell {
width: 120rpx; width: 120rpx;
height: 120rpx; height: 120rpx;
font-size: 52rpx; font-size: 54rpx;
} }
.code-input { .code-input {

View File

@ -397,15 +397,15 @@ onReachBottom(() => {
} }
.text-xs { .text-xs {
font-size: 22rpx; font-size: 24rpx;
} }
.text-sm { .text-sm {
font-size: 28rpx; font-size: 30rpx;
} }
.text-base { .text-base {
font-size: 32rpx; font-size: 34rpx;
} }
.font-bold { .font-bold {
@ -514,7 +514,7 @@ onReachBottom(() => {
.article-title { .article-title {
color: #333333; color: #333333;
font-size: 32rpx; font-size: 34rpx;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
} }
@ -523,7 +523,7 @@ onReachBottom(() => {
max-width: 402rpx; max-width: 402rpx;
color: #666666; color: #666666;
text-align: justify; text-align: justify;
font-size: 28rpx; font-size: 30rpx;
font-weight: 400; font-weight: 400;
line-height: normal; line-height: normal;
} }
@ -548,7 +548,7 @@ onReachBottom(() => {
.loading-text { .loading-text {
margin-top: 20rpx; margin-top: 20rpx;
font-size: 28rpx; font-size: 30rpx;
color: #999; color: #999;
} }
@ -558,7 +558,7 @@ onReachBottom(() => {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 30rpx 0; padding: 30rpx 0;
font-size: 24rpx; font-size: 26rpx;
color: #999; color: #999;
gap: 10rpx; gap: 10rpx;
} }

View File

@ -30,10 +30,13 @@
import { onLoad } from "@dcloudio/uni-app"; import { onLoad } from "@dcloudio/uni-app";
import FullPage from "@/components/full-page.vue"; import FullPage from "@/components/full-page.vue";
import api from "@/utils/api.js"; import api from "@/utils/api.js";
import { set } from "@/utils/cache";
import { toast } from "@/utils/widget.js";
import { ref } from "vue"; import { ref } from "vue";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import useAccountStore from "@/store/account.js"; import useAccountStore from "@/store/account.js";
const { account } = storeToRefs(useAccountStore()); const { account } = storeToRefs(useAccountStore());
const { login } = useAccountStore()
const loading = ref(true); const loading = ref(true);
const error = ref(""); const error = ref("");
const articleData = ref({ const articleData = ref({
@ -102,6 +105,7 @@ const loadArticle = async () => {
if (res.success && res.data) { if (res.success && res.data) {
// //
let date = ""; let date = "";
if (res.data.createTime) { if (res.data.createTime) {
const d = new Date(res.data.createTime); const d = new Date(res.data.createTime);
const year = d.getFullYear(); const year = d.getFullYear();
@ -115,6 +119,34 @@ const loadArticle = async () => {
content: processRichTextContent(res.data.content || ""), content: processRichTextContent(res.data.content || ""),
date: date, date: date,
}; };
if (isWechatChannels(res.data)) {
uni.openChannelsActivity({
finderUserName: res.data.wechatChannels.finderUserName,
feedId: res.data.wechatChannels.feedId,
fail: err => {
const errMsg = err.errMsg || '';
if (/cancel/i.test(errMsg)) {
return
}
toast(`打开视频号失败:${errMsg}`)
}
})
return
}
// if (res.data.link) {
// uni.openOfficialAccountArticle({
// url: res.data.link,
// fail: err => {
// const errMsg = err.errMsg || '';
// console.error(errMsg)
// console.log(errMsg)
// if (/cancel/i.test(errMsg)) {
// return
// }
// toast(`:${errMsg}`)
// }
// })
// }
} else { } else {
error.value = res.message || "加载文章失败"; error.value = res.message || "加载文章失败";
} }
@ -126,11 +158,41 @@ const loadArticle = async () => {
} }
}; };
function isWechatChannels(item) {
return item?.wechatChannels?.finderUserName && item?.wechatChannels?.feedId;
}
async function handleSend(sendId) {
try {
await login();
markArticleRead(sendId);
authToTeam(sendId)
} catch (e) {
console.log(e)
}
}
//
async function authToTeam(sendId) {
const res = await api("relateWechatAccountWithTeam", {
sendId,
openid: account.value?.openid,
unionid: account.value?.unionid,
appid: account.value?.appId,
})
if (res.success) {
set('home-invite-team-info', { teamId: res.data })
}
}
onLoad((options) => { onLoad((options) => {
corpId.value = options.corpId; corpId.value = options.corpId;
if (options.id) { if (options.id) {
articleId = options.id; articleId = options.id;
markArticleRead(options.sendId || ''); if (options.sendId) {
handleSend(options.sendId)
}
loadArticle(); loadArticle();
} else { } else {
error.value = "文章信息不完整"; error.value = "文章信息不完整";
@ -158,12 +220,12 @@ onLoad((options) => {
.loading-text { .loading-text {
margin-top: 20rpx; margin-top: 20rpx;
font-size: 28rpx; font-size: 30rpx;
color: #999; color: #999;
} }
.error-text { .error-text {
font-size: 28rpx; font-size: 30rpx;
color: #999; color: #999;
margin-bottom: 30rpx; margin-bottom: 30rpx;
text-align: center; text-align: center;
@ -175,7 +237,7 @@ onLoad((options) => {
color: #fff; color: #fff;
border: none; border: none;
border-radius: 8rpx; border-radius: 8rpx;
font-size: 28rpx; font-size: 30rpx;
} }
.article-content { .article-content {
@ -189,7 +251,7 @@ onLoad((options) => {
.article-title { .article-title {
display: block; display: block;
font-size: 36rpx; font-size: 38rpx;
font-weight: bold; font-weight: bold;
color: #333; color: #333;
line-height: 1.6; line-height: 1.6;
@ -198,7 +260,7 @@ onLoad((options) => {
.article-date { .article-date {
display: block; display: block;
font-size: 24rpx; font-size: 26rpx;
color: #999; color: #999;
} }

View File

@ -84,6 +84,7 @@ import { storeToRefs } from "pinia";
import dayjs from "dayjs"; import dayjs from "dayjs";
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 { toast } from "@/utils/widget.js";
import fullPage from "@/components/full-page.vue"; import fullPage from "@/components/full-page.vue";
import EmptyData from "@/components/empty-data.vue"; import EmptyData from "@/components/empty-data.vue";
@ -138,6 +139,7 @@ const mapRowToView = (row) => {
person: row?.customer?.name || "", person: row?.customer?.name || "",
team: row?.team?.name || "-", team: row?.team?.name || "-",
time: sendTime, time: sendTime,
articleInfo: row?.articleInfo,
}; };
}; };
@ -196,8 +198,50 @@ const loadArticleList = async (reset = false) => {
function goToDetail(item) { function goToDetail(item) {
if (!item?.articleId) return; if (!item?.articleId) return;
// if (isWechatChannels(item.articleInfo)) {
// uni.openChannelsActivity({
// finderUserName: item.articleInfo.wechatChannels.finderUserName,
// feedId: item.articleInfo.wechatChannels.feedId,
// success: () => {
// markArticleRead(item._id, item.articleId);
// },
// fail: err => {
// const errMsg = err.errMsg || '';
// if (/cancel/i.test(errMsg)) {
// return
// }
// toast(`:${errMsg}`)
// }
// })
// return
// }
uni.navigateTo({ url: `/pages/article/article-detail?sendId=${item._id}&id=${item.articleId}&corpId=${corpId.value}` }); uni.navigateTo({ url: `/pages/article/article-detail?sendId=${item._id}&id=${item.articleId}&corpId=${corpId.value}` });
} }
function isWechatChannels(item) {
return item?.wechatChannels?.finderUserName && item?.wechatChannels?.feedId;
}
async function markArticleRead(sendId, articleId) {
const unionid = account.value?.unionid;
if (!unionid || !articleId) return;
try {
const { success } = await api(
"addArticleReadRecord",
{ corpId: corpId.value, articleId, unionid, sendId },
false
);
if (success) {
const idx = articles.value.findIndex(i => i._id === sendId);
if (idx !== -1) {
articles.value[idx].status = "YES";
}
}
} catch (err) {
console.warn("markArticleRead failed:", err?.message || err);
}
};
onLoad(opts => { onLoad(opts => {
corpId.value = opts.corpId; corpId.value = opts.corpId;
teamId.value = opts.teamId; teamId.value = opts.teamId;
@ -350,15 +394,15 @@ onReachBottom(() => {
} }
.text-xs { .text-xs {
font-size: 22rpx; font-size: 24rpx;
} }
.text-sm { .text-sm {
font-size: 28rpx; font-size: 30rpx;
} }
.text-base { .text-base {
font-size: 32rpx; font-size: 34rpx;
} }
.font-bold { .font-bold {
@ -472,7 +516,7 @@ onReachBottom(() => {
.loading-text { .loading-text {
margin-top: 20rpx; margin-top: 20rpx;
font-size: 28rpx; font-size: 30rpx;
color: #999; color: #999;
} }
@ -482,7 +526,7 @@ onReachBottom(() => {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 30rpx 0; padding: 30rpx 0;
font-size: 24rpx; font-size: 26rpx;
color: #999; color: #999;
gap: 10rpx; gap: 10rpx;
} }

View File

@ -410,7 +410,7 @@ onMounted(() => {
.search-input { .search-input {
flex: 1; flex: 1;
margin-left: 16rpx; margin-left: 16rpx;
font-size: 28rpx; font-size: 30rpx;
} }
.content { .content {
@ -431,7 +431,7 @@ onMounted(() => {
.category-item { .category-item {
padding: 20rpx 24rpx; padding: 20rpx 24rpx;
font-size: 28rpx; font-size: 30rpx;
color: #333; color: #333;
text-align: center; text-align: center;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
@ -466,7 +466,7 @@ onMounted(() => {
.loading-text { .loading-text {
margin-top: 20rpx; margin-top: 20rpx;
font-size: 28rpx; font-size: 30rpx;
color: #999; color: #999;
} }
@ -487,7 +487,7 @@ onMounted(() => {
} }
.article-title { .article-title {
font-size: 28rpx; font-size: 30rpx;
color: #333; color: #333;
line-height: 1.6; line-height: 1.6;
word-break: break-all; word-break: break-all;
@ -503,13 +503,13 @@ onMounted(() => {
.article-date { .article-date {
flex: 1; flex: 1;
font-size: 24rpx; font-size: 26rpx;
color: #999; color: #999;
} }
.send-btn { .send-btn {
flex-shrink: 0; flex-shrink: 0;
font-size: 26rpx; font-size: 28rpx;
padding: 8rpx 32rpx; padding: 8rpx 32rpx;
height: auto; height: auto;
line-height: 1.4; line-height: 1.4;
@ -521,7 +521,7 @@ onMounted(() => {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 30rpx 0; padding: 30rpx 0;
font-size: 24rpx; font-size: 26rpx;
color: #999; color: #999;
gap: 10rpx; gap: 10rpx;
} }
@ -545,7 +545,7 @@ onMounted(() => {
.preview-title { .preview-title {
flex: 1; flex: 1;
font-size: 32rpx; font-size: 34rpx;
font-weight: bold; font-weight: bold;
color: #333; color: #333;
} }

View File

@ -40,6 +40,8 @@
class="mr-5 px-10 leading-normal text-white text-base bg-warning rounded-full">门诊信息</view> class="mr-5 px-10 leading-normal text-white text-base bg-warning rounded-full">门诊信息</view>
<view v-else-if="i.medicalType === 'inhospital'" <view v-else-if="i.medicalType === 'inhospital'"
class="mr-5 px-10 leading-normal text-white text-base bg-success rounded-full">住院信息</view> class="mr-5 px-10 leading-normal text-white text-base bg-success rounded-full">住院信息</view>
<view v-if="i.medicalType === 'physicalExaminationTemplate'"
class="mr-5 px-10 leading-normal text-white text-base bg-danger rounded-full">体检报告</view>
<view v-if="i.corp === '其他'" class="px-10 leading-normal text-white text-base bg-danger rounded-full"> <view v-if="i.corp === '其他'" class="px-10 leading-normal text-white text-base bg-danger rounded-full">
外院 外院
</view> </view>
@ -51,7 +53,7 @@
</view> </view>
<view v-if="i.rows.length" class="px-15 border-b"> <view v-if="i.rows.length" class="px-15 border-b">
<view v-for="row in i.rows" :key="row.key" class="flex leading-normal mb-10"> <view v-for="row in i.rows" :key="row.key" class="flex leading-normal mb-10">
<view class="flex-shrink-0 mr-5 w-90 text-base text-gray">{{ row.label }}</view> <view class="flex-shrink-0 mr-5 min-w-90 text-base text-gray">{{ row.label }}</view>
<view v-if="row.title === 'diagnosisName'" class="w-0 flex-grow text-base text-dark"> <view v-if="row.title === 'diagnosisName'" class="w-0 flex-grow text-base text-dark">
{{ row.value && row.value.join ? row.value.join(', ') : '' }} {{ row.value && row.value.join ? row.value.join(', ') : '' }}
</view> </view>
@ -96,7 +98,8 @@ import { toast } from '../../utils/widget';
const tempType = { const tempType = {
outpatient: '门诊信息', outpatient: '门诊信息',
inhospital: '住院信息' inhospital: '住院信息',
physicalExaminationTemplate: '体检报告'
} }
const empty = ref(false) const empty = ref(false)
const { useLoad, useShow } = useGuard(); const { useLoad, useShow } = useGuard();
@ -112,12 +115,13 @@ const page = ref(1);
const more = ref(false) const more = ref(false)
const loading = ref(false); const loading = ref(false);
const qrcode = computed(() => team.value && Array.isArray(team.value.qrcodes) ? team.value.qrcodes[0] : null) const qrcode = computed(() => team.value && Array.isArray(team.value.qrcodes) ? team.value.qrcodes[0] : null)
const healthTempList = computed(() => qrcode.value && Array.isArray(qrcode.value.healthTempList) ? qrcode.value.healthTempList.map(i => ({ label: tempType[i.templateType], value: i.templateType })).filter(i => i.label) : []) const healthTempList = computed(() => qrcode.value && Array.isArray(qrcode.value.healthTempList) ? qrcode.value.healthTempList.map(i => ({ enable: i.enable, label: tempType[i.templateType], value: i.templateType })).filter(i => i.label && i.enable) : [])
const tempShowField = ref(null); const tempShowField = ref(null);
const config = { const config = {
outpatient: ["corp", "deptName", "doctor", "diagnosisName", "files"], outpatient: ["corp", "deptName", "doctor", "diagnosisName", "files"],
inhospital: ["corp", "diagnosisName", "operation", "operationDate", "outhosDate", "files"], inhospital: ["corp", "diagnosisName", "operation", "operationDate", "outhosDate", "files"],
physicalExaminationTemplate: ['inspectPakageName']
}; };
function addArchive() { function addArchive() {
@ -197,7 +201,7 @@ async function getList() {
memberId: customerId.value, memberId: customerId.value,
page: page.value, page: page.value,
pageSize: 20, pageSize: 20,
medicalType: ['outpatient', 'inhospital'] medicalType: Object.keys(tempType)
} }
if (type.value !== 'all') { if (type.value !== 'all') {
params.medicalType = [type.value] params.medicalType = [type.value]

View File

@ -43,6 +43,8 @@ const type = ref('');
const visible = ref(false); const visible = ref(false);
const timeTitle = ref(''); const timeTitle = ref('');
const canEdit = ref(false) const canEdit = ref(false)
const nextTypes = ref([]);
const source = ref('');
const formData = computed(() => ({ ...record.value, ...form.value })); const formData = computed(() => ({ ...record.value, ...form.value }));
const displayFormItems = computed(() => { const displayFormItems = computed(() => {
@ -90,12 +92,31 @@ async function addHealthRecord() {
const res = await api('addMedicalRecord', data); const res = await api('addMedicalRecord', data);
if (res && res.success) { if (res && res.success) {
await toast('保存成功'); await toast('保存成功');
uni.navigateBack(); const isFill = await fillNext();
if (isFill) return;
if (source.value === 'afterArchive') {
uni.redirectTo({
url: `/pages/archive/archive-result?corpId=${corpId.value}&teamId=${teamId.value}&customerId=${customerId.value}`
})
} else {
uni.navigateBack();
}
} else { } else {
toast(res?.message || '保存失败'); toast(res?.message || '保存失败');
} }
} }
async function fillNext() {
const nextType = nextTypes.value[0];
const types = nextTypes.value.slice(1);
if (nextType) {
const url = `/pages/health/record?type=${nextType}&teamId=${teamId.value}&corpId=${corpId.value}&customerId=${customerId.value}&nextTypes=${types.join(',')}&source=${source.value}`
uni.redirectTo({ url });
return true
}
return false
}
async function updateHealthRecord() { async function updateHealthRecord() {
if (Object.keys(form.value).length === 0) { if (Object.keys(form.value).length === 0) {
uni.navigateBack(); uni.navigateBack();
@ -162,6 +183,9 @@ onLoad(options => {
customerId.value = options.customerId || ''; customerId.value = options.customerId || '';
corpId.value = options.corpId; corpId.value = options.corpId;
teamId.value = options.teamId; teamId.value = options.teamId;
source.value = options.source || '';
const nextTypeStr = typeof options.nextTypes === 'string' ? options.nextTypes : '';
nextTypes.value = nextTypeStr.split(',');
uni.setNavigationBarTitle({ title: id.value ? '编辑健康档案' : '新增健康档案' }) uni.setNavigationBarTitle({ title: id.value ? '编辑健康档案' : '新增健康档案' })
}) })
useLoad(options => { useLoad(options => {

View File

@ -125,7 +125,7 @@ watch(
.module-title { .module-title {
color: #000000; color: #000000;
font-size: 36rpx; font-size: 38rpx;
font-style: normal; font-style: normal;
font-weight: 600; font-weight: 600;
line-height: normal; line-height: normal;
@ -162,7 +162,7 @@ watch(
.article-title { .article-title {
color: #333333; color: #333333;
font-size: 32rpx; font-size: 34rpx;
font-weight: 500; font-weight: 500;
line-height: normal; line-height: normal;
} }
@ -171,7 +171,7 @@ watch(
max-width: 402rpx; max-width: 402rpx;
color: #666666; color: #666666;
text-align: justify; text-align: justify;
font-size: 28rpx; font-size: 30rpx;
font-weight: 400; font-weight: 400;
line-height: normal; line-height: normal;
} }

View File

@ -207,7 +207,7 @@ defineExpose({
.consult-title { .consult-title {
color: #000000; color: #000000;
font-size: 36rpx; font-size: 38rpx;
font-style: normal; font-style: normal;
font-weight: 600; font-weight: 600;
line-height: normal; line-height: normal;
@ -269,7 +269,7 @@ defineExpose({
} }
.item-label { .item-label {
font-size: 28rpx; font-size: 30rpx;
color: #666d76; color: #666d76;
text-align: center; text-align: center;
font-weight: 400; font-weight: 400;

View File

@ -4,7 +4,7 @@
<view class="module-title flex-shrink-0 truncate"> 成员档案 </view> <view class="module-title flex-shrink-0 truncate"> 成员档案 </view>
<view class="flex items-center leading-normal rounded-sm" style="padding-right: 0" @click="toManagePage()"> <view class="flex items-center leading-normal rounded-sm" style="padding-right: 0" @click="toManagePage()">
<image class="manage-icon mr-5" src="/static/home/archive-manage.png" mode="aspectFit"></image> <image class="manage-icon mr-5" src="/static/home/archive-manage.png" mode="aspectFit"></image>
<view style="font-size: 28rpx; color: #065bd6">档案管理</view> <view style="font-size: 30rpx; color: #065bd6">档案管理</view>
</view> </view>
</view> </view>
<view v-if="customers.length === 0" class="add-archive-card" @click="toManagePage()"> <view v-if="customers.length === 0" class="add-archive-card" @click="toManagePage()">
@ -14,9 +14,26 @@
</view> </view>
<image class="add-archive-bg" src="/static/home/add-archive-bg.png" mode="aspectFit"></image> <image class="add-archive-bg" src="/static/home/add-archive-bg.png" mode="aspectFit"></image>
</view> </view>
<scroll-view scroll-x="true"> <scroll-view scroll-x="true" :scroll-left="scrollLeft" @scroll="handleScroll">
<view class="flex flex-nowrap pb-5"> <view class="flex flex-nowrap pb-5">
<view v-for="i in customers" :key="i._id" class="customer-card flex-shrink-0 mr-15 relative" <view v-if="current" class="customer-card flex-shrink-0 mr-15 relative current-customer">
<!-- 关系标签 -->
<view v-if="current.relationship" class="relationship-tag"
:class="current.relationship === '本人' ? 'tag-blue' : 'tag-green'">
{{ current.relationship }}
</view>
<view class="flex flex-col items-center">
<view class="customer-name text-lg leading-normal font-semibold whitespace-nowrap mb-8 text-primary">
{{ current.name }}
</view>
</view>
<!-- 选中状态底部条和三角 -->
<view class="active-indicator">
<view class="active-bar"></view>
<view class="active-triangle"></view>
</view>
</view>
<view v-for="i in customersList" :key="i._id" class="customer-card flex-shrink-0 mr-15 relative"
:class="current && i._id === current._id ? 'current-customer' : ''" @click="toggle(i)"> :class="current && i._id === current._id ? 'current-customer' : ''" @click="toggle(i)">
<!-- 关系标签 --> <!-- 关系标签 -->
<view v-if="i.relationship" class="relationship-tag" <view v-if="i.relationship" class="relationship-tag"
@ -86,6 +103,10 @@ const props = defineProps({
type: Object, type: Object,
default: () => ({}), default: () => ({}),
}, },
referenceCustomerIds: {
type: Object,
default: () => ({}),
},
customers: { customers: {
type: Array, type: Array,
default: () => [], default: () => [],
@ -103,6 +124,8 @@ const { account, externalUserId } = storeToRefs(useAccount());
const { getExternalUserId } = useAccount() const { getExternalUserId } = useAccount()
const current = ref(null); const current = ref(null);
const customers = ref([]); const customers = ref([]);
const scrollLeft = ref(0);
const customersList = computed(() => customers.value.filter(i => i._id !== current.value?._id))
const canAuth = computed(() => { const canAuth = computed(() => {
if (current.value && props.team && props.team.teamId) { if (current.value && props.team && props.team.teamId) {
@ -157,16 +180,29 @@ function toHealthList() {
} }
function toggle(i) { function toggle(i) {
if (current.value && current.value._id === i._id) return;
current.value = i; current.value = i;
scrollLeft.value = scrollLeft.value === 0 ? 1 : 0;
} }
function toManagePage() { function toManagePage() {
const corpUserId = props.corpUserIds && props.corpUserIds[props.team.teamId] ? props.corpUserIds[props.team.teamId] : ""; const corpUserId = props.corpUserIds && props.corpUserIds[props.team.teamId] ? props.corpUserIds[props.team.teamId] : "";
const referenceCustomerId = props.referenceCustomerIds && props.referenceCustomerIds[props.team.teamId] ? props.referenceCustomerIds[props.team.teamId] : "";
uni.navigateTo({ uni.navigateTo({
url: `/pages/archive/archive-manage?corpUserId=${corpUserId}&corpId=${props.corpId}&teamId=${props.team.teamId}`, url: `/pages/archive/archive-manage?corpUserId=${corpUserId}&corpId=${props.corpId}&teamId=${props.team.teamId}&referenceCustomerId=${referenceCustomerId}`,
}); });
} }
let timer = null;
function handleScroll(e) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
scrollLeft.value = e.detail.scrollLeft;
}, 300);
}
async function auth() { async function auth() {
await confirm(`是否授权${props.team.name}提供服务`); await confirm(`是否授权${props.team.name}提供服务`);
const corpUserId = await getResponsiblePerson(); const corpUserId = await getResponsiblePerson();
@ -249,7 +285,7 @@ defineExpose({
.module-title { .module-title {
color: #000000; color: #000000;
font-size: 36rpx; font-size: 38rpx;
font-weight: 600; font-weight: 600;
line-height: normal; line-height: normal;
} }
@ -277,7 +313,7 @@ defineExpose({
top: -2rpx; top: -2rpx;
right: -10rpx; right: -10rpx;
padding: 4rpx 16rpx; padding: 4rpx 16rpx;
font-size: 20rpx; font-size: 22rpx;
line-height: normal; line-height: normal;
color: #fff; color: #fff;
border-top-left-radius: 16rpx; border-top-left-radius: 16rpx;
@ -392,14 +428,14 @@ defineExpose({
.info-title { .info-title {
color: #213e80; color: #213e80;
font-size: 32rpx; font-size: 34rpx;
font-weight: 400; font-weight: 400;
line-height: 56rpx; line-height: 56rpx;
} }
.info-subtitle { .info-subtitle {
color: #78808f; color: #78808f;
font-size: 24rpx; font-size: 26rpx;
} }
.arrow-icon-small { .arrow-icon-small {
@ -461,7 +497,7 @@ defineExpose({
} }
.add-archive-btn-text { .add-archive-btn-text {
font-size: 28rpx; font-size: 30rpx;
color: #ffffff; color: #ffffff;
font-weight: 500; font-weight: 500;
} }

View File

@ -2,11 +2,11 @@
<page-loading v-if="loading && teams.length === 0" /> <page-loading v-if="loading && teams.length === 0" />
<full-page v-else-if="teams.length && team" class="home-container" :pageStyle="pageStyle"> <full-page v-else-if="teams.length && team" class="home-container" :pageStyle="pageStyle">
<!-- <template #header> --> <!-- <template #header> -->
<team-head :team="team" :teams="teams" @changeTeam="changeTeam" /> <team-head :team="team" :customers="customers" :teams="teams" @changeTeam="changeTeam" />
<!-- </template> --> <!-- </template> -->
<view class="home-section home-section--first"> <view class="home-section home-section--first">
<customer-archive ref="archiveRef" :corpId="corpId" :corpUserIds="corpUserIds" :team="team" <customer-archive ref="archiveRef" :corpId="corpId" :corpUserIds="corpUserIds"
@update:customers="handleCustomersUpdate" /> :referenceCustomerIds="referenceCustomerIds" :team="team" @update:customers="handleCustomersUpdate" />
</view> </view>
<view class="home-section"> <view class="home-section">
<team-guide :team="team" /> <team-guide :team="team" />
@ -26,7 +26,7 @@
<script setup> <script setup>
import { computed, ref, watch } from "vue"; import { computed, ref, watch } from "vue";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { onLoad, onShow } from "@dcloudio/uni-app"; import { onLoad, onShow, onShareAppMessage } from "@dcloudio/uni-app";
// import useGuard from "@/hooks/useGuard"; // import useGuard from "@/hooks/useGuard";
import useAccount from "@/store/account"; import useAccount from "@/store/account";
import api from "@/utils/api"; import api from "@/utils/api";
@ -46,6 +46,8 @@ import pageLoading from "./loading.vue";
// const { useLoad, useShow } = useGuard(); // const { useLoad, useShow } = useGuard();
const { account } = storeToRefs(useAccount()); const { account } = storeToRefs(useAccount());
const { login, getTeams } = useAccount(); const { login, getTeams } = useAccount();
const env = __VITE_ENV__;
const shareAppVersion = env.MP_SHARE_WX_APP_VERSION;
const team = ref(null); const team = ref(null);
const teams = ref([]); const teams = ref([]);
@ -54,6 +56,7 @@ const customers = ref([]);
const consultRef = ref(null); const consultRef = ref(null);
const archiveRef = ref(null); const archiveRef = ref(null);
const corpUserIds = ref({}); const corpUserIds = ref({});
const referenceCustomerIds = ref({});
const corpId = computed(() => team.value?.corpId); const corpId = computed(() => team.value?.corpId);
@ -108,6 +111,9 @@ onShow(async () => {
if (inviteTeam && inviteTeam.teamId && inviteTeam.corpUserId) { if (inviteTeam && inviteTeam.teamId && inviteTeam.corpUserId) {
corpUserIds.value[inviteTeam.teamId] = inviteTeam.corpUserId; corpUserIds.value[inviteTeam.teamId] = inviteTeam.corpUserId;
} }
if (inviteTeam && inviteTeam.teamId && inviteTeam.referenceCustomerId) {
referenceCustomerIds.value[inviteTeam.teamId] = inviteTeam.referenceCustomerId;
}
if (account.value && account.value.openid) { if (account.value && account.value.openid) {
getMatchTeams(inviteTeam && inviteTeam.teamId ? inviteTeam.teamId : ''); getMatchTeams(inviteTeam && inviteTeam.teamId ? inviteTeam.teamId : '');
} else { } else {
@ -121,6 +127,18 @@ onShow(async () => {
} }
}); });
onShareAppMessage((res) => {
if (team.value && team.value.supportPatientForward === 'YES') {
const customer = customers.value[0];
const referenceCustomerId = customer ? customer._id : '';
return {
title: `${team.value.name}】这个团队不错,推荐给你!`,
type: shareAppVersion || 0, // 0 | 1 | 2
path: `/pages/login/redirect-page?teamId=${team.value.teamId}&corpId=${team.value.corpId}&type=archive&referenceCustomerId=${referenceCustomerId}`
}
}
})
watch(account, (n, o) => { watch(account, (n, o) => {
if (n && !o) { if (n && !o) {
getMatchTeams(); getMatchTeams();

View File

@ -152,7 +152,7 @@ defineExpose({
} }
.popup-title { .popup-title {
font-size: 32rpx; font-size: 34rpx;
font-weight: 600; font-weight: 600;
color: #333; color: #333;
text-align: center; text-align: center;
@ -203,7 +203,7 @@ defineExpose({
padding: 8rpx 16rpx; padding: 8rpx 16rpx;
background: linear-gradient(135deg, #ffa726 0%, #ff9800 100%); background: linear-gradient(135deg, #ffa726 0%, #ff9800 100%);
color: #fff; color: #fff;
font-size: 22rpx; font-size: 24rpx;
border-radius: 0 12rpx 0 12rpx; border-radius: 0 12rpx 0 12rpx;
font-weight: 600; font-weight: 600;
z-index: 2; z-index: 2;
@ -243,7 +243,7 @@ defineExpose({
} }
.user-name { .user-name {
font-size: 30rpx; font-size: 32rpx;
font-weight: 700; font-weight: 700;
color: #333; color: #333;
margin-bottom: 6rpx; margin-bottom: 6rpx;
@ -253,7 +253,7 @@ defineExpose({
} }
.user-detail { .user-detail {
font-size: 26rpx; font-size: 28rpx;
color: #666; color: #666;
font-weight: 500; font-weight: 500;
} }
@ -279,7 +279,7 @@ defineExpose({
} }
.add-text { .add-text {
font-size: 26rpx; font-size: 28rpx;
color: #1989fa; color: #1989fa;
font-weight: 600; font-weight: 600;
} }
@ -294,7 +294,7 @@ defineExpose({
height: 80rpx; height: 80rpx;
background: linear-gradient(135deg, #1989fa 0%, #0d6efd 100%); background: linear-gradient(135deg, #1989fa 0%, #0d6efd 100%);
color: #fff; color: #fff;
font-size: 32rpx; font-size: 34rpx;
font-weight: 700; font-weight: 700;
border-radius: 12rpx; border-radius: 12rpx;
border: none; border: none;

View File

@ -105,7 +105,7 @@ const qrcode = computed(() => {
.introduce-text { .introduce-text {
color: #333; color: #333;
font-size: 24rpx; font-size: 26rpx;
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
line-height: 36rpx; line-height: 36rpx;

View File

@ -6,23 +6,35 @@
<group-avatar classType="square" :size="120" :avatarList="currentTeam ? currentTeam.avatarList : []" /> <group-avatar classType="square" :size="120" :avatarList="currentTeam ? currentTeam.avatarList : []" />
</view> </view>
<view class="w-0 flex-grow"> <view class="w-0 flex-grow">
<view class="flex items-center mb-10"> <view class="flex items-center">
<view class="team-name truncate flex-shrink-0" :style="teamStyle">{{ team.name }}</view> <view class="w-0 flex-grow flex items-center mb-10">
<view v-if="teams.length > 1" class="flex-shrink-0 flex items-center switch-btn ml-10" <view class="team-name truncate flex-shrink-0" :style="teamStyle">{{ team.name }}</view>
@click="showDropDown = true"> <view v-if="teams.length > 1" class="flex-shrink-0 flex items-center switch-btn ml-10"
<image class="switch-icon" src="/static/home/switch-team.png" mode="aspectFit"></image> @click="showDropDown = true">
<image class="switch-icon" src="/static/home/switch-team.png" mode="aspectFit"></image>
</view>
</view>
<view v-if="menuButtonInfo && menuButtonInfo.width > 0" class="flex-shrink-0" :style="{
width: menuButtonInfo.width + 'px',
height: menuButtonInfo.height + 'px',
}">
</view> </view>
</view> </view>
<view v-if="currentTeam" class="text-base text-white truncate"> <div class="flex items-center">
{{ currentTeam.leaderCorp || currentTeam.corpName || '' }} <view v-if="currentTeam" class="w-0 flex-grow mr-5text-base text-white truncate">
</view> {{ currentTeam.licenseHospitalName || currentTeam.leaderCorp || currentTeam.corpName || '' }}
</view> </view>
<view v-if="menuButtonInfo && menuButtonInfo.width > 0" class="flex-shrink-0" :style="{ <view v-if="team && team.supportPatientForward === 'YES'"
width: menuButtonInfo.width + 'px', class="relative flex-shrink-0 flex items-center bg-white rounded-sm px-10">
height: menuButtonInfo.height + 'px', <image class="icon-share mr-5" src="/static/home/icon-share.svg"></image>
}"> <view class="text-sm text-warning">推荐</view>
<button open-type="share" class="absolute w-full h-full opacity-0"></button>
</view>
</div>
</view> </view>
</view> </view>
<view class="relative"> <view class="relative">
<view v-if="showDropDown" class="team-dropdown py-12 bg-white shadow-lg"> <view v-if="showDropDown" class="team-dropdown py-12 bg-white shadow-lg">
<scroll-view scroll-y="true" style="max-height: 50vh"> <scroll-view scroll-y="true" style="max-height: 50vh">
@ -37,7 +49,7 @@
{{ item.name }} {{ item.name }}
</view> </view>
<view class="text-base text-gray leading-normal"> <view class="text-base text-gray leading-normal">
{{ item.leaderCorp || item.corpName || '' }} {{ item.licenseHospitalName || item.leaderCorp || item.corpName || '' }}
</view> </view>
</view> </view>
<view class="flex"> <view class="flex">
@ -62,7 +74,7 @@
<!-- <view class="introduce-text flex-grow line-clamp-3"> <!-- <view class="introduce-text flex-grow line-clamp-3">
团队介绍: {{ team.teamTroduce }} 团队介绍: {{ team.teamTroduce }}
</view> --> </view> -->
<!-- <expandable-text textStyle="color:#333;font-size:24rpx" expandStyle="font-size:24rpx" <!-- <expandable-text textStyle="color:#333;font-size:26rpx" expandStyle="font-size:26rpx"
:longText="team.teamTroduce + team.teamTroduce" :line="3" :lineHeight="36" expandText="展开" foldText="收起" /> --> :longText="team.teamTroduce + team.teamTroduce" :line="3" :lineHeight="36" expandText="展开" foldText="收起" /> -->
<!-- <multi-lines-text :line="3" :text="team.teamTroduce + team.teamTroduce" :showButton="true" expandText="展开" <!-- <multi-lines-text :line="3" :text="team.teamTroduce + team.teamTroduce" :showButton="true" expandText="展开"
collapseText="收起" buttonTextColor="#333" /> --> collapseText="收起" buttonTextColor="#333" /> -->
@ -103,6 +115,10 @@ const props = defineProps({
type: Object, type: Object,
default: () => ({}), default: () => ({}),
}, },
customers: {
type: Array,
default: () => [],
},
teams: { teams: {
type: Array, type: Array,
default: () => [], default: () => [],
@ -112,11 +128,13 @@ const props = defineProps({
const currentTeam = computed(() => const currentTeam = computed(() =>
props.teams.find((i) => props.team && i.teamId === props.team.teamId) props.teams.find((i) => props.team && i.teamId === props.team.teamId)
); );
const qrcode = computed(() => { const qrcode = computed(() => {
const qrcodes = props.team && Array.isArray(props.team.qrcodes) ? props.team.qrcodes : []; const qrcodes = props.team && Array.isArray(props.team.qrcodes) ? props.team.qrcodes : [];
return qrcodes[0] || '' return qrcodes[0] || ''
}) })
const teamStyle = computed(() => `max-width:${props.teams.length ? 'calc(100% - 60rpx)' : '100%'}`) const teamStyle = computed(() => `max-width:${props.teams.length ? 'calc(100% - 60rpx)' : '100%'}`)
const firstCustomer = computed(() => props.customers && props.customers[0] ? props.customers[0] : {})
function select(team) { function select(team) {
emits("changeTeam", team); emits("changeTeam", team);
@ -126,7 +144,7 @@ function select(team) {
function toTeamDetail() { function toTeamDetail() {
if (props.team && props.team.teamId) { if (props.team && props.team.teamId) {
uni.navigateTo({ uni.navigateTo({
url: `/pages/team/team-detail?teamId=${props.team.teamId}&corpId=${props.team.corpId}&corpName=${encodeURIComponent(props.team.corpName || '')}` url: `/pages/team/team-detail?firstCustomerId=${firstCustomer.value._id || ''}&teamId=${props.team.teamId}&corpId=${props.team.corpId}&corpName=${encodeURIComponent(props.team.corpName || '')}`
}); });
} }
} }
@ -172,7 +190,7 @@ onMounted(() => {
.team-name { .team-name {
color: #ffffff; color: #ffffff;
font-size: 36rpx; font-size: 38rpx;
font-style: normal; font-style: normal;
font-weight: 600; font-weight: 600;
line-height: normal; line-height: normal;
@ -261,7 +279,7 @@ onMounted(() => {
.introduce-text { .introduce-text {
color: #333; color: #333;
font-size: 24rpx; font-size: 26rpx;
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
line-height: 36rpx; line-height: 36rpx;
@ -311,4 +329,13 @@ onMounted(() => {
.rounded-circle { .rounded-circle {
border-radius: 50%; border-radius: 50%;
} }
.icon-share {
width: 24rpx;
height: 24rpx;
}
.opacity-0 {
opacity: 0;
}
</style> </style>

View File

@ -114,7 +114,7 @@ watch(teamates, val => {
.module-title { .module-title {
color: #000000; color: #000000;
font-size: 36rpx; font-size: 38rpx;
font-style: normal; font-style: normal;
font-weight: 600; font-weight: 600;
line-height: normal; line-height: normal;
@ -122,13 +122,13 @@ watch(teamates, val => {
.member-job { .member-job {
color: #999999; color: #999999;
font-size: 24rpx; font-size: 26rpx;
font-weight: 400; font-weight: 400;
} }
.member-name { .member-name {
color: #333333; color: #333333;
font-size: 32rpx; font-size: 34rpx;
font-weight: 600; font-weight: 600;
line-height: normal; line-height: normal;
} }

View File

@ -2,7 +2,7 @@
<full-page> <full-page>
<template #header> <template #header>
<view class="page-item border-bottom bg-white"> <view class="page-item border-bottom bg-white">
<input class="search-input" placeholder-style="font-size:28rpx" placeholder="请搜索名称" v-model="name" <input class="search-input" placeholder-style="font-size:30rpx" placeholder="请搜索名称" v-model="name"
@input="search" /> @input="search" />
</view> </view>
</template> </template>
@ -120,7 +120,7 @@ onLoad(options => {
border: 1px solid #eee; border: 1px solid #eee;
padding: 16rpx 20rpx; padding: 16rpx 20rpx;
border-radius: 12rpx; border-radius: 12rpx;
font-size: 28rpx; font-size: 30rpx;
} }
@ -149,7 +149,7 @@ onLoad(options => {
.name { .name {
flex-grow: 1; flex-grow: 1;
margin-right: 20rpx; margin-right: 20rpx;
font-size: 28rpx; font-size: 30rpx;
} }
.page-item { .page-item {

View File

@ -0,0 +1,80 @@
<template>
<uni-popup ref="popup" type="center" :mask-click="false">
<view class="bg-white rounded overflow-hidden" style="width: 690rpx;">
<view class="flex text-center border-b">
<view class="relative px-15 py-12 flex-grow text-lg"
:class="type == 'agree' ? 'text-primary active-title' : 'text-dark'" @click="type = 'agree'">
用户协议
</view>
<view class="relative px-15 py-12 flex-grow text-lg"
:class="type != 'agree' ? 'text-primary active-title' : 'text-dark'" @click="type = 'privacyPolicy'">
隐私政策
</view>
</view>
<scroll-view v-if="type === 'agree'" scroll-y="true" class="popup-content-scroll">
<view class="px-15 py-12 text-base leading-normal" style="white-space: pre-wrap;">
{{ userAgreement }}
</view>
</scroll-view>
<scroll-view v-else scroll-y="true" class="popup-content-scroll">
<view class="px-15 py-12 text-base leading-normal text-dark" style="white-space: pre-wrap;">
{{ privacy }}
</view>
</scroll-view>
<view class="footer-buttons">
<button-footer hideden-shadow confirmText="我已阅读并同意" @cancel="close" @confirm="confirm()" />
</view>
</view>
</uni-popup>
</template>
<script setup>
import { ref, watch } from 'vue';
import privacy from './privacy-policy.js';
import userAgreement from './user-agreement.js';
import ButtonFooter from '@/components/button-footer.vue';
const emits = defineEmits(['close', 'confirm'])
const props = defineProps({
visible: {
type: Boolean,
default: false
}
})
const popup = ref()
const type = ref('agree')
function close() {
emits('close')
}
function confirm() {
emits('confirm')
}
watch(() => props.visible, n => {
if (n) {
popup.value && popup.value.open();
type.value = 'agree'
} else {
popup.value && popup.value.close()
}
})
</script>
<style lang="scss" scoped>
.active-title::after {
content: '';
position: absolute;
bottom: 4rpx;
left: calc(50% - 40rpx);
width: 80rpx;
height: 8rpx;
background: #0074ff;
border-radius: 4rpx;
}
.popup-content-scroll {
max-height: 50vh;
}
</style>

View File

@ -23,14 +23,14 @@
</view> </view>
<!-- 登录按钮 --> <!-- 登录按钮 -->
<view class="login-btn-wrap"> <view class="login-btn-wrap">
<button v-if="checked && account" class="login-btn" type="primary" @click="getPhoneNumber"> <button v-if="checked && account && account.mobile" class="login-btn" type="primary" @click="getPhoneNumber">
手机号快捷登录 手机号快捷登录
</button> </button>
<button v-else-if="checked" class="login-btn" type="primary" open-type="getPhoneNumber" <button v-else-if="checked" class="login-btn" type="primary" open-type="getPhoneNumber"
@getphonenumber="getPhoneNumber"> @getphonenumber="getPhoneNumber">
手机号快捷登录 手机号快捷登录
</button> </button>
<button v-else class="login-btn" type="primary" @click="remind()"> <button v-else class="login-btn" type="primary" @click="visible = true">
手机号快捷登录 手机号快捷登录
</button> </button>
</view> </view>
@ -43,6 +43,7 @@
<text class="link" @click="toAggreement('privacyPolicy')">隐私政策</text> <text class="link" @click="toAggreement('privacyPolicy')">隐私政策</text>
</view> </view>
</view> </view>
<agreement-popup :visible="visible" @close="visible = false" @confirm="onAgree()" />
</template> </template>
@ -56,6 +57,7 @@ import { get, set } from "@/utils/cache";
import { toast } from "@/utils/widget"; import { toast } from "@/utils/widget";
import groupAvatar from "@/components/group-avatar.vue"; import groupAvatar from "@/components/group-avatar.vue";
import agreementPopup from "./agreement-popup.vue";
const env = __VITE_ENV__; const env = __VITE_ENV__;
const appid = env.MP_WX_APP_ID; const appid = env.MP_WX_APP_ID;
@ -64,6 +66,7 @@ const checked = ref(false);
const redirectUrl = ref(""); const redirectUrl = ref("");
const { account } = storeToRefs(useAccountStore()); const { account } = storeToRefs(useAccountStore());
const { login } = useAccountStore(); const { login } = useAccountStore();
const visible = ref(false)
function attempRedirect(url) { function attempRedirect(url) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -85,8 +88,9 @@ function attempSwitchTab(url) {
}); });
} }
function remind() { function onAgree() {
toast("请先阅读并同意用户协议和隐私政策"); visible.value = false;
checked.value = true;
} }
function toAggreement(type) { function toAggreement(type) {
@ -102,7 +106,6 @@ function toHome() {
} }
async function bindTeam() { async function bindTeam() {
const res = await api('bindWxappWithTeam', { appid, corpId: team.value.corpId, teamId: team.value.teamId, openid: account.value.openid }); const res = await api('bindWxappWithTeam', { appid, corpId: team.value.corpId, teamId: team.value.teamId, openid: account.value.openid });
if (!res || !res.success) { if (!res || !res.success) {
@ -118,7 +121,7 @@ async function bindTeam() {
async function getPhoneNumber(e) { async function getPhoneNumber(e) {
const phoneCode = e && e.detail && e.detail.code; const phoneCode = e && e.detail && e.detail.code;
if (!account.value) { if (!account.value || !account.value.mobile) {
const res = await login(phoneCode); const res = await login(phoneCode);
if (!res) return; if (!res) return;
} }
@ -171,7 +174,7 @@ onLoad((opts) => {
line-height: 80rpx; line-height: 80rpx;
background: linear-gradient(270deg, #1b5cc8 2.26%, #0877f1 94.33%); background: linear-gradient(270deg, #1b5cc8 2.26%, #0877f1 94.33%);
color: #fff; color: #fff;
font-size: 30rpx; font-size: 32rpx;
border-radius: 48rpx; border-radius: 48rpx;
font-weight: 600; font-weight: 600;
box-shadow: 0 4rpx 16rpx rgba(59, 124, 255, 0.08); box-shadow: 0 4rpx 16rpx rgba(59, 124, 255, 0.08);
@ -256,14 +259,14 @@ onLoad((opts) => {
} }
.doctor-name { .doctor-name {
font-size: 40rpx; font-size: 42rpx;
font-weight: 600; font-weight: 600;
color: #1d2129; color: #1d2129;
margin-top: 40rpx; margin-top: 40rpx;
} }
.doctor-hospital { .doctor-hospital {
font-size: 28rpx; font-size: 30rpx;
color: #78808f; color: #78808f;
font-weight: 400; font-weight: 400;
margin-top: 20rpx; margin-top: 20rpx;
@ -276,13 +279,13 @@ onLoad((opts) => {
border-radius: 4rpx; border-radius: 4rpx;
border: 1rpx solid #1a3e8433; border: 1rpx solid #1a3e8433;
padding: 0 8rpx; padding: 0 8rpx;
font-size: 22rpx; font-size: 24rpx;
font-weight: 400; font-weight: 400;
margin-top: 20rpx; margin-top: 20rpx;
} }
.login-tip { .login-tip {
font-size: 32rpx; font-size: 34rpx;
color: #000000; color: #000000;
font-weight: 500; font-weight: 500;
text-align: center; text-align: center;
@ -307,7 +310,7 @@ onLoad((opts) => {
line-height: 96rpx; line-height: 96rpx;
background: linear-gradient(270deg, #1b5cc8 2.26%, #0877f1 94.33%); background: linear-gradient(270deg, #1b5cc8 2.26%, #0877f1 94.33%);
color: #fff; color: #fff;
font-size: 32rpx; font-size: 34rpx;
border-radius: 48rpx; border-radius: 48rpx;
font-weight: 600; font-weight: 600;
box-shadow: 0 4rpx 16rpx rgba(59, 124, 255, 0.08); box-shadow: 0 4rpx 16rpx rgba(59, 124, 255, 0.08);
@ -324,7 +327,7 @@ onLoad((opts) => {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
color: #b2b7c2; color: #b2b7c2;
font-size: 22rpx; font-size: 24rpx;
margin-top: 40rpx; margin-top: 40rpx;
gap: 8rpx; gap: 8rpx;
} }

View File

@ -15,6 +15,8 @@ import useAccountStore from "@/store/account";
const env = __VITE_ENV__; const env = __VITE_ENV__;
const appid = env.MP_WX_APP_ID; const appid = env.MP_WX_APP_ID;
const { account } = storeToRefs(useAccountStore()); const { account } = storeToRefs(useAccountStore());
const { login } = useAccountStore();
const loading = ref(false); const loading = ref(false);
@ -27,7 +29,7 @@ function copy() {
}) })
} }
async function changeTeam({ teamId, corpId, corpUserId }) { async function changeTeam({ teamId, corpId, corpUserId, qrid, referenceCustomerId }) {
loading.value = true; loading.value = true;
const res = await api("getTeamData", { teamId, corpId, withCorpName: true }); const res = await api("getTeamData", { teamId, corpId, withCorpName: true });
loading.value = false; loading.value = false;
@ -36,13 +38,19 @@ async function changeTeam({ teamId, corpId, corpUserId }) {
team.value.corpName = res.data.corpName; team.value.corpName = res.data.corpName;
set('home-invite-team-info', { set('home-invite-team-info', {
teamId: team.value.teamId, teamId: team.value.teamId,
corpUserId: corpUserId || '' corpUserId: corpUserId || '',
corpUserId,
qrid,
referenceCustomerId: referenceCustomerId || ''
}); });
if (account.value) { await login()
if (account.value && account.value.mobile) {
bindTeam(corpUserId) bindTeam(corpUserId)
} else { } else {
set("invite-team-info", { set("invite-team-info", {
corpUserId, corpUserId,
qrid,
referenceCustomerId,
corpId: team.value.corpId, corpId: team.value.corpId,
teamId: team.value.teamId, teamId: team.value.teamId,
corpName: team.value.corpName, corpName: team.value.corpName,
@ -79,16 +87,21 @@ async function bindTeam(corpUserId) {
} }
onLoad((options) => { onLoad((options) => {
opts.value = JSON.stringify(options) if (options.q) {
const href = opts.value = JSON.stringify(options)
typeof options.q === "string" ? decodeURIComponent(options.q) : ""; const href =
const [, url = ""] = href.split("?"); typeof options.q === "string" ? decodeURIComponent(options.q) : "";
const data = url.split("&").reduce((acc, cur) => { const [, url = ""] = href.split("?");
const [key, value] = cur.split("="); const data = url.split("&").reduce((acc, cur) => {
acc[key] = value; const [key, value] = cur.split("=");
return acc; acc[key] = value;
}, {}); return acc;
changeTeam(data); }, {});
changeTeam(data);
} else if (options.type === 'archive') {
changeTeam(options);
}
}); });
</script> </script>
<style> <style>

View File

@ -1,7 +1,7 @@
// SCSS 变量定义 // SCSS 变量定义
$font-size-text: 30rpx; $font-size-text: 32rpx;
$font-size-tip: 28rpx; $font-size-tip: 30rpx;
$font-size-title: 32rpx; $font-size-title: 34rpx;
$text-color-sub: #999; $text-color-sub: #999;
$primary-color: #0877F1; $primary-color: #0877F1;
@ -37,6 +37,13 @@ $primary-color: #0877F1;
justify-content: space-between; justify-content: space-between;
} }
.header-actions {
display: flex;
align-items: center;
gap: 12rpx;
flex-shrink: 0;
}
.patient-basic-info { .patient-basic-info {
display: flex; display: flex;
align-items: center; align-items: center;
@ -84,6 +91,26 @@ $primary-color: #0877F1;
} }
} }
.remind-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 8rpx 20rpx;
border-radius: 24rpx;
border: 2rpx solid #3876f6;
background: #fff;
}
.remind-btn:active {
opacity: 0.85;
}
.remind-btn-text {
font-size: 24rpx;
color: #3876f6;
line-height: 1.4;
}
.badge-processing { .badge-processing {
background: #d1ecf1; background: #d1ecf1;
@ -298,7 +325,7 @@ $primary-color: #0877F1;
} }
.username-text { .username-text {
font-size: 22rpx; font-size: 24rpx;
color: #999; color: #999;
line-height: 1.2; line-height: 1.2;
} }
@ -369,7 +396,7 @@ $primary-color: #0877F1;
} }
.message-text { .message-text {
font-size: 30rpx; font-size: 32rpx;
line-height: 1.4; line-height: 1.4;
word-wrap: break-word; word-wrap: break-word;
word-break: break-all; word-break: break-all;
@ -442,7 +469,7 @@ $primary-color: #0877F1;
.send-btn { .send-btn {
background: #3876f6; background: #3876f6;
color: #fff; color: #fff;
font-size: 28rpx; font-size: 30rpx;
font-weight: 600; font-weight: 600;
border: none; border: none;
border-radius: 40rpx; border-radius: 40rpx;
@ -477,7 +504,7 @@ $primary-color: #0877F1;
padding: 16rpx; padding: 16rpx;
background-color: #f3f5fa; background-color: #f3f5fa;
border-radius: 10rpx; border-radius: 10rpx;
font-size: 28rpx; font-size: 30rpx;
min-height: 80rpx; min-height: 80rpx;
max-height: 200rpx; max-height: 200rpx;
border: none; border: none;
@ -1177,7 +1204,7 @@ $primary-color: #0877F1;
} }
.voice-duration { .voice-duration {
font-size: 24rpx; font-size: 26rpx;
color: inherit; color: inherit;
} }

View File

@ -7,8 +7,9 @@
</view> </view>
<view class="input-area"> <view class="input-area">
<textarea v-if="!showVoiceInput" class="text-input" v-model="inputText" placeholder="我来说两句..." <textarea v-if="!showVoiceInput" class="text-input" v-model="inputText" placeholder="我来说两句..."
@confirm="sendTextMessage" @focus="handleInputFocus" @input="handleInput" @confirm="sendTextMessage" @focus="handleInputFocus" @input="handleInput"
:auto-height="true" :show-confirm-bar="false" :adjust-position="false" :hold-keyboard="true" /> @blur="handleInputBlur" :focus="isInputFocused" :auto-height="true" :show-confirm-bar="false"
:adjust-position="false" :hold-keyboard="true" />
<input v-else class="voice-input-btn" :class="{ recording: isRecording }" @touchstart="startRecord" <input v-else class="voice-input-btn" :class="{ recording: isRecording }" @touchstart="startRecord"
@touchmove="onRecordTouchMove" @touchend="stopRecord" @touchcancel="cancelRecord" :placeholder="isRecording ? '松开发送' : '按住说话'" disabled> @touchmove="onRecordTouchMove" @touchend="stopRecord" @touchcancel="cancelRecord" :placeholder="isRecording ? '松开发送' : '按住说话'" disabled>
</input> </input>
@ -85,6 +86,7 @@ const emit = defineEmits(["messageSent", "scrollToBottom"]);
// //
const inputText = ref(""); const inputText = ref("");
const isInputFocused = ref(false);
const showVoiceInput = ref(false); const showVoiceInput = ref(false);
const showMorePanel = ref(false); const showMorePanel = ref(false);
const isRecording = ref(false); const isRecording = ref(false);
@ -153,9 +155,9 @@ const sendTextMessage = async () => {
const textToSend = inputText.value; const textToSend = inputText.value;
inputText.value = ""; inputText.value = "";
await sendMessage("text", textToSend); await sendMessage("text", textToSend);
// //
nextTick(() => { nextTick(() => {
// hold-keyboard // hold-keyboard
@ -346,11 +348,21 @@ const morePanelButtons = [
function handleInputFocus() { function handleInputFocus() {
console.log("handleInputFocus"); console.log("handleInputFocus");
isInputFocused.value = true;
nextTick().then(() => { nextTick().then(() => {
emit("scrollToBottom"); emit("scrollToBottom");
}); });
} }
function handleInputBlur() {
isInputFocused.value = false;
}
function blurInput() {
isInputFocused.value = false;
uni.hideKeyboard();
}
function handleInput(e) { function handleInput(e) {
// textarea // textarea
nextTick().then(() => { nextTick().then(() => {
@ -358,6 +370,10 @@ function handleInput(e) {
}); });
} }
defineExpose({
blurInput,
});
onMounted(() => { onMounted(() => {
// //
initRecorderManager(); initRecorderManager();
@ -377,4 +393,4 @@ onUnmounted(() => {
<style scoped lang="scss"> <style scoped lang="scss">
@import "../chat.scss"; @import "../chat.scss";
</style> </style>

View File

@ -45,7 +45,7 @@ const handleReject = () => {
} }
.accept-text { .accept-text {
font-size: 28rpx; font-size: 30rpx;
color: #333; color: #333;
line-height: 1.6; line-height: 1.6;
} }
@ -60,7 +60,7 @@ const handleReject = () => {
flex: 1; flex: 1;
height: 80rpx; height: 80rpx;
border-radius: 8rpx; border-radius: 8rpx;
font-size: 28rpx; font-size: 30rpx;
border: none; border: none;
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -80,13 +80,13 @@ const handleApply = () => {
} }
.apply-title { .apply-title {
font-size: 32rpx; font-size: 34rpx;
font-weight: 600; font-weight: 600;
color: #333; color: #333;
} }
.apply-desc { .apply-desc {
font-size: 24rpx; font-size: 26rpx;
color: #999; color: #999;
} }
@ -99,7 +99,7 @@ const handleApply = () => {
background-color: #1890ff; background-color: #1890ff;
color: #fff; color: #fff;
border-radius: 8rpx; border-radius: 8rpx;
font-size: 28rpx; font-size: 30rpx;
font-weight: 500; font-weight: 500;
border: none; border: none;
display: flex; display: flex;

View File

@ -41,7 +41,7 @@ const handleCancel = () => {
} }
.cancel-text { .cancel-text {
font-size: 28rpx; font-size: 30rpx;
color: #333; color: #333;
line-height: 1.6; line-height: 1.6;
} }
@ -55,7 +55,7 @@ const handleCancel = () => {
.btn-reapply { .btn-reapply {
width: 100%; width: 100%;
heiger-radius: 8rpx; heiger-radius: 8rpx;
font-size: 28rpx; font-size: 30rpx;
border: none; border: none;
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -44,7 +44,7 @@
<view class="article-title">{{ getArticleData(message).title }}</view> <view class="article-title">{{ getArticleData(message).title }}</view>
<view class="article-desc">{{ getArticleData(message).desc }}</view> <view class="article-desc">{{ getArticleData(message).desc }}</view>
</view> </view>
<image v-if="getArticleData(message).imgUrl" class="article-image" :src="getArticleData(message).imgUrl" <image v-if="getArticleData(message).cover" class="article-image" :src="getArticleData(message).cover"
mode="aspectFill" /> mode="aspectFill" />
</view> </view>
@ -77,7 +77,12 @@
<script setup> <script setup>
import { computed } from "vue"; import { computed } from "vue";
import { storeToRefs } from "pinia";
import api from "@/utils/api.js";
import { getParsedCustomMessage } from "@/utils/chat-utils.js"; import { getParsedCustomMessage } from "@/utils/chat-utils.js";
import { toast } from '@/utils/widget'
import useAccountStore from "@/store/account.js";
// import MessageCard from "./message-card/message-card.vue"; // import MessageCard from "./message-card/message-card.vue";
const props = defineProps({ const props = defineProps({
@ -92,12 +97,22 @@ const props = defineProps({
}); });
defineEmits(["playVoice", "previewImage", "viewDetail"]); defineEmits(["playVoice", "previewImage", "viewDetail"]);
const { account, openid } = storeToRefs(useAccountStore());
// //
const isPlaying = computed(() => { const isPlaying = computed(() => {
return props.playingVoiceId === props.message.ID; return props.playingVoiceId === props.message.ID;
}); });
const payloadData = computed(() => {
try {
return JSON.parse(props.message.payload.data);
} catch (e) {
return {}
}
})
// //
const getImageStyle = (imageInfo) => { const getImageStyle = (imageInfo) => {
// 使 // 使
@ -195,6 +210,23 @@ const getArticleData = (message) => {
// //
const handleArticleClick = (message) => { const handleArticleClick = (message) => {
if (isWechatChannels()) {
uni.openChannelsActivity({
finderUserName: payloadData.value.wechatChannels.finderUserName,
feedId: payloadData.value.wechatChannels.feedId,
success: () => {
markArticleRead(payloadData.value.sendId, payloadData.value.articleId);
},
fail: err => {
const errMsg = err.errMsg || '';
if (/cancel/i.test(errMsg)) {
return
}
toast(`打开视频号失败:${errMsg}`)
}
})
return
}
const { articleId, sendId } = getArticleData(message); const { articleId, sendId } = getArticleData(message);
uni.navigateTo({ uni.navigateTo({
url: `/pages/article/article-detail?id=${articleId}&sendId=${sendId || ''}&corpId=${props.corpId}`, url: `/pages/article/article-detail?id=${articleId}&sendId=${sendId || ''}&corpId=${props.corpId}`,
@ -244,6 +276,25 @@ const handleSurveyClick = (message) => {
}) })
} }
}; };
function isWechatChannels() {
const wechatChannels = payloadData.value.wechatChannels || {};
return typeof wechatChannels.finderUserName === 'string' && wechatChannels.finderUserName.trim() && typeof wechatChannels.feedId === 'string' && wechatChannels.feedId.trim();
}
async function markArticleRead(sendId, articleId) {
const unionid = account.value?.unionid;
if (!unionid || !articleId) return;
try {
const { success } = await api(
"addArticleReadRecord",
{ corpId: props.corpId, articleId, unionid, sendId },
false
);
} catch (err) {
console.warn("markArticleRead failed:", err?.message || err);
}
};
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -159,7 +159,7 @@ const handleConfirm = () => {
} }
.modal-title { .modal-title {
font-size: 32rpx; font-size: 34rpx;
font-weight: 500; font-weight: 500;
color: #333; color: #333;
line-height: 1.5; line-height: 1.5;
@ -194,7 +194,7 @@ const handleConfirm = () => {
} }
.option-text { .option-text {
font-size: 28rpx; font-size: 30rpx;
color: #333; color: #333;
flex: 1; flex: 1;
} }
@ -220,13 +220,13 @@ const handleConfirm = () => {
} }
.check-mark { .check-mark {
font-size: 24rpx; font-size: 26rpx;
color: #fff; color: #fff;
font-weight: bold; font-weight: bold;
} }
.arrow-icon { .arrow-icon {
font-size: 40rpx; font-size: 42rpx;
color: #999; color: #999;
font-weight: 300; font-weight: 300;
} }
@ -241,7 +241,7 @@ const handleConfirm = () => {
.custom-textarea { .custom-textarea {
width: 100%; width: 100%;
min-height: 120rpx; min-height: 120rpx;
font-size: 28rpx; font-size: 30rpx;
color: #333; color: #333;
line-height: 1.6; line-height: 1.6;
background-color: transparent; background-color: transparent;
@ -253,7 +253,7 @@ const handleConfirm = () => {
.char-count { .char-count {
display: block; display: block;
text-align: right; text-align: right;
font-size: 24rpx; font-size: 26rpx;
color: #999; color: #999;
margin-top: 8rpx; margin-top: 8rpx;
} }
@ -270,7 +270,7 @@ const handleConfirm = () => {
flex: 1; flex: 1;
height: 80rpx; height: 80rpx;
border-radius: 8rpx; border-radius: 8rpx;
font-size: 28rpx; font-size: 30rpx;
border: none; border: none;
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -147,7 +147,7 @@ const notifyText = computed(() => {
.notify-text { .notify-text {
margin: 0 20rpx; margin: 0 20rpx;
font-size: 24rpx; font-size: 26rpx;
color: red; color: red;
white-space: nowrap; white-space: nowrap;
flex-shrink: 0; flex-shrink: 0;

View File

@ -1,41 +1,29 @@
<template> <template>
<page-meta <page-meta :page-style="'overflow:' + (keyboardHeight > 0 ? 'hidden' : 'visible')"></page-meta>
:page-style="'overflow:' + (keyboardHeight > 0 ? 'hidden' : 'visible')"
></page-meta>
<view class="chat-page" :style="{ paddingBottom: keyboardHeight + 'px' }"> <view class="chat-page" :style="{ paddingBottom: keyboardHeight + 'px' }">
<!-- 患者信息栏 --> <!-- 患者信息栏 -->
<view class="patient-info-bar" v-if="patientInfo.name"> <view class="patient-info-bar" v-if="patientInfo.name">
<view class="patient-info-content"> <view class="patient-info-content">
<view class="patient-basic-info"> <view class="patient-basic-info">
<text class="patient-name">{{ patientInfo.name }}</text> <text class="patient-name">{{ patientInfo.name }}</text>
<text class="patient-detail" <text class="patient-detail">{{ patientInfo.sex }} · {{ patientInfo.age }}</text>
>{{ patientInfo.sex }} · {{ patientInfo.age }}</text
>
</view> </view>
<view <view class="header-actions">
class="status-badge" <view class="status-badge" :class="chatStatusInfo.badgeClass" v-if="chatStatusInfo.badgeText">
:class="chatStatusInfo.badgeClass" <text class="badge-text">{{ chatStatusInfo.badgeText }}</text>
v-if="chatStatusInfo.badgeText" </view>
> <view v-if="showSubscribeEntry" class="remind-btn" @click="handleSubscribeReminder">
<text class="badge-text">{{ chatStatusInfo.badgeText }}</text> <text class="remind-btn-text">接收提醒</text>
</view>
</view> </view>
</view> </view>
</view> </view>
<!-- 聊天消息区域 --> <!-- 聊天消息区域 -->
<scroll-view <scroll-view class="chat-content" :style="{
class="chat-content" bottom: (keyboardHeight > 0 ? keyboardHeight + 60 : 60) + 'px',
:style="{ }" scroll-y="true" enhanced="true" bounces="false" :scroll-into-view="scrollIntoView" @scroll="onScroll"
bottom: (keyboardHeight > 0 ? keyboardHeight + 60 : 60) + 'px', @scrolltoupper="handleScrollToUpper" @click="closeMorePanel" ref="chatScrollView">
}"
scroll-y="true"
enhanced="true"
bounces="false"
:scroll-into-view="scrollIntoView"
@scroll="onScroll"
@scrolltoupper="handleScrollToUpper"
ref="chatScrollView"
>
<!-- 加载更多提示 --> <!-- 加载更多提示 -->
<view class="load-more-tip" v-if="messageList.length >= 15"> <view class="load-more-tip" v-if="messageList.length >= 15">
<view class="loading" v-if="isLoadingMore"> <view class="loading" v-if="isLoadingMore">
@ -51,21 +39,13 @@
<!-- 聊天消息列表 --> <!-- 聊天消息列表 -->
<view class="message-list" @click="closeMorePanel"> <view class="message-list" @click="closeMorePanel">
<view <view v-for="(message, index) in messageList" :key="message.ID" :id="`msg-${message.ID}`" class="message-item"
v-for="(message, index) in messageList"
:key="message.ID"
:id="`msg-${message.ID}`"
class="message-item"
:class="{ :class="{
'message-right': message.flow === 'out', 'message-right': message.flow === 'out',
'message-left': message.flow === 'in', 'message-left': message.flow === 'in',
}" }">
>
<!-- 时间分割线 --> <!-- 时间分割线 -->
<view <view v-if="shouldShowTime(message, index, messageList)" class="time-divider">
v-if="shouldShowTime(message, index, messageList)"
class="time-divider"
>
<text class="time-text">{{ formatTime(message.lastTime) }}</text> <text class="time-text">{{ formatTime(message.lastTime) }}</text>
</view> </view>
@ -76,31 +56,20 @@
<!-- 消息内容 --> <!-- 消息内容 -->
<view v-else class="message-content"> <view v-else class="message-content">
<!-- 发送者头像统一处理 --> <!-- 发送者头像统一处理 -->
<image <image v-if="message.flow === 'in'" class="doctor-msg-avatar" :src="getUserAvatar(message.from)"
v-if="message.flow === 'in'" mode="aspectFill" />
class="doctor-msg-avatar"
:src="getUserAvatar(message.from)"
mode="aspectFill"
/>
<!-- 发送者头像统一处理 --> <!-- 发送者头像统一处理 -->
<image <image v-if="message.flow === 'out'" class="user-msg-avatar" :src="getUserAvatar(message.from)"
v-if="message.flow === 'out'" mode="aspectFill" />
class="user-msg-avatar"
:src="getUserAvatar(message.from)"
mode="aspectFill"
/>
<!-- 消息内容区域 --> <!-- 消息内容区域 -->
<view class="message-bubble-container"> <view class="message-bubble-container">
<!-- 用户名显示 --> <!-- 用户名显示 -->
<view <view class="username-label" :class="{
class="username-label" left: message.flow === 'in',
:class="{ right: message.flow === 'out',
left: message.flow === 'in', }">
right: message.flow === 'out',
}"
>
<text class="username-text">{{ <text class="username-text">{{
chatMember[message.from]?.name chatMember[message.from]?.name
}}</text> }}</text>
@ -108,25 +77,15 @@
<view class="message-bubble" :class="getBubbleClass(message)"> <view class="message-bubble" :class="getBubbleClass(message)">
<!-- 消息内容 --> <!-- 消息内容 -->
<MessageTypes <MessageTypes :corpId="corpId" :message="message" :formatTime="formatTime"
:corpId="corpId" :playingVoiceId="playingVoiceId" @playVoice="playVoice" @previewImage="previewImage"
:message="message" @viewDetail="(message) => handleViewDetail(message)" />
:formatTime="formatTime"
:playingVoiceId="playingVoiceId"
@playVoice="playVoice"
@previewImage="previewImage"
@viewDetail="(message) => handleViewDetail(message)"
/>
</view> </view>
</view> </view>
<!-- 发送状态 --> <!-- 发送状态 -->
<view v-if="message.flow === 'out'" class="message-status"> <view v-if="message.flow === 'out'" class="message-status">
<text <text v-if="message.status === 'failed'" class="status-text failed">发送失败</text>
v-if="message.status === 'failed'"
class="status-text failed"
>发送失败</text
>
</view> </view>
</view> </view>
</view> </view>
@ -140,22 +99,12 @@
<ConsultApply v-if="showConsultApply" @apply="handleApplyConsult" /> <ConsultApply v-if="showConsultApply" @apply="handleApplyConsult" />
<!-- 聊天输入组件 --> <!-- 聊天输入组件 -->
<ChatInput <ChatInput v-if="!isEvaluationPopupOpen && !showConsultCancel && !showConsultApply" ref="chatInputRef"
v-if="!isEvaluationPopupOpen && !showConsultCancel && !showConsultApply" :timChatManager="timChatManager" :formatTime="formatTime" :groupId="chatInfo.conversationID
ref="chatInputRef"
:timChatManager="timChatManager"
:formatTime="formatTime"
:groupId="
chatInfo.conversationID
? chatInfo.conversationID.replace('GROUP', '') ? chatInfo.conversationID.replace('GROUP', '')
: '' : ''
" " :userId="openid" :corpId="corpId.value" :keyboardHeight="keyboardHeight"
:userId="openid" @scrollToBottom="() => scrollToBottom(true)" @messageSent="() => scrollToBottom(true)" />
:corpId="corpId.value"
:keyboardHeight="keyboardHeight"
@scrollToBottom="() => scrollToBottom(true)"
@messageSent="() => scrollToBottom(true)"
/>
</view> </view>
</template> </template>
@ -190,18 +139,28 @@ import ChatInput from "./components/chat-input.vue";
import SystemMessage from "./components/system-message.vue"; import SystemMessage from "./components/system-message.vue";
import ConsultCancel from "./components/consult-cancel.vue"; import ConsultCancel from "./components/consult-cancel.vue";
import ConsultApply from "./components/consult-apply.vue"; import ConsultApply from "./components/consult-apply.vue";
import {
checkConversationSubscribeEntryVisible,
requestConversationSubscribeMessage,
} from "@/utils/subscribe-message";
import {
SUBSCRIBE_MESSAGE_ROLE,
SUBSCRIBE_MESSAGE_SCENE,
} from "@/utils/subscribe-message-config";
const timChatManager = globalTimChatManager; const timChatManager = globalTimChatManager;
// corpId // corpId
const corpId = ref(""); const corpId = ref("");
const showSubscribeEntry = ref(false);
// //
const { account, openid, isIMInitialized } = storeToRefs(useAccountStore()); const { account, openid, isIMInitialized } = storeToRefs(useAccountStore());
const { initIMAfterLogin } = useAccountStore(); const { initIMAfterLogin, login } = useAccountStore();
// //
const chatInputRef = ref(null); const chatInputRef = ref(null);
const loginPromise = ref(null);
const groupId = ref(""); const groupId = ref("");
const { chatMember, getGroupInfo, getUserAvatar } = useGroupChat(groupId); const { chatMember, getGroupInfo, getUserAvatar } = useGroupChat(groupId);
@ -445,7 +404,10 @@ function getBubbleClass(message) {
// //
onLoad(async (options) => { onLoad(async (options) => {
groupId.value = options.groupID || ""; loginPromise.value = login();
await loginPromise.value;
loginPromise.value = null;
groupId.value = decodeURIComponent(options.groupID) || "";
messageList.value = []; messageList.value = [];
isLoading.value = false; isLoading.value = false;
if (options.conversationID) { if (options.conversationID) {
@ -517,7 +479,7 @@ const initTIMCallbacks = async () => {
loadMessageList(); loadMessageList();
} }
}); });
timChatManager.setCallback("onSDKNotReady", () => {}); timChatManager.setCallback("onSDKNotReady", () => { });
timChatManager.setCallback("onMessageReceived", (message) => { timChatManager.setCallback("onMessageReceived", (message) => {
console.log("页面收到消息:", { console.log("页面收到消息:", {
@ -610,16 +572,44 @@ const initTIMCallbacks = async () => {
) { ) {
seenIds.add(message.ID); seenIds.add(message.ID);
uniqueMessages.push(message); uniqueMessages.push(message);
} }
}); });
messageList.value = uniqueMessages;
console.log( const mergedMessages = [];
"消息列表已更新,原始", const mergedSeenIds = new Set();
messages.length, const existingMessages = Array.isArray(messageList.value)
"条,过滤后", ? messageList.value
uniqueMessages.length, : [];
"条消息"
); existingMessages.forEach((message) => {
if (!message?.ID) return;
if (message.conversationID !== chatInfo.value.conversationID) return;
if (mergedSeenIds.has(message.ID)) return;
mergedSeenIds.add(message.ID);
mergedMessages.push(message);
});
uniqueMessages.forEach((message) => {
if (!message?.ID) return;
if (mergedSeenIds.has(message.ID)) return;
mergedSeenIds.add(message.ID);
mergedMessages.push(message);
});
mergedMessages.sort((a, b) => {
const ta = Number(a?.lastTime || a?.time || 0) || 0;
const tb = Number(b?.lastTime || b?.time || 0) || 0;
return ta - tb;
});
messageList.value = mergedMessages;
console.log(
"消息列表已更新,原始",
messages.length,
"条,过滤后",
messageList.value.length,
"条消息"
);
isCompleted.value = data.isCompleted || false; isCompleted.value = data.isCompleted || false;
isLoadingMore.value = false; isLoadingMore.value = false;
@ -787,9 +777,11 @@ const scrollToBottom = (immediate = false) => {
}; };
// //
const closeMorePanel = () => { const closeMorePanel = () => {
uni.$emit("closeMorePanel"); uni.$emit("closeMorePanel");
}; chatInputRef.value?.blurInput?.();
uni.hideKeyboard();
};
// //
const onScroll = throttle((e) => { const onScroll = throttle((e) => {
@ -856,7 +848,12 @@ const handleScrollToUpper = async () => {
}; };
// //
onShow(() => { onShow(async () => {
if (loginPromise.value) {
await loginPromise.value;
}
loadSubscribeEntryState();
if (!account.value || !openid.value) { if (!account.value || !openid.value) {
uni.redirectTo({ uni.redirectTo({
url: "/pages/login/login", url: "/pages/login/login",
@ -908,6 +905,14 @@ onShow(() => {
} }
}); });
watch(
() => corpId.value,
() => {
loadSubscribeEntryState();
},
{ immediate: true }
);
// //
onHide(() => { onHide(() => {
stopIMMonitoring(); stopIMMonitoring();
@ -1063,6 +1068,31 @@ const handleApplyConsult = async () => {
} }
}; };
const handleSubscribeReminder = async () => {
await requestConversationSubscribeMessage({
role: SUBSCRIBE_MESSAGE_ROLE.PATIENT,
scene: SUBSCRIBE_MESSAGE_SCENE.CHAT,
conversationId: chatInfo.value.conversationID || "",
groupId: groupId.value || "",
corpId: corpId.value || "",
patientId: patientId.value || "",
userId: openid.value || account.value?.openid || "",
openid: openid.value || account.value?.openid || "",
unionid: account.value?.unionid || "",
extraData: {
orderStatus: orderStatus.value || "",
page: "pages/message/index",
},
});
};
const loadSubscribeEntryState = async () => {
showSubscribeEntry.value = await checkConversationSubscribeEntryVisible(
corpId.value || "",
true
);
};
// //
onUnmounted(() => { onUnmounted(() => {
clearMessageCache(); clearMessageCache();

View File

@ -1,6 +1,5 @@
<template> <template>
<view class="message-page"> <view class="message-page">
<!-- 消息列表 --> <!-- 消息列表 -->
<scroll-view class="message-list" scroll-y="true" refresher-enabled :refresher-triggered="refreshing" <scroll-view class="message-list" scroll-y="true" refresher-enabled :refresher-triggered="refreshing"
@refresherrefresh="handleRefresh" @scrolltolower="handleLoadMore"> @refresherrefresh="handleRefresh" @scrolltolower="handleLoadMore">
@ -52,6 +51,14 @@
}}</text> }}</text>
</view> </view>
</scroll-view> </scroll-view>
<view
v-if="showSubscribeEntry"
class="subscribe-entry"
@click="handleSubscribeReminder"
>
<text class="subscribe-entry-text">接收提醒</text>
</view>
</view> </view>
</template> </template>
@ -65,9 +72,19 @@ import { mergeConversationWithGroupDetails } from "@/utils/conversation-merger.j
import { globalUnreadListenerManager } from "@/utils/global-unread-listener.js"; import { globalUnreadListenerManager } from "@/utils/global-unread-listener.js";
import useGroupAvatars from "./hooks/use-group-avatars.js"; import useGroupAvatars from "./hooks/use-group-avatars.js";
import GroupAvatar from "@/components/group-avatar.vue"; import GroupAvatar from "@/components/group-avatar.vue";
import {
checkConversationSubscribeEntryVisible,
requestConversationSubscribeMessage,
} from "@/utils/subscribe-message";
import {
SUBSCRIBE_MESSAGE_ROLE,
SUBSCRIBE_MESSAGE_SCENE,
} from "@/utils/subscribe-message-config";
// //
const { account, openid, isIMInitialized, hasImCorpId } = storeToRefs(useAccountStore()); const { account, openid, isIMInitialized, hasImCorpId, teams } = storeToRefs(
useAccountStore()
);
const { initIMAfterLogin } = useAccountStore(); const { initIMAfterLogin } = useAccountStore();
// //
@ -76,6 +93,7 @@ const loading = ref(false);
const loadingMore = ref(false); const loadingMore = ref(false);
const hasMore = ref(false); const hasMore = ref(false);
const refreshing = ref(false); const refreshing = ref(false);
const showSubscribeEntry = ref(false);
// //
const { loadGroupAvatars, getAvatarList } = useGroupAvatars(); const { loadGroupAvatars, getAvatarList } = useGroupAvatars();
@ -492,14 +510,45 @@ const cleanMessageText = (text) => {
return text.replace(/[\r\n]+/g, " ").trim(); return text.replace(/[\r\n]+/g, " ").trim();
}; };
const handleSubscribeReminder = async () => {
await requestConversationSubscribeMessage({
role: SUBSCRIBE_MESSAGE_ROLE.PATIENT,
scene: SUBSCRIBE_MESSAGE_SCENE.LIST,
corpId: teams.value.find((item) => item?.corpId)?.corpId || "",
userId: openid.value || account.value?.openid || "",
openid: openid.value || account.value?.openid || "",
unionid: account.value?.unionid || "",
extraData: {
page: "pages/message/message",
},
});
};
const loadSubscribeEntryState = async () => {
const currentCorpId = teams.value.find((item) => item?.corpId)?.corpId || "";
showSubscribeEntry.value = await checkConversationSubscribeEntryVisible(
currentCorpId,
true
);
};
// //
onShow(async () => { onShow(async () => {
// tabBar // tabBar
// if (globalUnreadListenerManager.isInitialized) { // if (globalUnreadListenerManager.isInitialized) {
// await globalUnreadListenerManager.refreshBadge(); // await globalUnreadListenerManager.refreshBadge();
// } // }
await loadSubscribeEntryState();
}); });
watch(
() => teams.value.map((item) => item?.corpId).join(","),
() => {
loadSubscribeEntryState();
},
{ immediate: true }
);
// //
onHide(() => { onHide(() => {
console.log("【消息列表页】页面隐藏"); console.log("【消息列表页】页面隐藏");
@ -558,7 +607,7 @@ onUnmounted(() => {
} }
.header-title { .header-title {
font-size: 36rpx; font-size: 38rpx;
font-weight: 600; font-weight: 600;
color: #000; color: #000;
} }
@ -575,7 +624,7 @@ onUnmounted(() => {
} }
.badge-text { .badge-text {
font-size: 24rpx; font-size: 26rpx;
color: #fff; color: #fff;
font-weight: 600; font-weight: 600;
line-height: 1; line-height: 1;
@ -596,7 +645,7 @@ onUnmounted(() => {
} }
.loading-text { .loading-text {
font-size: 28rpx; font-size: 30rpx;
color: #999; color: #999;
} }
@ -607,7 +656,7 @@ onUnmounted(() => {
} }
.empty-text { .empty-text {
font-size: 28rpx; font-size: 30rpx;
color: #999; color: #999;
} }
@ -650,7 +699,7 @@ onUnmounted(() => {
} }
.unread-text { .unread-text {
font-size: 20rpx; font-size: 22rpx;
color: #fff; color: #fff;
line-height: 1; line-height: 1;
} }
@ -671,7 +720,7 @@ onUnmounted(() => {
} }
.name { .name {
font-size: 32rpx; font-size: 34rpx;
font-weight: 500; font-weight: 500;
color: #333; color: #333;
flex: 1; flex: 1;
@ -681,7 +730,7 @@ onUnmounted(() => {
} }
.time { .time {
font-size: 24rpx; font-size: 26rpx;
color: #999; color: #999;
margin-left: 16rpx; margin-left: 16rpx;
flex-shrink: 0; flex-shrink: 0;
@ -694,7 +743,7 @@ onUnmounted(() => {
} }
.preview-text { .preview-text {
font-size: 26rpx; font-size: 28rpx;
color: #999; color: #999;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -709,13 +758,41 @@ onUnmounted(() => {
} }
.load-more-text { .load-more-text {
font-size: 24rpx; font-size: 26rpx;
color: #999; color: #999;
} }
.patient-info { .patient-info {
font-size: 26rpx; font-size: 28rpx;
color: #999; color: #999;
padding-bottom: 10rpx; padding-bottom: 10rpx;
} }
.subscribe-entry {
position: fixed;
right: 32rpx;
bottom: 180rpx;
width: 116rpx;
height: 116rpx;
border-radius: 58rpx;
background: #fff;
border: 2rpx solid #3876f6;
box-shadow: 0 8rpx 24rpx rgba(56, 118, 246, 0.16);
display: flex;
align-items: center;
justify-content: center;
z-index: 20;
}
.subscribe-entry:active {
opacity: 0.85;
}
.subscribe-entry-text {
width: 56rpx;
font-size: 26rpx;
line-height: 1.4;
color: #3876f6;
text-align: center;
}
</style> </style>

View File

@ -244,7 +244,7 @@ onLoad(opts => {
} }
@at-root &__txt { @at-root &__txt {
font-size: 32rpx; font-size: 34rpx;
color: #666; color: #666;
} }
} }

View File

@ -302,15 +302,15 @@ onShow(async () => {
} }
.text-xs { .text-xs {
font-size: 22rpx; font-size: 24rpx;
} }
.text-sm { .text-sm {
font-size: 28rpx; font-size: 30rpx;
} }
.text-base { .text-base {
font-size: 32rpx; font-size: 34rpx;
} }
.font-bold { .font-bold {
@ -424,7 +424,7 @@ onShow(async () => {
.loading-text { .loading-text {
margin-top: 20rpx; margin-top: 20rpx;
font-size: 28rpx; font-size: 30rpx;
color: #999; color: #999;
} }
@ -434,7 +434,7 @@ onShow(async () => {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 30rpx 0; padding: 30rpx 0;
font-size: 24rpx; font-size: 26rpx;
color: #999; color: #999;
gap: 10rpx; gap: 10rpx;
} }

View File

@ -56,11 +56,13 @@
</view> </view>
</view> </view>
</view> </view>
<view v-if="list.length === 1" <view v-if="list.length === 1" class="py-12 px-15">
class="flex-shrink-0 mx-10 py-12 leading-normal text-white text-center rounded bg-primary" @click="submit()"> <view class="flex-shrink-0 py-10 text-white text-center rounded-full bg-primary" @click="submit()">
提交 提交
</view>
</view> </view>
</view> </view>
</template> </template>
<script> <script>

View File

@ -1,12 +1,12 @@
<template> <template>
<view class="h-full flex flex-col"> <view class="h-full flex flex-col">
<view class="flex-shrink-0 px-10 pt-5 text-base font-semibold text-center truncate">{{ surery.name }}</view> <view class="flex-shrink-0 px-10 pt-5 text-base font-semibold text-center truncate">{{ survey.name }}</view>
<view v-if="surery.description" <view v-if="survey.description"
class="flex-shrink-0 px-10 mt-10 text-sm leading-normal text-gray line-clamp-2 text-center"> class="flex-shrink-0 px-10 mt-10 text-sm leading-normal text-gray line-clamp-2 text-center">
{{ surery.description }} {{ survey.description }}
</view> </view>
<view class="flex items-center text-gray text-sm px-10 mt-10"> <view class="flex items-center text-gray text-sm px-10 mt-10">
<view v-if="surery.enableScore"> <view v-if="survey.enableScore">
当前得分<text class="text-primary text-base font-semibold min-w-5 inline-block">{{ allScore }} 当前得分<text class="text-primary text-base font-semibold min-w-5 inline-block">{{ allScore }}
</text> </text>
</view> </view>
@ -27,7 +27,7 @@
<view class="flex-grow pl-4 text-gray leading-normal" <view class="flex-grow pl-4 text-gray leading-normal"
:class="quesiton.value && quesiton.value === opt.value ? 'text-primary' : ''"> :class="quesiton.value && quesiton.value === opt.value ? 'text-primary' : ''">
{{ opt.label }} {{ opt.label }}
<text v-if="surery.enableScore && opt.score >= 0"> <text v-if="survey.enableScore && opt.score >= 0">
{{ opt.score }} {{ opt.score }}
</text> </text>
</view> </view>
@ -52,7 +52,7 @@ export default {
props: { props: {
customerName: { type: String, default: '' }, customerName: { type: String, default: '' },
list: { type: Array, default: () => ([]) }, list: { type: Array, default: () => ([]) },
surery: { type: Object, default: () => ({}) } survey: { type: Object, default: () => ({}) }
}, },
computed: { computed: {
allScore() { allScore() {
@ -66,6 +66,12 @@ export default {
return score return score
}, 0) }, 0)
} }
},
methods:{
logScore() {
console.log(this.survey)
console.log(this.allScore)
}
} }
} }
</script> </script>

View File

@ -141,7 +141,7 @@ onLoad(opts => {
@at-root &__customer { @at-root &__customer {
text-align: right; text-align: right;
font-size: 28rpx; font-size: 30rpx;
padding-bottom: 16rpx; padding-bottom: 16rpx;
margin-bottom: 24rpx; margin-bottom: 24rpx;
color: #333; color: #333;
@ -150,7 +150,7 @@ onLoad(opts => {
@at-root &__title { @at-root &__title {
text-align: center; text-align: center;
font-size: 32rpx; font-size: 34rpx;
font-weight: 600; font-weight: 600;
margin-bottom: 24rpx; margin-bottom: 24rpx;
} }
@ -158,7 +158,7 @@ onLoad(opts => {
@at-root &__desc { @at-root &__desc {
text-align: left; text-align: left;
color: #666; color: #666;
font-size: 28rpx; font-size: 30rpx;
margin-bottom: 24rpx; margin-bottom: 24rpx;
} }
@ -169,7 +169,7 @@ onLoad(opts => {
@at-root &__question { @at-root &__question {
position: relative; position: relative;
font-size: 32rpx; font-size: 34rpx;
margin-bottom: 24rpx; margin-bottom: 24rpx;
@at-root &--require::before { @at-root &--require::before {
@ -183,7 +183,7 @@ onLoad(opts => {
@at-root &__input { @at-root &__input {
padding: 10rpx 24rpx; padding: 10rpx 24rpx;
font-size: 28rpx; font-size: 30rpx;
border: 1px solid #eee; border: 1px solid #eee;
border-radius: 8rpx; border-radius: 8rpx;
} }
@ -199,7 +199,7 @@ onLoad(opts => {
margin-top: 30rpx; margin-top: 30rpx;
padding: 24rpx 30rpx; padding: 24rpx 30rpx;
border-radius: 8rpx; border-radius: 8rpx;
font-size: 28rpx; font-size: 30rpx;
color: #fff; color: #fff;
background: #006eff; background: #006eff;
text-align: center; text-align: center;
@ -217,7 +217,7 @@ onLoad(opts => {
@at-root &__label { @at-root &__label {
flex-grow: 1; flex-grow: 1;
font-size: 32rpx; font-size: 34rpx;
} }
} }
@ -236,7 +236,7 @@ onLoad(opts => {
} }
@at-root &__txt { @at-root &__txt {
font-size: 32rpx; font-size: 34rpx;
color: #666; color: #666;
} }
} }

View File

@ -347,15 +347,15 @@ onReachBottom(() => {
} }
.text-xs { .text-xs {
font-size: 22rpx; font-size: 24rpx;
} }
.text-sm { .text-sm {
font-size: 28rpx; font-size: 30rpx;
} }
.text-base { .text-base {
font-size: 32rpx; font-size: 34rpx;
} }
.font-bold { .font-bold {
@ -469,7 +469,7 @@ onReachBottom(() => {
.loading-text { .loading-text {
margin-top: 20rpx; margin-top: 20rpx;
font-size: 28rpx; font-size: 30rpx;
color: #999; color: #999;
} }
@ -479,7 +479,7 @@ onReachBottom(() => {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 30rpx 0; padding: 30rpx 0;
font-size: 24rpx; font-size: 26rpx;
color: #999; color: #999;
gap: 10rpx; gap: 10rpx;
} }

View File

@ -7,8 +7,16 @@
<group-avatar classType="square" :size="128" :avatarList="avatarList" /> <group-avatar classType="square" :size="128" :avatarList="avatarList" />
</view> </view>
<view class="w-0 flex-grow"> <view class="w-0 flex-grow">
<view class="name-title font-semibold text-dark truncate">{{ team.name }}</view> <view class="flex items-center">
<view class="corp-title text-gray truncate">{{ corpName }}</view> <view class="w-0 flex-grow mr-5 name-title font-semibold text-dark truncate">{{ team.name }}</view>
<view v-if="team && team.supportPatientForward === 'YES'"
class="relative flex-shrink-0 flex items-center bg-white rounded py-5 px-5 border">
<image class="icon-share mr-5" src="/static/home/icon-share.svg"></image>
<view class="text-base text-warning">推荐</view>
<button open-type="share" class="absolute w-full h-full opacity-0"></button>
</view>
</view>
<view class="corp-title text-gray truncate">{{ team.licenseHospitalName || corpName }}</view>
</view> </view>
</view> </view>
<view class="mt-15 px-15 leading-normal text-base text-blue"> <view class="mt-15 px-15 leading-normal text-base text-blue">
@ -54,7 +62,7 @@
</template> </template>
<script setup> <script setup>
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app'; import { onLoad, onShow, onShareAppMessage } from '@dcloudio/uni-app';
import useJob from '@/hooks/useJob'; import useJob from '@/hooks/useJob';
import api from '@/utils/api'; import api from '@/utils/api';
@ -65,6 +73,7 @@ const pageStyle = "background: linear-gradient(180deg, #065BD6 15.05%, #F6FAFA 9
const corpId = ref(''); const corpId = ref('');
const teamId = ref(''); const teamId = ref('');
const firstCustomerId = ref('');
const team = ref(null); const team = ref(null);
const corpName = ref(''); const corpName = ref('');
const { memberJob, memberList: list } = useJob(); const { memberJob, memberList: list } = useJob();
@ -113,6 +122,7 @@ async function getTeam() {
onLoad(options => { onLoad(options => {
corpId.value = options.corpId; corpId.value = options.corpId;
teamId.value = options.teamId; teamId.value = options.teamId;
firstCustomerId.value = options.firstCustomerId || '';
corpName.value = decodeURIComponent(options.corpName || ''); corpName.value = decodeURIComponent(options.corpName || '');
}) })
onShow(() => { onShow(() => {
@ -121,6 +131,17 @@ onShow(() => {
} }
}); });
onShareAppMessage((res) => {
if (team.value && team.value.supportPatientForward === 'YES') {
const referenceCustomerId = firstCustomerId.value || '';
return {
title: `${team.value.name}】这个团队不错,推荐给你!`,
type: shareAppVersion || 0, // 0 | 1 | 2
path: `/pages/login/redirect-page?teamId=${team.value.teamId}&corpId=${team.value.corpId}&type=archive&referenceCustomerId=${referenceCustomerId}`
}
}
})
watch(memberList, n => { watch(memberList, n => {
list.value = n list.value = n
}, { immediate: true }) }, { immediate: true })
@ -158,13 +179,13 @@ page {
.name-title { .name-title {
height: 64rpx; height: 64rpx;
line-height: 64rpx; line-height: 64rpx;
font-size: 44rpx; font-size: 46rpx;
} }
.corp-title { .corp-title {
height: 44rpx; height: 44rpx;
line-height: 44rpx; line-height: 44rpx;
font-size: 28rpx; font-size: 30rpx;
} }
.text-blue { .text-blue {
@ -217,7 +238,7 @@ page {
height: 48rpx; height: 48rpx;
text-align: center; text-align: center;
line-height: 48rpx; line-height: 48rpx;
font-size: 24rpx; font-size: 26rpx;
color: #fff; color: #fff;
font-weight: bold; font-weight: bold;
background: #065BD6; background: #065BD6;
@ -236,4 +257,13 @@ page {
height: 17px; height: 17px;
line-height: 17px; line-height: 17px;
} }
.icon-share {
width: 24rpx;
height: 24rpx;
}
.opacity-0 {
opacity: 0;
}
</style> </style>

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1779182564794" class="icon" viewBox="0 0 1152 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10762" xmlns:xlink="http://www.w3.org/1999/xlink" width="225" height="200"><path d="M851.2 736c0 32-19.2 70.4-51.2 70.4H230.4c-32 0-57.6-38.4-57.6-70.4V409.6c0-32 25.6-51.2 57.6-51.2h57.6l128-166.4H64c-32 0-64 19.2-64 51.2v659.2c0 32 32 57.6 64 57.6h902.4c32 0 57.6-25.6 57.6-57.6V505.6l-172.8 140.8v89.6zM390.4 627.2c0 6.4 0 6.4 6.4 12.8 51.2-140.8 153.6-249.6 288-268.8 25.6-6.4 83.2-6.4 83.2-6.4v192h44.8L1088 262.4 806.4 0h-44.8v160s-44.8 0-70.4 6.4c-198.4 38.4-339.2 243.2-300.8 460.8z" p-id="10763" fill="#ff9005"></path><path d="M851.2 736c0 32-19.2 70.4-51.2 70.4H230.4c-32 0-57.6-38.4-57.6-70.4V409.6c0-32 25.6-51.2 57.6-51.2h57.6l128-166.4H64c-32 0-64 19.2-64 51.2v659.2c0 32 32 57.6 64 57.6h902.4c32 0 57.6-25.6 57.6-57.6V505.6l-172.8 140.8v89.6zM390.4 627.2c0 6.4 0 6.4 6.4 12.8 51.2-140.8 153.6-249.6 288-268.8 25.6-6.4 83.2-6.4 83.2-6.4v192h44.8L1088 262.4 806.4 0h-44.8v160s-44.8 0-70.4 6.4c-198.4 38.4-339.2 243.2-300.8 460.8z" fill="#ff9005" p-id="10764"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

1
static/pdf.svg Normal file
View File

@ -0,0 +1 @@
<svg t="1713431721970" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1534" width="200" height="200"><path d="M910.2336 1024H113.7664C51.2 1024 0 972.8 0 910.2336V113.7664C0 51.2 51.2 0 113.7664 0h796.4672C972.8 0 1024 51.2 1024 113.7664v796.4672C1024 972.8 972.8 1024 910.2336 1024zM155.2128 555.4432h44.672c35.9936 1.2032 66.0224-8.704 89.984-29.6704 24.0128-20.9664 36.0192-48.3328 36.0192-82.1248 0-33.28-10.24-58.9568-30.7712-76.8768-20.5312-17.92-49.3568-26.88-86.5536-26.88h-97.792v344.2432h44.4416v-128.6912z m0-176.4352h45.3376c52.48 0 78.6688 22.1184 78.6688 66.432 0 22.8096-7.04 40.3456-21.12 52.5824-14.08 12.1856-34.6624 18.304-61.7728 18.304H155.2128v-137.3184z m234.0352 305.1008h94.6432c54.528 0 99.072-16.0768 133.6576-48.2048 34.6112-32.1536 51.8912-74.9056 51.8912-128.256 0-51.0976-17.28-91.8528-51.8912-122.2144-34.56-30.336-77.952-45.568-130.0992-45.568h-98.2016v344.2432z m44.416-304.6656h51.9936c41.0624 0 74.1632 10.8032 99.328 32.4608 25.216 21.6064 37.7856 53.888 37.7856 96.8704 0 43.008-12.2368 76.3392-36.7872 100.224-24.5248 23.8592-58.624 35.7632-102.3232 35.7632H433.664V379.4432z m479.5648 0v-39.5776h-177.3312v344.2432h44.4416v-150.4512h123.1104V494.592h-123.136v-115.1232h132.9152z" fill="#D93838" p-id="1535"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -14,11 +14,20 @@ export default defineStore("accountStore", () => {
const openid = ref(""); const openid = ref("");
const externalUserId = ref(''); const externalUserId = ref('');
const teams = ref([]); const teams = ref([]);
const hasImCorpId = computed(() => teams.value.some(i => i.corpId === 'wpLgjyawAA8N0gWmXgyJq8wpjGcOT7fg')); const hasImCorpId = computed(() => {
// 正式环境IM账号优先 暂时只有wpLgjyawAA8N0gWmXgyJq8wpjGcOT7fg机构的才开启IM账号
if (env.MP_VERIFY_IM_CORP_ID === 'YES') {
return teams.value.some(i => i.corpId === 'wpLgjyawAA8N0gWmXgyJq8wpjGcOT7fg')
}
return true
});
const teamsPromise = ref(null); const teamsPromise = ref(null);
async function login(phoneCode = '') { async function login(phoneCode = '') {
if (loading.value) return; if (loading.value) return;
if (account.value && account.value.mobile) {
return account.value
}
loading.value = true; loading.value = true;
try { try {
const { code } = await uni.login({ const { code } = await uni.login({

View File

@ -42,6 +42,7 @@ const urlsConfig = {
searchRateList: 'searchRateList', searchRateList: 'searchRateList',
submitRateRecord: 'submitRateRecord', submitRateRecord: 'submitRateRecord',
getRateRecord: 'getRateRecord', getRateRecord: 'getRateRecord',
relateWechatAccountWithTeam: 'relateWechatAccountWithTeam'
}, },
member: { member: {
addCustomer: 'add', addCustomer: 'add',
@ -58,7 +59,8 @@ const urlsConfig = {
updateMedicalRecord: 'updateMedicalRecord', updateMedicalRecord: 'updateMedicalRecord',
getUnionidToExternalUserid: 'getUnionidToExternalUserid', getUnionidToExternalUserid: 'getUnionidToExternalUserid',
getWxAppCustomerCount: "getWxAppCustomerCount", getWxAppCustomerCount: "getWxAppCustomerCount",
updateCustomer: 'update' updateCustomer: 'update',
getRefrencePeople: 'getRefrencePeople',
}, },
wecom: { wecom: {
addContactWay: 'getCorpFriendQrcode' addContactWay: 'getCorpFriendQrcode'
@ -72,7 +74,10 @@ const urlsConfig = {
getGroupListByGroupId: "getGroupListByGroupId", getGroupListByGroupId: "getGroupListByGroupId",
createConsultGroup: "createConsultGroup", createConsultGroup: "createConsultGroup",
cancelConsultApplication: "cancelConsultApplication", cancelConsultApplication: "cancelConsultApplication",
getGroupList: "getGroupList" getGroupList: "getGroupList",
getConversationSubscribeConfig: "getConversationSubscribeConfig",
saveConversationSubscribeResult: "saveConversationSubscribeResult",
sendConversationSubscribeEvent: "sendConversationSubscribeEvent"
}, },
survery: { survery: {
getMiniAppReceivedSurveryList: 'getMiniAppReceivedSurveryList', getMiniAppReceivedSurveryList: 'getMiniAppReceivedSurveryList',

View File

@ -0,0 +1,64 @@
const env = __VITE_ENV__;
export const SUBSCRIBE_MESSAGE_ROLE = {
PATIENT: "patient",
DOCTOR: "doctor",
};
export const SUBSCRIBE_MESSAGE_SCENE = {
DEFAULT: "default",
LIST: "list",
CHAT: "chat",
};
export const SUBSCRIBE_MESSAGE_EVENT = {
PATIENT_CONSULT_APPLY: "patient_consult_apply",
PATIENT_CHAT_MESSAGE: "patient_chat_message",
DOCTOR_ACCEPT: "doctor_accept",
DOCTOR_REJECT: "doctor_reject",
DOCTOR_CHAT_MESSAGE: "doctor_chat_message",
};
export const SUBSCRIBE_MESSAGE_TEMPLATES = {
consultationReply: {
code: "consultationReply",
role: SUBSCRIBE_MESSAGE_ROLE.PATIENT,
id:
env.MP_SUBSCRIBE_TEMPLATE_CONSULT_REPLY ||
"VF9AC-7Rr3E1drbxBCrxbC-rLTnidmlNXopKReSAd_w",
name: "咨询回复通知",
events: [
SUBSCRIBE_MESSAGE_EVENT.DOCTOR_ACCEPT,
SUBSCRIBE_MESSAGE_EVENT.DOCTOR_REJECT,
SUBSCRIBE_MESSAGE_EVENT.DOCTOR_CHAT_MESSAGE,
],
fields: ["患者姓名", "回复时间", "回复者", "所属机构"],
},
};
export const SUBSCRIBE_MESSAGE_SCENE_TEMPLATE_MAP = {
[SUBSCRIBE_MESSAGE_ROLE.PATIENT]: {
[SUBSCRIBE_MESSAGE_SCENE.DEFAULT]: ["consultationReply"],
[SUBSCRIBE_MESSAGE_SCENE.LIST]: ["consultationReply"],
[SUBSCRIBE_MESSAGE_SCENE.CHAT]: ["consultationReply"],
},
};
export function resolveSubscribeTemplates({
role,
scene = SUBSCRIBE_MESSAGE_SCENE.DEFAULT,
} = {}) {
const roleMap = SUBSCRIBE_MESSAGE_SCENE_TEMPLATE_MAP[role] || {};
const keys =
roleMap[scene] || roleMap[SUBSCRIBE_MESSAGE_SCENE.DEFAULT] || [];
const seen = new Set();
return keys
.map((key) => SUBSCRIBE_MESSAGE_TEMPLATES[key])
.filter((item) => item && item.id)
.filter((item) => {
if (seen.has(item.id)) return false;
seen.add(item.id);
return true;
});
}

222
utils/subscribe-message.js Normal file
View File

@ -0,0 +1,222 @@
import api from "@/utils/api";
import { toast } from "@/utils/widget";
import { resolveSubscribeTemplates } from "./subscribe-message-config";
const SUBSCRIBE_ACCEPT_STATUS = "accept";
const SUBSCRIBE_REJECT_STATUS = "reject";
const SUBSCRIBE_BAN_STATUS = "ban";
const SUBSCRIBE_FILTER_STATUS = "filter";
const SUBSCRIBE_CANCEL_STATUS = "cancel";
const SUBSCRIBE_FAILED_STATUS = "failed";
const subscribeDisplayConfigCache = new Map();
function canUseSubscribeMessage() {
return (
typeof wx !== "undefined" &&
typeof wx.requestSubscribeMessage === "function"
);
}
function requestSubscribeMessage(tmplIds = []) {
return new Promise((resolve) => {
wx.requestSubscribeMessage({
tmplIds,
success(res) {
resolve({ ok: true, res });
},
fail(err) {
resolve({ ok: false, err });
},
});
});
}
function normalizeFailStatus(err = {}) {
const errCode = Number(err.errCode || 0);
const errMsg = String(err.errMsg || "").toLowerCase();
if (errCode === 20004 || errMsg.includes("main switch")) {
return SUBSCRIBE_BAN_STATUS;
}
if (errCode === 20005 || errMsg.includes("ban")) {
return SUBSCRIBE_BAN_STATUS;
}
if (errMsg.includes("filter")) {
return SUBSCRIBE_FILTER_STATUS;
}
if (errMsg.includes("cancel")) {
return SUBSCRIBE_CANCEL_STATUS;
}
return SUBSCRIBE_FAILED_STATUS;
}
function buildTemplateResultRecords(templates = [], requestResult = {}, context = {}) {
const requestedAt = Date.now();
if (requestResult.ok) {
const res = requestResult.res || {};
return templates.map((template) => ({
role: context.role || "",
scene: context.scene || "",
conversationId: context.conversationId || "",
groupId: context.groupId || "",
corpId: context.corpId || "",
teamId: context.teamId || "",
patientId: context.patientId || "",
doctorId: context.doctorId || "",
userId: context.userId || "",
openid: context.openid || "",
unionid: context.unionid || "",
templateId: template.id,
templateCode: template.code,
templateName: template.name,
eventTypes: template.events,
status: String(res[template.id] || SUBSCRIBE_FAILED_STATUS),
rawResult: res,
requestedAt,
extraData: context.extraData || {},
}));
}
const status = normalizeFailStatus(requestResult.err);
return templates.map((template) => ({
role: context.role || "",
scene: context.scene || "",
conversationId: context.conversationId || "",
groupId: context.groupId || "",
corpId: context.corpId || "",
teamId: context.teamId || "",
patientId: context.patientId || "",
doctorId: context.doctorId || "",
userId: context.userId || "",
openid: context.openid || "",
unionid: context.unionid || "",
templateId: template.id,
templateCode: template.code,
templateName: template.name,
eventTypes: template.events,
status,
rawResult: requestResult.err || {},
requestedAt,
extraData: context.extraData || {},
}));
}
function buildToastMessage(records = [], reportResult = { success: false }) {
const accepted = records.some((item) => item.status === SUBSCRIBE_ACCEPT_STATUS);
if (accepted && reportResult?.success === false) {
return reportResult?.message || "提醒开启失败,请稍后再试";
}
if (records.some((item) => item.status === SUBSCRIBE_ACCEPT_STATUS)) {
return "会话消息提醒开启";
}
if (records.some((item) => item.status === SUBSCRIBE_BAN_STATUS)) {
return "请先在微信设置中开启订阅消息提醒";
}
if (records.some((item) => item.status === SUBSCRIBE_FILTER_STATUS)) {
return "当前提醒模板暂不可用";
}
if (records.some((item) => item.status === SUBSCRIBE_REJECT_STATUS)) {
return "你已拒绝本次提醒订阅";
}
if (records.some((item) => item.status === SUBSCRIBE_CANCEL_STATUS)) {
return "你已取消本次提醒订阅";
}
return "提醒订阅请求失败,请稍后再试";
}
async function reportSubscribeResult(records = []) {
if (!records.length) return { success: false };
try {
return await api(
"saveConversationSubscribeResult",
{
records,
},
false
);
} catch (error) {
console.error("保存订阅结果失败:", error);
return { success: false, message: error?.message || "保存失败" };
}
}
export async function checkConversationSubscribeEntryVisible(
corpId = "",
forceRefresh = false
) {
const normalizedCorpId = String(corpId || "").trim();
if (!normalizedCorpId) return false;
if (!forceRefresh && subscribeDisplayConfigCache.has(normalizedCorpId)) {
return subscribeDisplayConfigCache.get(normalizedCorpId);
}
try {
const result = await api(
"getConversationSubscribeConfig",
{ corpId: normalizedCorpId },
false
);
const enabled = !!result?.data?.enabled;
subscribeDisplayConfigCache.set(normalizedCorpId, enabled);
return enabled;
} catch (error) {
console.error("获取订阅提醒显示配置失败:", error);
subscribeDisplayConfigCache.set(normalizedCorpId, false);
return false;
}
}
export async function requestConversationSubscribeMessage(context = {}) {
const templates = resolveSubscribeTemplates({
role: context.role,
scene: context.scene,
});
const requestTemplates = templates.slice(0, 1);
if (!requestTemplates.length) {
await toast("暂未配置提醒模板");
return {
success: false,
code: "template_missing",
records: [],
};
}
if (!canUseSubscribeMessage()) {
await toast("当前微信版本不支持订阅消息");
return {
success: false,
code: "unsupported",
records: [],
};
}
const requestResult = await requestSubscribeMessage(
requestTemplates.map((item) => item.id)
);
const records = buildTemplateResultRecords(
requestTemplates,
requestResult,
context
);
const reportResult = await reportSubscribeResult(records);
await toast(buildToastMessage(records, reportResult));
const subscribeSuccess =
records.some((item) => item.status === SUBSCRIBE_ACCEPT_STATUS) &&
reportResult?.success !== false;
return {
success: subscribeSuccess,
reportResult,
records,
acceptedTemplateIds: records
.filter((item) => item.status === SUBSCRIBE_ACCEPT_STATUS)
.map((item) => item.templateId),
};
}