index.vue 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. <template>
  2. <div class="position-relative">
  3. <v-tabs v-model="tab" align-tabs="start" color="primary" bg-color="#f7f8fa" @update:modelValue="handleChangeTab">
  4. <v-tab v-for="(k, index) in tabList" :key="index" :value="k.value">{{ k.label }}</v-tab>
  5. </v-tabs>
  6. <div v-if="items?.length">
  7. <ItemPage class="mt-3" :items="items" :tab="tab" @preview="handlePreview" @refresh="handleChangePage(1)" />
  8. <CtPagination
  9. v-if="total > 0"
  10. :total="total"
  11. :page="query.pageNo"
  12. :limit="query.pageSize"
  13. @handleChange="handleChangePage"
  14. ></CtPagination>
  15. </div>
  16. <Empty v-else :elevation="false" />
  17. <!-- 生成实习证书 -->
  18. <div class="position-absolute position-relative" style="left: -9999px; bottom: 0;" ref="share">
  19. <img src="https://minio.citupro.com/dev/static/bgc.jpg" width="500" height="700" cover />
  20. <div class="cer-introduce">
  21. 兹有
  22. <span class="cer-text">{{ studentInfo?.schoolInfo?.name }}</span>
  23. <span class="cer-text">{{ studentInfo?.major?.nameCn }}</span>
  24. 专业<span class="cer-text">{{ itemData?.person?.name }}</span>
  25. 同学于<span class="cer-text">{{ itemData?.startTime ? timesTampChange(itemData?.startTime, 'Y-M-D') : '' }}</span>
  26. 至<span class="cer-text">{{ itemData?.endTime ? timesTampChange(itemData?.endTime, 'Y-M-D') : '' }}</span>
  27. 在<span class="cer-text">{{ formatName(itemData?.enterprise?.anotherName || itemData?.enterprise?.name) }}</span>
  28. 实习。
  29. </div>
  30. <div class="cer-comment">{{ itemData?.evaluate }}</div>
  31. <div class="cer-prove">特此证明。</div>
  32. <div class="cer-end">
  33. <div>{{ itemData?.createTime ? timesTampChange(itemData?.createTime, 'Y-M-D') : '' }}</div>
  34. </div>
  35. </div>
  36. </div>
  37. <Loading :visible="showLoading"></Loading>
  38. </template>
  39. <script setup>
  40. // 实习企业
  41. defineOptions({ name: 'PersonalCenterStudentInternshipCompany'})
  42. import { ref, onMounted } from 'vue'
  43. import { getStudentPracticePage } from '@/api/recruit/personal/student.js'
  44. import ItemPage from './item.vue'
  45. import { getDict } from '@/hooks/web/useDictionaries'
  46. import { dealDictObjData } from '@/utils/position'
  47. import { usePersonCenterStore } from '@/store/personCenter'
  48. import { formatName } from '@/utils/getText'
  49. import { timesTampChange } from '@/utils/date'
  50. import Snackbar from '@/plugins/snackbar'
  51. import html2canvas from 'html2canvas'
  52. import { DPR } from '@/utils'
  53. const tab = ref(1)
  54. const tabList = ref([])
  55. const items = ref([])
  56. const share = ref(null)
  57. const total = ref(0)
  58. const query = ref({
  59. pageNo: 1,
  60. pageSize: 10,
  61. status: '1'
  62. })
  63. const itemData = ref({})
  64. const studentInfo = ref(localStorage.getItem('studentInfo') ? JSON.parse(localStorage.getItem('studentInfo')) : {})
  65. const getList = async () => {
  66. query.value.studentProcessStatus = tabList.value[tab.value]?.value
  67. const result = await getStudentPracticePage(query.value)
  68. items.value = result?.list.map(e => {
  69. e.enterprise = dealDictObjData({}, e.enterprise)
  70. e.job = dealDictObjData({}, e.job)
  71. return e
  72. })
  73. total.value = result?.total || 0
  74. }
  75. onMounted(async () => {
  76. const { data } = await getDict('student_practice_status')
  77. tabList.value = data || []
  78. await getList()
  79. })
  80. const handleChangeTab = () => {
  81. items.value = []
  82. total.value = 0
  83. query.value.pageNo = 1
  84. getList()
  85. }
  86. const handleChangePage = (page) => {
  87. query.value.pageNo = page
  88. getList()
  89. }
  90. // 生成实习证书图片
  91. const showLoading = ref(false)
  92. const personCenterStore = usePersonCenterStore()
  93. const generateAndDownloadImage = async () => {
  94. try {
  95. const canvas = await html2canvas(share.value, { scale: DPR(), useCORS: true })
  96. const image = canvas.toDataURL().replace(/^data:image\/(png|jpg);base64,/, '')
  97. const fileName = `实习证书 - ${formatName(itemData.value?.enterpriseName)}`
  98. personCenterStore.setPreviewData([`data:image/png;base64,${image}`], 0, fileName)
  99. showLoading.value = false
  100. } catch (error) {
  101. console.error('图片生成失败', error)
  102. Snackbar.warning('加载失败,请稍后重试')
  103. showLoading.value = false
  104. }
  105. }
  106. // 实习证书预览
  107. const handlePreview = (item) => {
  108. itemData.value = item
  109. if (!share.value) return
  110. showLoading.value = true
  111. setTimeout(() => {
  112. generateAndDownloadImage()
  113. }, 1000)
  114. }
  115. </script>
  116. <style scoped lang="scss">
  117. .cer-text{
  118. text-decoration: underline;
  119. margin: 0 3px;
  120. font-weight: 700;
  121. }
  122. .cer-introduce{
  123. width: 70%;
  124. position: absolute;
  125. top: 51%;
  126. left: 50%;
  127. transform: translate(-50%,-50%);
  128. text-indent: 2em;
  129. }
  130. .cer-comment{
  131. width: 70%;
  132. position: absolute;
  133. top: 68%;
  134. left: 50%;
  135. transform: translate(-50%,-50%);
  136. text-indent: 2em;
  137. display: -webkit-box;
  138. -webkit-box-orient: vertical;
  139. -webkit-line-clamp: 3;
  140. overflow: hidden;
  141. text-overflow: ellipsis;
  142. }
  143. .cer-prove{
  144. width: 70%;
  145. position: absolute;
  146. top: 82%;
  147. left: 50%;
  148. transform: translate(-50%,-50%);
  149. text-indent: 2em;
  150. }
  151. .cer-end{
  152. position: absolute;
  153. top: 87%;
  154. right: 16%;
  155. }
  156. </style>