患者相关文件

This commit is contained in:
xiaoxiao 2025-07-11 17:15:50 +08:00
parent 971cd1c332
commit 4df444ed0e
17 changed files with 1159 additions and 69 deletions

View File

@ -84,3 +84,5 @@ export { PhotoGrids } from './src/main/ets/components/PhotoGrids'
export { ViewImageInfo } from './src/main/ets/models/ViewImageInfo'
export { ChangePhotoGrids } from './src/main/ets/components/ChangePhotoGrids'
export { InputPopWindow } from './src/main/ets/Views/InputPopWindow'

View File

@ -0,0 +1,53 @@
@CustomDialog
export struct InputPopWindow {
controller: CustomDialogController; // 控制器(必须)
@Prop title: string; // 提示语(从父组件传入)
@Link inputValue: string; // 输入框值(双向绑定)
// 按钮回调函数(通过构造函数传入)
private cancel?: () => void;
private confirm?: (value: string) => void;
build() {
Column() {
// 提示语标题
Text(this.title)
.fontSize(18)
.margin({ top: 20, bottom: 15 });
// 输入框
TextInput({ placeholder: '请输入内容', text: this.inputValue })
.width('90%')
.height(50)
.onChange((value: string) => {
this.inputValue = value; // 双向绑定更新值
})
.border({ width: 1, color: '#CCCCCC' })
.borderRadius(4)
// 按钮行(取消 + 确定)
Flex({ justifyContent: FlexAlign.SpaceAround }) {
Button('取消')
.backgroundColor('#FFFFFF')
.fontColor('#666666')
.onClick(() => {
this.cancel?.(); // 触发取消回调
this.controller.close(); // 关闭弹窗
})
Button('确定')
.backgroundColor('#317AFF')
.fontColor('#FFFFFF')
.onClick(() => {
this.confirm?.(this.inputValue); // 传递输入值给父组件
this.controller.close();
})
}
.margin({ top: 20, bottom: 10 })
.width('100%')
}
.width('100%')
.padding(10)
.borderRadius(16) // 圆角弹窗
}
}

View File

