index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. <template>
  2. <div class="submission-task-container">
  3. <el-card shadow="never">
  4. <template #header>
  5. <div class="flex justify-between items-center">
  6. <span class="text-lg font-bold">文件递交任务</span>
  7. </div>
  8. </template>
  9. <!-- 搜索栏 -->
  10. <el-form :model="queryParams" :inline="true" class="search-form">
  11. <el-form-item label="名称">
  12. <el-input v-model="queryParams.name" placeholder="请输入名称" clearable style="width: 240px"
  13. @keyup.enter="handleQuery" />
  14. </el-form-item>
  15. <el-form-item label="项目编号">
  16. <el-input v-model="queryParams.projectCode" placeholder="请输入项目编号" clearable style="width: 240px"
  17. @keyup.enter="handleQuery" />
  18. </el-form-item>
  19. <el-form-item label="项目名称">
  20. <el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable style="width: 240px"
  21. @keyup.enter="handleQuery" />
  22. </el-form-item>
  23. <!-- <el-form-item label="中心名称">-->
  24. <!-- <el-input v-model="queryParams.centerName" placeholder="请输入中心名称" clearable style="width: 240px"-->
  25. <!-- @keyup.enter="handleQuery" />-->
  26. <!-- </el-form-item>-->
  27. <el-form-item label="状态">
  28. <el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 120px">
  29. <el-option label="待递交" :value="0" />
  30. <el-option label="审核拒绝" :value="2" />
  31. </el-select>
  32. </el-form-item>
  33. <el-form-item>
  34. <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
  35. <el-button icon="Refresh" @click="resetQuery">重置</el-button>
  36. </el-form-item>
  37. </el-form>
  38. <!-- 任务列表 -->
  39. <el-table v-loading="loading" :data="taskList" border style="margin-top: 10px">
  40. <el-table-column prop="id" label="序号" width="80" align="center" />
  41. <el-table-column prop="name" label="名称" min-width="150" show-overflow-tooltip>
  42. <template #default="scope">
  43. <el-badge v-if="scope.row.status === 0 || scope.row.status === 2" is-dot class="badge-dot" />
  44. <span>{{ formatDocumentName(scope.row.name) }}</span>
  45. </template>
  46. </el-table-column>
  47. <el-table-column prop="type" label="类型" min-width="120" show-overflow-tooltip>
  48. <template #default="scope">
  49. <span v-if="scope.row.type === 0">非计划文档</span>
  50. <span v-else-if="scope.row.type === 1">计划文档</span>
  51. <span v-else>{{ scope.row.type }}</span>
  52. </template>
  53. </el-table-column>
  54. <el-table-column prop="documentType" label="计划文档类型" width="120" align="center">
  55. <template #default="scope">
  56. <dict-tag v-if="scope.row.documentType" :options="plan_document_type" :value="scope.row.documentType" />
  57. <span v-else>-</span>
  58. </template>
  59. </el-table-column>
  60. <el-table-column prop="status" label="状态" width="120" align="center">
  61. <template #default="scope">
  62. <DocumentStatusTag :status="scope.row.status" />
  63. </template>
  64. </el-table-column>
  65. <el-table-column prop="planSubmitter" label="计划递交人" width="120" align="center" />
  66. <el-table-column prop="deadline" label="截止时间" width="160" align="center">
  67. <template #default="scope">
  68. <span v-if="scope.row.deadline">{{ parseTime(scope.row.deadline) }}</span>
  69. <span v-else>-</span>
  70. </template>
  71. </el-table-column>
  72. <el-table-column prop="overdueDays" label="递交逾期天数" width="120" align="center">
  73. <template #default="scope">
  74. {{ calculateOverdueDays(scope.row.deadline, scope.row.submitTime, scope.row.status) }}
  75. </template>
  76. </el-table-column>
  77. <el-table-column prop="submitTime" label="递交时间" width="160" align="center">
  78. <template #default="scope">
  79. <span v-if="scope.row.submitTime">{{ scope.row.submitTime }}</span>
  80. <span v-else>-</span>
  81. </template>
  82. </el-table-column>
  83. <el-table-column prop="createTime" label="创建时间" width="160" align="center">
  84. <template #default="scope">
  85. <span v-if="scope.row.createTime">{{ scope.row.createTime }}</span>
  86. <span v-else>-</span>
  87. </template>
  88. </el-table-column>
  89. <el-table-column prop="sendStatus" label="寄送状态" width="100" align="center">
  90. <template #default="scope">
  91. <span v-if="!scope.row.sendFlag">-</span>
  92. <el-tag v-else-if="scope.row.sendStatus" type="success">已寄送</el-tag>
  93. <el-tag v-else type="warning">未寄送</el-tag>
  94. </template>
  95. </el-table-column>
  96. <!-- 操作列 -->
  97. <el-table-column label="操作" width="280" align="center" fixed="right" class-name="small-padding fixed-width">
  98. <template #default="scope">
  99. <el-button v-if="scope.row.status === 0 || scope.row.status === 2" type="primary" icon="Upload"
  100. style="padding: 0 5px; font-size: 10px; height: 24px" @click="handleTaskSubmit(scope.row)">
  101. 递交
  102. </el-button>
  103. <el-button v-hasPermi="['taskCenter:submission:logAudit']" type="info" icon="DocumentCopy"
  104. style="padding: 0 5px; font-size: 10px; height: 24px" @click="handleViewAuditLog(scope.row)">
  105. 审核记录
  106. </el-button>
  107. <el-button v-if="scope.row.sendFlag && !scope.row.sendStatus" v-hasPermi="['taskCenter:submission:send']"
  108. type="success" icon="Promotion" style="padding: 0 5px; font-size: 10px; height: 24px"
  109. @click="handleSend(scope.row)">
  110. 寄送
  111. </el-button>
  112. </template>
  113. </el-table-column>
  114. </el-table>
  115. <!-- 分页 -->
  116. <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize"
  117. :total="total" @pagination="getTaskList" />
  118. </el-card>
  119. <!-- 递交文档对话框 -->
  120. <el-dialog v-model="submitDialog.visible" :title="submitDialog.title" width="500px" append-to-body>
  121. <el-alert title="仅支持上传 PDF 格式文件" type="info" :closable="false" style="margin-bottom: 20px" />
  122. <el-form ref="submitFormRef" :model="submitForm" :rules="submitRules" label-width="120px">
  123. <el-form-item label="文件" prop="ossId">
  124. <fileUpload v-model="submitForm.ossId" :limit="1" :action="'/common/resource/oss/upload'" accept=".pdf" />
  125. </el-form-item>
  126. <el-form-item label="生效日期" prop="effectiveDate">
  127. <el-date-picker v-model="submitForm.effectiveDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择生效日期"
  128. style="width: 100%" />
  129. </el-form-item>
  130. </el-form>
  131. <template #footer>
  132. <div class="dialog-footer">
  133. <el-button :loading="submitButtonLoading" type="primary" @click="submitSubmitForm"> 确认递交 </el-button>
  134. <el-button @click="cancelSubmit">取消</el-button>
  135. </div>
  136. </template>
  137. </el-dialog>
  138. <!-- 审核记录对话框 -->
  139. <AuditLogDialog v-model:visible="auditLogDialog.visible" :document-id="auditLogDialog.documentId"
  140. :api-function="listSubmissionAuditLog" i18n-prefix="home.taskCenter.submission" />
  141. </div>
  142. </template>
  143. <script setup lang="ts">
  144. import { ref, reactive, onMounted, nextTick, getCurrentInstance } from 'vue';
  145. import { ElMessage, ElMessageBox } from 'element-plus';
  146. import type { FormInstance } from 'element-plus';
  147. import { listSubmissionTasks, submitDocument, listSubmissionAuditLog, sendDocument } from '@/api/home/taskCenter/submission';
  148. import { formatDocumentName } from '@/utils/ruoyi';
  149. import fileUpload from '@/components/FileUpload/index.vue';
  150. import DictTag from '@/components/DictTag/index.vue';
  151. import DocumentStatusTag from '@/components/DocumentStatusTag/index.vue';
  152. import AuditLogDialog from '@/components/AuditLogDialog/index.vue';
  153. import { Upload, DocumentCopy, Promotion } from '@element-plus/icons-vue';
  154. import { SubmissionTaskVO, SubmissionTaskSubmitForm } from '@/api/home/taskCenter/submission/types';
  155. const { proxy } = getCurrentInstance() as any;
  156. const { plan_document_type } = proxy.useDict('plan_document_type');
  157. const loading = ref(false);
  158. const submitButtonLoading = ref(false);
  159. // 当前任务
  160. const currentTask = ref<SubmissionTaskVO | null>(null);
  161. // 查询参数
  162. const queryParams = reactive({
  163. name: '',
  164. status: undefined as number | undefined,
  165. projectCode: '',
  166. projectName: '',
  167. centerName: '',
  168. pageNum: 1,
  169. pageSize: 10
  170. });
  171. // 任务列表数据
  172. const taskList = ref<SubmissionTaskVO[]>([]);
  173. const total = ref(0);
  174. // 递交对话框
  175. const submitDialog = reactive({
  176. visible: false,
  177. title: ''
  178. });
  179. // 递交表单ref
  180. const submitFormRef = ref<FormInstance>();
  181. // 递交表单数据
  182. const submitForm = ref({
  183. ossId: '',
  184. effectiveDate: ''
  185. });
  186. // 递交表单验证规则
  187. const submitRules = {
  188. ossId: [{ required: true, message: '请上传文件', trigger: 'change' }],
  189. effectiveDate: [{ required: true, message: '请选择生效日期', trigger: 'change' }]
  190. };
  191. // 审核记录对话框
  192. const auditLogDialog = reactive({
  193. visible: false,
  194. documentId: 0
  195. });
  196. /**
  197. * 解析时间格式
  198. * @param time 时间字符串
  199. * @param pattern 格式模式
  200. */
  201. const parseTime = (time: string, pattern = '{y}-{m}-{d}') => {
  202. if (!time) return '';
  203. const date = new Date(time);
  204. const formatObj: any = {
  205. y: date.getFullYear(),
  206. m: date.getMonth() + 1,
  207. d: date.getDate(),
  208. h: date.getHours(),
  209. i: date.getMinutes(),
  210. s: date.getSeconds(),
  211. a: date.getDay()
  212. };
  213. const time_str = pattern.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
  214. let value = formatObj[key];
  215. if (key === 'a') return ['日', '一', '二', '三', '四', '五', '六'][value];
  216. if (result.length > 0 && value < 10) {
  217. value = '0' + value;
  218. }
  219. return String(value);
  220. });
  221. return time_str;
  222. };
  223. /**
  224. * 计算逾期天数
  225. * @param deadline 截止日期
  226. * @param submitTime 递交日期
  227. * @param status 状态
  228. */
  229. const calculateOverdueDays = (deadline: string, submitTime: string, status: number) => {
  230. // 如果截止日期为空,返回 '-'
  231. if (!deadline) return '-';
  232. const deadlineDate = new Date(deadline);
  233. const today = new Date();
  234. // 设置时间为当天开始,避免时间部分影响比较
  235. today.setHours(0, 0, 0, 0);
  236. // 未递交的情况
  237. if (status === 0) {
  238. // 如果截止日期不早于今天,返回 '-'
  239. if (deadlineDate >= today) {
  240. return '-';
  241. }
  242. // 计算今天到截止日期的天数差
  243. const timeDiff = today.getTime() - deadlineDate.getTime();
  244. const dayDiff = Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
  245. return dayDiff > 0 ? dayDiff : '-';
  246. }
  247. // 已递交的情况
  248. else if (status === 1 || status === 2) {
  249. // 如果递交日期为空,按未递交处理
  250. if (!submitTime) {
  251. if (deadlineDate >= today) {
  252. return '-';
  253. }
  254. const timeDiff = today.getTime() - deadlineDate.getTime();
  255. const dayDiff = Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
  256. return dayDiff > 0 ? dayDiff : '-';
  257. }
  258. const submitDate = new Date(submitTime);
  259. // 设置时间为当天开始,避免时间部分影响比较
  260. submitDate.setHours(0, 0, 0, 0);
  261. // 如果递交日期早于等于截止日期,返回 '-'
  262. if (submitDate <= deadlineDate) {
  263. return '-';
  264. }
  265. // 计算递交日期与截止日期的天数差
  266. const timeDiff = submitDate.getTime() - deadlineDate.getTime();
  267. const dayDiff = Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
  268. return dayDiff > 0 ? dayDiff : '-';
  269. }
  270. // 其他状态返回 '-'
  271. else {
  272. return '-';
  273. }
  274. };
  275. /**
  276. * 获取任务列表
  277. */
  278. const getTaskList = async () => {
  279. try {
  280. loading.value = true;
  281. const res = await listSubmissionTasks(queryParams);
  282. if (res.code === 200) {
  283. taskList.value = res.rows || [];
  284. total.value = res.total || 0;
  285. } else {
  286. ElMessage.error(res.msg || '获取任务列表失败');
  287. }
  288. } catch (error) {
  289. console.error('获取任务列表失败:', error);
  290. ElMessage.error('获取任务列表失败');
  291. } finally {
  292. loading.value = false;
  293. }
  294. };
  295. /**
  296. * 处理搜索
  297. */
  298. const handleQuery = () => {
  299. queryParams.pageNum = 1;
  300. getTaskList();
  301. };
  302. /**
  303. * 重置搜索
  304. */
  305. const resetQuery = () => {
  306. queryParams.name = '';
  307. queryParams.status = undefined;
  308. queryParams.projectCode = '';
  309. queryParams.projectName = '';
  310. queryParams.centerName = '';
  311. handleQuery();
  312. };
  313. /**
  314. * 处理任务递交
  315. */
  316. const handleTaskSubmit = (row: SubmissionTaskVO) => {
  317. currentTask.value = row;
  318. submitForm.value = {
  319. ossId: '',
  320. effectiveDate: ''
  321. };
  322. submitDialog.visible = true;
  323. submitDialog.title = '递交文档';
  324. // 重置表单验证
  325. nextTick(() => {
  326. submitFormRef.value?.clearValidate();
  327. });
  328. };
  329. /**
  330. * 查看审核记录
  331. */
  332. const handleViewAuditLog = (row: SubmissionTaskVO) => {
  333. auditLogDialog.documentId = row.id;
  334. auditLogDialog.visible = true;
  335. };
  336. /**
  337. * 取消递交
  338. */
  339. const cancelSubmit = () => {
  340. submitDialog.visible = false;
  341. submitForm.value = {
  342. ossId: '',
  343. effectiveDate: ''
  344. };
  345. currentTask.value = null;
  346. };
  347. /**
  348. * 提交递交表单
  349. */
  350. const submitSubmitForm = () => {
  351. submitFormRef.value?.validate(async (valid: boolean) => {
  352. if (valid && currentTask.value) {
  353. submitButtonLoading.value = true;
  354. try {
  355. const submitData: SubmissionTaskSubmitForm = {
  356. documentId: currentTask.value.id,
  357. ossId: Number(submitForm.value.ossId),
  358. effectiveDate: submitForm.value.effectiveDate
  359. };
  360. await submitDocument(submitData);
  361. ElMessage.success('递交成功');
  362. submitDialog.visible = false;
  363. // 刷新任务列表
  364. await getTaskList();
  365. } catch (error) {
  366. console.error('递交失败:', error);
  367. ElMessage.error('递交失败');
  368. } finally {
  369. submitButtonLoading.value = false;
  370. }
  371. }
  372. });
  373. };
  374. /**
  375. * 处理寄送
  376. */
  377. const handleSend = (row: SubmissionTaskVO) => {
  378. ElMessageBox.confirm('确认已完成寄送?', '提示', {
  379. confirmButtonText: '确认',
  380. cancelButtonText: '取消',
  381. type: 'warning'
  382. })
  383. .then(async () => {
  384. try {
  385. await sendDocument(row.id);
  386. ElMessage.success('寄送确认成功');
  387. // 刷新任务列表
  388. await getTaskList();
  389. } catch (error) {
  390. console.error('寄送确认失败:', error);
  391. ElMessage.error('寄送确认失败');
  392. }
  393. })
  394. .catch(() => {
  395. // 用户取消操作
  396. });
  397. };
  398. onMounted(() => {
  399. getTaskList();
  400. });
  401. </script>
  402. <style scoped lang="scss">
  403. .submission-task-container {
  404. padding: 20px;
  405. .search-form {
  406. margin-bottom: 15px;
  407. }
  408. :deep(.el-table) {
  409. .cell {
  410. white-space: nowrap;
  411. }
  412. }
  413. .badge-dot {
  414. margin-right: 8px;
  415. vertical-align: middle;
  416. }
  417. }
  418. </style>