_sample.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. <script setup lang="ts" name="Sample">
  2. import apis from "@a"
  3. import dayjs from "dayjs"
  4. import ChickenModal from "@v/common/modal/chickenModal.vue"
  5. import type { ToolBtn } from "@@@/table/models"
  6. import EDetail from "../experiment/_detail.vue"
  7. import { Vue3NextQrcode } from "vue3-next-qrcode"
  8. const props = withDefaults(
  9. defineProps<{
  10. actions: ("update" | "delete" | "sample" | "destroy" | "flow" | "qrCode" | "audit" | "reject")[]
  11. customBtns?: ToolBtn[]
  12. status?: number
  13. createBy?: number
  14. }>(),
  15. {
  16. customBtns: () => {
  17. return []
  18. }
  19. }
  20. )
  21. const tableRef = ref()
  22. const modalRef = ref()
  23. const modalType = ref("C")
  24. const opts = reactive({
  25. columns: () =>
  26. [
  27. { field: "id", name: "样本ID", width: 100, isSort: true, visible: false, tooltip: true },
  28. {
  29. field: "sampleName",
  30. name: "样本名称",
  31. visible: true,
  32. isSort: false,
  33. width: "200",
  34. tooltip: true
  35. },
  36. {
  37. field: "description",
  38. name: "样本描述",
  39. visible: true,
  40. isSort: false,
  41. width: "auto",
  42. tooltip: true
  43. },
  44. // {
  45. // field: "batchId",
  46. // name: "取样批次",
  47. // visible: true,
  48. // isSort: false,
  49. // width: "auto",
  50. // tooltip: true
  51. // },
  52. {
  53. field: "electronicId",
  54. name: "取样个体电子ID",
  55. visible: true,
  56. isSort: false,
  57. width: "120",
  58. tooltip: true
  59. },
  60. // {field: "description", name: "样本描述", visible: true, isSort: false, width: "auto", tooltip: true},
  61. { field: "sampleType", name: "样品类型", visible: true, isSort: false, width: 120 },
  62. { field: "sampleTime", name: "取样时间", visible: true, isSort: false, width: 140 },
  63. { field: "sampleStatus", name: "样品状态", visible: true, isSort: false, width: 120 },
  64. { field: "actions", name: `操作`, width: 150 }
  65. ] as any[],
  66. queryParams: {
  67. sampleName: undefined,
  68. batchId: undefined,
  69. chickenId: undefined,
  70. electronicId: undefined,
  71. description: undefined,
  72. sampleType: undefined,
  73. sampleStatus: props.status
  74. },
  75. searchFormItems: [
  76. {
  77. field: "sampleName",
  78. label: "样本名称",
  79. class: "w-100",
  80. required: false,
  81. placeholder: "请输入样本名称",
  82. component: "I",
  83. listeners: {
  84. keyup: (e: KeyboardEvent) => {
  85. if (e.code == "Enter") {
  86. handleQuery()
  87. }
  88. }
  89. }
  90. },
  91. {
  92. field: "electronicId",
  93. disabled: true,
  94. label: "取样个体电子ID",
  95. class: "w-100",
  96. required: false,
  97. placeholder: "请选择个体电子ID",
  98. component: "I",
  99. append: "icon",
  100. appendClickFunc: () => {
  101. handleOpenChickenModal("search")
  102. }
  103. },
  104. {
  105. field: "sampleType",
  106. label: "样品类型",
  107. class: "w-100",
  108. required: false,
  109. component: "Dict",
  110. props: {
  111. placeholder: "请选择样品类型",
  112. dictType: "experiment_sample_type",
  113. valueIsNumber: 1,
  114. type: "select"
  115. },
  116. listeners: {
  117. change: () => {
  118. handleQuery()
  119. }
  120. }
  121. },
  122. {
  123. show: () => props.status == undefined,
  124. field: "sampleStatus",
  125. label: "样品状态",
  126. class: "w-100",
  127. required: false,
  128. component: "Dict",
  129. props: {
  130. placeholder: "请选择样品状态",
  131. dictType: "experiment_sample_status",
  132. valueIsNumber: 1,
  133. type: "select"
  134. },
  135. listeners: {
  136. change: () => {
  137. handleQuery()
  138. }
  139. }
  140. }
  141. ] as any,
  142. permission: "",
  143. handleBtns: [],
  144. handleFuns: {
  145. handleCreate,
  146. handleUpdate: () => {
  147. const row = tableRef.value.getSelected()
  148. handleUpdate(row)
  149. },
  150. handleDelete: () => {
  151. const rows = tableRef.value.getSelecteds()
  152. handleDelete(rows)
  153. }
  154. },
  155. customBtns: props.customBtns,
  156. tableListFun: apis.experiment.sampleApi.list,
  157. getEntityFun: apis.experiment.sampleApi.get,
  158. deleteEntityFun: apis.experiment.sampleApi.del,
  159. exportUrl: apis.experiment.sampleApi.exportUrl,
  160. exportName: "Sample",
  161. modalTitle: "实验样本",
  162. formItems: [
  163. {
  164. field: "sampleName",
  165. label: "样本名称",
  166. class: "w-100",
  167. required: true,
  168. placeholder: "请输入样本名称",
  169. component: "I"
  170. },
  171. // {
  172. // field: "batchId",
  173. // label: "取样批次",
  174. // class: "w-100",
  175. // required: false,
  176. // placeholder: "请输入取样批次",
  177. // component: "I"
  178. // },
  179. {
  180. show: () => modalType.value == "S",
  181. field: "electronicId",
  182. label: "取样个体电子ID",
  183. class: "w-100",
  184. required: true,
  185. placeholder: "请选择取样个体电子ID",
  186. component: "I",
  187. append: "icon",
  188. appendClickFunc: () => {
  189. handleOpenChickenModal("form")
  190. }
  191. },
  192. {
  193. show: () => modalType.value == "S",
  194. field: "sampleType",
  195. label: "样品类型",
  196. class: "w-100",
  197. required: true,
  198. component: "Dict",
  199. props: {
  200. placeholder: "请选择样品类型",
  201. dictType: "experiment_sample_type",
  202. type: "select",
  203. valueIsNumber: 1
  204. }
  205. },
  206. {
  207. field: "description",
  208. label: "样本描述",
  209. class: "w-100",
  210. required: false,
  211. placeholder: "请输入样本描述",
  212. component: "I",
  213. props: {
  214. type: "textarea",
  215. rows: 5
  216. }
  217. }
  218. ] as any,
  219. resetForm: () => {
  220. form.value = emptyFormData.value
  221. },
  222. labelWidth: "80px",
  223. emptyFormData: {
  224. id: undefined,
  225. sampleName: undefined,
  226. batchId: undefined,
  227. chickenId: undefined,
  228. electronicId: undefined,
  229. description: undefined,
  230. sampleType: undefined,
  231. sampleStatus: undefined
  232. }
  233. })
  234. const { queryParams, emptyFormData } = toRefs(opts)
  235. const form = ref<any>(emptyFormData.value)
  236. const modalTitle = computed(() => {
  237. return modalType.value == "C" || modalType.value == "U"
  238. ? "样本"
  239. : modalType.value == "S"
  240. ? "个体取样"
  241. : ""
  242. })
  243. /** 搜索按钮操作 */
  244. function handleQuery(query?: any) {
  245. query = query || tableRef.value?.getQueryParams() || queryParams.value
  246. addDateRange(query, query.dateRangeCreateTime)
  247. tableRef.value?.query(query)
  248. }
  249. /** 重置按钮操作 */
  250. function resetQuery(query?: any) {
  251. query = query || tableRef.value?.getQueryParams() || queryParams.value
  252. query.dateRangeCreateTime = [] as any
  253. addDateRange(query, query.dateRangeCreateTime)
  254. //
  255. }
  256. function handleCreate() {
  257. modalType.value = "C"
  258. tableRef.value.defaultHandleFuns.handleCreate()
  259. }
  260. /** 修改按钮操作 */
  261. function handleUpdate(row: any) {
  262. modalType.value = "U"
  263. tableRef.value.defaultHandleFuns.handleUpdate("", row)
  264. }
  265. function handleSample(row: any) {
  266. modalType.value = "S"
  267. apis.experiment.sampleApi.get(row.id).then((res: any) => {
  268. form.value = res.data
  269. form.value.sampleStatus = 2
  270. modalRef.value.changePrefixTitle("")
  271. modalRef.value.show()
  272. })
  273. }
  274. /** 删除按钮操作 */
  275. function handleDelete(rows: any[]) {
  276. tableRef.value.defaultHandleFuns.handleDelete("", rows)
  277. }
  278. /** 提交按钮 */
  279. function submitForm() {
  280. if (modalType.value == "C" || modalType.value == "U") {
  281. apis.experiment.sampleApi.addOrUpdate(form.value).then(() => {
  282. handleQuery()
  283. })
  284. } else if (modalType.value == "S") {
  285. apis.experiment.sampleApi.smaple(form.value).then(() => {
  286. message.msgSuccess("取样成功")
  287. handleQuery()
  288. })
  289. }
  290. }
  291. function checkBtnShow(btn: any, row: any) {
  292. if (props.actions.includes(btn)) {
  293. return true
  294. }
  295. return false
  296. }
  297. const chickenModalRef = ref()
  298. const chickenModalSelectType = ref()
  299. function handleOpenChickenModal(type: string) {
  300. chickenModalSelectType.value = type
  301. if (chickenModalSelectType.value == "search") {
  302. queryParams.value.chickenId = undefined
  303. queryParams.value.electronicId = ""
  304. tableRef.value.setQueryParams(queryParams.value)
  305. }
  306. chickenModalRef.value.open()
  307. }
  308. function onChickenConfirm(data: any) {
  309. if (chickenModalSelectType.value == "search") {
  310. queryParams.value.chickenId = data[0].id
  311. queryParams.value.electronicId = data[0].electronicId
  312. handleQuery(queryParams.value)
  313. } else if (chickenModalSelectType.value === "form") {
  314. console.log("data", data)
  315. form.value.chickenId = data[0].id
  316. form.value.electronicId = data[0].electronicId
  317. form.value.batchNum = data[0].batchNum
  318. }
  319. }
  320. function handleDestory(row) {
  321. message.confirm("确定要销毁该样本吗?").then(() => {
  322. apis.experiment.sampleApi.destroy(row.id).then(() => {
  323. handleQuery()
  324. })
  325. })
  326. }
  327. const flowModalRef = ref()
  328. const flowData = ref()
  329. function handleFlow(row) {
  330. apis.experiment.sampleApi.queryFlowLogs(row.id).then((res: any) => {
  331. flowData.value = res.data
  332. flowModalRef.value.show()
  333. })
  334. }
  335. const qrModalRef = ref()
  336. const qrModalData = ref()
  337. const qrCode = ref({
  338. logo: "/media/logo.png",
  339. size: 300,
  340. // colorDark: "#0e9489",
  341. colorDark: "#000000",
  342. text: ""
  343. })
  344. function handleQrCode(row) {
  345. qrModalData.value = row
  346. qrCode.value.text = `vb@sample@/sample/${row.id}`
  347. qrModalRef.value.show()
  348. }
  349. function handleDownloadQr(id: string) {
  350. // 获取整个元素,包括标题和二维码
  351. const element = document.querySelector("#" + id) as HTMLElement
  352. if (!element) return
  353. // 使用html2canvas将整个元素转换为图片
  354. import("html2canvas").then((html2canvas) => {
  355. html2canvas.default(element).then((canvas) => {
  356. // 将canvas转换为图片blob
  357. canvas.toBlob((blob) => {
  358. if (!blob) return
  359. // 创建下载链接
  360. const url = URL.createObjectURL(blob)
  361. const a = document.createElement("a")
  362. a.href = url
  363. a.download = `${qrModalData.value.sampleName}样品二维码`
  364. document.body.appendChild(a)
  365. a.click()
  366. document.body.removeChild(a)
  367. URL.revokeObjectURL(url)
  368. })
  369. })
  370. })
  371. }
  372. const detailRef = ref()
  373. function handleDetail(row: any) {
  374. apis.experiment.sampleApi.queryExperiment(row.id).then((res) => {
  375. detailRef.value.open(res.data)
  376. })
  377. }
  378. const pdfPreviewRef = ref()
  379. function handlePreviewPdf(file: string) {
  380. const fileId = file.split("$")[0],
  381. fileName = file.split("$")[1]
  382. pdfPreviewRef.value.open({
  383. fileId,
  384. fileName
  385. })
  386. }
  387. // function handlePrintQr(id: string) {
  388. // const printContent = document.querySelector("#" + id)
  389. // if (!printContent) return
  390. // // 创建打印样式
  391. // const style = document.createElement("style")
  392. // style.innerHTML = `
  393. // @media print {
  394. // body * {
  395. // display: none !important;
  396. // }
  397. // #print-area, #print-area * {
  398. // display: block !important;
  399. // }
  400. // #print-area {
  401. // position: absolute;
  402. // top: 0;
  403. // left: 0;
  404. // width: 100vw;
  405. // height: 600px;
  406. // display: flex !important;
  407. // flex-direction: column;
  408. // align-items: center;
  409. // justify-content: center;
  410. // font-family: Arial, sans-serif;
  411. // }
  412. // }
  413. // `
  414. // document.head.appendChild(style)
  415. // // 创建打印区域
  416. // const printArea = document.createElement("div")
  417. // printArea.id = "print-area"
  418. // // printArea.innerHTML = `
  419. // // <div style="width:100%;display:flex;flex-direction: column;align-items: center;justify-content: center;">${printContent.innerHTML}</div>
  420. // // `
  421. // printArea.innerHTML = printContent.innerHTML
  422. // document.body.appendChild(printArea)
  423. // // 打印
  424. // window.print()
  425. // // 清理
  426. // setTimeout(() => {
  427. // document.head.removeChild(style)
  428. // document.body.removeChild(printArea)
  429. // }, 500)
  430. // }
  431. </script>
  432. <template>
  433. <div class="app-container">
  434. <VbDataTable
  435. ref="tableRef"
  436. keyField="id"
  437. :columns="[]"
  438. :columns-fun="opts.columns"
  439. :handle-perm="opts.permission"
  440. :handle-btns="opts.handleBtns"
  441. :handle-funs="opts.handleFuns"
  442. :search-form-items="opts.searchFormItems"
  443. :custom-btns="opts.customBtns"
  444. :remote-fun="opts.tableListFun"
  445. :get-entity-fun="opts.getEntityFun"
  446. :delete-entity-fun="opts.deleteEntityFun"
  447. :export-url="opts.exportUrl"
  448. :export-name="opts.exportName"
  449. :modal="modalRef"
  450. :reset-form-fun="opts.resetForm"
  451. v-model:form-data="form"
  452. v-model:query-params="queryParams"
  453. :check-multiple="true"
  454. :reset-search-form-fun="resetQuery"
  455. :custom-search-fun="handleQuery">
  456. <template #sampleType="{ row }">
  457. <DictTag
  458. type="experiment_sample_type"
  459. :value-is-number="1"
  460. :value="row.sampleType"></DictTag>
  461. </template>
  462. <template #sampleStatus="{ row }">
  463. <DictTag
  464. type="experiment_sample_status"
  465. :value-is-number="1"
  466. :value="row.sampleStatus"></DictTag>
  467. </template>
  468. <template #sampleTime="{ row }">
  469. <template v-if="row.sampleTime">
  470. {{ dayjs(row.sampleTime).format("YYYY/MM/DD HH:mm:ss") }}
  471. </template>
  472. <template v-else>-</template>
  473. </template>
  474. <template #actions="{ row }">
  475. <template v-if="row.sampleStatus == 1">
  476. <vb-tooltip v-if="checkBtnShow('update', row)" content="修改" placement="top">
  477. <el-button
  478. link
  479. type="primary"
  480. @click="handleUpdate(row)"
  481. v-hasPermission="'experiment:sample:edit'">
  482. <template #icon>
  483. <VbIcon icon-name="notepad-edit" icon-type="duotone" class="fs-3"></VbIcon>
  484. </template>
  485. </el-button>
  486. </vb-tooltip>
  487. <vb-tooltip v-if="checkBtnShow('sample', row)" content="取样" placement="top">
  488. <el-button link type="success" @click="handleSample(row)">
  489. <template #icon>
  490. <VbIcon icon-name="shield-tick" icon-type="duotone" class="fs-3"></VbIcon>
  491. </template>
  492. </el-button>
  493. </vb-tooltip>
  494. </template>
  495. <template v-if="row.sampleStatus == 2 || row.sampleStatus == 3">
  496. <vb-tooltip v-if="checkBtnShow('flow', row)" content="流转记录" placement="top">
  497. <el-button link type="primary" @click="handleFlow(row)">
  498. <template #icon>
  499. <VbIcon icon-name="book" icon-type="duotone" class="fs-3"></VbIcon>
  500. </template>
  501. </el-button>
  502. </vb-tooltip>
  503. <vb-tooltip v-if="checkBtnShow('qrCode', row)" content="生成二维码" placement="top">
  504. <el-button link type="success" @click="handleQrCode(row)">
  505. <template #icon>
  506. <VbIcon icon-name="scan-barcode" icon-type="duotone" class="fs-3"></VbIcon>
  507. </template>
  508. </el-button>
  509. </vb-tooltip>
  510. </template>
  511. <template v-if="row.sampleStatus == 2">
  512. <vb-tooltip v-if="checkBtnShow('destroy', row)" content="销毁" placement="top">
  513. <el-button
  514. link
  515. type="danger"
  516. @click="handleDestory(row)"
  517. v-hasPermission="'experiment:sample:destroy'">
  518. <template #icon>
  519. <VbIcon icon-name="trash-square" icon-type="duotone" class="fs-3"></VbIcon>
  520. </template>
  521. </el-button>
  522. </vb-tooltip>
  523. </template>
  524. <template v-if="row.sampleStatus == 3">
  525. <vb-tooltip v-if="checkBtnShow('destroy', row)" content="实验详情" placement="top">
  526. <el-button link type="warning" @click="handleDetail(row)">
  527. <template #icon>
  528. <VbIcon icon-name="book-open" icon-type="duotone" class="fs-3"></VbIcon>
  529. </template>
  530. </el-button>
  531. </vb-tooltip>
  532. </template>
  533. <template v-if="row.sampleStatus == 1 || row.sampleStatus == 4">
  534. <vb-tooltip v-if="checkBtnShow('delete', row)" content="删除" placement="top">
  535. <el-button
  536. link
  537. type="danger"
  538. @click="handleDelete([row])"
  539. v-hasPermission="'experiment:sample:remove'">
  540. <template #icon>
  541. <VbIcon icon-name="trash-square" icon-type="duotone" class="fs-3"></VbIcon>
  542. </template>
  543. </el-button>
  544. </vb-tooltip>
  545. </template>
  546. </template>
  547. </VbDataTable>
  548. <VbModal
  549. v-model:modal="modalRef"
  550. :title="modalTitle"
  551. :form-data="form"
  552. :form-items="opts.formItems"
  553. :label-width="opts.labelWidth"
  554. append-to-body
  555. @confirm="submitForm"></VbModal>
  556. <ChickenModal
  557. ref="chickenModalRef"
  558. modal-title="请选择电子ID"
  559. :multiple="false"
  560. @confirm="onChickenConfirm"></ChickenModal>
  561. <VbModal
  562. v-model:modal="qrModalRef"
  563. title="样品二维码"
  564. :confirm-btn="false"
  565. :close-btn-class="'btn btn-danger'"
  566. append-to-body>
  567. <template #body>
  568. <div class="w-100 d-flex justify-content-center align-items-center">
  569. <div id="qr" class="d-flex flex-column align-items-center">
  570. <span class="fs-5 fw-bold mt-5 mb-0 qr-title">
  571. {{ qrModalData?.sampleName }} 样品二维码
  572. </span>
  573. <Vue3NextQrcode
  574. :text="qrCode.text"
  575. :size="qrCode.size"
  576. :color-dark="qrCode.colorDark"
  577. :logo-image="qrCode.logo"
  578. :logo-scale="0.15"
  579. :logo-margin="0"
  580. :logo-corner-radius="20"
  581. :margin="20" />
  582. </div>
  583. </div>
  584. <div class="text-center">
  585. <el-button type="primary" class="mx-5 w-150px" @click="handleDownloadQr('qr')">
  586. 保存
  587. </el-button>
  588. <!-- <el-button type="primary" class="mx-5 w-150px" @click="handlePrintQr('qr')">
  589. 打印
  590. </el-button> -->
  591. </div>
  592. </template>
  593. </VbModal>
  594. <VbModal
  595. v-model:modal="flowModalRef"
  596. title="样品流转记录"
  597. modal-dialog-style="max-width: 500px"
  598. :confirm-btn="false"
  599. :close-btn-class="'btn btn-danger'"
  600. append-to-body>
  601. <template #body>
  602. <el-table :data="flowData" stripe :show-header="false">
  603. <!-- <el-table-column label="序号" type="index" width="60" align="center"></el-table-column> -->
  604. <el-table-column label="序号" width="100" type="index" align="center"></el-table-column>
  605. <el-table-column label="操作人" prop="handlerName" align="center"></el-table-column>
  606. <el-table-column label="操作时间" prop="handleTime" align="center">
  607. <template #default="{ row }">
  608. <span>{{ dayjs(row.handleTime).format("YYYY/MM/DD HH:mm:ss") }}</span>
  609. </template>
  610. </el-table-column>
  611. </el-table>
  612. </template>
  613. </VbModal>
  614. <EDetail ref="detailRef"></EDetail>
  615. <VbPdfPreview ref="pdfPreviewRef" />
  616. </div>
  617. </template>