Compare commits

..

1 Commits

Author SHA1 Message Date
huxuejian
6ace7bd8fb feat: 页面提交 2026-01-20 19:04:24 +08:00
28 changed files with 2687 additions and 17 deletions

View File

@ -0,0 +1,58 @@
<template>
<common-cell :name="name" :required="required">
<view class="form-content__wrapper" @click="select()">
<view class="flex-main-content truncate" :class="str ? '' : 'form__placeholder'">
{{ str || placeholder }}
</view>
<uni-icons class="form-arrow" type="arrowright"></uni-icons>
</view>
</common-cell>
</template>
<script setup>
import { computed } from 'vue';
import { set } from '@/utils/cache';
import commonCell from '../common-cell.vue';
const emits = defineEmits(['change']);
const props = defineProps({
form: {
type: Object,
default: () => ({})
},
name: {
default: ''
},
required: {
type: Boolean,
default: false
},
title: {
default: ''
},
})
const placeholder = computed(() => `请输入${props.name || ''}`)
const value = computed(() => props.form && props.form && Array.isArray(props.form[props.title]) ? props.form[props.title] : []);
const str = computed(() => value.value.join(','))
function change(e) {
emits('change', {
title: props.title,
value: e
})
}
function select() {
const eventName = `onChangeMultDisease_${+new Date()}`
uni.$once(eventName, change);
set('diagnosis-list-selections', value.value)
uni.navigateTo({
url: `/pages/library/diagnosis-list?eventName=${eventName}`
})
}
</script>
<style>
@import '../cell-style.css';
</style>

View File

@ -0,0 +1,110 @@
<template>
<view class="textarea-row">
<view class="form-row__label">
{{ name }}<text v-if="required" class="form-cell--required"></text>
</view>
<view class="flex flex-wrap">
<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-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>
</view>
<view v-if="value.length < 3"
class="upload-item border-primary mt-10 flex items-center justify-center text-primary" @click="addImage()">
<uni-icons type="camera" :size="40" color="#0074ff"></uni-icons>
</view>
</view>
</view>
</template>
<script setup>
import { computed } from 'vue';
import { upload } from '@/utils/http';
import { loading, hideLoading, toast } from '@/utils/widget';
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] : [])
const files = computed(() => value.value.map(i => {
return {
url: i.url,
name: i.name,
type: i.type,
isImage: /image/i.test(i.type)
}
}))
function addImage() {
uni.chooseImage({
count: 1,
success: async (res) => {
loading();
const result = await upload(res.tempFilePaths[0]);
hideLoading();
if (result) {
change([...value.value, { url: result, type: 'image/png' }])
} else {
toast('上传失败')
}
}
})
}
function change(value) {
emits('change', {
title: props.title,
value: value
})
}
function remove(idx) {
const value = [...files.value];
value.splice(idx, 1);
change(value)
}
</script>
<style lang="scss" scoped>
.upload-item {
position: relative;
width: 160rpx;
height: 160rpx;
border-radius: 16rpx;
box-sizing: border-box;
margin-right: 30rpx;
margin-bottom: 10rpx;
}
.textarea-row {
padding: 24rpx 30rpx;
border-bottom: 1px solid #eee;
font-size: 28rpx;
}
.remove-icon {
position: absolute;
top: -30rpx;
right: -30rpx;
z-index: 2;
}
</style>

View File