@ -40,6 +40,7 @@ export class BasicConstant {
static readonly patientCard = BasicConstant.urlExpertAPI+'patientCard'
static readonly toAddNickname = BasicConstant.urlExpert+'toAddNickname'
static readonly patientDetail = BasicConstant.urlExpert+'patientDetail'
static readonly GroupList = BasicConstant.urlExpertApp+'GroupList'
static readonly getStartpage=BasicConstant.urlExpertApp + "startpage";
static readonly meetingListV2=BasicConstant.urlExpertAPI + "meetingListV2";
static readonly meetingV2Video=BasicConstant.urlExpertAPI + "meetingV2Video";

View File

@ -15,3 +15,5 @@ export { BuildOrEditGroupPage } from './src/main/ets/components/BuildOrEditGroup
export { PatientsListComp } from './src/main/ets/components/PatientsListComp'
export { PatientDetailsComp } from './src/main/ets/components/PatientDetailsComp'
export { GroupManagementComp } from './src/main/ets/components/GroupManagementComp'

View File

@ -0,0 +1,231 @@
import { BasicConstant, hdHttp, HdResponse, authStore, HdNav , HdLoadingDialog, ChangeUtil,DefaultHintProWindows,InputPopWindow } from '@itcast/basic/Index'
import { promptAction, router } from '@kit.ArkUI'
import { groupModel } from '../models/PatientsGroupModel'
import HashMap from '@ohos.util.HashMap';
import { BusinessError } from '@kit.BasicServicesKit';
@Component
export struct GroupManagementComp {
@State params:Record<string, string> = router.getParams() as Record<string, string>
private hintWindowDialog!: CustomDialogController
@State dialogInputValue: string = ''; // 绑定输入框的值
// InputPopWindow: CustomDialogController; // 控制器实例
@State groupList: Array<Record<string,string>> = []
@State selectedGroups: Array<Record<string,string>> = []
dialog: CustomDialogController = new CustomDialogController({
builder: HdLoadingDialog({ message: '加载中...' }),
customStyle: true,
alignment: DialogAlignment.Center
})
private hintPopWindowDialog() {
this.hintWindowDialog = new CustomDialogController({
builder:DefaultHintProWindows({
controller:this.hintWindowDialog,
message:'最多只能选三项',
confirmTitleColor: '#333333',
selectedButton: (index:number)=>{
this.hintWindowDialog.close();
}
}),
alignment: DialogAlignment.Center,
cornerRadius:24,
backgroundColor: ('rgba(0,0,0,0.5)'),
})
}
aboutToAppear() {
this.hintPopWindowDialog()
this.addShowTagAction()
this.fetchGroups()
}
addShowTagAction() {
if (!ChangeUtil.stringIsUndefinedAndNull(this.params.groupNames)) {
this.selectedGroups = convertToRecordArray(String(this.params.groupNames),String(this.params.groupUuids))
}
}
fetchGroups() {
this.dialog.open()
hdHttp.post<string>(BasicConstant.GroupList, {
"expert_uuid": authStore.getUser().uuid
} as Record<string,string>).then(async (res: HdResponse<string>) => {
this.dialog.close();
let json:Record<string,string | Array<Record<string,string>>> = JSON.parse(res+'') as Record<string,string | Array<Record<string,string>>>
if(json.code == '1') {
console.info('上一层传过来的group:',this.params.groupNames)
// groupUuids
this.groupList = json.data as Array<Record<string,string>>
} else {
console.error('获取分组列表信息失败:'+json.message)
promptAction.showToast({ message: String(json.message), duration: 1000 })
}
}).catch((err: BusinessError) => {
this.dialog.close();
console.error(`Response fails: ${err}`);
})
}
onSelectGroup(item: Record<string,string>) {
const idx = this.selectedGroups.findIndex(g => g.uuid === item.uuid)
if (idx > -1) {
this.selectedGroups.splice(idx, 1)
this.selectedGroups = [...this.selectedGroups]
} else {
if (this.selectedGroups.length >= 3) {
this.hintWindowDialog.open()
return
}
this.selectedGroups.push(item)
this.selectedGroups = [...this.selectedGroups]
}
}
getSelected(item:Record<string,string>):boolean {
let selected = this.selectedGroups.some(g => g.uuid === item.uuid)
return selected
}
isChangeColor(showItem:Record<string,string>):boolean {
return this.selectedGroups.some(item=>`${item.uuid}` === `${showItem.uuid}`)
}
onAddGroup() {
}
confirmAddGroup() {
// if (!this.newGroupName.trim()) {
// promptAction.showToast({ message: '请输入分组名', duration: 1000 })
// return
// }
// if (this.newGroupName.length > 10) {
// promptAction.showToast({ message: '最多10个字符', duration: 1000 })
// return
// }
// this.addLoading = true
// const params = {
// expert_uuid: authStore.getUser().uuid,
// name: this.newGroupName.trim(),
// patient_uuid: ''
// }
// hdHttp.post<string>(BasicConstant.addGroup, params).then(async (res: HdResponse<string>) => {
// this.addLoading = false
// let json = JSON.parse(res + '') as { code: number, message: string }
// if (json.code === 1) {
// this.showAddDialog = false
// this.addDialog.close()
// this.fetchGroups()
// promptAction.showToast({ message: '添加成功', duration: 1000 })
// } else {
// promptAction.showToast({ message: json.message, duration: 1000 })
// }
// }).catch(() => {
// this.addLoading = false
// })
}
build() {
Column() {
HdNav({ title: '分组管理', showRightIcon: false, hasBorder: true, rightText: '保存', showRightText: true })
// 已选分组
Row() {
Flex({ justifyContent: FlexAlign.Start, wrap: FlexWrap.Wrap }) {
ForEach(this.selectedGroups, (item: Record<string, string>) => {
Text(item.name)
.border({ width: 1, color: $r('app.color.main_color') })
.fontColor($r('app.color.main_color'))
.fontSize(16)
.borderRadius(10)
.padding({
left: 10,
right: 10,
top: 5,
bottom: 5
})
.margin({ right: 10 })
.borderRadius(20)
})
}
}
.alignItems(VerticalAlign.Top)
.justifyContent(FlexAlign.Start)
.width('100%').height(100)
.backgroundColor(Color.White)
.padding({left:10,top:10,right:10})
Column() {
Row() {
Text('所有分组').fontColor($r('app.color.main_color')).fontSize(15)
Text('(选择或取消分组)').fontColor('#999999').fontSize(15)
}
.margin({ left: 10, top: 10 })
.justifyContent(FlexAlign.Start)
Row() {
Flex({ justifyContent: FlexAlign.Start, wrap: FlexWrap.Wrap }) {
ForEach(this.groupList, (item: Record<string, string>) => {
Text(item.name)
.border({ width: 1, color: this.getSelected(item)||this.isChangeColor(item) ? '#981308' : '#999999' })
.fontColor(this.getSelected(item)||this.isChangeColor(item) ? '#981308' : '#999999')
.fontSize(16)
.padding({
left: 10,
right: 10,
top: 5,
bottom: 5
})
.margin({ left: 10, top: 10})
.borderRadius(10)
.textAlign(TextAlign.Center)
.backgroundColor(Color.White)
.onClick(() => this.onSelectGroup(item))
})
}
}
.justifyContent(FlexAlign.Start)
}
.width('100%')
.height('40%')
.alignItems(HorizontalAlign.Start)
.justifyContent(FlexAlign.Start)
.backgroundColor(Color.White)
.margin({top:10})
.layoutWeight(1)
Text('添加分组')
.width('100%')
.height(50)
.fontSize(20)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.backgroundColor('#3CC7C0')
.margin({bottom:10})
.onClick(()=>this.onAddGroup())
}
.height('100%')
.backgroundColor('#f4f4f4')
.justifyContent(FlexAlign.Start)
.alignItems(HorizontalAlign.Start)
}
}
export function convertToRecordArray(
namesStr: string,
uuidsStr: string
): Record<string, string>[] {
const names: string[] = namesStr.split(",").map(item => item.trim());
const uuids: string[] = uuidsStr.split(",").map(item => item.trim());
const maxLength: number = Math.max(names.length, uuids.length);
const result: Record<string, string>[] = [];
for (let i = 0; i < maxLength; i++) {
const name: string = i < names.length ? names[i] : "";
const uuid: string = i < uuids.length ? uuids[i] : "";
const newObject:Record<string,string> = {"name":name,"uuid":uuid} as Record<string,string>
result.push(newObject);
}
return result;
}

View File

@ -181,7 +181,7 @@ export struct PatientApplyPage {
.onClick(()=>{
router.pushUrl({
url: 'pages/WebView/WebPage', // 目标url
params: {url:BasicConstant.wxUrl+'expert/expertcodeimg?expert_uuid='+authStore.getUser().uuid,title:'我的二维码'}
params: {url:BasicConstant.wxUrl+'expert/expertcodeimg?expert_uuid='+authStore.getUser().uuid,title:'随访二维码'}
})
})
}.width('100%').height(56).backgroundColor(Color.White).alignItems(VerticalAlign.Top)

View File

@ -1,11 +1,13 @@
import { authStore, HdNav, PositionSelectedSheet } from '@itcast/basic';
import { promptAction, router } from '@kit.ArkUI'
import { HdLoadingDialog,DefaultHintProWindows } from '@itcast/basic'
import { authStore, ChangeUtil, HdNav } from '@itcast/basic';
import { LevelMode, promptAction, router } from '@kit.ArkUI'
import { HdLoadingDialog } from '@itcast/basic'
import { BasicConstant,hdHttp, HdResponse ,logger} from '@itcast/basic/Index'
import { BusinessError } from '@kit.BasicServicesKit';
import HashMap from '@ohos.util.HashMap';
import { patientListModel } from '../models/PatientsGroupModel'
import measure from '@ohos.measure';
import { TextSectionAttribute,LastSpanAttribute } from '../utils/Models'
import { applyListModel } from '../models/ApplyModel'
import { TextExpandView } from '../views/TextExpandView'
import call from '@ohos.telephony.call'
@Component
export struct PatientDetailsComp {
@ -16,10 +18,13 @@ export struct PatientDetailsComp {
@State footerArray:Array<Record<string,string | ResourceStr>> = []
@State patientCase:Array<Record<string,string>> = []
@State patientData:Record<string,string> = {}
@State patientData2:Record<string,string> = {}
@State medicalHistoryContent:string = ''
@State isExpanded: boolean = false; // 展开状态
@State showExpandBtn: boolean = false; // 是否显示操作按钮
@State patientData2:applyListModel = {}
@State patientGroupData:Record<string,string> = {}
@State patientAge:string = ''
@State nationName:string = ''
@State addresString:string = ''
@State medicalHistoryContent:TextSectionAttribute = new TextSectionAttribute()
@State lastSpanAttribute:LastSpanAttribute = new LastSpanAttribute(0, 7, ['展开全部', '收起'], 15, $r('app.color.main_color'))
dialog: CustomDialogController = new CustomDialogController({
builder: HdLoadingDialog({ message: '加载中...' }),
@ -50,6 +55,7 @@ export struct PatientDetailsComp {
router.back()
} else {
if(json.code == '200') {
this.patientGroupData = json
this.getPatientDetailsData(String(json.group["name"]))
} else {
console.error('患者详情请求失败:'+json.message)
@ -71,16 +77,8 @@ export struct PatientDetailsComp {
logger.info('Response toAddNickname'+res);
let json:Record<string,string | Record<string,string>> = JSON.parse(res+'') as Record<string,string | Record<string,string>>;
if(json.code == '1') {
this.getPatientData()
this.patientData = json.patientEx as Record<string,string>
let nickname = this.patientData.nickname
let note = this.patientData.note
let mobile = this.patientData.mobile
if (nickname.length>0) {
this.groupArray = [{"title":"备注","content":String(nickname),"prompt":"给患者添加备注名"},{"title":"分组","content":String(groupType),"prompt":"通过分组给患者分类"},{"title":"描述","content":String(note),"prompt":"补充患者关键信息,方便随访患者"},{"title":"电话号码","content":String(mobile),"prompt":""}]
} else {
this.groupArray = [{"title":"分组","content":String(groupType),"prompt":"通过分组给患者分类"},{"title":"描述","content":String(note),"prompt":"补充患者关键信息,方便随访患者"},{"title":"电话号码","content":String(mobile),"prompt":""}]
}
this.getPatientData(groupType)
} else {
console.error('获取患者信息失败:'+json.message)
promptAction.showToast({ message: String(json.message), duration: 1000 })
@ -91,7 +89,7 @@ export struct PatientDetailsComp {
})
}
getPatientData() {
getPatientData(groupType:string) {
this.dialog.open()
hdHttp.post<string>(BasicConstant.patientDetail, {
"patientUuid":String(this.params.patient_uuid)
@ -100,9 +98,46 @@ export struct PatientDetailsComp {
logger.info('Response patientDetail'+res);
let json:Record<string,string | Record<string,string> | Array<Record<string,string>>> = JSON.parse(res+'') as Record<string,string | Record<string,string> | Array<Record<string,string>>>;
if(json.code == '1') {
this.patientData2 = json.data as Record<string,string>
this.medicalHistoryContent = String(json.medicalHistoryContent)
this.patientData2 = json.data as applyListModel
this.patientData2.note = String(this.patientGroupData.group["note"])
this.patientData2.nickname = String(this.patientData.nickname)
this.patientData2.groupType = String(this.patientGroupData.group["name"])
this.patientData2.groupUuid = String(this.patientGroupData.group["uuid"])
this.patientData2.patientUuid = String(this.patientData2.uuid)
if (String(json.age) == '0') {
this.patientAge = '0'
} else if (String(json.age) == '-1') {
this.patientAge = '未知'
} else {
this.patientAge = String(json.age)
}
if (ChangeUtil.stringIsUndefinedAndNull(String(this.patientData2.nationName))) {
this.nationName = '未知'
} else {
this.nationName = String(this.patientData2.nationName)
}
let countyName = ChangeUtil.stringIsUndefinedAndNull(String(this.patientData2.countyName))?'':String(this.patientData2.countyName)
let provName = ChangeUtil.stringIsUndefinedAndNull(String(this.patientData2.provName))?'':String(this.patientData2.provName)
let cityName = ChangeUtil.stringIsUndefinedAndNull(String(this.patientData2.cityName))?'':String(this.patientData2.cityName)
if (String(countyName) == String(cityName)) {
this.addresString = String(provName)+String(cityName)
} else {
this.addresString = String(provName)+String(provName)+String(countyName)
}
if (this.addresString.length <= 0) {
this.addresString = '未知'
}
let content = ChangeUtil.stringIsUndefinedAndNull(String(json.medicalHistoryContent))?'':String(json.medicalHistoryContent)
this.medicalHistoryContent = new TextSectionAttribute(content, 2, '#666666', 15, 16, 350)
this.patientCase = json.patientCase as Array<Record<string,string>>
let nickname = this.patientData.nickname
let note = this.patientData.note
let mobile = this.patientData2.mobile
if (ChangeUtil.stringIsUndefinedAndNull(String(nickname))) {
this.groupArray = [{"title":"备注","content":String(nickname),"prompt":"给患者添加备注名"},{"title":"分组","content":String(groupType),"prompt":"通过分组给患者分类"},{"title":"描述","content":String(note),"prompt":"补充患者关键信息,方便随访患者"},{"title":"电话号码","content":String(mobile),"prompt":""}]
} else {
this.groupArray = [{"title":"分组","content":String(groupType),"prompt":"通过分组给患者分类"},{"title":"描述","content":String(note),"prompt":"补充患者关键信息,方便随访患者"},{"title":"电话号码","content":String(mobile),"prompt":""}]
}
} else {
console.error('获取患者信息失败:'+json.message)
promptAction.showToast({ message: String(json.message), duration: 1000 })
@ -130,16 +165,125 @@ export struct PatientDetailsComp {
}
})
Scroll(this.scroller){
this.historyView()
this.footerView()
}.width('100%').height('calc(100% - 56vp)').backgroundColor('#f4f4f4')
Column(){
this.patientsView()
this.otherMsgView()
this.historyView()
if (this.patientCase.length > 0) {
this.patientCaseView()
}
this.footerView()
}
.width('100%')
.justifyContent(FlexAlign.Start)
}
.width('100%')
.height('calc(100% - 56vp)')
.backgroundColor('#f4f4f4')
.scrollBar(BarState.Off)
.align(Alignment.TopStart)
}
.width('100%').height('100%')
}
.height('100%')
}
@Builder
patientsView(){
Row(){
Image(BasicConstant.urlImage+this.patientData2.photo)
.alt($r('app.media.userPhoto_default'))
.width(60)
.height(60)
.borderRadius(6)
.margin({left:15,top:15})
Column(){
Row({space:5}){
Text(ChangeUtil.stringIsUndefinedAndNull(this.patientData.nickname)?this.patientData2.realName:this.patientData.nickname)
.fontSize(18)
.fontColor('#333333')
Image(String(this.patientData2.sex) == '0'?$r('app.media.patient_details_man'):$r('app.media.patient_details_wuman'))
.width(18)
.height(18)
}
.margin({top:15})
.justifyContent(FlexAlign.Start)
.alignItems(VerticalAlign.Top)
if (!ChangeUtil.stringIsUndefinedAndNull(this.patientData.nickname)) {
Text('昵称:'+String(this.patientData2.realName))
.fontSize(14)
.fontColor('#666666')
.margin({top:8})
}
Text('年龄:'+this.patientAge+' | '+'民族:'+this.nationName)
.fontSize(14)
.fontColor('#666666')
.margin({top:8})
Text('城区:'+this.addresString)
.fontSize(14)
.fontColor('#666666')
.margin({top:8,bottom:30})
}
.margin({left:15})
.justifyContent(FlexAlign.Start)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.backgroundColor(Color.White)
.justifyContent(FlexAlign.Start)
.alignItems(VerticalAlign.Top)
.margin({bottom:15})
}
@Builder
otherMsgView(){
List(){
ForEach(this.groupArray,(item:Record<string,string>)=>{
ListItem(){
Row() {
Text(item.title)
.fontSize(15)
.fontColor('#333333')
.margin({left:15})
.layoutWeight(1)
Text(ChangeUtil.stringIsUndefinedAndNull(item.content)?item.prompt:item.content)
.fontSize(15)
.fontColor(!ChangeUtil.stringIsUndefinedAndNull(item.content)&&item.title == '电话号码'?$r('app.color.main_color'):'#666666')
.textAlign(TextAlign.End)
.margin({right:5})
.maxLines(1)
.textOverflow({overflow:TextOverflow.Ellipsis})
.width('calc(100% - 100vp)')
Image($r('app.media.course_invoice_to_details'))
.width(6)
.height(10)
.margin({right:10})
.visibility(item.title == '电话号码'?Visibility.Hidden:Visibility.Visible)
}
.width('100%')
.height(49)
.backgroundColor(Color.White)
.onClick(()=>{
if (item.title == '电话号码') {
if (call.hasVoiceCapability() && !ChangeUtil.stringIsUndefinedAndNull(item.content)) {
call.makeCall(item.content, (err) => {
if (err) console.error("拨号失败:" + JSON.stringify(err));
});
}
} else {
router.pushUrl({
url:'pages/PatientsPage/PatientMsgSetPage',
params:{"model":this.patientData2}
})
}
})
}.width('100%').height(50)
})
}
.width('100%')
.margin({bottom:15})
}
@Builder
historyView(){
Column({space:10}){
@ -147,33 +291,60 @@ export struct PatientDetailsComp {
.fontSize(15)
.fontColor('#333333')
Text(this.medicalHistoryContent)
.fontSize(16)
.lineHeight(22)
.maxLines(this.isExpanded ? 0 : 2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.onAreaChange((_, area) => {
let fullHeight = measure.measureTextSize({
textContent: this.medicalHistoryContent,
fontSize: 15,
maxLines:2
}).height;
this.showExpandBtn = Number(area.height) < Number(fullHeight);
if (this.medicalHistoryContent.title) {
TextExpandView({
textSectionAttribute: this.medicalHistoryContent,
lastSpanAttribute: this.lastSpanAttribute
})
// 操作按钮(独立可点击区域)
if (this.showExpandBtn) {
Text(this.isExpanded ? "...收起" : "..展开全部")
.fontSize(15)
.fontColor($r('app.color.main_color')) // 红色标识可点击
.onClick(() => {
this.isExpanded = !this.isExpanded; // 切换状态
})
} else {
Text('暂无')
.fontSize(14)
.fontColor('#666666')
.width('100%')
.textAlign(TextAlign.Start)
}
}
.alignItems(HorizontalAlign.Start)
.backgroundColor(Color.White)
.width('100%')
.padding(15)
.margin({bottom:15})
}
@Builder
patientCaseView(){
Column(){
Text('检查报告')
.fontSize(15)
.fontColor('#333333')
.margin({bottom:10})
Grid(){
ForEach(this.patientCase,(item:Record<string,string>)=>{
GridItem(){
Column(){
Text(String(item.createDate).substring(0,10))
.fontSize(15)
.fontColor('#999999')
Text(String(item.diseaseName))
.fontSize(15)
.fontColor('#333333')
.margin({top:10})
}
.justifyContent(FlexAlign.Center)
.width(130)
.height(70)
.backgroundColor('#f4f4f4')
.borderRadius(3)
}
})
}
}
.alignItems(HorizontalAlign.Start)
.backgroundColor(Color.White)
.width('100%')
.padding(15)
.margin({bottom:15})
}
@Builder
@ -186,6 +357,7 @@ export struct PatientDetailsComp {
.width(20).height(20)
Text(item.title)
.fontSize(15)
.fontColor('#333333')
}
.height(49)
.width('100%')

View File

@ -1,4 +1,4 @@
import { authStore, HdNav } from '@itcast/basic';
import { authStore, ChangeUtil, HdNav } from '@itcast/basic';
import { applyListModel } from '../models/ApplyModel'
import HashMap from '@ohos.util.HashMap';
import { HdLoadingDialog } from '@itcast/basic'
@ -20,14 +20,14 @@ interface paramsCallData {
@Component
export struct PatientSetMsgPage {
@State params:paramsCallData = router.getParams() as paramsCallData;
@State noteName: string | undefined = '';
@State contentFrist:string | undefined = '';
@State groupName: string = '通过分组给患者分类';
@State maxDescribe:string = '0';
@State descibe:string = '';
@State isNote:boolean = false;
@State isDescibe:boolean = false;
@State params:paramsCallData = router.getParams() as paramsCallData
@State noteName: string | undefined = ChangeUtil.stringIsUndefinedAndNull(this.params.model.nickname)?'':String(this.params.model.nickname)
@State contentFrist:string | undefined = ''
@State groupName: string = ChangeUtil.stringIsUndefinedAndNull(this.params.model.groupType)?'通过分组给患者分类':String(this.params.model.groupType)
@State maxDescribe:string = '0'
@State descibe:string = ChangeUtil.stringIsUndefinedAndNull(this.params.model.note)?'':String(this.params.model.note)
@State isNote:boolean = false
@State isDescibe:boolean = false
dialog: CustomDialogController = new CustomDialogController({
builder: HdLoadingDialog({ message: '加载中...' }),
@ -108,16 +108,18 @@ export struct PatientSetMsgPage {
}
})
Row() {
Text('申请消息为:'+this.contentFrist)
.fontSize(15).fontColor('#666666')
Text('填入')
.fontSize(15).fontColor('#3CC7C0')
}.justifyContent(FlexAlign.Start).margin({left:15,top:5})
// .visibility(this.contentFrist?Visibility.Visible:Visibility.Hidden)
.onClick(()=>{
if (!ChangeUtil.stringIsUndefinedAndNull(this.contentFrist)) {
Row() {
Text('申请消息为:'+this.contentFrist)
.fontSize(15).fontColor('#666666')
Text('填入')
.fontSize(15).fontColor('#3CC7C0')
}.justifyContent(FlexAlign.Start).margin({left:15,top:5})
// .visibility(this.contentFrist?Visibility.Visible:Visibility.Hidden)
.onClick(()=>{
this.noteName = this.contentFrist?.substring(2);
})
})
}
Text('分组')
.margin({left:15,top:15})
@ -137,7 +139,10 @@ export struct PatientSetMsgPage {
.borderRadius(4)
.margin({left:15,top:10})
.onClick(()=>{
router.pushUrl({
url:'pages/PatientsPage/GroupManagementPage',
params:{groupNames:this.params.model.groupType,groupUuids:this.params.model.groupUuid}
})
})
Text('描述')
@ -145,7 +150,7 @@ export struct PatientSetMsgPage {
.fontSize(15).fontColor('#333333')
Column() {
TextArea({ placeholder: '补充患者关键信息,方便随访患者' })
TextArea({ placeholder: '补充患者关键信息,方便随访患者',text:this.descibe })
.fontSize(15).placeholderColor('#999999')
.fontColor('#333333')
.backgroundColor('#f4f4f4')

View File

@ -25,6 +25,15 @@ export class applyListModel {
expertUuid?:string;
status?:number;
content?:string;
nationName?:string;
provName?:string;
cityName?:string;
countyName?:string;
nation?:string;
note?:string
nickname?:string
groupType?:string
groupUuid?:string
}
export interface historyObjectModel {

View File

@ -0,0 +1,218 @@
/*
* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// [Start RichTextModel]
export class RichTextModel {
// All the text
text: string = '';
// Array of information
textArray: RichTextContentModel[] = [];
// fontSize
fontSize: number = 16;
}
export class RichTextContentModel {
// The index value of a single piece of information throughout the text
index: number = 0;
// The length of a single text (the agreed length in the case of an image)
length: number = 0;
// The type of text
type: string = '';
// images
images: string[] = [];
// content
content: string = '';
// Hyperlinks
link: string = '';
// fontColor
fontColor: string = '#000';
// fontSize
fontSize: number = 16;
// Image width
imgWidth: number = 16;
// Image height
imgHeight: number = 16;
// If the number of lines exceeds the limit, the entire message will be intercepted, and the content to be displayed will be intercepted
shortContent: string = '';
}
// [End RichTextModel]
// [Start TextExpand_TabData]
export class TabData {
id: number;
name: string | Resource;
// ItemList: ItemData[] = [
// new ItemData(),
// new ItemData(),
// new ItemData()
// ];
constructor(id: number, name: string | Resource) {
this.id = id;
this.name = name
}
}
export class ItemData {
// profileImg: Resource = $r('app.media.head_0');
// profileImg1: Resource = $r('app.media.head_1');
// rawTitle: Resource | string = $r('app.string.text_expand_long_title')
// imgUrlList: Resource[] | string[] = [
// $r('app.media.text_expand_img2'),
// $r('app.media.text_expand_img3'),
// $r('app.media.text_expand_img4')
// ];
// imgUrl: Resource | string = $r('app.media.text_expand_img2')
// iconUrlList: Resource[] | string[] = [
// $r('app.media.ic_pl_light'),
// $r('app.media.ic_sc_light'),
// $r('app.media.ic_dz_light'),
// $r('app.media.ic_fx_light')
// ]
}
// [End TextExpand_TabData]
// [Start RichTextExpandModel]
/**
* Rich text expands the view model
* @param {RichTextContentModel[]} textContentArray - Textual content
* @param {boolean} needProcess - Whether to display the Expand and collapse button
* @param {exceedOneLine} boolean - When adding the word 'collapse', more than one line needs to be wrapped
*/
export class RichTextExpandModel {
textContentArray: RichTextContentModel[] = [];
needProcess: boolean = true;
exceedOneLine: boolean = false;
}
// [End RichTextExpandModel]
// [Start RichTextSectionAttribute]
/**
* Rich Text Attribute
*
* @param {ResourceStr} title - Textual content
* @param {number} {maxLines} - maxLines
* @param {ResourceStr} {fontColor} - fontColor
* @param { Resource | number | string} {fontSize} - fontSize
* @param { number } {imgWidth} - Image width
* @param { number } {lineHeight} - Image height
* @param {number} lineHeight - lineHeight
* @param {Resource | number | string} contraintWidth - Sets the maximum width of the text on the line
*/
export class RichTextSectionAttribute {
title: ResourceStr = '';
maxLines: number;
fontColor: ResourceStr;
fontSize: number;
imgWidth: number;
imgHeight: number;
lineHeight: number;
constraintWidth: number;
constructor(title: ResourceStr = '', maxLines: number = 3, fontColor: ResourceStr = '#000',
fontSize: number = 16, imgWidth: number = 0, imgHeight: number = 0, lineHeight: number = 16,
constraintWidth: number = 350) {
this.title = title;
this.maxLines = maxLines;
this.fontColor = fontColor;
this.fontSize = fontSize;
this.imgWidth = imgWidth;
this.imgHeight = imgHeight;
this.lineHeight = lineHeight;
this.constraintWidth = constraintWidth;
}
}
// [End RichTextSectionAttribute]
// [Start LastSpanAttribute]
/**
* Text that controls text folding
*
* @param {number} lastSpanType - Type (0 for text)
* @param {number} charactersNumber - The number of characters in the collapsed text or image
* @param {ResourceStr[]} -Text or image content
* @param {size} Text or image size
* @param {color} color
*/
export class LastSpanAttribute {
lastSpanType: number;
charactersNumber: number;
content: string[];
size: ResourceStr | number;
color: ResourceStr | Color;
constructor(lastSpanType: number, charactersNumber: number = 1,
content: string[], size: ResourceStr | number, color: ResourceStr | Color = Color.Orange) {
this.lastSpanType = lastSpanType;
this.charactersNumber = charactersNumber;
this.content = content;
this.size = size;
this.color = color;
}
}
// [End LastSpanAttribute]
// [Start TextExpandModel]
/**
* Plain text expands the view model
* @param {ResourceStr} title - Textual content
* @param {boolean} needProcess - Whether to display the Expand and collapse button
* @param {exceedOneLine} boolean - When adding the word 'collapse', more than one line needs to be wrapped
*/
export class TextExpandModel {
title: ResourceStr = '';
needProcess: boolean = true;
exceedOneLine: boolean = false;
}
// [End TextExpandModel]
// [Start TextSectionAttribute]
/**
* Plain Text Attribute
*
* @param {ResourceStr} title - Textual content
* @param {number} {maxLines} - maxLines
* @param {ResourceStr} {fontColor} - fontColor
* @param { Resource | number | string} {fontSize} - fontSize
* @param {number} lineHeight - lineHeight
* @param {Resource | number | string} contraintWidth - Sets the maximum width of the text on the line
*/
export class TextSectionAttribute {
title: ResourceStr = '';
maxLines: number;
fontColor: ResourceStr;
fontSize: Resource | number | string;
lineHeight: number;
constraintWidth: Resource | number | string;
constructor(title: ResourceStr = '', maxLines: number = 2, fontColor: ResourceStr = '#666666',
fontSize: Resource | number | string = '15vp', lineHeight: number = 16,
constraintWidth: Resource | number | string = 350) {
this.title = title;
this.maxLines = maxLines;
this.fontColor = fontColor;
this.fontSize = fontSize;
this.lineHeight = lineHeight;
this.constraintWidth = constraintWidth;
}
}
// [End TextSectionAttribute]

View File

@ -0,0 +1,241 @@
/*
* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// [Start TextUtils]
import { BusinessError } from '@kit.BasicServicesKit';
import { RichTextContentModel, RichTextModel, RichTextSectionAttribute, TextSectionAttribute } from './Models';
import { text } from '@kit.ArkGraphics2D';
const suffix: string = '...';
export class TextUtils {
/**
* Gets the collapsed short paragraph string
*
* @param uiContext UI Context
* @param textSectionAttribute Plain Text Attribute
* @param lastSpan Expand the collapse button text
* @returns Short paragraph strings
*/
// [Start TextUtils_getShortText]
public static getShortText(uiContext: UIContext, textSectionAttribute: TextSectionAttribute, lastSpan: string): string {
let text = TextUtils.getStringFromResource(uiContext, textSectionAttribute.title);
const minLinesTextSize: SizeOptions | undefined = uiContext?.getMeasureUtils().measureTextSize({
textContent: text,
fontSize: textSectionAttribute.fontSize,
maxLines: textSectionAttribute.maxLines,
wordBreak: WordBreak.BREAK_ALL,
constraintWidth: textSectionAttribute.constraintWidth
});
const minHeight: Length | undefined = minLinesTextSize?.height;
if (minHeight === undefined) {
return '';
}
// Use the dichotomy to find strings that are exactly two lines in length
let textStr: string[] = Array.from(text); //Split the string to avoid special characters and inconsistent sizes
let leftCursor: number = 0;
let rightCursor: number = textStr.length;
let cursor: number = Math.floor(rightCursor / 2);
let tempTitle: string = '';
while (true) {
tempTitle = text.substring(0, cursor) + suffix + lastSpan;
const currentLinesTextSize: SizeOptions | undefined = uiContext?.getMeasureUtils().measureTextSize({
textContent: tempTitle,
fontSize: textSectionAttribute.fontSize,
wordBreak: WordBreak.BREAK_ALL,
constraintWidth: textSectionAttribute.constraintWidth
});
const currentLineHeight: Length | undefined = currentLinesTextSize?.height;
if (currentLineHeight === undefined) {
return '';
}
if (currentLineHeight > minHeight) {
// The current character has exceeded two lines, continue to look to the left
rightCursor = cursor;
cursor = leftCursor + Math.floor((cursor - leftCursor) / 2);
} else {
// The current character is less than two lines, it may be OK, but you still need to look to the right
leftCursor = cursor;
cursor += Math.floor((rightCursor - cursor) / 2);
}
if (Math.abs(rightCursor - leftCursor) <= 1) {
// The two pointers basically coincide, which means that they have been found
break;
}
}
return text.substring(0, cursor) + suffix;
}
// [End TextUtils_getShortText]
// [Start TextUtils_getStringFromResource]
public static getStringFromResource(uiContext: UIContext, source: Resource | string): string {
try {
if (typeof source === 'string') {
return source;
}
if (uiContext?.getHostContext()) {
return uiContext?.getHostContext()!.resourceManager.getStringSync(source);
}
return '';
} catch (error) {
let code = (error as BusinessError).code;
let message = (error as BusinessError).message;
console.log('getStringFromResource' + code + message);
return '';
}
}
// [End TextUtils_getStringFromResource]
/**
* Set up paragraph layout
* @param uiContext UI Context
* @param contentArray Typography content
* @param fontSize
* @param textWidth
*/
public static getParagraph(uiContext: UIContext, contentArray: RichTextContentModel[], fontSize: number,
textMaxWidth: number): text.Paragraph {
// Set up paragraph layout
// [Start TextUtils_paragraphBuilder]
let myTextStyle: text.TextStyle = {
fontSize: uiContext?.fp2px(fontSize)
};
let myParagraphStyle: text.ParagraphStyle = {
textStyle: myTextStyle,
align: text.TextAlign.START,
maxLines: 300, // Just specify a large enough number of rows
breakStrategy: text.BreakStrategy.GREEDY,
wordBreak: text.WordBreak.BREAK_WORD
};
let fontCollection = new text.FontCollection();
let paragraphGraphBuilder = new text.ParagraphBuilder(myParagraphStyle, fontCollection);
// [End TextUtils_paragraphBuilder]
contentArray.forEach((item, index) => {
if (item.type === 'topic') {
// The text specifies the style
paragraphGraphBuilder.pushStyle({
fontSize: fontSize,
});
paragraphGraphBuilder.addText(item.content);
} else if (item.type === 'images') {
item.images.forEach(() => {
// Add a placeholder to specify the style
// [Start TextUtils_addPlaceholder]
paragraphGraphBuilder.addPlaceholder({
width: item.imgWidth,
height: item.imgHeight,
align: text.PlaceholderAlignment.BOTTOM_OF_ROW_BOX,
baseline: text.TextBaseline.IDEOGRAPHIC,
baselineOffset: 0
});
// [End TextUtils_addPlaceholder]
});
} else if (item.type === 'link') {
// [Start TextUtils_addText]
paragraphGraphBuilder.pushStyle({
fontSize: fontSize,
});
paragraphGraphBuilder.addText(item.content);
// [End TextUtils_addText]
} else {
paragraphGraphBuilder.pushStyle({
fontSize: fontSize,
});
paragraphGraphBuilder.addText(item.content);
}
})
// [Start TextUtils_getParagraph_build]
let paragraph = paragraphGraphBuilder.build();
paragraph.layoutSync(textMaxWidth);
// [End TextUtils_getParagraph_build]
return paragraph;
}
// [End TextUtils_getParagraph]
// [Start TextUtils_getShortRichText]
/**
* Get the collapsed dash word data
*
* @param uiContext UI Context
* @param dataModel Content Model
* @param textSectionAttribute Rich Text Attribute
* @param lastSpan Expand the collapse button text
* @returns Short paragraph strings
*/
public static getShortRichText(uiContext: UIContext, dataModel: RichTextModel, textSectionAttribute: RichTextSectionAttribute,
lastSpan: string): RichTextContentModel[] {
let paragraph =
TextUtils.getParagraph(uiContext, dataModel.textArray, dataModel.fontSize, textSectionAttribute.constraintWidth);
const minLinesTextSize: SizeOptions | undefined = uiContext?.getMeasureUtils().measureTextSize({
textContent: suffix + lastSpan,
fontSize: dataModel.fontSize,
});
const widthMore = uiContext?.px2vp(Number(minLinesTextSize?.width));
// Calculates the coordinates of the last text before three points
let x: number = 0;
let y: number = 0;
// [Start TextUtils_getShortRichText_y]
for (let i = 0; i < textSectionAttribute.maxLines; i++) {
y += i === textSectionAttribute.maxLines - 1 ? paragraph.getLineHeight(i) / 2 : paragraph.getLineHeight(i);
}
// [End TextUtils_getShortRichText_y]
// [Start TextUtils_getShortRichText_x]
if (paragraph.getLineWidth(textSectionAttribute.maxLines - 1) + Number(widthMore) >
textSectionAttribute.constraintWidth) {
x = textSectionAttribute.constraintWidth - Number(widthMore);
} else {
x = paragraph.getLineWidth(textSectionAttribute.maxLines - 1)
}
// [End TextUtils_getShortRichText_x]
// [Start TextUtils_getShortRichText_Index]
// The conversion coordinates correspond to the index
let positionWithAffinity = paragraph.getGlyphPositionAtCoordinate(x, y);
let index = 0;
if (positionWithAffinity.affinity === text.Affinity.UPSTREAM) {
index = positionWithAffinity.position;
} else {
index = positionWithAffinity.position + 1;
}
// [Start TextUtils_getShortRichText_Index]
// The position of the last character
let lastIndex = index - 1;
let shortContentArray: RichTextContentModel[] = [];
for (let i = 0; i < dataModel.textArray.length; i++) {
let model = dataModel.textArray[i];
// Determine which text message is in the truncated position, process it, and sort out the display content
if (0 <= lastIndex - model.index && lastIndex - model.index <= model.length) {
model.shortContent = model.content.substring(0, lastIndex - model.index) + suffix;
let b = lastIndex - model.index;
let a = model.content.substring(0, lastIndex - model.index);
shortContentArray.push(model);
return shortContentArray;
} else {
shortContentArray.push(model);
}
}
return [];
}
}
// [End TextUtils]

View File

@ -0,0 +1,141 @@
/*
* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// [Start TextExpandView]
import { LastSpanAttribute, TextExpandModel, TextSectionAttribute } from '../utils/Models';
import { TextUtils } from '../utils/TextUtils';
@Component
export struct TextExpandView {
//[StartExclude TextExpandView]
// Text chapter attribute class
@Prop @Watch('textSectionAttributeChange') textSectionAttribute: TextSectionAttribute;
// Controls the text or image properties of text folding
@Prop lastSpanAttribute: LastSpanAttribute;
// Whether to expand or not
@State expanded: boolean = false;
// Text expansion properties
@State textModifier: TextExpandModel = new TextExpandModel();
uiContext = this.getUIContext()
aboutToAppear(): void {
this.getIsExpanded();
}
//[EndExclude TextExpandView]
// [Start TextExpandView_textSectionAttributeChange]
textSectionAttributeChange() {
this.textModifier.title = this.textSectionAttribute.title;
// this.lastSpanAttribute.content = this.lastSpanAttribute.content
// 重置展开状态
this.expanded = false;
this.textModifier.exceedOneLine = false;
this.getIsExpanded();
}
// [End TextExpandView_textSectionAttributeChange]
// [Start TextExpandView_getIsExpanded]
getIsExpanded() {
let titleSize: SizeOptions = this.uiContext.getMeasureUtils().measureTextSize({
textContent: this.textSectionAttribute.title, //The text content is calculated
lineHeight: this.textSectionAttribute.lineHeight,
constraintWidth: this.textSectionAttribute.constraintWidth, //The text layout width is calculated
fontSize: this.textSectionAttribute.fontSize //The text font size is calculated
});
let height = this.getUIContext().px2vp(Number(titleSize.height));
if (height <= this.textSectionAttribute.lineHeight * 2) {
this.textModifier.needProcess = false;
this.textModifier.title = this.textSectionAttribute.title;
return;
} else {
this.textModifier.title = this.textSectionAttribute.title
this.textModifier.needProcess = true;
}
// 初始状态显示截断的文本
this.collapseText();
}
// [End TextExpandView_getIsExpanded]
build() {
Column({ space: 3 }) {
//[StartExclude TextExpandView]
Text() {
Span(this.textModifier.title)
if (this.textModifier.needProcess && !this.textModifier.exceedOneLine) {
Span(this.lastSpanAttribute.content[0])
.fontColor(this.lastSpanAttribute.color)
} else if (this.textModifier.needProcess) {
Span(this.lastSpanAttribute.content[1])
.fontColor(this.lastSpanAttribute.color)
}
}
.fontSize(this.lastSpanAttribute.size)
.width(this.textSectionAttribute.constraintWidth)
.lineHeight(this.textSectionAttribute.lineHeight)
//[EndExclude TextExpandView]
}
.onClick(() => {
if (!this.textModifier.needProcess) {
return;
}
this.process();
})
}
// [Start TextExpandView_process]
process(): void {
if (this.expanded) {
this.expanded = false;
this.collapseText();
this.textModifier.exceedOneLine = false;
} else {
this.expanded = true;
this.expandText();
this.textModifier.exceedOneLine = true;
}
}
// [End TextExpandView_process]
// [Start TextExpandView_expandText]
// Expand text
expandText(): void {
if (this.textModifier.needProcess) {
this.textModifier.title = this.textSectionAttribute.title;
}
}
// [End TextExpandView_expandText]
// [Start TextExpandView_collapseText]
// Collapse text
collapseText(): void {
if (!this.textModifier.needProcess) {
return;
}
this.textModifier.title =
TextUtils.getShortText(
this.uiContext,
this.textSectionAttribute,
this.expanded ? `${this.lastSpanAttribute.content[1]}` : `${this.lastSpanAttribute.content[0]}`);
// [End TextExpandView_collapseText]
}
}
// [End TextExpandView]

Binary file not shown.

After

Width:  |  Height:  |  Size: 968 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,14 @@
import { GroupManagementComp } from 'patient'
@Entry
@Component
struct GroupManagementPage {
build() {
RelativeContainer() {
GroupManagementComp()
}
.height('100%')
.width('100%')
}
}

View File

@ -40,6 +40,7 @@
"pages/Netease/PreviewPhotoPage",
"pages/Netease/InterrogationDetailCompPage",
"pages/Netease/PatientSimplyPage",
"pages/Netease/MyOpinionPage"
"pages/Netease/MyOpinionPage",
"pages/PatientsPage/GroupManagementPage"
]
}