Form.vue 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897
  1. <template>
  2. <el-drawer
  3. v-model="dialogVisible"
  4. :title="dialogTitle"
  5. size="70%"
  6. class="risk-disclosure-statement-form"
  7. :close-on-click-modal="false"
  8. :close-on-press-escape="false"
  9. :destroy-on-close="true"
  10. @close="handleClosed"
  11. >
  12. <el-form
  13. ref="formRef"
  14. :model="dataForm"
  15. label-width="120px"
  16. :rules="dataRule"
  17. v-loading="loading"
  18. :disabled="isView"
  19. >
  20. <!-- 长者搜索 - 参照 ProcessForm.vue 中的 SelectElder 使用方式 -->
  21. <div class="section-title">长者信息</div>
  22. <el-row :gutter="0">
  23. <el-col :span="12">
  24. <el-form-item label="长者姓名" prop="elderId">
  25. <SelectElder
  26. ref="selectElderRef"
  27. v-model="dataForm.elderId"
  28. @elder="handleSelectElder"
  29. type="1"
  30. :tId="dataForm.tenantId"
  31. :disabled="isEdit || isView"
  32. />
  33. </el-form-item>
  34. </el-col>
  35. <!-- <el-col :span="12">-->
  36. <!-- <el-form-item label="证件号码">-->
  37. <!-- <TgInput v-model="dataForm.idCard" disabled />-->
  38. <!-- </el-form-item>-->
  39. <!-- </el-col>-->
  40. </el-row>
  41. <!-- 长者详情展示 - 对应图片中的表一表头信息 -->
  42. <div class="elder-detail-wrap">
  43. <el-descriptions :column="2" border size="small">
  44. <el-descriptions-item label="姓名">{{ elderDetail.elderName || '-' }}</el-descriptions-item>
  45. <el-descriptions-item label="性别">{{ getDictLabel(DICT_TYPE.SYSTEM_USER_SEX, elderDetail.elderSex) || '-' }}</el-descriptions-item>
  46. <el-descriptions-item label="年龄">{{ elderDetail.elderAge || '-' }}岁</el-descriptions-item>
  47. <el-descriptions-item label="床号">{{ elderDetail.bedName || '-' }}</el-descriptions-item>
  48. </el-descriptions>
  49. </div>
  50. <!-- 服务安全风险告知书内容 -->
  51. <div class="section-title">服务安全风险知情告知书</div>
  52. <div class="notice-content">
  53. <p class="notice-header">尊敬的老年人/相关第三方/监护人:</p>
  54. <p class="notice-text">
  55. 您好!感谢您对本机构的信任和支持。
  56. </p>
  57. <p class="notice-text">
  58. 经我院评估您或您相关第三方的身体状况,您或您亲属在我院养老期间,存在下列服务安全风险。为保证对您或您相关第三方的服务质量,特向您告知!我院将针对老年人的情况做好相关防范措施,请您理解、配合、并支持相关工作措施的落实。
  59. </p>
  60. <!-- 风险项目表格 -->
  61. <div class="risk-table-wrapper">
  62. <table class="risk-table">
  63. <thead>
  64. <tr>
  65. <th class="col-index">序号</th>
  66. <th class="col-project">项目</th>
  67. <th class="col-level">风险程度</th>
  68. <th class="col-remark">备注</th>
  69. </tr>
  70. </thead>
  71. <tbody>
  72. <tr v-for="(item, index) in riskTableItems" :key="index">
  73. <td class="col-index">{{ index + 1 }}</td>
  74. <td class="col-project">{{ item.name }}:</td>
  75. <td class="col-level">
  76. <el-select
  77. v-model="item.recordId"
  78. placeholder="请选择"
  79. style="width: 180px"
  80. :disabled="isView"
  81. @change="(val: any) => handleRiskLevelChange(item, val)"
  82. >
  83. <el-option
  84. v-for="option in getRiskOptions(item.key)"
  85. :key="option.value"
  86. :label="option.label"
  87. :value="option.value"
  88. />
  89. </el-select>
  90. </td>
  91. <td class="col-remark">
  92. <span class="remark-text">风险程度:</span>
  93. <el-checkbox v-model="item.lowChecked" :disabled="isView" size="small">低危</el-checkbox>
  94. <el-checkbox v-model="item.mediumChecked" :disabled="isView" size="small">中危</el-checkbox>
  95. <el-checkbox v-model="item.highChecked" :disabled="isView" size="small">高危</el-checkbox>
  96. </td>
  97. </tr>
  98. </tbody>
  99. </table>
  100. </div>
  101. <!-- 预计下次评估日期 -->
  102. <div class="next-assess-date">
  103. <span class="date-label">预计下次评估日期:</span>
  104. <el-date-picker
  105. v-model="dataForm.firstRisk"
  106. value-format="YYYY-MM-DD"
  107. type="date"
  108. placeholder="选择日期"
  109. style="width: 150px"
  110. :disabled="isView"
  111. />
  112. <span class="date-value">{{ dataForm.firstRisk ? formatDate(dataForm.firstRisk) : '无' }}</span>
  113. </div>
  114. <!-- 告知签名区域 -->
  115. <div class="signature-section">
  116. <div class="signature-title">告知签名:</div>
  117. <p class="signature-desc">
  118. 通过对老年人的整体评估,老年人有可能发生以上风险,其风险及防范措施已向老年人或相关第三方进行了如实告知。
  119. </p>
  120. <p class="signature-declare">
  121. (老年人或相关第三方声明:本人已知悉并签字确认。)
  122. </p>
  123. <div class="signature-item">
  124. <span class="signature-label">经办人(护理人员):</span>
  125. </div>
  126. <div class="signature-item">
  127. <span class="signature-label">老年人或相关第三方签名:</span>
  128. </div>
  129. </div>
  130. </div>
  131. </el-form>
  132. <template #footer>
  133. <el-button v-if="!isView" @click="handleClosed">取消</el-button>
  134. <el-button v-if="!isView" type="primary" @click="submitForm" :loading="submitLoading">确定</el-button>
  135. <el-button v-if="isView" @click="handleClosed">关闭</el-button>
  136. <el-button v-if="isView" type="success" @click="handleExport">打印</el-button>
  137. </template>
  138. </el-drawer>
  139. </template>
  140. <script setup lang="ts">
  141. import { DICT_TYPE, getDictLabel } from '@/utils/dict'
  142. import { getElderInfoById } from '@/api/elderly/elder/elderly-Info'
  143. import {
  144. addSafetyRiskNotice,
  145. updateSafetyRiskNotice,
  146. getSafetyRiskNoticeById
  147. } from '@/api/elderly/apply/check-in'
  148. import { getTenantId } from '@/utils/auth'
  149. import { useUserStore } from '@/store/modules/user'
  150. import {
  151. asphyxiationPage,
  152. pressureSoresPage,
  153. fallDownPage,
  154. fallPreventionPage,
  155. burnPreventionPage,
  156. wanderAwayPage,
  157. ngasrPage,
  158. entertainmentPage,
  159. mmseGetPage
  160. } from '@/api/social-work'
  161. const message = useMessage() // 消息弹窗
  162. const userStore = useUserStore()
  163. defineOptions({ name: 'RiskDisclosureStatementForm' })
  164. const emit = defineEmits(['success'])
  165. const dialogVisible = ref(false)
  166. const dialogTitle = ref('新增服务安全风险知情告知书')
  167. const loading = ref(false)
  168. const submitLoading = ref(false)
  169. const isEdit = ref(false)
  170. const isView = ref(false)
  171. const formRef = ref()
  172. const selectElderRef = ref()
  173. // 长者详情
  174. const elderDetail = ref<any>({})
  175. // 风险项目表格数据 - name 用于匹配接口
  176. const riskTableItems = ref([
  177. { key: 'asphyxiation', name: '防噎食评估', api: asphyxiationPage, recordId: null as any, riskLevel: '', lowChecked: false, mediumChecked: false, highChecked: false },
  178. { key: 'pressureUlcer', name: '防压疮评估', api: pressureSoresPage, recordId: null as any, riskLevel: '', lowChecked: false, mediumChecked: false, highChecked: false },
  179. { key: 'fall', name: '防跌倒风险评估', api: fallDownPage, recordId: null as any, riskLevel: '', lowChecked: false, mediumChecked: false, highChecked: false },
  180. { key: 'bedFall', name: '防坠床评估', api: fallPreventionPage, recordId: null as any, riskLevel: '', lowChecked: false, mediumChecked: false, highChecked: false },
  181. { key: 'scald', name: '防烫伤评估', api: burnPreventionPage, recordId: null as any, riskLevel: '', lowChecked: false, mediumChecked: false, highChecked: false },
  182. { key: 'wandering', name: '防走失风险评估', api: wanderAwayPage, recordId: null as any, riskLevel: '', lowChecked: false, mediumChecked: false, highChecked: false },
  183. { key: 'selfHarm', name: '防自伤和他伤', api: ngasrPage, recordId: null as any, riskLevel: '', lowChecked: false, mediumChecked: false, highChecked: false },
  184. { key: 'foodDrug', name: '防食品药品误食评估', api: mmseGetPage, recordId: null as any, riskLevel: '', lowChecked: false, mediumChecked: false, highChecked: false },
  185. { key: 'entertainment', name: '防文娱活动意外', api: entertainmentPage, recordId: null as any, riskLevel: '', lowChecked: false, mediumChecked: false, highChecked: false }
  186. ])
  187. // 接口返回的风险数据选项(模拟数据,实际从接口获取)
  188. const riskOptionsMap = ref<Record<string, any[]>>({
  189. asphyxiation: [],
  190. pressureUlcer: [],
  191. fall: [],
  192. bedFall: [],
  193. scald: [],
  194. wandering: [],
  195. selfHarm: [],
  196. foodDrug: [],
  197. entertainment: []
  198. })
  199. // 获取风险选项(从接口数据)
  200. const getRiskOptions = (key: string) => {
  201. // 如果接口返回了数据,使用接口数据
  202. const options = riskOptionsMap.value[key]
  203. if (options && options.length > 0) {
  204. return options
  205. }
  206. // 默认选项
  207. return []
  208. }
  209. // 加载长者风险数据(动态调用各九防接口)
  210. // preserveSelection: 是否保留已保存的选择(用于编辑模式)
  211. const loadElderRiskData = async (elderId: string, preserveSelection = false) => {
  212. try {
  213. // 遍历每个风险项目,动态调用对应的接口
  214. for (const item of riskTableItems.value) {
  215. if (item.api && typeof item.api === 'function') {
  216. try {
  217. const params = {
  218. pageNo: 1,
  219. pageSize: 100,
  220. elderId: elderId
  221. }
  222. const res = await item.api(params)
  223. if (res && res.list && res.list.length > 0) {
  224. // 解析所有记录,从 assessData 中获取 riskLevel
  225. const options: any[] = []
  226. res.list.forEach((record: any) => {
  227. let riskLevel = record.riskLevel || 'none'
  228. let assessDate = record.assessDate || ''
  229. // 尝试从 assessData 解析 riskLevel
  230. if (record.assessData) {
  231. try {
  232. const assessData = JSON.parse(record.assessData)
  233. if (assessData.riskLevel) {
  234. riskLevel = assessData.riskLevel
  235. }
  236. // 优先使用 assessData 中的 assessDate
  237. if (assessData.assessDate) {
  238. assessDate = assessData.assessDate
  239. }
  240. } catch (e) {
  241. console.error('解析 assessData 失败', e)
  242. }
  243. }
  244. // 构建选项标签:风险等级 + 评估日期
  245. const dateStr = assessDate ? ` (${assessDate})` : ''
  246. options.push({
  247. label: getRiskLevelLabel(riskLevel) + dateStr,
  248. value: record.id, // 使用记录ID作为value,确保唯一性
  249. riskLevel: riskLevel, // 存储riskLevel用于后续使用
  250. recordId: record.id,
  251. assessDate: assessDate
  252. })
  253. })
  254. // 按评估日期降序排序,最新的排在前面
  255. options.sort((a, b) => {
  256. const dateA = a.assessDate ? new Date(a.assessDate).getTime() : 0
  257. const dateB = b.assessDate ? new Date(b.assessDate).getTime() : 0
  258. return dateB - dateA
  259. })
  260. // 设置选项数据
  261. riskOptionsMap.value[item.key] = options
  262. // 如果不是保留选择模式,则默认选中日期最新的一条记录
  263. if (!preserveSelection) {
  264. const latestRecord = options[0]
  265. item.recordId = latestRecord.recordId // 存储选中的记录ID
  266. item.riskLevel = latestRecord.riskLevel // 存储riskLevel用于显示和提交
  267. } else {
  268. // 保留选择模式下,根据已保存的 recordId 从 options 中找到对应的选项并更新 riskLevel
  269. const selectedOption = options.find((opt: any) => opt.value === item.recordId)
  270. if (selectedOption) {
  271. item.riskLevel = selectedOption.riskLevel // 更新 riskLevel 为最新的值
  272. }
  273. }
  274. } else {
  275. // 如果没有数据且不是保留选择模式,清空当前项的选择
  276. if (!preserveSelection) {
  277. item.recordId = null
  278. item.riskLevel = ''
  279. }
  280. }
  281. } catch (err) {
  282. console.error(`获取${item.name}数据失败`, err)
  283. }
  284. }
  285. }
  286. } catch (e) {
  287. console.error('获取风险数据失败', e)
  288. }
  289. }
  290. // 获取风险等级标签
  291. const getRiskLevelLabel = (riskLevel: string) => {
  292. switch (riskLevel) {
  293. case 'none': return '无风险'
  294. case 'low': return '低风险'
  295. case 'medium': return '中风险'
  296. case 'high': return '高风险'
  297. default: return '无风险'
  298. }
  299. }
  300. // 根据风险等级更新复选框状态
  301. const updateRiskCheckboxes = (item: any, riskLevel: string) => {
  302. item.lowChecked = riskLevel === 'low'
  303. item.mediumChecked = riskLevel === 'medium'
  304. item.highChecked = riskLevel === 'high'
  305. }
  306. // 处理风险等级选择变化
  307. const handleRiskLevelChange = (item: any, recordId: any) => {
  308. // 从选项中查找对应的riskLevel
  309. const options = getRiskOptions(item.key)
  310. const selectedOption = options.find((opt: any) => opt.value === recordId)
  311. if (selectedOption) {
  312. item.riskLevel = selectedOption.riskLevel
  313. // 根据风险等级更新复选框
  314. //updateRiskCheckboxes(item, selectedOption.riskLevel)
  315. }
  316. }
  317. // 表单数据
  318. const dataForm = reactive({
  319. id: undefined,
  320. elderId: '',
  321. idCard: '',
  322. tenantId: undefined,
  323. // 风险项目数据 (JSON格式存储到 riskData)
  324. riskData: '',
  325. otherRisk: '',
  326. // 预计下次评估日期
  327. nextAssessDate: '',
  328. // 首次告知签名
  329. firstRisk: '',
  330. firstNurseSign: '',
  331. firstElderSign: '',
  332. firstSignDate: '',
  333. // 第二次告知签名
  334. secondRisk: '',
  335. secondNurseSign: '',
  336. secondElderSign: '',
  337. secondSignDate: '',
  338. secondChangeRisk: '',
  339. secondRiskLevel: '',
  340. // 其他
  341. remark: '',
  342. nurseSign: '',
  343. elderSign: '',
  344. signDate: ''
  345. })
  346. // 表单校验规则
  347. const dataRule = {
  348. elderId: [{ required: true, message: '长者不能为空', trigger: 'change' }]
  349. }
  350. // 选择长者 - 参照 ProcessForm.vue 中的使用方式
  351. const handleSelectElder = async (item: any) => {
  352. if (item && item.id) {
  353. dataForm.elderId = item.id
  354. dataForm.idCard = item.idCard || ''
  355. // 清空之前的风险数据和选项
  356. riskOptionsMap.value = {
  357. asphyxiation: [],
  358. pressureUlcer: [],
  359. fall: [],
  360. bedFall: [],
  361. scald: [],
  362. wandering: [],
  363. selfHarm: [],
  364. foodDrug: [],
  365. entertainment: []
  366. }
  367. riskTableItems.value.forEach(tableItem => {
  368. tableItem.recordId = null
  369. tableItem.riskLevel = ''
  370. tableItem.lowChecked = false
  371. tableItem.mediumChecked = false
  372. tableItem.highChecked = false
  373. })
  374. // 获取长者详情
  375. try {
  376. const res = await getElderInfoById(item.id)
  377. elderDetail.value = res
  378. // 加载长者的风险评估数据
  379. await loadElderRiskData(item.id)
  380. } catch (e) {
  381. console.error('获取长者详情失败', e)
  382. }
  383. }
  384. }
  385. // 打开弹窗
  386. const open = async (row?: any, viewMode = false) => {
  387. dialogVisible.value = true
  388. isEdit.value = false
  389. isView.value = viewMode
  390. dialogTitle.value = viewMode ? '查看服务安全风险知情告知书' : '新增服务安全风险知情告知书'
  391. resetForm()
  392. dataForm.tenantId = getTenantId()
  393. if (row && row.id) {
  394. isEdit.value = !viewMode
  395. dialogTitle.value = viewMode ? '查看服务安全风险知情告知书' : '编辑服务安全风险知情告知书'
  396. loading.value = true
  397. try {
  398. const res = await getSafetyRiskNoticeById(row.id)
  399. Object.assign(dataForm, res)
  400. // 解析风险项目
  401. if (res.riskData) {
  402. const parsedData = JSON.parse(res.riskData)
  403. // 解析风险表格数据
  404. riskTableItems.value = riskTableItems.value.map(item => {
  405. const saved = parsedData[item.key]
  406. if (saved) {
  407. return {
  408. ...item,
  409. recordId: saved.recordId || saved.id || null, // 恢复选中的记录ID(兼容旧数据)
  410. riskLevel: saved.level || '',
  411. lowChecked: saved.remark?.low || false,
  412. mediumChecked: saved.remark?.medium || false,
  413. highChecked: saved.remark?.high || false
  414. }
  415. }
  416. return item
  417. })
  418. // 解析其他风险
  419. if (parsedData.otherRisk !== undefined) {
  420. dataForm.otherRisk = parsedData.otherRisk
  421. }
  422. // 加载选项数据,但不覆盖已保存的选择
  423. await loadElderRiskData(res.elderId, true)
  424. }
  425. // 获取长者详情
  426. if (res.elderId) {
  427. const elderRes = await getElderInfoById(res.elderId)
  428. elderDetail.value = elderRes
  429. // 调用 SelectElder 组件的 upData 方法来设置值,但不触发 elder 事件(避免重复加载风险数据)
  430. selectElderRef.value?.upData?.(res.elderName, res.elderId, false)
  431. }
  432. } catch (e) {
  433. console.error('获取详情失败', e)
  434. } finally {
  435. loading.value = false
  436. }
  437. }
  438. }
  439. // 日期格式化
  440. const formatDate = (date: any) => {
  441. if (!date) return ''
  442. const d = new Date(date)
  443. return `${d.getFullYear()}年${d.getMonth() + 1}月${d.getDate()}日`
  444. }
  445. // 重置表单
  446. const resetForm = () => {
  447. Object.assign(dataForm, {
  448. id: undefined,
  449. elderId: '',
  450. idCard: '',
  451. tenantId: getTenantId(),
  452. riskData: '',
  453. otherRisk: '',
  454. nextAssessDate: '',
  455. firstRisk: '',
  456. firstNurseSign: '',
  457. firstElderSign: '',
  458. firstSignDate: '',
  459. secondRisk: '',
  460. secondNurseSign: '',
  461. secondElderSign: '',
  462. secondSignDate: '',
  463. secondChangeRisk: '',
  464. secondRiskLevel: '',
  465. remark: '',
  466. nurseSign: '',
  467. elderSign: '',
  468. signDate: ''
  469. })
  470. elderDetail.value = {}
  471. riskTableItems.value = [
  472. { key: 'asphyxiation', name: '防噎食评估', api: asphyxiationPage, recordId: null as any, riskLevel: '', lowChecked: false, mediumChecked: false, highChecked: false },
  473. { key: 'pressureUlcer', name: '防压疮评估', api: pressureSoresPage, recordId: null as any, riskLevel: '', lowChecked: false, mediumChecked: false, highChecked: false },
  474. { key: 'fall', name: '防跌倒风险评估', api: fallDownPage, recordId: null as any, riskLevel: '', lowChecked: false, mediumChecked: false, highChecked: false },
  475. { key: 'bedFall', name: '防坠床评估', api: fallPreventionPage, recordId: null as any, riskLevel: '', lowChecked: false, mediumChecked: false, highChecked: false },
  476. { key: 'scald', name: '防烫伤评估', api: burnPreventionPage, recordId: null as any, riskLevel: '', lowChecked: false, mediumChecked: false, highChecked: false },
  477. { key: 'wandering', name: '防走失风险评估', api: wanderAwayPage, recordId: null as any, riskLevel: '', lowChecked: false, mediumChecked: false, highChecked: false },
  478. { key: 'selfHarm', name: '防自伤和他伤', api: ngasrPage, recordId: null as any, riskLevel: '', lowChecked: false, mediumChecked: false, highChecked: false },
  479. { key: 'foodDrug', name: '防食品药品误食评估', api: mmseGetPage, recordId: null as any, riskLevel: '', lowChecked: false, mediumChecked: false, highChecked: false },
  480. { key: 'entertainment', name: '防文娱活动意外', api: entertainmentPage, recordId: null as any, riskLevel: '', lowChecked: false, mediumChecked: false, highChecked: false }
  481. ]
  482. riskOptionsMap.value = {
  483. asphyxiation: [],
  484. pressureUlcer: [],
  485. fall: [],
  486. bedFall: [],
  487. scald: [],
  488. wandering: [],
  489. selfHarm: [],
  490. foodDrug: [],
  491. entertainment: []
  492. }
  493. }
  494. // 关闭弹窗
  495. const handleClosed = () => {
  496. dialogVisible.value = false
  497. isView.value = false
  498. }
  499. // 提交表单
  500. const submitForm = async () => {
  501. if (!formRef.value) return
  502. const valid = await formRef.value.validate()
  503. if (!valid) return
  504. submitLoading.value = true
  505. try {
  506. // 处理风险项目数据 - 将表格数据转换为存储格式
  507. const riskDataObj: Record<string, any> = {}
  508. riskTableItems.value.forEach(item => {
  509. riskDataObj[item.key] = {
  510. id: item.recordId, // 选中的评估记录ID
  511. level: item.riskLevel, // 风险等级
  512. // 备注信息
  513. remark: {
  514. low: item.lowChecked,
  515. medium: item.mediumChecked,
  516. high: item.highChecked
  517. }
  518. }
  519. })
  520. const submitData = {
  521. ...dataForm,
  522. riskData: JSON.stringify(riskDataObj)
  523. }
  524. if (isEdit.value) {
  525. await updateSafetyRiskNotice(submitData)
  526. } else {
  527. await addSafetyRiskNotice(submitData)
  528. }
  529. message.success('操作成功')
  530. dialogVisible.value = false
  531. emit('success')
  532. } catch (e) {
  533. console.error('提交失败', e)
  534. } finally {
  535. submitLoading.value = false
  536. }
  537. }
  538. // 打印功能
  539. const handleExport = () => {
  540. // 创建打印窗口
  541. const printWindow = window.open('', '_blank')
  542. if (!printWindow) {
  543. message.error('请允许弹出窗口以进行打印')
  544. return
  545. }
  546. // 获取长者信息
  547. const elderName = elderDetail.value.elderName || ''
  548. const elderSex = getDictLabel(DICT_TYPE.SYSTEM_USER_SEX, elderDetail.value.elderSex) || ''
  549. const elderAge = elderDetail.value.elderAge || ''
  550. const bedName = elderDetail.value.bedName || ''
  551. const nurseLevelName = elderDetail.value.nurseLevelName || ''
  552. const tenantName = userStore.getTenantName || '' // 当前登录的机构名称
  553. // 构建风险表格HTML
  554. let riskTableHtml = ''
  555. riskTableItems.value.forEach((item, index) => {
  556. const riskLevelText = getRiskLevelText(item.riskLevel)
  557. const lowChecked = item.lowChecked ? '☑' : '☐'
  558. const mediumChecked = item.mediumChecked ? '☑' : '☐'
  559. const highChecked = item.highChecked ? '☑' : '☐'
  560. riskTableHtml += `
  561. <tr>
  562. <td style="border: 1px solid #333; padding: 8px; text-align: center;">${index + 1}</td>
  563. <td style="border: 1px solid #333; padding: 8px;">${item.name}</td>
  564. <td style="border: 1px solid #333; padding: 8px; text-align: center;">${riskLevelText}</td>
  565. <td style="border: 1px solid #333; padding: 8px;">
  566. <span>风险程度:</span>
  567. <span>${lowChecked}低危</span>
  568. <span style="margin-left: 10px;">${mediumChecked}中危</span>
  569. <span style="margin-left: 10px;">${highChecked}高危</span>
  570. </td>
  571. </tr>
  572. `
  573. })
  574. // 预计下次评估日期
  575. const nextAssessDate = dataForm.nextAssessDate ? formatDate(dataForm.nextAssessDate) : '无'
  576. // 构建打印内容
  577. const printContent = `
  578. <!DOCTYPE html>
  579. <html>
  580. <head>
  581. <meta charset="UTF-8">
  582. <title>服务安全风险知情告知书</title>
  583. <style>
  584. body { font-family: 'SimSun', serif; font-size: 14px; line-height: 1.6; }
  585. .print-container { width: 210mm; margin: 0 auto; padding: 20mm; }
  586. .title { text-align: center; font-size: 22px; font-weight: bold; margin-bottom: 20px; }
  587. .subtitle { text-align: center; font-size: 16px; margin-bottom: 30px; }
  588. .info-row { display: flex; margin-bottom: 10px; }
  589. .info-item { flex: 3; }
  590. .section-title { font-weight: bold; margin: 20px 0 10px; }
  591. .content-text { text-indent: 2em; margin-bottom: 10px; }
  592. table { width: 100%; border-collapse: collapse; margin: 15px 0; }
  593. th { background-color: #f5f5f5; font-weight: bold; }
  594. .signature-section { margin-top: 30px; }
  595. .signature-title { font-weight: bold; margin-bottom: 10px; }
  596. .signature-item { margin: 15px 0; }
  597. .signature-line { display: inline-block; width: 200px; border-bottom: 1px solid #333; margin-left: 10px; }
  598. .next-date { margin: 15px 0; padding: 10px; background-color: #f5f5f5; }
  599. @media print { body { margin: 0; } .print-container { padding: 10mm; } }
  600. </style>
  601. </head>
  602. <body>
  603. <div class="print-container">
  604. <div class="tenant-name" style="text-align: center; font-size: 18px; font-weight: bold; margin-bottom: 10px;">${tenantName}</div>
  605. <div class="title">服务安全风险知情告知书</div>
  606. <div class="info-row">
  607. <div class="info-item">姓名:${elderName}</div>
  608. <div class="info-item" style="flex: 2">性别:${elderSex}</div>
  609. <div class="info-item" style="flex: 2">年龄:${elderAge}岁</div>
  610. <div class="info-item">床号:${bedName}</div>
  611. <div class="info-item">护理级别:${nurseLevelName}</div>
  612. </div>
  613. <div class="section-title">尊敬的老年人/相关第三方/监护人:</div>
  614. <div class="content-text">您好!感谢您对本机构的信任和支持。</div>
  615. <div class="content-text">
  616. 经我院评估您或您相关第三方的身体状况,您或您亲属在我院养老期间,存在下列服务安全风险。
  617. 为保证对您或您相关第三方的服务质量,特向您告知!我院将针对老年人的情况做好相关防范措施,
  618. 请您理解、配合、并支持相关工作措施的落实。
  619. </div>
  620. <table>
  621. <thead>
  622. <tr>
  623. <th style="border: 1px solid #333; padding: 8px; width: 60px;">序号</th>
  624. <th style="border: 1px solid #333; padding: 8px;">项目</th>
  625. <th style="border: 1px solid #333; padding: 8px; width: 100px;">风险程度</th>
  626. <th style="border: 1px solid #333; padding: 8px;">备注</th>
  627. </tr>
  628. </thead>
  629. <tbody>
  630. ${riskTableHtml}
  631. </tbody>
  632. </table>
  633. <div class="next-date">
  634. <strong>预计下次评估日期:</strong>${dataForm.firstRisk}
  635. </div>
  636. <div class="signature-section">
  637. <div class="signature-title">告知签名:</div>
  638. <div class="content-text">
  639. 通过对老年人的整体评估,老年人有可能发生以上风险,其风险及防范措施已向老年人或相关第三方进行了如实告知。
  640. </div>
  641. <div style="color: #666; font-size: 12px; margin-bottom: 20px;">
  642. (老年人或相关第三方声明:本人已知悉并签字确认。)
  643. </div>
  644. <div class="signature-item">
  645. <span>经办人(护理人员):</span>
  646. </div>
  647. <div class="signature-item">
  648. <span>老年人或相关第三方签名:</span>
  649. </div>
  650. </div>
  651. ${dataForm.remark ? `<div class="section-title">其他备注:</div><div>${dataForm.remark}</div>` : ''}
  652. </div>
  653. <script>
  654. window.onload = function() {
  655. setTimeout(function() {
  656. window.print();
  657. }, 500);
  658. };
  659. <\/script>
  660. </body>
  661. </html>
  662. `
  663. printWindow.document.write(printContent)
  664. printWindow.document.close()
  665. }
  666. // 获取风险等级文本
  667. const getRiskLevelText = (level: string): string => {
  668. const levelMap: Record<string, string> = {
  669. 'none': '无风险',
  670. 'low': '低风险',
  671. 'medium': '中风险',
  672. 'high': '高风险'
  673. }
  674. return levelMap[level] || level || '-'
  675. }
  676. defineExpose({ open })
  677. </script>
  678. <style scoped lang="scss">
  679. .section-title {
  680. font-size: 16px;
  681. font-weight: bold;
  682. color: #333;
  683. margin: 20px 0 15px;
  684. padding-left: 10px;
  685. border-left: 4px solid #409eff;
  686. }
  687. .elder-detail-wrap {
  688. margin: 15px 0;
  689. padding: 15px;
  690. background-color: #f5f7fa;
  691. border-radius: 4px;
  692. }
  693. .notice-content {
  694. padding: 15px;
  695. background-color: #fafafa;
  696. border-radius: 4px;
  697. margin-bottom: 20px;
  698. .notice-header {
  699. font-weight: bold;
  700. margin-bottom: 10px;
  701. }
  702. .notice-text {
  703. text-indent: 2em;
  704. line-height: 1.8;
  705. margin-bottom: 10px;
  706. }
  707. }
  708. // 风险表格样式
  709. .risk-table-wrapper {
  710. margin: 20px 0;
  711. overflow-x: auto;
  712. .risk-table {
  713. width: 100%;
  714. border-collapse: collapse;
  715. font-size: 14px;
  716. th, td {
  717. border: 1px solid #dcdfe6;
  718. padding: 10px 8px;
  719. text-align: center;
  720. }
  721. th {
  722. background-color: #f5f7fa;
  723. font-weight: bold;
  724. }
  725. .col-index {
  726. width: 60px;
  727. }
  728. .col-project {
  729. width: 180px;
  730. text-align: left;
  731. padding-left: 15px;
  732. }
  733. .col-level {
  734. width: 140px;
  735. }
  736. .col-remark {
  737. width: auto;
  738. text-align: left;
  739. padding-left: 15px;
  740. .remark-text {
  741. margin-right: 10px;
  742. color: #606266;
  743. }
  744. :deep(.el-checkbox) {
  745. margin-right: 15px;
  746. }
  747. }
  748. }
  749. }
  750. // 预计下次评估日期
  751. .next-assess-date {
  752. display: flex;
  753. align-items: center;
  754. margin: 20px 0;
  755. padding: 15px;
  756. background-color: #f5f7fa;
  757. border-radius: 4px;
  758. .date-label {
  759. font-weight: bold;
  760. margin-right: 15px;
  761. color: #333;
  762. }
  763. .date-value {
  764. margin-left: 15px;
  765. color: #606266;
  766. }
  767. }
  768. // 签名区域
  769. .signature-section {
  770. margin-top: 30px;
  771. padding: 20px;
  772. background-color: #fafafa;
  773. border-radius: 4px;
  774. border: 1px solid #ebeef5;
  775. .signature-title {
  776. font-size: 16px;
  777. font-weight: bold;
  778. margin-bottom: 15px;
  779. color: #333;
  780. }
  781. .signature-desc {
  782. text-indent: 2em;
  783. line-height: 1.8;
  784. margin-bottom: 10px;
  785. color: #606266;
  786. }
  787. .signature-declare {
  788. text-indent: 2em;
  789. margin-bottom: 25px;
  790. color: #909399;
  791. font-size: 13px;
  792. }
  793. .signature-item {
  794. display: flex;
  795. align-items: center;
  796. margin-bottom: 20px;
  797. .signature-label {
  798. font-weight: bold;
  799. white-space: nowrap;
  800. color: #333;
  801. }
  802. .signature-line {
  803. flex: 1;
  804. height: 1px;
  805. border-bottom: 1px solid #333;
  806. margin-left: 10px;
  807. min-width: 200px;
  808. }
  809. }
  810. }
  811. .other-risk {
  812. margin-top: 15px;
  813. }
  814. </style>