登陆授权流程调整

This commit is contained in:
wangdongbo 2026-01-21 13:37:54 +08:00
parent c9df1e6341
commit f6b504a2bd
9 changed files with 392 additions and 380 deletions

218
App.vue
View File

@ -1,351 +1,353 @@
<script> <script>
import useAccountStore from "@/store/account.js";
export default { export default {
onLaunch: function () { onLaunch: function () {
console.log('App Launch: ') // pinia store getActivePinia
}, const { login } = useAccountStore();
onShow: function () { login();
console.log('App Show') console.log("App Launch: ");
}, },
onHide: function () { onShow: function () {
console.log('App Hide') console.log("App Show");
} },
} onHide: function () {
console.log("App Hide");
},
};
</script> </script>
<style lang="scss"> <style lang="scss">
$primary-color: #0074ff; $primary-color: #0074ff;
page { page {
height: 100%; height: 100%;
background: #f5f5f5; background: #f5f5f5;
} }
.shadow-up { .shadow-up {
box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px,
rgba(0, 0, 0, 0.1) 0px -10px 15px -3px, rgba(0, 0, 0, 0.1) 0px -4px 6px -4px; rgba(0, 0, 0, 0.1) 0px -10px 15px -3px, rgba(0, 0, 0, 0.1) 0px -4px 6px -4px;
} }
.shadow-lg { .shadow-lg {
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
} }
.relative { .relative {
position: relative; position: relative;
} }
.inline-block { .inline-block {
display: inline-block; display: inline-block;
} }
.flex { .flex {
display: flex; display: flex;
} }
.flex-col { .flex-col {
flex-direction: column; flex-direction: column;
} }
.flex-wrap { .flex-wrap {
flex-wrap: wrap; flex-wrap: wrap;
} }
.flex-grow { .flex-grow {
flex-grow: 1; flex-grow: 1;
} }
.flex-shrink-0 { .flex-shrink-0 {
flex-shrink: 0; flex-shrink: 0;
} }
.items-center { .items-center {
align-items: center; align-items: center;
} }
.justify-center { .justify-center {
justify-content: center; justify-content: center;
} }
.justify-between { .justify-between {
justify-content: space-between; justify-content: space-between;
} }
.justify-around { .justify-around {
justify-content: space-around; justify-content: space-around;
} }
.justify-end { .justify-end {
justify-content: flex-end; justify-content: flex-end;
} }
.bg-gray { .bg-gray {
background: #f5f5f5; background: #f5f5f5;
} }
.bg-primary { .bg-primary {
background: $primary-color; background: $primary-color;
} }
.bg-success { .bg-success {
background: green; background: green;
} }
.bg-white { .bg-white {
background: #fff; background: #fff;
} }
.bg-warning { .bg-warning {
background: orange; background: orange;
} }
.bg-danger { .bg-danger {
background: rgb(248 113 113); background: rgb(248 113 113);
} }
.py-5 { .py-5 {
padding-top: 10rpx; padding-top: 10rpx;
padding-bottom: 10rpx; padding-bottom: 10rpx;
} }
.p-15 { .p-15 {
padding: 30rpx; padding: 30rpx;
} }
.p-10 { .p-10 {
padding: 20rpx; padding: 20rpx;
} }
.px-5 { .px-5 {
padding-left: 10rpx; padding-left: 10rpx;
padding-right: 10rpx; padding-right: 10rpx;
} }
.px-10 { .px-10 {
padding-left: 20rpx; padding-left: 20rpx;
padding-right: 20rpx; padding-right: 20rpx;
} }
.px-12 { .px-12 {
padding-left: 24rpx; padding-left: 24rpx;
padding-right: 24rpx; padding-right: 24rpx;
} }
.px-15 { .px-15 {
padding-left: 30rpx; padding-left: 30rpx;
padding-right: 30rpx; padding-right: 30rpx;
} }
.pt-5 { .pt-5 {
padding-top: 10rpx; padding-top: 10rpx;
} }
.pt-15 { .pt-15 {
padding-top: 30rpx; padding-top: 30rpx;
} }
.break-all { .break-all {
word-break: break-all; word-break: break-all;
} }
.pb-5 { .pb-5 {
padding-bottom: 10rpx; padding-bottom: 10rpx;
} }
.pb-10 { .pb-10 {
padding-bottom: 20rpx; padding-bottom: 20rpx;
} }
.py-10 { .py-10 {
padding-top: 20rpx; padding-top: 20rpx;
padding-bottom: 20rpx; padding-bottom: 20rpx;
} }
.py-12 { .py-12 {
padding-top: 24rpx; padding-top: 24rpx;
padding-bottom: 24rpx; padding-bottom: 24rpx;
} }
.py-15 { .py-15 {
padding-top: 30rpx; padding-top: 30rpx;
padding-bottom: 30rpx; padding-bottom: 30rpx;
} }
.mr-5 { .mr-5 {
margin-right: 10rpx; margin-right: 10rpx;
} }
.mr-10 { .mr-10 {
margin-right: 20rpx; margin-right: 20rpx;
} }
.ml-15 { .ml-15 {
margin-left: 30rpx; margin-left: 30rpx;
} }
.mb-5 { .mb-5 {
margin-bottom: 10rpx; margin-bottom: 10rpx;
} }
.mb-10 { .mb-10 {
margin-bottom: 20rpx; margin-bottom: 20rpx;
} }
.mt-10 { .mt-10 {
margin-top: 20rpx; margin-top: 20rpx;
} }
.mt-15 { .mt-15 {
margin-top: 30rpx; margin-top: 30rpx;
} }
.mt-12 { .mt-12 {
margin-top: 24rpx; margin-top: 24rpx;
} }
.mx-10 { .mx-10 {
margin-left: 20rpx; margin-left: 20rpx;
margin-right: 20rpx; margin-right: 20rpx;
} }
.mx-auto { .mx-auto {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
} }
.rounded { .rounded {
border-radius: 16rpx; border-radius: 16rpx;
} }
.rounded-sm { .rounded-sm {
border-radius: 8rpx; border-radius: 8rpx;
} }
.rounded-full { .rounded-full {
border-radius: 999px; border-radius: 999px;
} }
.text-center { .text-center {
text-align: center; text-align: center;
} }
.text-right { .text-right {
text-align: right; text-align: right;
} }
.text-gray { .text-gray {
color: #999; color: #999;
} }
.text-black { .text-black {
color: #000; color: #000;
} }
.text-dark { .text-dark {
color: #333; color: #333;
} }
.text-danger { .text-danger {
color: #f56c6c; color: #f56c6c;
} }
.text-primary { .text-primary {
color: $primary-color; color: $primary-color;
} }
.text-success { .text-success {
color: #67c23a; color: #67c23a;
} }
.text-white { .text-white {
color: #fff; color: #fff;
} }
.text-warning { .text-warning {
color: #f56c6c; color: #f56c6c;
} }
.text-sm { .text-sm {
font-size: 24rpx; font-size: 24rpx;
} }
.text-base { .text-base {
font-size: 28rpx; font-size: 28rpx;
} }
.text-lg { .text-lg {
font-size: 32rpx; font-size: 32rpx;
} }
.text-xl { .text-xl {
font-size: 36rpx; font-size: 36rpx;
} }
.leading-normal { .leading-normal {
line-height: 1.5; line-height: 1.5;
} }
.border { .border {
border: 1px solid #eee; border: 1px solid #eee;
} }
.border-dashed { .border-dashed {
border: 1px dashed #eee; border: 1px dashed #eee;
} }
.border-b { .border-b {
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
} }
.border-auto { .border-auto {
border: 1px solid; border: 1px solid;
} }
.border-dashed-auto { .border-dashed-auto {
border: 1px dashed; border: 1px dashed;
} }
.border-primary { .border-primary {
border: 1px solid $primary-color; border: 1px solid $primary-color;
} }
.font-semibold { .font-semibold {
font-weight: bold; font-weight: bold;
} }
.truncate { .truncate {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.w-0 { .w-0 {
width: 0; width: 0;
} }
.w-full { .w-full {
width: 100%; width: 100%;
} }
.overflow-hidden { .overflow-hidden {
overflow: hidden; overflow: hidden;
} }
.border-box { .border-box {
box-sizing: border-box; box-sizing: border-box;
} }
.line-clamp-2 { .line-clamp-2 {
display: -webkit-box; display: -webkit-box;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
overflow: hidden; overflow: hidden;
} }
.h-full { .h-full {
height: 100%; height: 100%;
} }
.safe-bottom-padding { .safe-bottom-padding {
height: env(safe-area-inset-bottom); height: env(safe-area-inset-bottom);
} }
</style> </style>

View File

@ -1,38 +0,0 @@
const env = __VITE_ENV__;
/**
* 上传文件图片等
* zdh-hlw-patient /file/upload 接口保持一致
*/
export function uploadFile(tempFilePath, businessType, accessLevel = 'public') {
return new Promise((resolve) => {
uni.uploadFile({
url: `${env.MP_API_BASE_URL}/file/upload`,
filePath: tempFilePath,
name: 'file',
formData: {
businessType,
accessLevel,
},
success: (res) => {
try {
const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
if (data && data.success) {
resolve(data.data);
} else {
resolve();
}
} catch (e) {
console.log('upload file parse error:', e);
resolve();
}
},
fail: (err) => {
console.log('upload file error:', err);
resolve();
},
});
});
}

View File

@ -112,7 +112,6 @@ onLoad((opts) => {
console.log("redirectUrl", redirectUrl.value); console.log("redirectUrl", redirectUrl.value);
return; return;
} }
// redirect useGuard redirectUrl
if (opts.redirect) { if (opts.redirect) {
redirectUrl.value = decodeURIComponent(opts.redirect); redirectUrl.value = decodeURIComponent(opts.redirect);
} else { } else {

View File

@ -1,45 +1,49 @@
<template> <template>
<view class="dept-page"> <view class="dept-page">
<view class="dept-container bg-white"> <view class="dept-container bg-white">
<scroll-view class="sidebar" scroll-y> <view class="sidebar">
<view <view class="sidebar-search">
v-for="item in level1List" <input
:key="item._id" v-model="keyword"
:class="[ class="search-input"
'sidebar-item', type="text"
item._id === selectedParentId ? 'active' : '', confirm-type="search"
]" placeholder="搜索一级科室"
@click="selectParent(item)" placeholder-class="search-placeholder"
> />
<text class="sidebar-text">{{ item.hlwDeptName }}</text> <view v-if="keyword" class="clear-btn" @click="keyword = ''"
>清空</view
>
</view> </view>
</scroll-view> <scroll-view class="sidebar-list" scroll-y>
<view
v-for="item in filteredLevel1List"
:key="item.deptId"
:class="[
'sidebar-item',
item.deptId === selectedParentId ? 'active' : '',
]"
@click="selectParent(item)"
>
<text class="sidebar-text">{{
item.hlwDeptName || item.deptName
}}</text>
</view>
</scroll-view>
</view>
<scroll-view class="content" scroll-y> <scroll-view class="content" scroll-y>
<view v-if="contentList.length" class="content-list"> <view v-if="contentList.length" class="content-list">
<view <view
v-for="child in contentList" v-for="child in contentList"
:key="child._id" :key="child.deptId"
:class="['dept-item', isSelected(child) ? 'selected' : '']" :class="['dept-item', isSelected(child) ? 'selected' : '']"
@click="selectDept(child)"
> >
<view class="dept-name-row" @click="selectDept(child)"> <view class="dept-name-row">
<view class="dept-name">{{ child.deptName }}</view> <view class="dept-name">{{ child.deptName }}</view>
<text v-if="isSelected(child)" class="check-tag">已选</text> <text v-if="isSelected(child)" class="check-tag">已选</text>
</view> </view>
<view
v-if="child.children && child.children.length"
class="dept-sub-list"
>
<view
v-for="sub in child.children"
:key="sub._id"
:class="['dept-sub-item', isSelected(sub) ? 'selected' : '']"
@click.stop="selectDept(sub)"
>
<text class="dept-sub-text">{{ sub.deptName }}</text>
<text v-if="isSelected(sub)" class="check-dot" />
</view>
</view>
</view> </view>
</view> </view>
<view v-else class="empty-wrapper"> <view v-else class="empty-wrapper">
@ -47,16 +51,20 @@
</view> </view>
</scroll-view> </scroll-view>
</view> </view>
<view class="footer-tip"> <view class="footer-tip">
如没有符合的内容请联系客服 如没有符合的内容请联系客服
<text class="link" @click="contactService">点击添加客服</text> <text class="link" @click="contactService">点击添加客服</text>
</view> </view>
</view> </view>
</template> </template>
<script setup> <script setup>
import { computed, ref } from "vue"; import { computed, ref, watch } from "vue";
import { onLoad } from "@dcloudio/uni-app"; import { onLoad } from "@dcloudio/uni-app";
import { storeToRefs } from "pinia";
import useGuard from "@/hooks/useGuard.js"; import useGuard from "@/hooks/useGuard.js";
import useAccountStore from "@/store/account.js";
import EmptyData from "@/components/empty-data.vue"; import EmptyData from "@/components/empty-data.vue";
import api from "@/utils/api"; import api from "@/utils/api";
import { toast } from "@/utils/widget"; import { toast } from "@/utils/widget";
@ -66,6 +74,7 @@ const { useLoad } = useGuard();
const deptList = ref([]); const deptList = ref([]);
const selectedParentId = ref(""); const selectedParentId = ref("");
const selectedDeptId = ref(""); const selectedDeptId = ref("");
const keyword = ref("");
const sorter = (a, b) => { const sorter = (a, b) => {
const sortA = typeof a.sort === "number" ? a.sort : 0; const sortA = typeof a.sort === "number" ? a.sort : 0;
@ -80,6 +89,14 @@ const level1List = computed(() =>
deptList.value.filter((i) => i.level === 1).sort(sorter) deptList.value.filter((i) => i.level === 1).sort(sorter)
); );
const filteredLevel1List = computed(() => {
const key = keyword.value.trim().toLowerCase();
if (!key) return level1List.value;
return level1List.value.filter((i) =>
i.deptName.toString().toLowerCase().includes(key)
);
});
const contentList = computed(() => { const contentList = computed(() => {
const children = deptList.value const children = deptList.value
.filter( .filter(
@ -87,31 +104,20 @@ const contentList = computed(() => {
i.level === 2 && i.parentId && i.parentId === selectedParentId.value i.level === 2 && i.parentId && i.parentId === selectedParentId.value
) )
.sort(sorter); .sort(sorter);
if (children.length === 0) { return children;
//
const currentParent = deptList.value.find(
(i) => i._id === selectedParentId.value
);
return currentParent ? [{ ...currentParent, children: [] }] : [];
}
const level3List = deptList.value.filter((i) => i.level === 3);
return children.map((item) => ({
...item,
children: level3List.filter((c) => c.parentId === item._id).sort(sorter),
}));
}); });
function isSelected(item) { function isSelected(item) {
const targetId = selectedDeptId.value; const targetId = selectedDeptId.value;
return !!targetId && (item.deptId === targetId || item._id === targetId); return !!targetId && (item.deptId === targetId || item.deptId === targetId);
} }
async function fetchDeptList() { async function fetchDeptList() {
uni.showLoading({ title: "加载中..." }); uni.showLoading({ title: "加载中..." });
try { try {
const res = await api("getDeptList"); const res = await api("getDeptList");
if (res && res.success && Array.isArray(res.data.list)) { if (res && res.success && Array.isArray(res.data)) {
deptList.value = res.data.list; deptList.value = res.data;
hydrateSelectedParent(); hydrateSelectedParent();
} else { } else {
toast(res?.message || "获取科室失败"); toast(res?.message || "获取科室失败");
@ -130,7 +136,7 @@ function hydrateSelectedParent() {
const match = const match =
deptList.value.find( deptList.value.find(
(i) => (i) =>
i._id === selectedDeptId.value || i.deptId === selectedDeptId.value i.deptId === selectedDeptId.value || i.deptId === selectedDeptId.value
) || null; ) || null;
if (match?.parentId) { if (match?.parentId) {
selectedParentId.value = match.parentId; selectedParentId.value = match.parentId;
@ -144,12 +150,25 @@ function hydrateSelectedParent() {
// //
const first = level1List.value[0]; const first = level1List.value[0];
if (first) { if (first) {
selectedParentId.value = first._id; selectedParentId.value = first.deptId;
} }
} }
watch(
filteredLevel1List,
(list) => {
if (!list.length) return;
const exists = list.some((i) => i.deptId === selectedParentId.value);
if (!exists) {
selectedParentId.value = list[0].deptId;
}
},
{ immediate: false }
);
function selectParent(item) { function selectParent(item) {
selectedParentId.value = item._id; selectedParentId.value = item.deptId;
console.log("selectedParentId", selectedParentId.value);
} }
function emitSelection(item) { function emitSelection(item) {
@ -158,14 +177,16 @@ function emitSelection(item) {
? getOpenerEventChannel() ? getOpenerEventChannel()
: null; : null;
eventChannel?.emit("deptSelected", { eventChannel?.emit("deptSelected", {
id: item._id, id: item.deptId,
deptId: item.deptId || item._id, deptId: item.deptId || item.deptId,
name: item.deptName, name: item.deptName,
level: item.level,
parentId: item.parentId,
}); });
} }
function selectDept(item) { function selectDept(item) {
selectedDeptId.value = item.deptId || item._id; selectedDeptId.value = item.deptId || item.deptId;
emitSelection(item); emitSelection(item);
uni.navigateBack(); uni.navigateBack();
} }
@ -211,9 +232,43 @@ onLoad((opts) => {
} }
.sidebar { .sidebar {
width: 210rpx; width: 230rpx;
background: #f8f9fb; background: #f8f9fb;
border-right: 1px solid #eef0f5; border-right: 1px solid #eef0f5;
display: flex;
flex-direction: column;
overflow-y: auto;
}
.sidebar-search {
padding: 16rpx 14rpx 12rpx;
display: flex;
align-items: center;
gap: 10rpx;
}
.search-input {
flex: 1;
height: 64rpx;
padding: 0 20rpx;
border-radius: 32rpx;
background: #fff;
border: 1px solid #e5e8ef;
font-size: 26rpx;
}
.search-placeholder {
color: #a0a8b8;
}
.clear-btn {
font-size: 24rpx;
color: #2a6ff2;
padding: 6rpx 10rpx;
}
.sidebar-list {
flex: 1;
} }
.sidebar-item { .sidebar-item {
@ -237,6 +292,13 @@ onLoad((opts) => {
line-height: 1.4; line-height: 1.4;
} }
.sidebar-empty {
padding: 40rpx 16rpx;
font-size: 24rpx;
color: #8c94a6;
text-align: center;
}
.content { .content {
flex: 1; flex: 1;
background: #fff; background: #fff;

View File

@ -3,17 +3,13 @@
<!-- 表单区域 --> <!-- 表单区域 -->
<view class="form-section bg-white"> <view class="form-section bg-white">
<!-- 姓名 --> <!-- 姓名 -->
<common-cell name="姓名" :required="true"> <form-input
<view class="form-content__wrapper"> name="姓名"
<input :required="true"
class="flex-main-content text-right" :form="formData"
v-model="formData.name" title="name"
placeholder="请输入" @change="handleFieldChange"
placeholder-style="color: #999" />
/>
<uni-icons class="form-arrow" type="arrowright"></uni-icons>
</view>
</common-cell>
<!-- 头像 --> <!-- 头像 -->
<common-cell name="头像"> <common-cell name="头像">
<view class="form-content__wrapper" @click="chooseAvatar"> <view class="form-content__wrapper" @click="chooseAvatar">
@ -24,9 +20,6 @@
:src="formData.avatar" :src="formData.avatar"
mode="aspectFill" mode="aspectFill"
/> />
<view v-else class="avatar-placeholder">
<text class="avatar-icon">👤</text>
</view>
</view> </view>
<uni-icons class="form-arrow" type="arrowright"></uni-icons> <uni-icons class="form-arrow" type="arrowright"></uni-icons>
</view> </view>
@ -39,14 +32,12 @@
:range="genderOptions" :range="genderOptions"
@change="handleFieldChange" @change="handleFieldChange"
/> />
<!-- 手机号不可修改 --> <!-- 手机号不可修改 -->
<common-cell name="手机号 (不可修改)"> <common-cell name="手机号 (不可修改)">
<view class="form-content__wrapper"> <view class="form-content__wrapper">
<view class="flex-main-content text-dark">{{ formData.phone }}</view> <view class="flex-main-content text-dark">{{ formData.mobile }}</view>
</view> </view>
</common-cell> </common-cell>
<!-- 岗位 --> <!-- 岗位 -->
<form-select <form-select
name="岗位" name="岗位"
@ -55,7 +46,6 @@
:range="positionOptions" :range="positionOptions"
@change="handleFieldChange" @change="handleFieldChange"
/> />
<!-- 职称 --> <!-- 职称 -->
<form-select <form-select
name="职称" name="职称"
@ -64,9 +54,8 @@
:range="titleOptions" :range="titleOptions"
@change="handleFieldChange" @change="handleFieldChange"
/> />
<!-- 科室 --> <!-- 科室 -->
<common-cell name="科室" :required="true"> <common-cell name="科室">
<view class="form-content__wrapper" @click="openDepartmentSelect"> <view class="form-content__wrapper" @click="openDepartmentSelect">
<view <view
class="flex-main-content text-right" class="flex-main-content text-right"
@ -78,6 +67,7 @@
</view> </view>
</common-cell> </common-cell>
<!-- 个人介绍 --> <!-- 个人介绍 -->
<form-textarea <form-textarea
name="个人介绍" name="个人介绍"
@ -87,7 +77,6 @@
@change="handleFieldChange" @change="handleFieldChange"
/> />
</view> </view>
<!-- 底部按钮 --> <!-- 底部按钮 -->
<view class="button-footer"> <view class="button-footer">
<view class="btn btn-cancel" @click="handleCancel">取消</view> <view class="btn btn-cancel" @click="handleCancel">取消</view>
@ -99,26 +88,22 @@
<script setup> <script setup>
import { ref } from "vue"; import { ref } from "vue";
import useGuard from "@/hooks/useGuard.js"; import useGuard from "@/hooks/useGuard.js";
import { import { chooseAndUploadImage } from "@/utils/file.js";
getDoctorInfoByAccountId,
updateDoctorInfo,
} from "@/api/doctor/doctor.js";
import { uploadFile } from "@/api/file.js";
import useAccountStore from "@/store/account.js"; import useAccountStore from "@/store/account.js";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import CommonCell from "@/components/form-template/common-cell.vue"; import CommonCell from "@/components/form-template/common-cell.vue";
import FormInput from "@/components/form-template/form-cell/form-input.vue";
import FormSelect from "@/components/form-template/form-cell/form-select.vue"; import FormSelect from "@/components/form-template/form-cell/form-select.vue";
import FormTextarea from "@/components/form-template/form-cell/form-textarea.vue"; import FormTextarea from "@/components/form-template/form-cell/form-textarea.vue";
import api from "@/utils/api.js";
const { account, mobile } = storeToRefs(useAccountStore()); const { account, doctorInfo } = storeToRefs(useAccountStore());
const { useLoad } = useGuard(); const { useLoad } = useGuard();
// //
const formData = ref({ const formData = ref({
name: "", name: "",
avatar: "", avatar: "",
gender: "", gender: "",
phone: "", mobile: "",
position: "", position: "",
title: "", title: "",
department: "", department: "",
@ -130,7 +115,7 @@ const formData = ref({
// //
const genderOptions = ["男", "女"]; const genderOptions = ["男", "女"];
const positionOptions = ["医生", "护士", "药师", "技师", "其他"]; const positionOptions = ["医生", "护士", "药师", "技师", "其他"];
const titleOptions = ["主任医师", "副主任医师", "主治医师", "住院医师", "其他"]; const titleOptions = ["主任医师", "副主任医师", "主治医师", "医师", "其他"];
// //
const handleFieldChange = (e) => { const handleFieldChange = (e) => {
@ -138,40 +123,17 @@ const handleFieldChange = (e) => {
}; };
// //
const chooseAvatar = () => { const chooseAvatar = async () => {
uni.chooseImage({ const uploadRes = await chooseAndUploadImage({
count: 1, businessType: "other",
sizeType: ["compressed"], accessLevel: "public",
sourceType: ["album", "camera"], loadingTitle: "上传中...",
success: async (res) => {
const tempFilePath = res.tempFilePaths[0];
uni.showLoading({
title: "上传中...",
});
try {
const uploadRes = await uploadFile(tempFilePath, "other", "public");
if (uploadRes && uploadRes.previewUrl) {
formData.value.avatar = uploadRes.previewUrl;
uni.hideLoading();
uni.showToast({
title: "上传成功",
icon: "success",
});
} else {
throw new Error("上传失败");
}
} catch (error) {
uni.hideLoading();
uni.showToast({
title: "上传失败",
icon: "none",
});
}
},
}); });
if (uploadRes?.previewUrl) {
formData.value.avatar = uploadRes.previewUrl;
}
}; };
//
const handleCancel = () => { const handleCancel = () => {
uni.navigateBack(); uni.navigateBack();
}; };
@ -186,98 +148,45 @@ const handleSave = async () => {
}); });
return; return;
} }
uni.showLoading({ uni.showLoading({
title: "保存中...", title: "保存中...",
}); });
try {
//
const result = await updateDoctorInfo({
name: formData.value.name,
avatar: formData.value.avatar,
gender: formData.value.gender,
position: formData.value.position,
title: formData.value.title,
departmentId: formData.value.departmentId,
department: formData.value.departmentName || formData.value.department,
intro: formData.value.intro,
});
uni.hideLoading();
if (result && result.success) {
uni.showToast({
title: "保存成功",
icon: "success",
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
} else {
uni.showToast({
title: result?.message || "保存失败",
icon: "none",
});
}
} catch (error) {
uni.hideLoading();
uni.showToast({
title: "保存失败",
icon: "none",
});
console.error("保存失败:", error);
}
};
//
const loadDoctorInfo = async () => {
if (!account.value?.id) {
return;
}
try {
uni.showLoading({
title: "加载中...",
});
const result = await getDoctorInfoByAccountId(account.value.id);
uni.hideLoading();
if (result && result.success && result.data) {
const doctorInfo = result.data;
formData.value = {
name: doctorInfo.name || "",
avatar: doctorInfo.avatar || "",
gender: doctorInfo.gender || "",
phone: doctorInfo.phone || mobile.value || "",
position: doctorInfo.position || "",
title: doctorInfo.title || "",
department: doctorInfo.department || "",
departmentName: doctorInfo.department || "",
departmentId: doctorInfo.departmentId || "",
intro: doctorInfo.intro || "",
};
} else {
//
if (mobile.value) {
formData.value.phone = mobile.value;
}
}
} catch (error) {
uni.hideLoading();
console.error("加载医生信息失败:", error);
}
}; };
useLoad(() => { useLoad(() => {
// debugger
if (mobile.value && !formData.value.phone) { if (doctorInfo.value) {
formData.value.phone = mobile.value; formData.value = doctorInfo.value;
} else {
formData.value.mobile = account.value.mobile;
} }
loadDoctorInfo();
}); });
//
const createDoctorInfo = async () => {
let params = {
anotherName: formData.value.name,
avatar: formData.value.avatar,
gender: formData.value.gender,
mobile: formData.value.mobile,
deptIds: [],
};
const res = await api("addCorpMember", {
params,
});
if (res.success && res.data) {
uni.showToast({
title: "创建成功",
icon: "success",
});
} else {
uni.showToast({
title: "创建失败",
icon: "none",
});
console.error("创建医生信息失败:", res);
}
};
// //
const openDepartmentSelect = () => { const openDepartmentSelect = () => {
uni.navigateTo({ uni.navigateTo({

View File

@ -1,20 +1,20 @@
export default [ export default [
{ {
path: 'pages/message/message', path: 'pages/message/message',
meta: { title: '首页', login: true }, meta: { title: '首页', login: false },
style: { navigationStyle: 'custom' } style: { navigationStyle: 'custom' }
}, },
{ {
path: 'pages/work/work', path: 'pages/work/work',
meta: { title: '工作台', login: true } meta: { title: '工作台', login: false }
}, },
{ {
path: 'pages/work/profile', path: 'pages/work/profile',
meta: { title: '完善个人信息', login: true } meta: { title: '完善个人信息', login: false }
}, },
{ {
path: 'pages/work/department-select', path: 'pages/work/department-select',
meta: { title: '选择科室', login: true } meta: { title: '选择科室', login: false }
} }
] ]

View File

@ -12,14 +12,14 @@ export default defineStore("accountStore", () => {
const loading = ref(false) const loading = ref(false)
// IM 相关 // IM 相关
const openid = ref(""); const openid = ref("");
const isIMInitialized = ref(false); // 医生信息
// 手机号(从授权登录获取) const doctorInfo = ref(null);
const mobile = ref(uni.getStorageSync('user_mobile') || '');
async function login(phoneCode = '') { async function login(phoneCode = '') {
debugger
if (loading.value) return; if (loading.value) return;
loading.value = true; loading.value = true;
try { try {
const { code } = await uni.login({ const { code } = await uni.login({
appid, appid,
@ -32,18 +32,23 @@ export default defineStore("accountStore", () => {
phoneCode, phoneCode,
code, code,
}); });
loading.value = false loading.value = false;
console.log(res) if (res.success && res.data) {
if (res.success && res.data && res.data.mobile) { if (!res.data.mobile) {
account.value = res.data; const pages = getCurrentPages();
openid.value = res.data.openid || res.data.openId || res.data.userId || ""; const current = pages[pages.length - 1];
isIMInitialized.value = false; const params = current && current.options
clearInitIMPromise(); ? Object.keys(current.options).map(key => `${key}=${current.options[key]}`).join('&')
// 存储手机号 : '';
if (res.data.mobile) { const redirectUrl = current && current.route ? `/${current.route}${params ? `?${params}` : ''}` : '';
mobile.value = res.data.mobile; const target = redirectUrl ? `/pages/login/login?redirect=${encodeURIComponent(redirectUrl)}` : '/pages/login/login';
uni.setStorageSync('user_mobile', res.data.mobile); uni.redirectTo({ url: target });
return;
} }
account.value = res.data;
openid.value = res.data.openid;
debugger;
await getDoctorInfo(openid.value);
return res.data return res.data
} }
} }
@ -54,25 +59,18 @@ export default defineStore("accountStore", () => {
loading.value = false loading.value = false
} }
/** async function getDoctorInfo(weChatOpenId) {
* 登录后初始化 IM供聊天页调用
* @param {string} userID - IM userID通常用 openid/openId
*/
async function initIMAfterLogin(userID) {
const uid = userID || openid.value;
if (!uid) {
toast("缺少 openid无法初始化IM");
return false;
}
try { try {
await getInitIMPromise(uid, true); const res = await api('getCorpMemberData', {
isIMInitialized.value = true; weChatOpenId,
return true; });
if (res.success && res.data) {
doctorInfo.value = res.data;
}
} catch (e) { } catch (e) {
isIMInitialized.value = false; console.error('获取医生信息失败:', e);
return false;
} }
} }
return { account, openid, isIMInitialized, mobile, login, initIMAfterLogin } return { account, openid, doctorInfo, login }
}) })

View File

@ -7,7 +7,10 @@ const urlsConfig = {
getTeamData: 'getTeamData', getTeamData: 'getTeamData',
queryWxJoinedTeams: 'queryWxJoinedTeams', queryWxJoinedTeams: 'queryWxJoinedTeams',
wxAppLogin: 'wxAppLogin', wxAppLogin: 'wxAppLogin',
getDeptList:'getHlwDeptList', getDeptList: 'getRealDeptList',
getHospitalList: 'getRealHospital',
addCorpMember: 'addCorpMember',
getCorpMemberData: 'getCorpMemberData'
}, },
knowledgeBase: { knowledgeBase: {
@ -25,6 +28,7 @@ const urlsConfig = {
wecom: { wecom: {
addContactWay: 'addContactWay' addContactWay: 'addContactWay'
} }
} }
const urls = Object.keys(urlsConfig).reduce((acc, path) => { const urls = Object.keys(urlsConfig).reduce((acc, path) => {
const config = urlsConfig[path] || {}; const config = urlsConfig[path] || {};
@ -55,3 +59,4 @@ export default async function api(urlId, data) {
} }
}) })
} }

75
utils/file.js Normal file
View File

@ -0,0 +1,75 @@
const env = __VITE_ENV__;
export async function uploadFile(tempFilePath, businessType, accessLevel = 'public') {
try {
const res = await new Promise((resolve, reject) => {
uni.uploadFile({
url: `${env.MP_API_BASE_URL}/upload`,
filePath: tempFilePath,
name: 'file',
formData: { businessType, accessLevel },
success: (resp) => resolve(resp),
fail: (err) => reject(err),
});
});
const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
if (data && data.success) {
return data.data;
}
} catch (e) {
console.log('upload file error:', e);
}
return undefined;
}
/**
* 选择单张图片并上传成功返回上传结果接口返回的 data.data
* - 内部会处理 loading / toast
* - 失败或取消返回 null
*/
export async function chooseAndUploadImage(options = {}) {
const {
count = 1,
sizeType = ['compressed'],
sourceType = ['album', 'camera'],
businessType = 'other',
accessLevel = 'public',
loadingTitle = '上传中...',
successToast = '上传成功',
failToast = '上传失败',
} = options;
const imageResult = await new Promise((resolve) => {
uni.chooseImage({
count,
sizeType,
sourceType,
success: (res) => resolve(res),
fail: () => resolve(null),
});
});
const tempFilePath = imageResult?.tempFilePaths?.[0];
if (!tempFilePath) {
return null;
}
uni.showLoading({ title: loadingTitle });
try {
const uploadRes = await uploadFile(tempFilePath, businessType, accessLevel);
uni.hideLoading();
if (uploadRes) {
uni.showToast({ title: successToast, icon: 'success' });
return uploadRes;
}
uni.showToast({ title: failToast, icon: 'none' });
return null;
} catch (e) {
uni.hideLoading();
uni.showToast({ title: failToast, icon: 'none' });
return null;
}
}