@ -1,11 +1,21 @@
<template>
<form-datepicker v-if="attrs.type === 'date'" v-bind="attrs" :form="form" :disableChange="disableChange" @change="change" />
<form-input v-else-if="attrs.type === 'input'" v-bind="attrs" :form="form" :disableChange="disableChange" @change="change" />
<form-radio v-else-if="attrs.type === 'radio'" v-bind="attrs" :form="form" :disableChange="disableChange" @change="change" />
<form-region v-else-if="attrs.type === 'region'" v-bind="attrs" :form="form" :disableChange="disableChange" @change="change" />
<form-select v-else-if="attrs.type === 'select'" v-bind="attrs" :form="form" :disableChange="disableChange" @change="change" />
<form-datepicker v-if="attrs.type === 'date'" v-bind="attrs" :form="form" :disableChange="disableChange"
@change="change" />
<form-input v-else-if="attrs.type === 'input'" v-bind="attrs" :form="form" :disableChange="disableChange"
@change="change" />
<form-radio v-else-if="attrs.type === 'radio'" v-bind="attrs" :form="form" :disableChange="disableChange"
@change="change" />
<form-region v-else-if="attrs.type === 'region'" v-bind="attrs" :form="form" :disableChange="disableChange"
@change="change" />
<form-select v-else-if="attrs.type === 'select'" v-bind="attrs" :form="form" :disableChange="disableChange"
@change="change" />
<form-textarea v-else-if="attrs.type === 'textarea'" v-bind="attrs" :form="form" :disableChange="disableChange"
@change="change" />
<form-mult-disease v-else-if="attrs.type === 'selfMultipleDiseases'" v-bind="attrs" :form="form"
@change="change"></form-mult-disease>
<form-mult-disease v-else-if="attrs.type === 'diagnosis'" v-bind="attrs" :form="form"
@change="change"></form-mult-disease>
<form-upload v-else-if="attrs.type === 'files'" v-bind="attrs" :form="form" @change="change" />
<!--
<form-operation v-else-if="attrs.title === 'surgicalHistory'" v-bind="attrs" :form="form" @change="change"
@ -25,6 +35,8 @@ import formRadio from './form-radio.vue';
import formDatepicker from './form-datepicker.vue';
import formRegion from './form-region.vue';
import formTextarea from './form-textarea.vue';
import formMultDisease from './form-multiple-diseases.vue';
import formUpload from './form-upload.vue';
defineProps({
form: {

View File

@ -1,9 +1,7 @@
<template>
<template v-for="item in formItems" :key="item.title">
<form-cell v-if="item.cellType === 'formCellItem'" v-bind="item" :form="form" :disableChange="disabledMap[item.title]"
@change="change" @addRule="addRule" />
<custom-cell v-else-if="item.cellType === 'customCellItem'" v-bind="item" :form="form"
:readonly="disabledMap[item.title]" @change="change" @addRule="addRule" />
<form-cell v-if="item.cellType === 'formCellItem'" v-bind="item" :form="form"
:disableChange="disabledMap[item.title]" @change="change" @addRule="addRule" />
</template>
<form-cell />
</template>
@ -34,9 +32,9 @@ const props = defineProps({
}
})
const formCellType = ['input', 'select', 'date', 'radio', 'region', 'textarea', 'multiSelectAndOther', 'selfMultipleDiseases'];
const formCellType = ['input', 'select', 'date', 'radio', 'region', 'textarea', 'multiSelectAndOther', 'selfMultipleDiseases', 'files','diagnosis'];
const formCellTitle = ['surgicalHistory'];
const customCellType = ['BMI', 'selfMultipleDiseases', 'bloodPressure', 'diagnosis'];
const customCellType = ['BMI', 'bloodPressure'];
const disabledMap = computed(() => props.disableTitles.reduce((m, i) => {
if (typeof i === 'string' && i.trim()) {
m[i] = true;

View File

@ -15,7 +15,65 @@
{
"path": "pages/work/work",
"style": {
<<<<<<< HEAD
"navigationBarTitleText": "柚健康"
}
},
{
"path": "pages/archive/archive-manage",
"style": {
"navigationBarTitleText": "档案管理"
}
},
{
"path": "pages/archive/edit-archive",
"style": {
"navigationBarTitleText": "新增档案"
}
},
{
"path": "pages/article/article-list",
"style": {
"navigationBarTitleText": "健康宣教"
}
},
{
"path": "pages/health/list",
"style": {
"navigationBarTitleText": "健康信息"
}
},
{
"path": "pages/health/record",
"style": {
"navigationBarTitleText": "健康信息"
}
},
{
"path": "pages/library/diagnosis-list",
"style": {
"navigationBarTitleText": "选择诊断"
}
},
{
"path": "pages/team/team-detail",
"style": {
"navigationBarTitleText": "团队介绍"
}
},
{
"path": "pages/team/homepage",
"style": {
"navigationBarTitleText": "个人主页"
}
},
{
"path": "pages/team/friend",
"style": {
"navigationBarTitleText": "添加好友"
=======
"navigationBarTitleText": "工作台"
>>>>>>> e838f8af157c539c27370cbdd7d4a3bbc09e184b
}
}
],

View File

@ -0,0 +1,106 @@
<template>
<full-page :customScroll="empty">
<view v-if="customers.length === 0" class="flex items-center justify-center h-full">
<empty-data />
</view>
<view class="p-15">
<view v-for="customer in customers" :key="customer._id" class="bg-white rounded shadow-lg mb-10">
<view class="flex items-center px-15 py-12 border-b">
<view class="flex-shrink-0 mr-5 text-lg text-dark font-semibold">{{ customer.name }}</view>
<view v-if="customer.relationship"
class="flex-shrink-0 mr-5 border-primary text-primary px-10 text-sm leading-normal rounded-sm">
{{ customer.relationship }}
</view>
<view class="flex-grow mr-5"></view>
<view v-if="enableHis && customer.isConnectHis"
class="px-15 py-5 text-sm leading-normal bg-success text-white rounded-sm">
未关联档案
</view>
<view v-else-if="enableHis" class="px-15 py-5 text-sm leading-normal bg-warning text-white rounded-sm">未关联档案
</view>
</view>
<view class="px-15 py-12 border-b">
<view class="text-base leading-normal">
<text v-if="customer.sex"> {{ customer.sex }}</text>
<text v-if="customer.sex && customer.age">/</text>
<text v-if="customer.age"> {{ customer.age }}</text>
<text v-if="customer.sex || customer.age"></text>
<text v-if="customer.mobile"> {{ customer.mobile }}</text>
</view>
<view class="text-base leading-normal">证件号{{ customer.idCard || '--' }}</view>
</view>
<view class="px-15 py-12 flex justify-end">
<view v-if="enableHis && !customer.isConnectHis" class="mr-10 text-base text-primary">关联档案</view>
<view class="mr-10 text-base text-success" @click="changeArchive(customer)">完善个人信息</view>
<view class="mr-10 text-base text-danger" @click="unBindArchive(customer)">删除档案</view>
</view>
</view>
</view>
<template #footer>
<button-footer confirmText="新增档案" :showCancel="false" @confirm="addArchive()" />
</template>
</full-page>
</template>
<script setup>
import { ref } from 'vue';
import { storeToRefs } from 'pinia'
import useGuard from '@/hooks/useGuard';
import useAccount from '@/store/account';
import api from '@/utils/api';
import ButtonFooter from '@/components/button-footer.vue';
import EmptyData from '@/components/empty-data.vue';
import FullPage from '@/components/full-page.vue';
import { confirm, toast } from '../../utils/widget';
const empty = ref(false)
const { useLoad, useShow } = useGuard();
const { account } = storeToRefs(useAccount());
const corpId = ref('');
const teamId = ref('');
const enableHis = ref(false);
const customers = ref([]);
function addArchive() {
uni.navigateTo({
url: `/pages/archive/edit-archive?teamId=${teamId.value}&corpId=${corpId.value}`
})
}
function changeArchive(customer) {
uni.navigateTo({
url: `/pages/archive/edit-archive?teamId=${teamId.value}&corpId=${corpId.value}&id=${customer._id}`
})
}
async function getMembers() {
const res = await api('getTeamCustomers', { corpId: corpId.value, teamId: teamId.value, miniAppId: account.value.openid });
customers.value = res && Array.isArray(res.data) ? res.data : [];
// enableHis.value = res && typeof res.enableHis === 'boolean' ? res.enableHis : false;
if (!res || !res.data) {
toast(res?.message || '获取档案信息失败')
}
}
async function unBindArchive(customer) {
await confirm('确定删除档案吗?')
const res = await api('unbindMiniAppArchive', { id: customer._id, corpId: corpId.value, teamId: teamId.value, miniAppId: account.value.openid });
if (res && res.success) {
await toast('删除成功');
getMembers();
} else {
toast(res?.message || '删除失败')
}
}
useLoad(options => {
teamId.value = options.teamId;
corpId.value = options.corpId;
})
useShow(() => {
if (teamId.value && corpId.value) {
getMembers()
}
})
</script>

View File

@ -0,0 +1,98 @@
<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">
您在{{ corpName }}医客通平台已存在档案请选择档案绑定
</view>
<scroll-view scroll-y="true" class="popup-content-scroll">
<view class="px-15 py-12">
<view v-for="customer in customers" :key="customer._id"
class="flex items-center p-10 mb-10 rounded-sm bg-gray" @click="id = customer._id">
<view class="flex-grow w-0 mr-5 text-base leading-normal text-dark">
<view class="flex items-center">
<view class="flex-shrink-0 min-w-60">姓名</view>
<view>{{ customer.name }}</view>
</view>
<view class="flex items-center">
<view class="flex-shrink-0 min-w-60">性别</view>
<view>{{ customer.sex || '--' }}</view>
</view>
<view class="flex items-center">
<view class="flex-shrink-0 min-w-60">手机号</view>
<view>{{ customer.mobile }}</view>
</view>
</view>
<image class="check-icon" :src="`/static/form/${id === customer._id ? 'checked' : 'uncheck'}.svg`"></image>
</view>
</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">
<button-footer hideden-shadow confirmText="确定" :showCancel="false" @confirm="confirm()" />
</view>
</view>
</uni-popup>
</template>
<script setup>
import { ref, watch } from 'vue';
import { toast } from '@/utils/widget';
import ButtonFooter from '@/components/button-footer.vue';
const emits = defineEmits(['close', 'confirm'])
const props = defineProps({
corpName: {
type: String,
default: ''
},
customers: {
type: Array,
default: () => []
},
visible: {
type: Boolean,
default: false
}
})
const popup = ref()
const id = ref('')
function close() {
emits('close')
}
function confirm() {
if (props.customers.some(i => i._id === id.value && id.value)) {
emits('confirm', id.value)
} else {
toast('请选择档案')
}
}
watch(() => props.visible, n => {
if (n) {
popup.value && popup.value.open()
} else {
popup.value && popup.value.close()
}
})
</script>
<style lang="scss" scoped>
.min-w-60 {
min-width: 120rpx;
}
.check-icon {
width: 48rpx;
height: 48rpx;
}
</style>

View File

@ -0,0 +1,180 @@
<template>
<full-page v-if="!visible" :customScroll="empty">
<view v-if="formItems.length === 0" class="flex items-center justify-center h-full">
<empty-data />
</view>
<view v-else class="p-15">
<view class="bg-white rounded shadow-lg">
<form-template ref="tempRef" :disableTitles="disableTitles" :items="formItems" :form="formData"
@change="change($event)" />
</view>
</view>
<template #footer>
<button-footer confirmText="新增档案" :showCancel="false" @confirm="confirm()" />
</template>
</full-page>
<bind-popup :customers="customers" :corpName="corpName" :visible="visible" @close="visible = false"
@confirm="bindArchive($event)" />
<verify-popup :visible="verifyVisible" @close="verifyVisible = false" />
</template>
<script setup>
import { computed, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { onLoad } from "@dcloudio/uni-app";
import dayjs from 'dayjs';
import useGuard from '@/hooks/useGuard';
import useAccount from '@/store/account';
import api from '@/utils/api';
import { toast } from '@/utils/widget';
import validate from '@/utils/validate';
import ButtonFooter from '@/components/button-footer.vue';
import EmptyData from '@/components/empty-data.vue';
import FullPage from '@/components/full-page.vue';
import bindPopup from './bind-popup.vue';
import verifyPopup from './verify-popup.vue';
import formTemplate from '@/components/form-template/index.vue';
const empty = ref(false)
const { useLoad } = useGuard();
const { account } = storeToRefs(useAccount());
const corpId = ref('');
const corpName = ref('');
const customer = ref({});
const customerId = ref('');
const customers = ref([]);
const disableTitles = ref(['mobile']);
const form = ref({});
const formItems = ref([]);
const loading = ref(false);
const teamId = ref('');
const tempRef = ref(null);
const customerArchive = ref(null);
const verifyVisible = ref(false);
const visible = ref(false);
const formData = computed(() => ({ ...customer.value, ...form.value }));
function back() {
const pages = getCurrentPages();
if (pages.length > 1) {
uni.navigateBack();
} else {
uni.redirectTo({ url: `/pages/home/home?corpId=${corpId.value}&teamId=${teamId.value}` })
}
}
function change({ title, value }) {
if (title) {
form.value[title] = value;
}
if (title !== 'idCard') return;
const [isIdCard, birthday, gender] = validate.isChinaId(value);
if (isIdCard) {
form.value.birthday = birthday;
form.value.sex = gender == 'MALE' ? '男' : '女';
const age = dayjs().diff(birthday, 'year');
form.value.age = Math.max(1, age);
}
}
function confirm() {
if (!tempRef.value.verify() || Object.keys(form.value).length === 0) return;
if (customerId.value) {
updateArchive();
} else {
addArchive();
}
}
async function addArchive() {
if (loading.value) return;
loading.value = true;
const params = {
...form.value,
addMethod: 'customerManual',
teamId: teamId.value,
corpId: corpId.value,
mobile: account.value.mobile,
miniAppId: account.value.openid
}
loading.value = false;
const res = await api('addCustomer', { params });
if (res && res.success) {
back()
} else {
toast(res?.message || '新增档案失败');
}
}
async function bindArchive(customerId) {
const res = await api('bindMiniAppArchive', { id: customerId, corpId: corpId.value, teamId: teamId.value, miniAppId: account.value.openid });
if (res && res.success) {
await toast('绑定成功');
uni.reLaunch({ url: `/pages/home/home?corpId=${corpId.value}&teamId=${teamId.value}` })
} else {
toast(res?.message || '绑定失败');
}
// customerArchive.value = customers.value.find(item => item.customerId === customerId);
// verifyVisible.value = true;
}
async function init() {
if (customerId.value) {
await getCustomer()
} else {
const res = await getArchives();
if (res.length > 0) {
visible.value = true;
}
}
await getBaseForm();
}
async function getArchives() {
const res = await api('getUnbindMiniAppCustomers', { corpId: corpId.value, mobile: account.value.mobile });
if (res && res.success) {
corpName.value = res.corpName;
customers.value = Array.isArray(res.data) ? res.data : [];
} else {
toast(res?.message || '查询档案信息失败');
return Promise.reject()
}
return customers.value
}
async function getBaseForm() {
const res = await api('getTeamBaseInfo', { corpId: corpId.value, teamId: teamId.value });
if (res && res.success) {
formItems.value = Array.isArray(res.data) ? res.data : [];
} else {
toast(res?.message || '查询失败');
return Promise.reject()
}
}
async function getCustomer() {
const res = await api('getCustomerByCustomerId', { customerId: customerId.value });
console.log(res.data)
if (res && res.success && res.data) {
customer.value = res.data;
} else {
await toast(res?.message || '查询档案信息失败');
uni.navigateBack();
return Promise.reject()
}
}
onLoad(options => {
customerId.value = options.id || '';
uni.setNavigationBarTitle({ title: customerId.value ? '编辑档案' : '新增档案' })
})
useLoad(options => {
teamId.value = options.teamId;
corpId.value = options.corpId;
init();
})
</script>
<style scoped></style>

View File

@ -0,0 +1,108 @@
<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="mt-15 px-15 pt-15 text-base text-dark">
请填写档案人身份证号后四位以确认身份
</view>
<view class="relative flex justify-between mt-15 px-15 py-12 overflow-hidden">
<view v-for="i in 4" :key="i"
class="flex items-center justify-center code-cell text-large font-semibold border-auto text-gray rounded"
:class="i === activeIndex ? 'text-primary' : 'text-gray'">
<text :class="i === activeIndex ? 'text-primary' : 'text-dark'">{{ validCodes[i - 1] || '' }}</text>
</view>
<input class="code-input" type="idcard" :focus="focus" @focus="onFocus" @blur="onBlur" v-model="codes" />
</view>
<view class="footer-buttons">
<button-footer hideden-shadow confirmText="确定" :showCancel="false" @confirm="confirm()" />
</view>
</view>
</uni-popup>
</template>
<script setup>
import { computed, ref, watch } from 'vue';
import { toast } from '@/utils/widget';
import ButtonFooter from '@/components/button-footer.vue';
const emits = defineEmits(['close', 'confirm'])
const props = defineProps({
corpName: {
type: String,
default: ''
},
customers: {
type: Array,
default: () => []
},
visible: {
type: Boolean,
default: false
}
})
const popup = ref()
const codes = ref('');
const validCodes = computed(() => codes.value.replace(/\s/g, '').split('').slice(0, 4));
const codeStr = computed(() => validCodes.value.join(''));
const activeIndex = computed(() => Math.min(validCodes.value.length + 1, 4))
const focus = ref(false)
const timer = ref(null)
function close() {
emits('close')
}
function confirm() {
if (props.customers.some(i => i._id === id.value && id.value)) {
emits('confirm', id.value)
} else {
toast('请选择档案')
}
}
function onFocus() {
focus.value = true
}
function onBlur() {
focus.value = false
}
watch(() => props.visible, n => {
if (n) {
codes.value = '';
popup.value && popup.value.open();
focus.value = false;
setTimeout(() => focus.value = true, 500)
} else {
popup.value && popup.value.close()
}
})
watch(codes, n => {
if (timer.value) clearTimeout(timer.value);
timer.value = setTimeout(() => {
codes.value = codeStr.value;
}, 500)
})
</script>
<style lang="scss" scoped>
.code-cell {
width: 120rpx;
height: 120rpx;
font-size: 52rpx;
}
.code-input {
position: absolute;
width: 200%;
height: 100%;
top: 0;
left: -100%;
}
</style>

View File

@ -0,0 +1,55 @@
<template>
<full-page :customScroll="articles.length === 0" @reachBottom="loadMore()">
<view v-if="articles.length === 0" class="flex items-center justify-center h-full">
<empty-data />
</view>
<view v-else class="p-15">
<view v-for="article in articles" :key="article._id" class="flex px-15 mb-10 py-12 shadow-lg bg-white rounded">
<image class="flex-shrink-0 mr-10 cover" :src="article.cover || '/static/book.svg'" />
<view class="w-0 flex-grow">
<view class="text-base leading-normal font-semibold truncate mb-5">
{{ article.title }}
</view>
<view v-if="article.summary" class="text-base text-gray line-clamp-2">
{{ article.summary }}
</view>
</view>
</view>
</view>
</full-page>
</template>
<script setup>
import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import api from '@/utils/api';
import FullPage from '@/components/full-page.vue';
import EmptyData from '@/components/empty-data.vue';
const corpId = ref('');
const ids = ref('');
const articles = ref([]);
onLoad((options) => {
corpId.value = options.corpId;
ids.value = options.ids;
if (ids.value) {
getArticles()
}
});
async function getArticles() {
const res = await api('getArticleByIds', { corpId: corpId.value, ids: ids.value });
articles.value = res && Array.isArray(res.list) ? res.list : [];
}
function loadMore() {
console.log('addArchive')
}
</script>
<style scoped>
.cover {
width: 128rpx;
height: 128rpx;
}
</style>

278
pages/health/list.vue Normal file
View File

@ -0,0 +1,278 @@
<template>
<full-page :customScroll="empty">
<template #header>
<view class="flex items-center bg-white text-base px-15 py-12">
<view class="flex-shrink-0 mr-5 text-gray">档案所属人</view>
<view class="text-dark">{{ name }}</view>
</view>
<view class="flex items-center py-12 px-15">
<view class="min-w-90 flex items-center bg-white rounded-sm px-10 mr-5" @click="selectType()">
<view class="leading-normal text-base py-5 px-10 text-dark mr-5">
{{ tempType[type] || '全部' }}
</view>
<uni-icons type="down" size="16" color="#666" />
</view>
<view class="w-100 flex items-center bg-white rounded-sm mr-5">
<uni-datetime-picker v-model="dates" type="daterange">
<view class="flex items-center rounded-sm mr-5 px-10 bg-white">
<view class="flex-grow w-0 text-dark truncate leading-normal text-base py-5 mr-5">
{{ dates.length ? dates.join(' ~ ') : '时间筛选' }}
</view>
<uni-icons type="down" size="16" color="#666" />
</view>
</uni-datetime-picker>
</view>
<view class="flex-grow"></view>
</view>
</template>
<view v-if="list.length === 0" class="flex items-center justify-center h-full">
<empty-data />
</view>
<view v-else class="px-15 pb-15">
<!-- v-for="customer in list" :key="customer._id" -->
<view v-for="i in list" :key="i._id" class="bg-white rounded shadow-lg mb-10"
@click="toRecord(i.medicalType, i._id)">
<view class="flex items-center py-12 px-15">
<view class="text-lg font-semibold text-dark mr-5">
{{ i.date }}
</view>
<view v-if="i.medicalType === 'outpatient'"
class="mr-5 px-10 leading-normal text-white text-base bg-warning rounded-full">门诊信息</view>
<view v-else-if="i.medicalType === 'inhospital'"
class="mr-5 px-10 leading-normal text-white text-base bg-success rounded-full">住院信息</view>
<view v-if="i.corp === '其他'" class="px-10 leading-normal text-white text-base bg-danger rounded-full">
外院
</view>
<view class="flex-grow"></view>
<view class="flex items-center text-base text-primary">
<view class="mr-5">查看详情</view>
<uni-icons type="right" :size="14" color="#0074ff" />
</view>
</view>
<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 class="flex-shrink-0 mr-5 w-90 text-base text-gray">{{ row.label }}</view>
<view v-if="row.title === 'diagnosisName'" class="w-0 flex-grow text-base text-dark">
{{ row.value && row.value.join ? row.value.join(', ') : '' }}
</view>
<view v-else-if="row.title === 'files'" class="w-0 flex-grow text-base text-dark">
<view v-if="row.value && row.value.length" class="w-full flex flex-wrap">
<image v-for="file in row.value" :src="file.url" class="file-icon" @click.stop="preview(file)" />
</view>
</view>
<view v-else class="w-0 flex-grow text-base text-dark">
{{ row.value }}
</view>
</view>
<!-- <view class="flex leading-normal mb-10">
<view class="flex-shrink-0 mr-5 w-90 text-base text-gray">临床诊断</view>
<view class="w-0 flex-grow text-base text-dark">龋齿</view>
</view>
<view class="flex leading-normal mb-10">
<view class="flex-shrink-0 mr-5 w-90 text-base text-gray">临床诊断</view>
<view class="w-0 flex-grow text-base text-dark">龋齿</view>
</view> -->
</view>
<view v-if="i.dataSource === 'customerReport'" class="px-15 py-12 text-base text-dark">
记录时间{{ i.createTime }} 记录人自己
</view>
</view>
</view>
<template #footer>
<button-footer v-if="healthTempList.length" confirmText="+新增健康档案" :showCancel="false" @confirm="addArchive()" />
</template>
</full-page>
</template>
<script setup>
import { computed, ref, watch } from 'vue';
import dayjs from 'dayjs';
import useGuard from '@/hooks/useGuard';
import api from '@/utils/api';
import ButtonFooter from '@/components/button-footer.vue';
import EmptyData from '@/components/empty-data.vue';
import FullPage from '@/components/full-page.vue';
import { toast } from '../../utils/widget';
const tempType = {
outpatient: '门诊信息',
inhospital: '住院信息'
}
const empty = ref(false)
const { useLoad, useShow } = useGuard();
const corpId = ref('');
const customerId = ref('');
const dates = ref([])
const list = ref([]);
const name = ref('');
const team = ref(null);
const type = ref('all');
const teamId = ref('');
const page = ref(1);
const more = ref(false)
const loading = ref(false);
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 tempShowField = ref(null);
const config = {
outpatient: ["corp", "deptName", "doctor", "diagnosisName", "files"],
inhospital: ["corp", "diagnosisName", "operation", "operationDate", "outhosDate", "files"],
};
function addArchive() {
if (healthTempList.value.length > 1) {
uni.showActionSheet({
title: '选择新增档案类型',
itemList: healthTempList.value.map(i => i.label),
success: function (res) {
toRecord(healthTempList.value[res.tapIndex].value)
}
});
} else {
toRecord(healthTempList.value[0].value)
}
}
function getTempRows(type, data) {
if (config[type] && tempShowField.value && tempShowField.value[type]) {
const list = [];
config[type].forEach(i => {
if (tempShowField.value[type][i]) {
list.push({ title: i, label: tempShowField.value[type][i], value: data[i], key: `${i}_${data._id}` })
}
})
return list;
}
return []
}
function toRecord(type, id = '') {
uni.navigateTo({
url: `/pages/health/record?type=${type}&teamId=${teamId.value}&corpId=${corpId.value}&id=${id}&customerId=${customerId.value}`
})
}
function preview(file) {
if (/image/i.test(file.type)) {
uni.previewImage({
urls: [file.url]
})
}
}
function selectType() {
const itemList = [{ label: '全部', value: 'all' }, ...healthTempList.value]
uni.showActionSheet({
title: '选择新增档案类型',
itemList: itemList.map(i => i.label),
success: function (res) {
type.value = itemList[res.tapIndex].value;
page.value = 1;
getList()
}
});
}
async function getTeam() {
const res = await api('getTeamData', { teamId: teamId.value, corpId: corpId.value });
if (res && res.data) {
team.value = res.data;
} else {
toast(res?.message || '获取团队信息失败')
}
}
async function getList() {
if (!tempShowField.value) {
await getTeamHealthTemps()
}
if (loading.value) return;
const params = {
corpId: corpId.value,
memberId: customerId.value,
page: page.value,
pageSize: 20,
medicalType: ['outpatient', 'inhospital']
}
if (type.value !== 'all') {
params.medicalType = [type.value]
}
if (dates.value.length) {
params.startTime = dates.value[0];
params.endTime = dates.value[1];
}
loading.value = true;
const res = await api('getCustomerMedicalRecord', params);
loading.value = false;
const arr = res && Array.isArray(res.list) ? res.list.map(i => ({
...i,
date: i.sortTime && dayjs(i.sortTime).isValid ? dayjs(i.sortTime).format('YYYY-MM-DD') : '',
createTime: i.createTime && dayjs(i.createTime).isValid ? dayjs(i.createTime).format('YYYY-MM-DD HH:mm') : '',
rows: getTempRows(i.medicalType, i)
})) : [];
list.value = page.value === 1 ? arr : [...list.value, ...arr];
more.value = page.value < res.pages;
console.log(list.value)
}
async function getTeamHealthTemps() {
const res = await api('getTeamHealthTemps', { teamId: teamId.value, corpId: corpId.value });
if (res && res.success) {
const arr = res && Array.isArray(res.data) ? res.data : [];
tempShowField.value = arr.reduce((m, item) => {
const templateList = Array.isArray(item.templateList) ? item.templateList : [];
const data = {}
templateList.forEach(i => (data[i.title] = i.name));
m[item.templateType] = data
return m
}, {})
} else {
toast(res?.message || '获取团队健康档案模板失败');
return Promise.reject()
}
}
useLoad(options => {
name.value = decodeURIComponent(options.name || '');
teamId.value = options.teamId;
corpId.value = options.corpId;
customerId.value = options.id || '';
getTeam()
// getTeamHealthTemps();
})
useShow(() => {
page.value = 1;
getList()
})
watch(dates, n => {
page.value = 1;
getList()
})
</script>
<style>
.min-w-90 {
min-width: 180rpx;
}
.w-90 {
width: 160rpx;
}
.w-100 {
width: 240rpx;
}
.whitespace-nowrap {
white-space: nowrap;
}
.file-icon {
margin-right: 10rpx;
margin-bottom: 10rpx;
width: 80rpx;
height: 80rpx;
}
</style>

176
pages/health/record.vue Normal file
View File

@ -0,0 +1,176 @@
<template>
<full-page v-if="!visible">
<view v-if="formItems.length === 0" class="flex items-center justify-center h-full">
<empty-data />
</view>
<view v-else class="p-15" :class="canEdit ? '' : 'disabled'">
<view class="bg-white rounded shadow-lg">
<form-template ref="tempRef" :items="displayFormItems" :form="formData" @change="change($event)" />
</view>
</view>
<template #footer>
<button-footer v-if="canEdit" confirmText="保存" :showCancel="false" @confirm="confirm()" />
</template>
</full-page>
</template>
<script setup>
import { computed, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { onLoad } from "@dcloudio/uni-app";
import dayjs from 'dayjs';
import useGuard from '@/hooks/useGuard';
import useAccount from '@/store/account';
import api from '@/utils/api';
import { toast } from '@/utils/widget';
import ButtonFooter from '@/components/button-footer.vue';
import EmptyData from '@/components/empty-data.vue';
import FullPage from '@/components/full-page.vue';
import formTemplate from '@/components/form-template/index.vue';
const { useLoad } = useGuard();
const { account } = storeToRefs(useAccount());
const corpId = ref('');
const customerId = ref('');
const id = ref('');
const form = ref({});
const record = ref({});
const formItems = ref([]);
const teamId = ref('');
const tempRef = ref(null);
const type = ref('');
const visible = ref(false);
const timeTitle = ref('');
const canEdit = ref(false)
const formData = computed(() => ({ ...record.value, ...form.value }));
const displayFormItems = computed(() => {
const list = formItems.value.map(i => {
const item = { ...i };
if (i.referenceField && i.referenceValue) {
item.displayRow = formData.value[i.referenceField] === i.referenceValue;
} else {
item.displayRow = true
}
return item
})
return list.filter(i => i.displayRow);
})
function change({ title, value }) {
if (title) {
form.value[title] = value;
}
}
function confirm() {
if (!tempRef.value.verify() || Object.keys(form.value).length === 0) return;
if (id.value) {
updateHealthRecord();
} else {
addHealthRecord()
}
}
async function addHealthRecord() {
const data = {
...form.value,
corpId: corpId.value,
memberId: customerId.value,
// teamId: teamId.value,
dataSource: 'customerReport',
miniAppId: account.value.openid,
medicalType: type.value
}
if (timeTitle.value) {
data.sortTime = data[timeTitle.value] && dayjs(data[timeTitle.value]).isValid() ? dayjs(data[timeTitle.value]).valueOf() : dayjs().valueOf();
}
const res = await api('addMedicalRecord', data);
if (res && res.success) {
await toast('保存成功');
uni.navigateBack();
} else {
toast(res?.message || '保存失败');
}
}
async function updateHealthRecord() {
if (Object.keys(form.value).length === 0) {
uni.navigateBack();
return;
};
const data = {
...form.value,
_id: id.value,
corpId: corpId.value,
memberId: customerId.value,
miniAppId: account.value.openid,
medicalType: type.value
}
if (timeTitle.value) {
data.sortTime = formData.value[timeTitle.value] && dayjs(formData.value[timeTitle.value]).isValid() ? dayjs(formData.value[timeTitle.value]).valueOf() : dayjs().valueOf();
}
const res = await api('updateMedicalRecord', data);
if (res && res.success) {
await toast('保存成功');
uni.navigateBack();
} else {
toast(res?.message || '保存失败');
}
}
async function init() {
await getBaseForm();
if (id.value) {
getRecord()
} else {
canEdit.value = true
}
}
async function getBaseForm() {
const res = await api('getTeamHealthTemplate', { corpId: corpId.value, teamId: teamId.value, templateType: type.value });
if (res && res.success) {
formItems.value = Array.isArray(res.data) ? res.data : [];
timeTitle.value = typeof res.timeTitle === 'string' ? res.timeTitle : '';
} else {
toast(res?.message || '查询失败');
return Promise.reject()
}
}
async function getRecord() {
const res = await api('getMedicalRecordById', {
corpId: corpId.value,
memberId: customerId.value,
_id: id.value,
medicalType: type.value
});
if (res && res.success) {
record.value = res.record
canEdit.value = record.value.dataSource === 'customerReport'
} else {
await toast(res?.message || '查询失败');
uni.navigateBack();
}
}
onLoad(options => {
type.value = options.type;
id.value = options.id || '';
customerId.value = options.customerId || '';
corpId.value = options.corpId;
teamId.value = options.teamId;
uni.setNavigationBarTitle({ title: id.value ? '编辑健康档案' : '新增健康档案' })
})
useLoad(options => {
init();
})
</script>
<style scoped>
.disabled {
pointer-events: none;
}
</style>

View File

@ -0,0 +1,78 @@
<template>
<view v-if="articles.length" class="mt-12 px-15 flex items-center justify-between">
<view class="text-lg font-semibold text-dark">健康宣教</view>
<view class="flex items-center" @click="toList()">
<view class="mr-5 text-base text-gray">更多</view>
<uni-icons type="right" color="#999"></uni-icons>
</view>
</view>
<view class="px-15 mt-10">
<view class="shadow-lg bg-white rounded">
<view v-for="article in articles" :key="article._id"
class="flex px-15 py-12 border-b border-solid border-gray-200">
<image class="flex-shrink-0 mr-10 cover" :src="article.cover || '/static/book.svg'" />
<view class="w-0 flex-grow">
<view class="text-base leading-normal font-semibold truncate mb-5">
{{ article.title }}
</view>
<view v-if="article.summary" class="text-base text-gray line-clamp-2">
{{ article.summary }}
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { computed, ref, watch } from 'vue';
import api from '@/utils/api';
const props = defineProps({
team: {
type: Object,
default: () => ({})
}
})
const articles = ref([])
const qrcode = computed(() => {
const qrcodes = props.team && Array.isArray(props.team.qrcodes) ? props.team.qrcodes : [];
return qrcodes[0] || {}
})
const articleIds = computed(() => {
const articles = qrcode.value && Array.isArray(qrcode.value.articles) ? qrcode.value.articles : [];
const ids = articles.map(item => item._id).filter(i => typeof i === 'string' && i.trim());
return qrcode.value.enableAnnounce === 'YES' ? ids : [];
})
function toList() {
uni.navigateTo({ url: `/pages/article/article-list?corpId=${props.team.corpId}&ids=${articleIds.value.join()}` })
}
async function getArticles() {
const res = await api('getArticleByIds', { corpId: props.team.corpId, ids: articleIds.value.join() });
articles.value = res && Array.isArray(res.list) ? res.list : [];
}
watch(articleIds, n => {
if (n.length) {
getArticles()
} else {
articles.value = []
}
}, { immediate: true })
</script>
<style scoped>
.cover {
width: 128rpx;
height: 128rpx;
}
.min-w-120 {
min-width: 240rpx;
}
.w-80 {
width: 160rpx;
}
</style>

View File

@ -0,0 +1,180 @@
<template>
<view class="px-15 py-12 mb-10 bg-white shadow-lg">
<view class="mb-10 flex items-center justify-between">
<view class="flex-shrink-0 text-lg font-semibold truncate">
成员档案
</view>
<view class="px-10 leading-normal border-dashed-auto text-base text-primary rounded-sm" @click="toManagePage()">
档案管理
</view>
</view>
<view v-if="customers.length === 0" class="flex items-center justify-center h-80 border-dashed text-dark rounded">
<uni-icons type="plusempty" size="16" color="#999"></uni-icons>
<view class="text-base text-dark">新建档案</view>
</view>
<scroll-view scroll-x="true">
<view class="flex flex-nowrap pb-5 ">
<view v-for="i in customers" :key="i._id"
class="flex-shrink-0 min-w-100 mr-10 p-10 rounded relative border-primary"
:class="current && i._id === current._id ? 'bg-primary current-customer' : ''" @click="toggle(i)">
<view class="flex justify-between mb-5">
<view class="text-base leading-normal font-semibold whitespace-nowrap"
:class="current && i._id === current._id ? 'text-white' : 'text-dark'">
{{ i.name }}
</view>
<view v-if="i.relationship" class="flex-shrink-0 px-5 rounded-sm border-auto text-sm leading-normal"
:class="current && i._id === current._id ? 'text-white' : 'text-gray'">
{{ i.relationship }}
</view>
</view>
<view class="text-base leading-normal h-normal"
:class="current && i._id === current._id ? 'text-white' : 'text-gray'">
{{ i.sex }} {{ i.age > 0 ? i.age + '岁' : '' }}
</view>
</view>
</view>
</scroll-view>
<view v-if="canAuth" class="px-10 py-5 mt-5 flex items-center bg-danger rounded-sm">
<view class="mr-5 w-0 flex-grow text-base text-white">
该档案还未授权本服务团队点击右侧授权按钮我们将更精准的为您服务
</view>
<view class="px-12 py-5 text-base rounded-sm text-dark bg-white">
授权
</view>
</view>
<view v-if="current" class="flex mt-10">
<view class="flex-grow p-10 rounded bg-gray mr-10" @click="fillBaseInfo()">
<view class="flex items-center justify-between mb-5">
<view class="text-lg">个人基本信息</view>
<uni-icons color="#999" type="arrowright"></uni-icons>
</view>
<!-- <view v-if="formError.base" class="text-sm text-danger">
请完善您的个人信息 v-else
</view> -->
<view class="text-base text-gray">基础信息填写</view>
</view>
<!-- v-if="healthTempList && healthTempList.length" -->
<view class="flex-grow p-10 rounded bg-gray" @click="toHealthList()">
<view class="flex items-center justify-between mb-5">
<view class="text-lg">个人健康信息</view>
<uni-icons color="#999" type="arrowright"></uni-icons>
</view>
<view class="text-base text-gray">健康信息列表</view>
</view>
</view>
</view>
</template>
<script setup>
import { computed, ref, watch } from 'vue';
import { storeToRefs } from 'pinia'
import useAccount from '@/store/account';
import api from '@/utils/api';
import { toast } from '@/utils/widget';
const props = defineProps({
corpId: {
type: String,
default: ''
},
customers: {
type: Array,
default: () => []
},
team: {
type: Object,
default: () => ({})
}
})
const { account } = storeToRefs(useAccount());
const current = ref(null);
const customers = ref([]);
const canAuth = computed(() => {
if (current.value && props.team && props.team.teamId) {
const teamIds = Array.isArray(current.value.teamId) ? current.value.teamId : [];
return !teamIds.includes(props.team.teamId);
}
return false
})
function fillBaseInfo() {
if (canAuth.value) {
toast('请先授权本服务团队')
} else {
uni.navigateTo({
url: `/pages/archive/edit-archive?teamId=${props.team.teamId}&corpId=${props.corpId}&id=${current.value._id}`
})
}
}
function toHealthList() {
if (canAuth.value) {
toast('请先授权本服务团队')
} else {
const name = `${current.value.name} ${current.value.relationship ? `(${current.value.relationship})` : ''}`
uni.navigateTo({
url: `/pages/health/list?teamId=${props.team.teamId}&corpId=${props.corpId}&id=${current.value._id}&name=${encodeURIComponent(name)}`
})
}
}
function toggle(i) {
current.value = i;
}
function toManagePage() {
uni.navigateTo({ url: `/pages/archive/archive-manage?corpId=${props.corpId}&teamId=${props.team.teamId}` })
}
async function getCustomers() {
const res = await api('getMiniAppCustomers', { miniAppId: account.value.openid, corpId: props.corpId });
if (res && res.success) {
customers.value = res && Array.isArray(res.data) ? res.data : [];
} else {
toast(res.message || '获取档案失败');
}
}
watch(() => props.corpId, n => {
if (n) {
getCustomers()
} else {
customers.value = [];
}
}, { immediate: true });
watch(customers, n => {
if (n.length && !(current.value && n.some(i => i._id === current.value._id))) {
toggle(n[0]);
} else {
current.value = null;
}
})
</script>
<style scoped>
.h-80 {
height: 160rpx;
}
.h-normal {
height: 1.5em;
}
.min-w-100 {
min-width: 200rpx;
}
.current-customer::after {
bottom: -1px;
left: calc(50% - 10px);
border: 10px solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
border-bottom-color: white;
}
</style>

75
pages/home/home.vue Normal file
View File

@ -0,0 +1,75 @@
<template>
<page-loading v-if="loading" />
<full-page v-else-if="teams.length && team">
<template #header>
<team-head :team="team" :teams="teams" @changeTeam="changeTeam" />
<view class="pb-10"></view>
</template>
<customer-archive :corpId="corpId" :team="team" />
<team-mate :team="team" />
<article-list :team="team" />
</full-page>
<yc-home v-else />
</template>
<script setup>
import { computed, ref, watch } from 'vue';
import { storeToRefs } from 'pinia'
import useGuard from '@/hooks/useGuard';
import useAccount from '@/store/account';
import api from '@/utils/api';
import { toast } from '@/utils/widget';
import FullPage from '@/components/full-page.vue';
import articleList from './article-list.vue';
import customerArchive from './customer-archive.vue';
import teamHead from './team-head.vue';
import teamMate from './team-mate.vue';
import ycHome from './yc-home.vue';
import pageLoading from './loading.vue';
const { useLoad, useShow } = useGuard();
const { account } = storeToRefs(useAccount());
const team = ref(null);
const teams = ref([]);
const loading = ref(true)
const corpId = computed(() => team.value?.corpId);
async function changeTeam({ teamId, corpId, corpName }) {
loading.value = true;
const res = await api('getTeamData', { teamId, corpId });
loading.value = false;
if (res && res.data) {
team.value = res.data;
team.value.corpName = corpName;
} else {
toast(res?.message || '获取团队信息失败')
}
}
async function getTeams() {
loading.value = true;
const res = await api('queryWxJoinedTeams', { openid: account.value.openid });
teams.value = res && Array.isArray(res.data) ? res.data : [];
const valid = team.value && teams.value.some(item => item.teamId === team.value.teamId);
if (teams.value.length) {
changeTeam(teams.value[0])
return
} else if (!valid) {
team.value = null;
}
loading.value = false
}
useLoad(opts => {
if (opts.teamId) {
team.value = { teamId: opts.teamId, corpId: opts.corpId };
}
})
useShow(() => {
getTeams();
})
</script>

213
pages/home/loading.vue Normal file
View File

@ -0,0 +1,213 @@
<template>
<view class="loader-container">
<view class="loader">
<view class="hexagon-container">
<view class="hexagon hex_1"></view>
<view class="hexagon hex_2"></view>
<view class="hexagon hex_3"></view>
<view class="hexagon hex_4"></view>
<view class="hexagon hex_5"></view>
<view class="hexagon hex_6"></view>
<view class="hexagon hex_7"></view>
</view>
</view>
</view>
</template>
<script>
</script>
<style lang="scss" scoped>
.loader-container {
position: fixed;
left: 0;
right: 0;
bottom: 0;
top: 0;
z-index: 9999;
background-color: #fefefe;
}
.loader {
position: fixed;
top: 45%;
left: 50%;
width: 80px;
height: 80px;
margin: -40px 0px 0px -40px;
background-color: transparent;
border-radius: 50%;
border: 1px solid rgb(0, 122, 255); //#007aff
// animation: rotate3 3s linear infinite;
&:before {
content: '';
width: 82px;
height: 82px;
display: block;
position: absolute;
border: 1px solid rgba(0, 122, 255, 0.514);
border-radius: 50%;
top: -1px;
left: -1px;
box-sizing: border-box;
// border-bottom-color: transparent;
// border-left-color: transparent;
// border-right-color: transparent;
clip: rect(0px, 17.5px, 17.5px, 0px);
z-index: 10;
animation: rotate infinite;
animation-duration: 3s;
animation-timing-function: linear;
}
&:after {
content: '';
width: 82px;
height: 82px;
display: block;
position: absolute;
border: 1px solid rgba(0, 122, 255, 0.11);
border-radius: 50%;
top: -1px;
left: -1px;
box-sizing: border-box;
// transform: rotate(30deg);
// border-bottom-color: transparent;
// border-left-color: transparent;
// border-right-color: transparent;
clip: rect(0px, 82px, 75px, 0px);
z-index: 9;
// animation: rotate2 infinite, rotate3 infinite;
// animation-duration: 3s;
// animation-timing-function: linner;
animation: rotate2 3s linear infinite;
}
}
.hexagon-container {
position: relative;
top: 16.5px;
left: 20.5px;
border-radius: 50%;
}
.hexagon {
position: absolute;
width: 20px;
height: 11.5px;
background-color: #007aff;
&:before {
content: "";
position: absolute;
top: -5.5px;
left: 0;
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-bottom: 6px solid #007aff;
}
&:after {
content: "";
position: absolute;
top: 11px;
left: 0;
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 5.7px solid #007aff;
}
}
$hexagons: (
(1, 0px, 0px),
(2, 0px, 21px),
(3, 18px, 31.5px),
(4, 36px, 21px),
(5, 36px, 0px),
(6, 18px, -10.5px),
(7, 18px, 10.5px)
);
$time: 3s;
$delay: $time / 14;
@each $hexagon in $hexagons {
$index: nth($hexagon, 1);
$top: nth($hexagon, 2);
$left: nth($hexagon, 3);
.hexagon.hex_#{$index} {
top: $top;
left: $left;
animation: Animasearch $time ease-in-out infinite;
animation-delay: $delay * $index;
}
}
@keyframes Animasearch {
0% {
transform: scale(1);
opacity: 1;
}
15%,
50% {
transform: scale(0.5);
opacity: 0;
}
65% {
transform: scale(1);
opacity: 1;
}
}
@keyframes rotate {
0% {
transform: rotate(0);
clip: rect(0px, 17.5px, 17.5px, 0px);
}
50% {
clip: rect(0px, 20px, 20px, 0px);
}
100% {
transform: rotate(360deg);
clip: rect(0px, 17.5px, 17.5px, 0px);
}
}
@keyframes rotate2 {
0% {
transform: rotate(0deg);
clip: rect(0px, 82, 75px, 0px);
}
50% {
clip: rect(0px, 82, 0px, 0px);
transform: rotate(360deg);
}
100% {
transform: rotate(720deg);
clip: rect(0px, 164px, 75px, 0px);
}
}
@keyframes rotate3 {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

123
pages/home/team-head.vue Normal file
View File

@ -0,0 +1,123 @@
<template>
<view>
<view :style="{ height: statusBarHeight }" class="bg-primary"></view>
<view class="relative z-3 flex items-center px-15 py-12 bg-primary">
<view class="flex-shrink-0 mr-5">
<group-avatar :size="120" :avatarList="currentTeam ? currentTeam.avatarList : []" />
</view>
<view class="w-0 flex-grow ">
<view class="flex mb-10">
<view class="w-0 flex-grow truncate text-lg font-semibold text-white">{{ team.name }}</view>
<view v-if="teams.length > 1" class="flex-shinrk-0 flex items-center px-10 bg-white rounded-sm"
@click="showDropDown = true">
<view class="text-base">切换</view>
<uni-icons type="down" size="12"></uni-icons>
</view>
</view>
<view v-if="currentTeam" class="text-base text-white truncate">{{ currentTeam.corpName }}</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 class="relative">
<view v-if="showDropDown" class="team-dropdown py-12 bg-white shadow-lg">
<scroll-view scroll-y="true" style="max-height: 50vh;">
<view class="px-15">
<view v-for="item in teams" :key="item.teamId" class="mb-10 p-10 flex items-center bg-gray rounded-sm">
<view class="flex-shrink-0 mr-5">
<group-avatar :size="96" :avatarList="item.avatarList" />
</view>
<view class="w-0 flex-grow mr-5">
<view class="mb-5 text-lg font-semibold text-dark">{{ item.name }}</view>
<view class="text-base text-gray leading-normal">{{ item.corpName }}</view>
</view>
<view class="flex">
<image class="check-icon"
:src="team && team.teamId === item.teamId ? '/static/form/checked.svg' : '/static/form/unchecked.svg'">
</image>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
<view v-if="team.teamTroduce" class="px-15 py-12 flex bg-white border-b shadow-lg">
<image class="laba-icon flex-shrink-0 mr-5" src="/static/laba.svg"></image>
<view class="w-0 flex-grow text-sm text-gray leading-normal line-clamp-2">{{ team.teamTroduce }} </view>
</view>
</view>
<view v-if="showDropDown" class="mask" @click="showDropDown = false"></view>
</template>
<script setup>
import { computed, ref, onMounted, watch } from 'vue';
import groupAvatar from '@/components/group-avatar.vue';
const statusBarHeight = ref('50px');
const menuButtonInfo = ref(null);
const showDropDown = ref(false)
const emits = defineEmits(['changeTeam']);
const props = defineProps({
team: {
type: Object,
default: () => ({})
},
teams: {
type: Array,
default: () => []
}
})
const currentTeam = computed(() => props.teams.find(i => props.team && i.teamId === props.team.teamId))
watch(() => props.teams, (teams) => {
if (teams.length && !(currentTeam.value && teams.some(i => i.teamId === currentTeam.value.teamId))) {
emits('changeTeam', teams[0])
}
})
onMounted(() => {
const win = uni.getWindowInfo();
if (win && win.statusBarHeight > 0) {
statusBarHeight.value = win.statusBarHeight + 'px';
}
menuButtonInfo.value = uni.getMenuButtonBoundingClientRect();
})
</script>
<style scoped>
.laba-icon {
width: 36rpx;
height: 36rpx;
}
.check-icon {
width: 40rpx;
height: 40rpx;
}
.mask {
position: fixed;
z-index: 2;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.3);
}
.z-3 {
z-index: 3;
}
.team-dropdown {
position: absolute;
left: 0;
right: 0;
top: 0;
z-index: 3;
border-bottom-left-radius: 16rpx;
border-bottom-right-radius: 16rpx;
}
</style>

75
pages/home/team-mate.vue Normal file
View File

@ -0,0 +1,75 @@
<template>
<view class="mt-12 px-15 flex items-center justify-between">
<view class="text-lg font-semibold text-dark">团队成员</view>
<view class="flex items-center" @click="toTeamDetail()">
<view class="mr-5 text-base text-gray">团队详情</view>
<uni-icons type="right" color="#999"></uni-icons>
</view>
</view>
<view class="px-15 mt-10">
<scroll-view scroll-x="true">
<view class="flex flex-nowrap pb-5 border-b">
<view v-for="i in teamates" :key="i.userid"
class="flex flex-shrink-0 min-w-120 p-10 mr-10 rounded-sm border-auto text-primary bg-white"
@click="toHomePage(i)">
<image class="flex-shrink-0 avatar mr-5" :src="i.avatar || '/static/default-avatar.png'" />
<view class="flex-grow flex flex-col">
<view class="text-lg font-semibold text-dark whitespace-nowrap">
{{ i.anotherName }}
</view>
<view class="text-base text-gray truncate">
医生
</view>
<view v-if="i.canAddFriend" class="w-80 text-base leading-none border text-center text-dark rounded-full"
@click.stop="toQrcode(i)">
添加好友
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
team: {
type: Object,
default: () => ({})
}
})
const teamates = computed(() => {
const friendlyMembers = props.team && Array.isArray(props.team.friendlyMembers) ? props.team.friendlyMembers : [];
const memberList = props.team && Array.isArray(props.team.memberList) ? props.team.memberList : [];
return memberList.map(i => ({ ...i, canAddFriend: friendlyMembers.includes(i.userid) }))
})
function toHomePage(item) {
uni.navigateTo({ url: `/pages/team/homepage?userid=${item.userid}&corpId=${item.corpId}` })
}
function toQrcode(item) {
uni.navigateTo({ url: `/pages/team/friend?userid=${item.userid}&corpId=${item.corpId}` })
}
function toTeamDetail() {
uni.navigateTo({ url: `/pages/team/team-detail?teamId=${props.team.teamId}&corpId=${props.team.corpId}&corpName=${encodeURIComponent(props.team.corpName)}` })
}
</script>
<style scoped>
.avatar {
width: 120rpx;
height: 128rpx;
}
.min-w-120 {
min-width: 240rpx;
}
.w-80 {
width: 160rpx;
}
</style>

45
pages/home/yc-home.vue Normal file
View File

@ -0,0 +1,45 @@
<template>
<view class="h-full flex flex-col">
<view class="flex-shrink-0 w-full">
<view :style="{ height: statusBarHeight }" class="bg-primary"></view>
<view class="relative z-3 flex items-center px-15 py-12 bg-primary">
<view class="flex-shrink-0 mr-10">
<image class="logo" src="/static/logo-plain.png"></image>
</view>
<view class="w-0 flex-grow">
<view class="text-lg font-semibold text-white">柚健康 </view>
<view class="leading-normal text-base text-white truncate">生命全周期健康管理伙伴</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 class="flex-grow flex flex-col items-center justify-center bg-white">
<empty-data :showText="false" />
<view class="mb-10 text-lg text-dark font-semibold">暂无团队</view>
<view class="text-lg text-dark font-semibold">需要扫团队二维码绑定服务团队哦</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import emptyData from '@/components/empty-data.vue';
const statusBarHeight = ref('50px');
const menuButtonInfo = ref(null);
onMounted(() => {
const win = uni.getWindowInfo();
if (win && win.statusBarHeight > 0) {
statusBarHeight.value = win.statusBarHeight + 'px';
}
menuButtonInfo.value = uni.getMenuButtonBoundingClientRect();
})
</script>
<style scoped>
.logo {
width: 120rpx;
height: 120rpx;
}
</style>

View File

@ -0,0 +1,159 @@
<template>
<full-page>
<template #header>
<view class="page-item border-bottom bg-white">
<input class="search-input" placeholder-style="font-size:28rpx" placeholder="请搜索名称" v-model="name"
@input="search" />
</view>
</template>
<view v-for="i in list" :key="i.label" class="search-item bg-white" @click="select(i.label)">
<view class="name">{{ i.label }}</view>
<icon class="icon-checked" :class="selectMap[i.label] ? '' : 'hidden'" type="success_no_circle" size="18" />
</view>
<template #footer>
<button-footer confirmText="确定" :showCancel="false" @confirm="confirm()" />
</template>
</full-page>
</template>
<script setup>
import { computed, ref } from 'vue';
import { storeToRefs } from 'pinia';
import { onLoad } from "@dcloudio/uni-app";
import api from '@/utils/api';
import { remove, get } from '@/utils/cache';
import useDebounce from '@/utils/useDebounce';
import { toast } from '@/utils/widget';
import ButtonFooter from '@/components/button-footer.vue';
import FullPage from '@/components/full-page.vue';
const name = ref('');
const list = ref([]);
const page = ref(1);
const pageSize = ref(30);
const more = ref(false);
const selections = ref([]);
const eventName = ref('')
const selectMap = computed(() => selections.value.reduce((val, i) => {
i && (val[i] = i);
return val
}, {}))
const search = useDebounce(() => {
page.value = 1;
getList()
})
function confirm() {
if (selections.value.length) {
uni.$emit(eventName.value, selections.value);
uni.navigateBack()
} else {
toast('请选择疾病')
}
}
function select(label) {
if (selections.value.includes(label)) {
selections.value = selections.value.filter(i => i !== label)
} else {
selections.value.push(label)
}
}
async function getList() {
const res = await api('getPageDisease', { name: name.value.trim(), page: page.value, pageSize: pageSize.value });
const diseases = res && Array.isArray(res.list) ? res.list.map(i => ({
label: i.diseaseName,
value: i.diseaseName
})).filter(i => i.label !== name.value.trim()) : [];
const pages = res && res.pages ? res.pages : 0;
more.value = pages > page.value;
if (page.value === 1) {
const data = name.value.trim() ? [{
label: name.value.trim(),
value: name.value.trim()
}] : [];
data.push(...diseases);
list.value = data
} else {
list.value = [...list.value, ...diseases];
}
}
onLoad(options => {
eventName.value = options.eventName;
const data = get('diagnosis-list-selections');
selections.value = Array.isArray(data) ? data : [];
remove('diagnosis-list-selections');
if (selections.value.length) {
list.value = selections.value.map(i => ({
label: i,
value: i
}))
} else {
getList()
}
})
</script>
<style scoped lang="scss">
.hidden {
opacity: 0;
}
.border-bottom {
border-bottom: 1px solid #eee;
}
.border-top {
border-top: 1px solid #eee;
}
.list-page {
display: flex;
flex-direction: column;
height: 100%;
height: 100vh;
}
.search-input {
border: 1px solid #eee;
padding: 16rpx 20rpx;
border-radius: 12rpx;
font-size: 28rpx;
}
.scroll-wrapper {
position: relative;
flex-grow: 1;
}
.list-scroll {
position: absolute;
inset: 0;
}
.search-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 30rpx;
border-bottom: 1px solid #eee;
}
.icon-checked {
flex-shrink: 0;
}
.name {
flex-grow: 1;
margin-right: 20rpx;
font-size: 28rpx;
}
.page-item {
flex-shrink: 0;
padding: 30rpx;
}
</style>

View File

@ -38,10 +38,19 @@ async function changeTeam({ teamId, corpId, corpName }) {
}
onLoad(options => {
teamId.value = options.teamId || '';
corpId.value = options.corpId || '';
changeTeam({ teamId: teamId.value, corpId: corpId.value });
const href = typeof options.q === 'string' ? decodeURIComponent(options.q) : '';
const [, url = ''] = href.split('?');
const data = url.split('&').reduce((acc, cur) => {
console.log(cur)
const [key, value] = cur.split('=');
console.log(key, '=====', value)
acc[key] = value;
return acc;
}, {})
changeTeam(data)
})
</script>
<style>
.flash-logo {

93
pages/team/friend.vue Normal file
View File

@ -0,0 +1,93 @@
<template>
<view v-if="member" class="flex flex-col h-full items-center justify-center">
<view class="business-card">
<view class="flex">
<image class="mr-10 avatar" :src="member.avatar || '/static/default-avatar.png'"></image>
<view class="w-0 flex-grow leading-normal">
<view class="flex items-center">
<view class="mr-5 text-lg font-semibold text-dark">{{ member.anotherName }}</view>
<view class="text-base text-warning">@企业微信</view>
</view>
<view class="text-base text-dark">咨询师</view>
</view>
</view>
<view class="mt-12 border-primary qrcode p-15 mx-auto rounded" @click="previewImage()">
<image v-if="qrcode" class="h-full w-full" :src="qrcode"></image>
</view>
</view>
</view>
</template>
<script setup>
import { computed, ref } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import api from '@/utils/api';
import { toast } from '@/utils/widget';
const corpId = ref('');
const userid = ref('');
const qrcode = ref('')
const member = ref(null);
const corpNames = computed(() => {
const corpNames = member.value && Array.isArray(member.value.corpNames) ? member.value.corpNames : [];
return corpNames.join('、');
})
const deptNames = computed(() => {
const deptNames = member.value && Array.isArray(member.value.deptNames) ? member.value.deptNames : [];
return deptNames.join('、');
})
function previewImage() {
if (!qrcode.value) return;
uni.previewImage({
urls: [qrcode.value]
})
}
async function getMember() {
const res = await api('getCorpMemberHomepageInfo', { userid: userid.value, corpId: corpId.value });
if (res && res.success) {
member.value = res.data;
if (!qrcode.value) {
getQrcode();
}
} else {
toast(res.message || '获取医生信息失败')
}
}
async function getQrcode() {
const res = await api('addContactWay', { corpUserId: userid.value, corpId: corpId.value });
if (res && res.data) {
qrcode.value = res.data;
}
}
onLoad((options) => {
corpId.value = options.corpId;
userid.value = options.userid;
});
onShow(() => {
if (corpId.value && userid.value) {
getMember()
}
})
</script>
<style scoped>
.business-card {
width: 600rpx;
}
.avatar {
width: 80rpx;
height: 96rpx;
}
.qrcode {
width: 560rpx;
height: 560rpx;
}
</style>

159
pages/team/homepage.vue Normal file
View File

@ -0,0 +1,159 @@
<template>
<view v-if="member" class="flex p-15 bg-whtie shadow-lg">
<view class="flex-grow w-0 mr-10 leading-normal">
<view>
<text class="mr-5 text-xl text-dark font-semibold">{{ member.anotherName }}</text>
<text class="text-base text-gray">医生</text>
</view>
<view class="flex">
<view class="flex-shrink-0 text-base text-gray">机构部门</view>
<view class="flex-shrink-0 text-base text-dark">{{ deptNames }}</view>
</view>
<view class="flex">
<view class="flex-shrink-0 text-base text-gray">执业机构</view>
<view class="flex-shrink-0 text-base text-dark">{{ corpNames }}</view>
</view>
</view>
<image class="avatar" :src="member.avatar"></image>
</view>
<view v-if="member" class="p-15 mt-12 leading-normal bg-white shadow-lg">
<view class="flex items-center" @click="expand = !expand">
<image class="flex-shrink-0 mr-10 section-icon" src="https://picsum.photos/300/300"></image>
<view class="w-0 flex-grow text-lg font-semibold">个人简介</view>
<uni-icons v-if="member.memberTroduce" :type="expand ? 'up' : 'down'" size="16"></uni-icons>
</view>
<view class="mt-10 text-dark text-base" :class="expand ? '' : 'line-clamp-4'">
{{ member.memberTroduce || '暂无简介' }}
</view>
<view class="mt-20 flex items-center">
<image class="flex-shrink-0 mr-10 section-icon" src="https://picsum.photos/300/300"></image>
<view class="w-0 flex-grow text-lg font-semibold">门诊时间</view>
</view>
<view class="mt-10 text-dark text-base">
{{ member.outpatientTime || '暂无门诊时间' }}
</view>
<view class="mt-20 flex items-center">
<image class="flex-shrink-0 mr-10 section-icon" src="https://picsum.photos/300/300"></image>
<view class="w-0 flex-grow text-lg font-semibold">对外联系电话</view>
</view>
<view class="mt-10 text-dark" :class="member.callNumber ? 'text-primary' : 'text-gray'" @click="callNumber()">
{{ member.callNumber || '暂无联系电话' }}
</view>
<view class="mt-20 flex items-center">
<image class="flex-shrink-0 mr-10 section-icon" src="https://picsum.photos/300/300"></image>
<view class="w-0 flex-grow text-lg font-semibold">便民服务</view>
</view>
<view class="mt-10 flex">
<view class="px-15 py-5 text-base text-white bg-primary rounded-sm">预约挂号</view>
<view class="px-15 py-5 ml-15 text-base text-white bg-primary rounded-sm">在线咨询</view>
</view>
</view>
<view v-if="qrcode" class="p-15 mt-12 leading-normal bg-white shadow-lg">
<view class="text-lg font-semibold text-center text-dark">
点击下方二维码加我为好友
</view>
<view class="mt-12 border-primary qrcode p-15 mx-auto rounded" @click="previewImage()">
<image :src="qrcode" class="h-full w-full"></image>
</view>
</view>
<view class="safe-bottom-padding"></view>
</template>
<script setup>
import { computed, ref, watch } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import api from '@/utils/api';
import { toast } from '@/utils/widget';
const corpId = ref('');
const userid = ref('');
const showQrcode = ref(false);
const qrcode = ref('')
const member = ref(null);
const expand = ref(false);
const corpNames = computed(() => {
const corpNames = member.value && Array.isArray(member.value.corpNames) ? member.value.corpNames : [];
return corpNames.join('、');
})
const deptNames = computed(() => {
const deptNames = member.value && Array.isArray(member.value.deptNames) ? member.value.deptNames : [];
return deptNames.join('、');
})
function callNumber() {
if (member.value && member.value.callNumber) {
uni.makePhoneCall({
phoneNumber: member.value.callNumber
})
}
}
function previewImage() {
uni.previewImage({
urls: [qrcode.value]
})
}
async function getMember() {
const res = await api('getCorpMemberHomepageInfo', { userid: userid.value, corpId: corpId.value });
if (res && res.success) {
member.value = res.data;
if ( showQrcode.value && !qrcode.value) {
getQrcode();
}
} else {
toast(res.message || '获取医生信息失败')
}
}
async function getQrcode() {
const res = await api('addContactWay', { corpUserId: userid.value, corpId: corpId.value });
if (res && res.data ) {
qrcode.value = res.data;
}
}
onLoad((options) => {
corpId.value = options.corpId;
userid.value = options.userid;
showQrcode.value = options.showQrcode;
});
onShow(() => {
if (corpId.value && userid.value) {
getMember()
}
})
</script>
<style>
page {
overflow: auto;
}
.avatar {
width: 120rpx;
height: 128rpx;
}
.section-icon {
width: 48rpx;
height: 48rpx;
}
.mt-20 {
margin-top: 40rpx;
}
.line-clamp-4 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 4;
overflow: hidden;
}
.qrcode {
width: 560rpx;
height: 560rpx;
}
</style>

150
pages/team/team-detail.vue Normal file
View File

@ -0,0 +1,150 @@
<template>
<view v-if="team" class="p-15">
<view class="flex items-center">
<view class="flex-shrink-0 mr-10">
<group-avatar :size="96" :avatarList="avatarList" />
</view>
<view class="flex-grow w-0 leading-noraml">
<view class="mb-5 text-lg font-semibold text-dark">{{ team.name }}</view>
<view class="text-base text-gray">
{{ corpName }}
</view>
</view>
</view>
<view v-if="team.teamTroduce" class="mt-12 text-base text-dark leading-normal break-all">
{{ team.teamTroduce }}
</view>
<template v-if="teammate.leaders.length">
<view class="min-w-100 inline-block mt-12 px-10 py-5 text-center text-base text-white bg-primary rounded-sm">
团队负责人
</view>
<view v-for="i in teammate.leaders" :key="i._id" class="mt-12 flex p-10 border-primary rounded-sm"
@click="toHomePage(i.userid)">
<image class="flex-shrink-0 mr-10 avatar" :src="i.avatar || '/static/default-avatar.png'"></image>
<view class="w-0 flex-grow leading-normal">
<view class="flex items-center justify-between">
<view class="flex-grow w-0">
<text class="mr-5 text-lg text-dark font-semibold">{{ i.anotherName }}</text>
<text class="text-base text-dark">医生</text>
</view>
<view v-if="friendlyMember[i.userid]"
class="px-10 leading-normal text-sm border-auto text-primary rounded-full"
@click.stop="toFriend(i.userid)">
添加好友
</view>
</view>
<view class="line-clamp-2 text-base text-gray">
{{ i.memberTroduce || '暂无简介' }}
</view>
</view>
</view>
</template>
<template v-if="teammate.members.length">
<view class="min-w-100 inline-block mt-12 px-10 py-5 text-center text-base text-white bg-primary rounded-sm">团队成员
</view>
<view v-for="i in teammate.members" :key="i._id" class="mt-12 flex p-10 border-primary rounded-sm"
@click="toHomePage(i.userid)">
<image class="flex-shrink-0 mr-10 avatar" :src="i.avatar || '/static/default-avatar.png'"></image>
<view class="w-0 flex-grow leading-normal">
<view class="flex items-center justify-between">
<view class="flex-grow w-0">
<text class="mr-5 text-lg text-dark font-semibold">{{ i.anotherName }}</text>
<text class="text-base text-dark">医生</text>
</view>
<view v-if="friendlyMember[i.userid]"
class="px-10 leading-normal text-sm border-auto text-primary rounded-full"
@click.stop="toFriend(i.userid)">
添加好友
</view>
</view>
<view class="line-clamp-2 text-base text-gray">
{{ i.memberTroduce || '暂无简介' }}
</view>
</view>
</view>
</template>
<view class="safe-bottom-padding"></view>
</view>
</template>
<script setup>
import { computed, ref } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import api from '@/utils/api';
import groupAvatar from '@/components/group-avatar.vue';
const corpId = ref('');
const teamId = ref('');
const team = ref(null);
const corpName = ref('');
const memberList = computed(() => team.value && Array.isArray(team.value.memberList) ? team.value.memberList : [])
const avatarList = computed(() => memberList.value.map(i => i.avatar || '/static/default-avatar.png').filter(Boolean))
const teammate = computed(() => {
const memberLeaderList = team.value && Array.isArray(team.value.memberLeaderList) ? team.value.memberLeaderList : [];
return memberList.value.reduce((data, item) => {
if (memberLeaderList.includes(item.userid)) {
data.leaders.push(item)
} else {
data.members.push(item)
}
return data
}, { leaders: [], members: [] })
})
const friendlyMember = computed(() => {
const friendlyMembers = team.value && Array.isArray(team.value.friendlyMembers) ? team.value.friendlyMembers : [];
return friendlyMembers.reduce((data, item) => {
data[item] = true;
return data
}, {})
})
function toFriend(userid) {
uni.navigateTo({ url: `/pages/team/friend?corpId=${corpId.value}&userid=${userid}` })
}
function toHomePage(userid) {
uni.navigateTo({ url: `/pages/team/homepage?corpId=${corpId.value}&userid=${userid}&showQrcode=${friendlyMember[userid] ? 'YES' : ''}` })
}
async function getTeam() {
const res = await api('getTeamData', { teamId: teamId.value, corpId: corpId.value });
if (res && res.data) {
team.value = res.data;
} else {
toast(res?.message || '获取团队信息失败')
}
}
onLoad(options => {
corpId.value = options.corpId;
teamId.value = options.teamId;
corpName.value = decodeURIComponent(options.corpName || '');
})
onShow(() => {
if (teamId.value && corpId.value) {
getTeam()
}
});
</script>
<style>
page {
background: white;
overflow: auto;
}
.min-w-100 {
min-width: 200rpx;
}
.avatar {
width: 120rpx;
height: 128rpx;
}
</style>

View File

@ -3,6 +3,50 @@ export default [
path: 'pages/home/home',
meta: { title: '首页', login: true },
style: { navigationStyle: 'custom' }
},
{
path: 'pages/login/login',
meta: { title: '柚健康' },
},
{
path: 'pages/login/redirect-page',
meta: { title: '柚健康' },
},
{
path: 'pages/archive/archive-manage',
meta: { title: '档案管理', login: true }
},
{
path: 'pages/archive/edit-archive',
meta: { title: '新增档案', login: true }
},
{
path: 'pages/article/article-list',
meta: { title: '健康宣教', login: true }
},
{
path: 'pages/health/list',
meta: { title: '健康信息', login: true }
},
{
path: 'pages/health/record',
meta: { title: '健康信息', login: true }
},
{
path: 'pages/library/diagnosis-list',
meta: { title: '选择诊断' }
},
{
path: 'pages/team/team-detail',
meta: { title: '团队介绍', login: true }
},
{
path: 'pages/team/homepage',
meta: { title: '个人主页', login: true }
},
{
path: 'pages/team/friend',
meta: { title: '添加好友', login: true }
}
]

1
static/file.svg Normal file
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="1768890544193" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9729" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M 518.72 238.4 l -84.8 -95.68 c -8 -9.28 -20.16 -14.72 -32.64 -14.72 H 74.56 C 50.24 128 30.4 147.52 30.4 171.84 v 696.64 c 0 24.32 19.52 43.84 43.84 43.84 H 969.6 c 24.32 0 43.84 -19.52 43.84 -43.84 V 297.28 c 0 -24.32 -19.52 -43.84 -43.84 -43.84 H 551.68 c -12.48 0 -24.64 -5.44 -32.96 -15.04 Z" fill="#06a8f5" p-id="9730"></path></svg>

After

Width:  |  Height:  |  Size: 671 B

View File

@ -7,19 +7,26 @@ const urlsConfig = {
getTeamData: 'getTeamData',
queryWxJoinedTeams: 'queryWxJoinedTeams',
wxAppLogin: 'wxAppLogin',
getTeamHealthTemplate: 'getTeamHealthTemplate',
getTeamHealthTemps: "getTeamHealthTemps",
},
knowledgeBase: {
getArticleByIds: 'getArticleByIds'
getArticleByIds: 'getArticleByIds',
getPageDisease: "getPageDisease"
},
member: {
addCustomer: 'add',
addMedicalRecord: 'addMedicalRecord',
bindMiniAppArchive: "bindMiniAppArchive",
getCustomerByCustomerId: 'getCustomerByCustomerId',
getMiniAppCustomers: 'getMiniAppCustomers',
getTeamCustomers: 'getTeamCustomers',
getUnbindMiniAppCustomers: 'getUnbindMiniAppCustomers',
getCustomerMedicalRecord: 'getCustomerMedicalRecord',
getMedicalRecordById: 'getMedicalRecordById',
unbindMiniAppArchive: 'unbindMiniAppArchive',
updateMedicalRecord: 'updateMedicalRecord'
},
wecom: {
addContactWay: 'addContactWay'
@ -38,7 +45,6 @@ const urls = Object.keys(urlsConfig).reduce((acc, path) => {
})
return acc
}, {})
console.log('urls: ', urls)
export default async function api(urlId, data) {
const config = urls[urlId];

View File

@ -202,5 +202,28 @@ export default request;
export const uploadUrl = `${baseUrl}/upload`;
export function getFullPath(path) {
return `${baseUrl}${path}`;
return `${baseUrl}/${path}`;
}
export function upload(path) {
return new Promise((resolve) => {
uni.uploadFile({
url: uploadUrl, // 替换为你的上传接口地址
filePath: path,
name: 'file',
fileType: 'image',
success: (res) => {
try {
const url = JSON.parse(res.data).filePath;
resolve(url ? getFullPath(url) : '')
} catch (e) {
resolve()
}
},
fail: res => {
resolve()
}
})
})
}