workDraft.vue 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948
  1. <template>
  2. <div class="work-draft-container">
  3. <div class="editor-header">
  4. <span class="editor-title">工作底稿在线编辑:</span>
  5. <el-button type="primary" size="small" @click="handleOnlineEdit">
  6. 保存
  7. </el-button>
  8. </div>
  9. <div class="editor-wrapper">
  10. <ht-editor
  11. v-model="workingPaperContent"
  12. class="working-paper-editor"
  13. height="500px"
  14. width="100%"
  15. :config="editorConfig"
  16. @ready="onEditorReady"
  17. />
  18. </div>
  19. <!-- 工作底稿列表 -->
  20. <div>
  21. <el-button type="primary" size="small" @click="handleAddWorkingPaper">
  22. 核增核减记录
  23. </el-button>
  24. </div>
  25. <el-table
  26. v-loading="loading"
  27. :data="workingPaperRecords"
  28. style="width: 100%; margin-top: 20px"
  29. border
  30. >
  31. <!-- <el-table-column type="selection" width="55"></el-table-column> -->
  32. <el-table-column
  33. prop="id"
  34. label="序号"
  35. width="80 "
  36. header-align="center"
  37. align="center"
  38. >
  39. <template slot-scope="scope">
  40. {{ scope.$index + 1 }}
  41. </template>
  42. </el-table-column>
  43. <el-table-column
  44. prop="auditSubject"
  45. label="核增核减科目"
  46. width="180"
  47. header-align="center"
  48. align="left"
  49. ></el-table-column>
  50. <el-table-column
  51. prop="basicInfo"
  52. label="基本情况"
  53. width="200"
  54. header-align="center"
  55. align="left"
  56. ></el-table-column>
  57. <el-table-column
  58. prop="auditDesc"
  59. label="核增核减说明"
  60. header-align="center"
  61. align="center"
  62. ></el-table-column>
  63. <el-table-column
  64. prop="auditTime"
  65. label="时间"
  66. width="180"
  67. header-align="center"
  68. align="center"
  69. ></el-table-column>
  70. <el-table-column
  71. label="附件"
  72. width="120"
  73. header-align="center"
  74. align="center"
  75. >
  76. <template slot-scope="scope">
  77. <el-button
  78. v-if="scope.row.attachmentUrl"
  79. type="text"
  80. size="small"
  81. icon="iconfont-5039297 icon-wenjian"
  82. @click="handlePreviewWorkingPaperAttachment(scope.row)"
  83. >
  84. 查看
  85. </el-button>
  86. </template>
  87. </el-table-column>
  88. <el-table-column
  89. label="操作"
  90. width="150"
  91. header-align="center"
  92. align="center"
  93. >
  94. <template slot-scope="scope">
  95. <el-button
  96. type="text"
  97. size="small"
  98. @click="handleEditWorkingPaper(scope.row)"
  99. >
  100. 修改
  101. </el-button>
  102. <el-button
  103. type="text"
  104. size="small"
  105. @click="handleDeleteWorkingPaper(scope.row)"
  106. >
  107. 删除
  108. </el-button>
  109. </template>
  110. </el-table-column>
  111. </el-table>
  112. <!-- 工作底稿编辑弹窗 -->
  113. <el-dialog
  114. :title="workingPaperDialogTitle"
  115. :visible.sync="workingPaperDialogVisible"
  116. width="70%"
  117. :modal="false"
  118. append-to-body
  119. >
  120. <el-form
  121. ref="workingPaperForm"
  122. :model="workingPaperForm"
  123. :rules="workingPaperRules"
  124. label-width="120px"
  125. >
  126. <el-form-item label="被审核科目" prop="subject">
  127. <el-input
  128. v-model="workingPaperForm.subject"
  129. placeholder="请输入被审核科目"
  130. maxlength="30"
  131. show-word-limit
  132. />
  133. </el-form-item>
  134. <el-form-item label="基本情况" prop="basicSituation">
  135. <el-input
  136. v-model="workingPaperForm.basicSituation"
  137. type="textarea"
  138. :rows="4"
  139. maxlength="500"
  140. show-word-limit
  141. placeholder="请输入基本情况"
  142. />
  143. </el-form-item>
  144. <el-form-item label="核增核减说明" prop="description">
  145. <el-input
  146. v-model="workingPaperForm.description"
  147. type="textarea"
  148. :rows="4"
  149. maxlength="500"
  150. show-word-limit
  151. placeholder="请输入核增核减说明"
  152. />
  153. </el-form-item>
  154. <el-form-item label="日期">
  155. <el-date-picker
  156. v-model="workingPaperForm.auditDate"
  157. type="date"
  158. placeholder="选择日期"
  159. style="width: 100%"
  160. />
  161. </el-form-item>
  162. <el-form-item label="时间范围">
  163. <div style="display: flex; gap: 10px">
  164. <el-time-picker
  165. v-model="workingPaperForm.startTime"
  166. format="HH:mm"
  167. placeholder="开始时间"
  168. style="flex: 1"
  169. @change="handleStartTimeChange"
  170. />
  171. <span>-</span>
  172. <el-time-picker
  173. v-model="workingPaperForm.endTime"
  174. format="HH:mm"
  175. placeholder="结束时间"
  176. style="flex: 1"
  177. :picker-options="endTimePickerOptions"
  178. />
  179. </div>
  180. </el-form-item>
  181. <el-form-item label="上传附件" prop="fileList">
  182. <el-upload
  183. class="upload-demo"
  184. :action="''"
  185. :http-request="handleFileUpload"
  186. :on-remove="handleFileRemove"
  187. :before-upload="beforeFileUpload"
  188. :on-success="handleFileUploadSuccess"
  189. :on-error="handleFileUploadError"
  190. :on-progress="handleFileUploadProgress"
  191. :file-list="workingPaperForm.fileList"
  192. :limit="1"
  193. :on-exceed="handleFileExceed"
  194. >
  195. <el-tooltip
  196. v-show="
  197. !workingPaperForm.fileList ||
  198. workingPaperForm.fileList.length === 0
  199. "
  200. effect="dark"
  201. placement="top"
  202. :open-delay="200"
  203. content="支持 pdf/doc/docx/xls/xlsx/csv,单个文件≤50MB,最多1个"
  204. >
  205. <el-button size="small" type="primary">选择文件</el-button>
  206. </el-tooltip>
  207. </el-upload>
  208. </el-form-item>
  209. </el-form>
  210. <div slot="footer" class="dialog-footer">
  211. <el-button type="primary" @click="handleWorkingPaperSubmit">
  212. 确定
  213. </el-button>
  214. <el-button @click="workingPaperDialogVisible = false">关闭</el-button>
  215. </div>
  216. </el-dialog>
  217. </div>
  218. </template>
  219. <script>
  220. import {
  221. getTaskDraftList,
  222. addTaskDraft,
  223. deleteTaskDraft,
  224. getTaskDraftOnlineEdit,
  225. saveTaskDraftOnlineEdit,
  226. } from '@/api/audit/taskDraft'
  227. import { uploadFile } from '@/api/file'
  228. export default {
  229. name: 'WorkDraft',
  230. props: {
  231. id: {
  232. type: [String, Number],
  233. default: null,
  234. },
  235. },
  236. data() {
  237. return {
  238. loading: false,
  239. editorConfig: {
  240. // 可以在这里覆盖全局配置的选项
  241. height: '300px',
  242. placeholder: '请输入内容...',
  243. excludeMenus: ['image', 'video'],
  244. },
  245. // 工作底稿数据
  246. workingPaperContent: '',
  247. // 工作底稿记录列表
  248. workingPaperRecords: [],
  249. // 工作底稿弹窗
  250. workingPaperDialogVisible: false,
  251. workingPaperDialogTitle: '添加工作底稿',
  252. isEditWorkingPaper: false,
  253. workingPaperForm: {
  254. id: '',
  255. subject: '',
  256. basicSituation: '',
  257. description: '',
  258. auditDate: '',
  259. startTime: '',
  260. endTime: '',
  261. attachments: [],
  262. fileList: [],
  263. attachmentUrl: '',
  264. },
  265. workingPaperRules: {
  266. subject: [
  267. { required: true, message: '请输入被审核科目', trigger: 'blur' },
  268. ],
  269. basicSituation: [
  270. { required: true, message: '请输入基本情况', trigger: 'blur' },
  271. ],
  272. description: [
  273. { required: true, message: '请输入核增核减说明', trigger: 'blur' },
  274. ],
  275. },
  276. }
  277. },
  278. computed: {
  279. // 结束时间选择器的配置,限制结束时间不能小于开始时间
  280. endTimePickerOptions() {
  281. return {
  282. selectableRange: this.getEndTimeSelectableRange(),
  283. }
  284. },
  285. },
  286. watch: {
  287. id: {
  288. handler(newVal) {
  289. if (newVal) {
  290. this.getWorkingPaperRecords()
  291. this.getWorkingPaperContent()
  292. }
  293. },
  294. immediate: true,
  295. },
  296. },
  297. mounted() {
  298. if (this.id) {
  299. this.getWorkingPaperRecords()
  300. this.getWorkingPaperContent()
  301. }
  302. },
  303. methods: {
  304. // 获取结束时间可选择的范围
  305. getEndTimeSelectableRange() {
  306. if (!this.workingPaperForm.startTime) {
  307. // 如果没有开始时间,可以选择所有时间
  308. return '00:00:00 - 23:59:59'
  309. }
  310. // 获取开始时间的小时和分钟
  311. let startHour = 0
  312. let startMinute = 0
  313. if (this.workingPaperForm.startTime instanceof Date) {
  314. startHour = this.workingPaperForm.startTime.getHours()
  315. startMinute = this.workingPaperForm.startTime.getMinutes()
  316. } else if (typeof this.workingPaperForm.startTime === 'string') {
  317. const parts = this.workingPaperForm.startTime.split(':')
  318. if (parts.length >= 2) {
  319. startHour = parseInt(parts[0]) || 0
  320. startMinute = parseInt(parts[1]) || 0
  321. }
  322. }
  323. // 开始时间加1分钟,作为最小可选时间
  324. let minHour = startHour
  325. let minMinute = startMinute + 1
  326. if (minMinute >= 60) {
  327. minMinute = 0
  328. minHour += 1
  329. }
  330. if (minHour >= 24) {
  331. minHour = 23
  332. minMinute = 59
  333. }
  334. // 格式化为 HH:mm:ss 格式
  335. const minTime = `${String(minHour).padStart(2, '0')}:${String(
  336. minMinute
  337. ).padStart(2, '0')}:00`
  338. return `${minTime} - 23:59:59`
  339. },
  340. // 开始时间变化时的处理
  341. handleStartTimeChange() {
  342. // 如果结束时间存在且小于开始时间,则清空结束时间或提示
  343. if (this.workingPaperForm.endTime && this.workingPaperForm.startTime) {
  344. const startTime = this.getTimeMinutes(this.workingPaperForm.startTime)
  345. const endTime = this.getTimeMinutes(this.workingPaperForm.endTime)
  346. if (endTime <= startTime) {
  347. this.$message.warning('结束时间不能小于或等于开始时间,请重新选择')
  348. this.workingPaperForm.endTime = null
  349. }
  350. }
  351. },
  352. // 获取时间的总分钟数(用于比较)
  353. getTimeMinutes(time) {
  354. if (!time) return 0
  355. let hours = 0
  356. let minutes = 0
  357. if (time instanceof Date) {
  358. hours = time.getHours()
  359. minutes = time.getMinutes()
  360. } else if (typeof time === 'string') {
  361. const parts = time.split(':')
  362. if (parts.length >= 2) {
  363. hours = parseInt(parts[0]) || 0
  364. minutes = parseInt(parts[1]) || 0
  365. }
  366. }
  367. return hours * 60 + minutes
  368. },
  369. // 获取工作底稿列表
  370. async getWorkingPaperRecords() {
  371. if (!this.id) {
  372. return
  373. }
  374. try {
  375. this.loading = true
  376. const res = await getTaskDraftList({ taskId: this.id })
  377. if (res && res.value) {
  378. // 处理数据,格式化时间显示
  379. this.workingPaperRecords = (res.value || []).map((item) => {
  380. // 格式化时间显示,如果后端返回的是 time 字段,直接使用
  381. let auditTime = ''
  382. if (item.time) {
  383. auditTime = item.time
  384. } else if (item.auditTime) {
  385. auditTime = item.auditTime
  386. } else if (item.auditDate) {
  387. const date = new Date(item.auditDate)
  388. const formattedDate = `${date.getFullYear()}-${String(
  389. date.getMonth() + 1
  390. ).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
  391. const startTime = item.startTime || '00:00'
  392. const endTime = item.endTime || '23:59'
  393. auditTime = `${formattedDate} ${startTime}-${endTime}`
  394. }
  395. // 字段名映射
  396. return {
  397. ...item,
  398. auditSubject: item.subject || item.auditSubject,
  399. basicInfo: item.basicSituation || item.basicInfo,
  400. auditDesc: item.description || item.auditDesc,
  401. auditTime: auditTime,
  402. }
  403. })
  404. } else {
  405. this.workingPaperRecords = []
  406. }
  407. } catch (error) {
  408. // this.$message.error('获取工作底稿列表失败')
  409. console.error('获取工作底稿列表失败:', error)
  410. this.workingPaperRecords = []
  411. } finally {
  412. this.loading = false
  413. }
  414. },
  415. onEditorReady(editor) {
  416. // 编辑器初始化完成后的回调
  417. console.log('编辑器已就绪', editor)
  418. // 可以在这里获取编辑器实例,进行更多操作
  419. },
  420. // 获取工作底稿在线编辑内容
  421. async getWorkingPaperContent() {
  422. if (!this.id) {
  423. return
  424. }
  425. try {
  426. const res = await getTaskDraftOnlineEdit({ taskId: this.id })
  427. if (res && res.code === 200 && res.value) {
  428. // 回显内容到编辑器
  429. this.workingPaperContent = res.value.content || ''
  430. } else {
  431. // 如果没有数据,初始化为空
  432. this.workingPaperContent = ''
  433. }
  434. } catch (error) {
  435. console.error('获取工作底稿在线编辑内容失败:', error)
  436. this.workingPaperContent = ''
  437. }
  438. },
  439. // 保存工作底稿在线编辑内容
  440. async handleOnlineEdit() {
  441. if (!this.id) {
  442. this.$message.warning('缺少任务ID')
  443. return
  444. }
  445. try {
  446. // 获取当前时间,格式化为 yyyy-MM-dd HH:mm:ss
  447. const now = new Date()
  448. const year = now.getFullYear()
  449. const month = String(now.getMonth() + 1).padStart(2, '0')
  450. const day = String(now.getDate()).padStart(2, '0')
  451. const hours = String(now.getHours()).padStart(2, '0')
  452. const minutes = String(now.getMinutes()).padStart(2, '0')
  453. const seconds = String(now.getSeconds()).padStart(2, '0')
  454. const updateTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
  455. const params = {
  456. taskId: this.id,
  457. content: this.workingPaperContent || '',
  458. updateTime: updateTime,
  459. }
  460. const res = await saveTaskDraftOnlineEdit(params)
  461. if (res && res.code === 200) {
  462. this.$message.success(res.message || '保存成功')
  463. } else {
  464. this.$message.error(res.message || '保存失败')
  465. }
  466. } catch (error) {
  467. console.error('保存工作底稿在线编辑内容失败:', error)
  468. // this.$message.error('保存失败')
  469. }
  470. },
  471. // 工作底稿操作
  472. handleAddWorkingPaper() {
  473. this.isEditWorkingPaper = false
  474. this.workingPaperDialogTitle = '添加工作底稿'
  475. this.workingPaperForm = {
  476. id: '',
  477. subject: '',
  478. basicSituation: '',
  479. description: '',
  480. auditDate: new Date(),
  481. startTime: '',
  482. endTime: '',
  483. attachments: [],
  484. fileList: [],
  485. attachmentUrl: '',
  486. }
  487. // 重置表单验证状态
  488. if (this.$refs.workingPaperForm) {
  489. this.$refs.workingPaperForm.resetFields()
  490. }
  491. this.workingPaperDialogVisible = true
  492. },
  493. handleEditWorkingPaper(row) {
  494. this.isEditWorkingPaper = true
  495. this.workingPaperDialogTitle = '修改工作底稿'
  496. // 解析时间字符串
  497. let auditDate = new Date()
  498. let startTime = null
  499. let endTime = null
  500. // 优先使用 time 字段,如果没有则使用 auditTime,再没有则使用分开的日期时间
  501. const timeStr = row.time || row.auditTime || ''
  502. if (timeStr) {
  503. const timeParts = timeStr.split(' ')
  504. if (timeParts.length >= 2) {
  505. const date = timeParts[0]
  506. const timeRange = timeParts[1].split('-')
  507. auditDate = new Date(date)
  508. // 将时间字符串转换为 Date 对象
  509. if (timeRange[0]) {
  510. const [hours, minutes] = timeRange[0].split(':')
  511. startTime = new Date()
  512. startTime.setHours(
  513. parseInt(hours) || 0,
  514. parseInt(minutes) || 0,
  515. 0,
  516. 0
  517. )
  518. }
  519. if (timeRange[1]) {
  520. const [hours, minutes] = timeRange[1].split(':')
  521. endTime = new Date()
  522. endTime.setHours(
  523. parseInt(hours) || 0,
  524. parseInt(minutes) || 0,
  525. 0,
  526. 0
  527. )
  528. }
  529. }
  530. } else if (row.auditDate) {
  531. auditDate = new Date(row.auditDate)
  532. // 将时间字符串转换为 Date 对象
  533. if (row.startTime) {
  534. const [hours, minutes] = row.startTime.split(':')
  535. startTime = new Date()
  536. startTime.setHours(
  537. parseInt(hours) || 0,
  538. parseInt(minutes) || 0,
  539. 0,
  540. 0
  541. )
  542. }
  543. if (row.endTime) {
  544. const [hours, minutes] = row.endTime.split(':')
  545. endTime = new Date()
  546. endTime.setHours(parseInt(hours) || 0, parseInt(minutes) || 0, 0, 0)
  547. }
  548. }
  549. // 处理文件列表
  550. let fileList = []
  551. if (row.attachmentUrl) {
  552. fileList = [
  553. {
  554. name: row.fileName || '附件',
  555. url: row.attachmentUrl,
  556. response: { savePath: row.attachmentUrl },
  557. },
  558. ]
  559. }
  560. // 字段名映射
  561. this.workingPaperForm = {
  562. ...row,
  563. subject: row.subject || row.auditSubject || '',
  564. basicSituation: row.basicSituation || row.basicInfo || '',
  565. description: row.description || row.auditDesc || '',
  566. auditDate: auditDate,
  567. startTime: startTime,
  568. endTime: endTime,
  569. fileList: fileList,
  570. attachmentUrl: row.attachmentUrl || '',
  571. }
  572. this.workingPaperDialogVisible = true
  573. },
  574. async handleDeleteWorkingPaper(row) {
  575. try {
  576. await this.$confirm('确定要删除这条工作底稿吗?', '提示', {
  577. confirmButtonText: '确定',
  578. cancelButtonText: '取消',
  579. type: 'warning',
  580. })
  581. const res = await deleteTaskDraft({ id: row.id })
  582. if (res.code === 200) {
  583. this.$message.success('工作底稿已删除')
  584. // 重新获取列表
  585. this.getWorkingPaperRecords()
  586. } else {
  587. this.$message.error(res.message || '删除失败')
  588. }
  589. } catch (error) {
  590. if (error !== 'cancel') {
  591. // this.$message.error('删除失败')
  592. console.error('删除工作底稿失败:', error)
  593. }
  594. }
  595. },
  596. async handleWorkingPaperSubmit() {
  597. try {
  598. await this.$refs.workingPaperForm.validate()
  599. // 验证时间范围
  600. if (
  601. this.workingPaperForm.startTime &&
  602. this.workingPaperForm.endTime
  603. ) {
  604. const startMinutes = this.getTimeMinutes(
  605. this.workingPaperForm.startTime
  606. )
  607. const endMinutes = this.getTimeMinutes(
  608. this.workingPaperForm.endTime
  609. )
  610. if (endMinutes <= startMinutes) {
  611. this.$message.error('结束时间不能小于或等于开始时间')
  612. return
  613. }
  614. }
  615. // 格式化日期
  616. const auditDate = new Date(this.workingPaperForm.auditDate)
  617. const formattedDate = `${auditDate.getFullYear()}-${String(
  618. auditDate.getMonth() + 1
  619. ).padStart(2, '0')}-${String(auditDate.getDate()).padStart(2, '0')}`
  620. // 格式化时间(如果没有选择时间,使用默认值)
  621. const startTime = this.workingPaperForm.startTime
  622. ? this.formatTime(this.workingPaperForm.startTime)
  623. : '00:00'
  624. const endTime = this.workingPaperForm.endTime
  625. ? this.formatTime(this.workingPaperForm.endTime)
  626. : '23:59'
  627. // 合并日期和时间范围为 time 字段
  628. const time = `${formattedDate} ${startTime}-${endTime}`
  629. // 构造提交数据
  630. const formData = {
  631. taskId: this.id,
  632. subject: this.workingPaperForm.subject,
  633. basicSituation: this.workingPaperForm.basicSituation,
  634. description: this.workingPaperForm.description,
  635. time: time,
  636. attachmentUrl: this.workingPaperForm.attachmentUrl,
  637. }
  638. // 如果是编辑模式,添加 id
  639. if (this.isEditWorkingPaper && this.workingPaperForm.id) {
  640. formData.id = this.workingPaperForm.id
  641. }
  642. const res = await addTaskDraft(formData)
  643. if (res.code === 200) {
  644. this.$message.success(
  645. this.isEditWorkingPaper ? '工作底稿修改成功' : '工作底稿添加成功'
  646. )
  647. this.workingPaperDialogVisible = false
  648. // 重新获取列表
  649. this.getWorkingPaperRecords()
  650. } else {
  651. this.$message.error(
  652. res.message || (this.isEditWorkingPaper ? '修改失败' : '添加失败')
  653. )
  654. }
  655. } catch (error) {
  656. if (error !== 'cancel') {
  657. // this.$message.error(
  658. // this.isEditWorkingPaper ? '修改失败' : '添加失败'
  659. // )
  660. console.error('提交工作底稿失败:', error)
  661. }
  662. }
  663. },
  664. // 格式化时间为 HH:mm 格式
  665. formatTime(time) {
  666. if (!time) return ''
  667. if (typeof time === 'string') {
  668. // 如果已经是字符串格式 HH:mm,直接返回
  669. if (/^\d{2}:\d{2}$/.test(time)) {
  670. return time
  671. }
  672. }
  673. // 如果是 Date 对象
  674. if (time instanceof Date) {
  675. const hours = String(time.getHours()).padStart(2, '0')
  676. const minutes = String(time.getMinutes()).padStart(2, '0')
  677. return `${hours}:${minutes}`
  678. }
  679. return ''
  680. },
  681. handlePreviewWorkingPaperAttachment(row) {
  682. if (!row || !row.attachmentUrl) {
  683. this.$message.warning('该记录没有附件')
  684. return
  685. }
  686. // 获取附件URL,如果是逗号分隔的字符串,取第一个
  687. let attachmentUrl = ''
  688. if (typeof row.attachmentUrl === 'string') {
  689. attachmentUrl = row.attachmentUrl.split(',')[0].trim()
  690. } else if (Array.isArray(row.attachmentUrl)) {
  691. attachmentUrl = row.attachmentUrl[0]
  692. } else {
  693. attachmentUrl = row.attachmentUrl
  694. }
  695. if (!attachmentUrl) {
  696. this.$message.warning('该记录没有附件')
  697. return
  698. }
  699. try {
  700. // 处理文件URL
  701. let _fileUrl = ''
  702. if (attachmentUrl.startsWith('http')) {
  703. _fileUrl = attachmentUrl
  704. } else {
  705. _fileUrl = (window.context && window.context.form) + attachmentUrl
  706. }
  707. // 对文件URL进行Base64编码
  708. const encodedUrl = encodeURIComponent(Base64.encode(_fileUrl))
  709. // 构建 kkFileView 预览URL
  710. window.open(`${host}:8012/onlinePreview?url=${encodedUrl}`)
  711. } catch (e) {
  712. console.error('文件预览失败: ', e)
  713. this.$message.error('文件预览失败')
  714. }
  715. },
  716. // 文件上传前验证
  717. beforeFileUpload(file) {
  718. const allowedTypes = [
  719. 'application/pdf',
  720. 'application/msword',
  721. 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  722. 'application/vnd.ms-excel',
  723. 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  724. 'text/csv',
  725. ]
  726. const allowedExtensions = [
  727. '.pdf',
  728. '.doc',
  729. '.docx',
  730. '.xls',
  731. '.xlsx',
  732. '.csv',
  733. ]
  734. const fileExtension = '.' + file.name.split('.').pop().toLowerCase()
  735. // 检查文件格式
  736. const isCorrectType = allowedTypes.includes(file.type)
  737. const isCorrectExtension = allowedExtensions.includes(fileExtension)
  738. if (!isCorrectType && !isCorrectExtension) {
  739. this.$message.error(
  740. '只允许上传 pdf, doc, docx, xls, xlsx, csv 格式的文件!'
  741. )
  742. return false
  743. }
  744. // 检查文件大小 (50MB)
  745. const isLt50M = file.size / 1024 / 1024 < 50
  746. if (!isLt50M) {
  747. this.$message.error('文件大小不能超过 50MB!')
  748. return false
  749. }
  750. return true
  751. },
  752. // 自定义上传方法
  753. async handleFileUpload(options) {
  754. const { file, onProgress, onSuccess, onError } = options
  755. // 检查是否已经上传了文件
  756. if (
  757. this.workingPaperForm.fileList &&
  758. this.workingPaperForm.fileList.length >= 1
  759. ) {
  760. this.$message.warning('只能上传一个文件,请先删除已上传的文件')
  761. if (onError) {
  762. onError(new Error('只能上传一个文件'))
  763. }
  764. return
  765. }
  766. const formData = new FormData()
  767. formData.append('file', file)
  768. try {
  769. // 显示上传进度
  770. if (onProgress) {
  771. onProgress({ percent: 0 })
  772. }
  773. // 调用上传API
  774. const uploadRes = await uploadFile('/api/file/v1/upload', formData, {
  775. onUploadProgress: (progressEvent) => {
  776. // 计算上传进度
  777. if (progressEvent.total) {
  778. const percent = Math.round(
  779. (progressEvent.loaded * 100) / progressEvent.total
  780. )
  781. if (onProgress) {
  782. onProgress({ percent })
  783. }
  784. }
  785. },
  786. })
  787. // 检查上传结果
  788. if (uploadRes && uploadRes.code === 200 && uploadRes.value) {
  789. const fileInfo = uploadRes.value
  790. // 构造文件信息对象,符合element-ui upload组件的格式
  791. const fileObj = {
  792. uid: file.uid,
  793. name: file.name,
  794. status: 'success',
  795. size: file.size,
  796. response: fileInfo,
  797. url: fileInfo.savePath || fileInfo.url,
  798. }
  799. if (onSuccess) {
  800. onSuccess(fileInfo, fileObj)
  801. this.workingPaperForm.attachmentUrl =
  802. fileInfo.savePath || fileInfo.url
  803. }
  804. this.$message.success(`${file.name} 上传成功`)
  805. } else {
  806. throw new Error(uploadRes?.message || '上传失败,请稍后重试')
  807. }
  808. } catch (error) {
  809. console.error('文件上传失败:', error)
  810. // this.$message.error(`文件上传失败:${error.message || '未知错误'}`)
  811. if (onError) {
  812. onError(error)
  813. }
  814. }
  815. },
  816. // 上传成功回调
  817. handleFileUploadSuccess(response, file, fileList) {
  818. // 更新文件列表,添加文件URL信息
  819. this.workingPaperForm.fileList = fileList.map((item) => {
  820. if (item.uid === file.uid && response) {
  821. return {
  822. ...item,
  823. url: response.savePath || response.url || item.url,
  824. response: response,
  825. }
  826. }
  827. return item
  828. })
  829. },
  830. // 上传进度回调
  831. handleFileUploadProgress(event, file, fileList) {
  832. // element-ui 会自动处理上传进度显示
  833. },
  834. // 上传失败回调
  835. handleFileUploadError(err, file, fileList) {
  836. console.error('文件上传错误:', err)
  837. this.$message.error(`${file.name} 上传失败`)
  838. // 从文件列表中移除失败的文件
  839. this.workingPaperForm.fileList = fileList.filter(
  840. (item) => item.uid !== file.uid
  841. )
  842. },
  843. // 超出文件数量限制
  844. handleFileExceed(files, fileList) {
  845. this.$message.warning(
  846. `当前限制选择 1 个文件,本次选择了 ${files.length} 个文件,共选择了 ${fileList.length} 个文件`
  847. )
  848. },
  849. // 移除文件
  850. handleFileRemove(file, fileList) {
  851. this.workingPaperForm.fileList = fileList
  852. this.workingPaperForm.attachmentUrl = ''
  853. this.$message.info(`${file.name} 已移除`)
  854. },
  855. },
  856. }
  857. </script>
  858. <style scoped lang="scss">
  859. .work-draft-container {
  860. width: 100%;
  861. }
  862. .editor-header {
  863. display: flex;
  864. justify-content: space-between;
  865. align-items: center;
  866. margin-bottom: 20px;
  867. }
  868. .editor-title {
  869. font-size: 16px;
  870. font-weight: bold;
  871. }
  872. .editor-wrapper {
  873. width: 100%;
  874. margin-bottom: 20px;
  875. }
  876. .working-paper-editor {
  877. width: 100% !important;
  878. }
  879. :deep(.inputs.ht-form-inputs__inline) {
  880. width: 100%;
  881. }
  882. :deep(.ht-editor) {
  883. width: 100% !important;
  884. }
  885. :deep(.ht-container) {
  886. width: 100% !important;
  887. }
  888. .dialog-footer {
  889. text-align: right;
  890. }
  891. .dialog-footer .el-button {
  892. margin-left: 10px;
  893. }
  894. </style>