Bläddra i källkod

Merge branch 'master' of http://47.107.245.0:3000/xiongxing/kyj-yanglao-web-new

unknown 1 vecka sedan
förälder
incheckning
c4f8bd3a11

+ 17 - 0
src/api/elderly/nursing/index.ts

@@ -372,4 +372,21 @@ export const getNursingInspection = (params) => {
     url: `elderly/nursingInspection/page`,
     params
   })
+}
+
+// =================长者巡房项目(巡视项目)================
+/** 长者巡房项目分页 */
+export const getElderlyItemsRoundPage = (params) => {
+  return request.get({
+    url: 'elderly-items-round/page',
+    params
+  })
+}
+
+/** 长者巡房项目详情 */
+export const getElderlyItemsRound = (id) => {
+  return request.get({
+    url: 'elderly-items-round/get',
+    params: { id }
+  })
 }

+ 1 - 1
src/main.ts

@@ -51,7 +51,7 @@ import { Flex } from 'ant-design-vue'
 import 'ant-design-vue/dist/reset.css'
 import fetchPlugin from './config/axios/fetch';
 //版本号
-export const MAIN_VERSION = '4.4.5'
+export const MAIN_VERSION = '4.4.6'
 
 // 创建实例
 const setupAll = async () => {

+ 30 - 2
src/utils/formatTime.ts

@@ -73,6 +73,34 @@ export function formatDate(date: Date, format?: string): string {
 }
 
 /**
+ * 后端时间展示:支持秒/毫秒时间戳、纯数字字符串、Date、可解析的日期字符串
+ * @param value 秒级时间戳(10 位左右)、毫秒时间戳、或其它可解析值
+ * @param pattern dayjs 格式,默认 YYYY-MM-DD HH:mm:ss
+ */
+export function formatBackendDateTime(value: unknown, pattern = 'YYYY-MM-DD HH:mm:ss'): string {
+  if (value == null || value === '') return ''
+  if (value instanceof Date) {
+    const d = dayjs(value)
+    return d.isValid() ? d.format(pattern) : ''
+  }
+  if (typeof value === 'number' && Number.isFinite(value)) {
+    const ms = value < 1e12 ? value * 1000 : value
+    const d = dayjs(ms)
+    return d.isValid() ? d.format(pattern) : ''
+  }
+  const s = String(value).trim()
+  if (!s) return ''
+  if (/^\d+$/.test(s)) {
+    const n = Number(s)
+    const ms = n < 1e12 ? n * 1000 : n
+    const d = dayjs(ms)
+    return d.isValid() ? d.format(pattern) : ''
+  }
+  const d = dayjs(s)
+  return d.isValid() ? d.format(pattern) : ''
+}
+
+/**
  * 获取当前的日期+时间
  */
 export function getNowDateTime() {
@@ -198,7 +226,7 @@ export function formatPast2(ms: number): string {
  * @param cellValue 字段值
  */
 export function dateFormatter(_row: any, _column: TableColumnCtx<any>, cellValue: any): string {
-  return cellValue ? formatDate(cellValue) : ''
+  return formatBackendDateTime(cellValue)
 }
 
 /**
@@ -209,7 +237,7 @@ export function dateFormatter(_row: any, _column: TableColumnCtx<any>, cellValue
  * @param cellValue 字段值
  */
 export function dateFormatter2(_row: any, _column: TableColumnCtx<any>, cellValue: any): string {
-  return cellValue ? formatDate(cellValue, 'YYYY-MM-DD') : ''
+  return formatBackendDateTime(cellValue, 'YYYY-MM-DD')
 }
 
 /**

+ 35 - 0
src/views/elderly/nursing/column.ts

@@ -177,3 +177,38 @@ export const NurseLogColumns = reactive([
     field: 'nurseLevelName'
   },
 ])
+
+// =============巡房项目日志(长者巡房项目)=====================
+export const ElderlyItemsRoundLogColumns = reactive([
+  {
+    label: '长者姓名',
+    field: 'elderName',
+  },
+  {
+    label: '记录人',
+    field: 'recorder',
+  },
+  {
+    label: '角色',
+    field: 'roleLabel',
+  },
+  {
+    label: '巡房时间',
+    field: 'roundTime',
+    type: '9',
+    width: 170
+  },
+  {
+    label: '巡视项目组',
+    field: 'itemsLabel',
+  },
+  {
+    label: '备注',
+    field: 'remark',
+  },
+  {
+    label: '创建时间',
+    field: 'createTime',
+    type: '9',
+  }
+])

+ 158 - 0
src/views/elderly/nursing/room-Inspection-project-log/Detail.vue

@@ -0,0 +1,158 @@
+<template>
+  <Dialog
+    v-model="dialogVisible"
+    width="800px"
+    title="巡房项目详情"
+    class="form-tag-dialog"
+    scroll
+    @close="handleClosed"
+  >
+    <el-descriptions v-loading="loading" :column="2" border>
+      <el-descriptions-item label="记录人">{{ detail.recorder || '-' }}</el-descriptions-item>
+      <el-descriptions-item label="角色">{{ getRoleSubmitLabel(detail.role) }}</el-descriptions-item>
+      <el-descriptions-item label="长者姓名">{{ detail.elderName || '-' }}</el-descriptions-item>
+      <el-descriptions-item label="房间号">{{ detail.roomName ?? '-' }}</el-descriptions-item>
+      <el-descriptions-item label="巡房时间" :span="2">{{
+        formatBackendDateTime(detail.roundTime) || '-'
+      }}</el-descriptions-item>
+      <el-descriptions-item label="巡视项目组" :span="2">
+        <template v-if="formatItems(detail.items).length">
+          <el-tag
+            v-for="(it, i) in formatItems(detail.items)"
+            :key="i"
+            class="mr-5px mb-5px"
+            size="small"
+            effect="plain"
+          >
+            {{ it }}
+          </el-tag>
+        </template>
+        <span v-else>-</span>
+      </el-descriptions-item>
+      <el-descriptions-item label="语音" :span="2">
+        <template v-if="voiceParts.hasAny">
+          <div v-for="(url, i) in voiceParts.audios" :key="'audio-' + i" class="voice-row">
+            <audio :src="url" controls class="voice-audio"></audio>
+          </div>
+          <div v-for="(t, i) in voiceParts.texts" :key="'text-' + i" class="voice-text">
+            {{ t }}
+          </div>
+        </template>
+        <span v-else>-</span>
+      </el-descriptions-item>
+      <el-descriptions-item label="备注" :span="2">{{ detail.remark || '-' }}</el-descriptions-item>
+      <el-descriptions-item label="创建时间">{{ formatBackendDateTime(detail.createTime) || '-' }}</el-descriptions-item>
+      <el-descriptions-item label="更新时间">{{ formatBackendDateTime(detail.updateTime) || '-' }}</el-descriptions-item>
+      <el-descriptions-item label="创建人">{{ detail.creator || '-' }}</el-descriptions-item>
+      <el-descriptions-item label="更新人">{{ detail.updater || '-' }}</el-descriptions-item>
+    </el-descriptions>
+    <template #footer>
+      <el-button @click="handleClosed">关闭</el-button>
+    </template>
+  </Dialog>
+</template>
+
+<script setup lang="ts">
+import { getElderlyItemsRound } from '@/api/elderly/nursing'
+import { formatBackendDateTime } from '@/utils/formatTime'
+import { getRoleSubmitLabel } from './roleSubmitLabel'
+
+defineOptions({ name: 'ElderlyItemsRoundDetail' })
+
+export type ElderlyItemsRoundDetailVO = {
+  id?: number
+  recorder?: string
+  role?: string
+  elderId?: number
+  elderName?: string
+  buildId?: number
+  floorId?: number
+  roomId?: number
+  roomName?: string
+  items?: string | string[]
+  voice?: string
+  remark?: string
+  tenantId?: number
+  /** 巡房时间(时间戳或与后端约定格式) */
+  roundTime?: string | number
+  createTime?: string | number
+  updateTime?: string | number
+  creator?: string
+  updater?: string
+}
+
+const dialogVisible = ref(false)
+const loading = ref(false)
+const detail = reactive<ElderlyItemsRoundDetailVO>({})
+
+function emptyDetail() {
+  Object.keys(detail).forEach((k) => delete (detail as Recordable)[k])
+}
+
+/** items:按中英文逗号拆分;数组则每项再拆分。voice 仍用本函数(同分隔规则) */
+function splitDelimited(raw: unknown): string[] {
+  if (raw == null || raw === '') return []
+  if (Array.isArray(raw)) {
+    return raw.flatMap((x) => splitDelimited(String(x)))
+  }
+  return String(raw)
+    .split(/[,,]+/)
+    .map((s) => s.trim())
+    .filter(Boolean)
+}
+
+function formatItems(items: string | string[] | undefined) {
+  return splitDelimited(items)
+}
+
+const voiceParts = computed(() => {
+  const parts = splitDelimited(detail.voice)
+  const audios: string[] = []
+  const texts: string[] = []
+  for (const p of parts) {
+    if (/^https?:\/\//i.test(p)) {
+      audios.push(p)
+    } else {
+      texts.push(p)
+    }
+  }
+  return { audios, texts, hasAny: audios.length > 0 || texts.length > 0 }
+})
+
+const open = async (row: Recordable) => {
+  const id = row?.id as number | undefined
+  if (id == null) return
+  dialogVisible.value = true
+  loading.value = true
+  emptyDetail()
+  try {
+    const data = (await getElderlyItemsRound(id)) as ElderlyItemsRoundDetailVO
+    Object.assign(detail, data ?? {})
+  } finally {
+    loading.value = false
+  }
+}
+
+defineExpose({ open })
+
+const handleClosed = () => {
+  emptyDetail()
+  dialogVisible.value = false
+}
+</script>
+
+<style lang="scss" scoped>
+.voice-audio {
+  max-width: 100%;
+  height: 32px;
+  vertical-align: middle;
+}
+.voice-row + .voice-row {
+  margin-top: 8px;
+}
+.voice-text {
+  margin-top: 6px;
+  color: var(--el-text-color-regular);
+  word-break: break-all;
+}
+</style>

+ 167 - 0
src/views/elderly/nursing/room-Inspection-project-log/index.vue

@@ -0,0 +1,167 @@
+<template>
+  <!-- 搜索 -->
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="80px"
+    >
+      <el-form-item prop="tenantIds">
+        <TenantSelect
+          v-model="queryParams.tenantIds"
+          placeholder="请选择机构名称"
+          prop="tenantIds"
+        />
+      </el-form-item>
+      <el-form-item label="长者名称" prop="elderName">
+        <TgInput @keyup.enter="handleQuery" v-model="queryParams.elderName" class="!w-160px" />
+      </el-form-item>
+      <el-form-item label="角色" prop="role">
+        <el-select v-model="queryParams.role" placeholder="请选择角色" clearable class="!w-160px">
+          <el-option
+            v-for="opt in roleSubmitOptions"
+            :key="opt.enumKey"
+            :label="opt.label"
+            :value="opt.enumKey"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="巡房时间" prop="roundTime">
+        <el-date-picker
+          v-model="queryParams.roundTime"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          value-format="YYYY-MM-DD"
+          class="!w-280px"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+  <ContentWrap>
+    <Table2
+      v-loading="loading"
+      :list="list"
+      :columns="ElderlyItemsRoundLogColumns"
+      :queryParams="queryParams"
+    >
+      <template #pre="{ scope }">
+        <el-button link type="primary" @click="openDetail(scope)">查看</el-button>
+      </template>
+    </Table2>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <Detail ref="detailRef" />
+  </ContentWrap>
+</template>
+<script setup lang="ts">
+import { getElderlyItemsRoundPage } from '@/api/elderly/nursing'
+import Detail from './Detail.vue'
+import { ElderlyItemsRoundLogColumns } from '../column'
+import { useUserStore } from '@/store/modules/user'
+import { ROLE_SUBMIT_LABEL, getRoleSubmitLabel } from './roleSubmitLabel'
+import dayjs from 'dayjs'
+
+const userStore = useUserStore()
+
+const roleSubmitOptions = Object.entries(ROLE_SUBMIT_LABEL).map(([enumKey, label]) => ({
+  enumKey,
+  label
+}))
+
+defineOptions({ name: 'RoomInspectionProjectLog' })
+
+/** 默认巡房时间:[今天, 今天起往后一个月](按日历月加一个月) */
+function createDefaultRoundTimeRange(): string[] {
+  const start = dayjs().startOf('day')
+  const end = start.add(1, 'month')
+  return [start.format('YYYY-MM-DD'), end.format('YYYY-MM-DD')]
+}
+
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  elderName: '',
+  role: '',
+  roundTime: createDefaultRoundTimeRange(),
+  tenantIds: userStore.orgTenantId
+})
+
+const loading = ref(true)
+const total = ref(0)
+const list = ref<Recordable[]>([])
+const queryFormRef = ref()
+
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+const resetQuery = () => {
+  queryFormRef.value?.resetFields()
+  queryParams.roundTime = createDefaultRoundTimeRange()
+  handleQuery()
+}
+
+const buildPageParams = () => {
+  const p = { ...queryParams } as Recordable
+  if (!Array.isArray(p.roundTime) || p.roundTime.length < 2) {
+    delete p.roundTime
+  }
+  if (p.role === '' || p.role == null) {
+    delete p.role
+  }
+  return p
+}
+
+/** 巡视项目组:按中英文逗号拆分;若为数组则对每一项再拆分后合并(兼容 ["a,b","c"]) */
+function splitItemsDelimited(raw: unknown): string[] {
+  if (raw == null || raw === '') return []
+  if (Array.isArray(raw)) {
+    return raw.flatMap((x) => splitItemsDelimited(String(x)))
+  }
+  return String(raw)
+    .split(/[,,]+/)
+    .map((s) => s.trim())
+    .filter(Boolean)
+}
+
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await getElderlyItemsRoundPage(buildPageParams())
+    const raw = data.list ?? []
+    list.value = raw.map((r: Recordable) => ({
+      ...r,
+      itemsLabel: splitItemsDelimited(r.items).join('、'),
+      roleLabel: getRoleSubmitLabel(r.role)
+    }))
+    total.value = data.total ?? 0
+  } finally {
+    loading.value = false
+  }
+}
+
+const detailRef = ref<{ open: (row: Recordable) => void }>()
+const openDetail = (row: Recordable) => {
+  detailRef.value?.open(row)
+}
+
+onMounted(() => {
+  getList()
+})
+</script>

+ 18 - 0
src/views/elderly/nursing/room-Inspection-project-log/roleSubmitLabel.ts

@@ -0,0 +1,18 @@
+export const ROLE_SUBMIT_LABEL = Object.freeze({
+  MEDICAL_ORDERLY: '护理员',
+  DOCTOR: '医生',
+  SOCIALWORK: '社工'
+} as const)
+
+export type RoleSubmitKey = keyof typeof ROLE_SUBMIT_LABEL
+
+/** 列表/详情展示:枚举 key -> 中文;未知或已是中文则原样 */
+export function getRoleSubmitLabel(role: unknown): string {
+  if (role == null || role === '') return '-'
+  const s = String(role).trim()
+  if (!s) return '-'
+  if (s in ROLE_SUBMIT_LABEL) {
+    return ROLE_SUBMIT_LABEL[s as RoleSubmitKey]
+  }
+  return s
+}