index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  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>{{ 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
  128. v-model="submitForm.effectiveDate"
  129. type="date"
  130. value-format="YYYY-MM-DD"
  131. placeholder="请选择生效日期"
  132. style="width: 100%"
  133. />
  134. </el-form-item>
  135. </el-form>
  136. <template #footer>
  137. <div class="dialog-footer">
  138. <el-button :loading="submitButtonLoading" type="primary" @click="submitSubmitForm"> 确认递交 </el-button>
  139. <el-button @click="cancelSubmit">取消</el-button>
  140. </div>
  141. </template>
  142. </el-dialog>
  143. <!-- 审核记录对话框 -->
  144. <AuditLogDialog v-model:visible="auditLogDialog.visible" :document-id="auditLogDialog.documentId"
  145. :api-function="listSubmissionAuditLog" i18n-prefix="home.taskCenter.submission" />
  146. </div>
  147. </template>
  148. <script setup lang="ts">
  149. import { ref, reactive, onMounted, nextTick, getCurrentInstance } from 'vue';
  150. import { ElMessage, ElMessageBox } from 'element-plus';
  151. import type { FormInstance } from 'element-plus';
  152. import { listSubmissionTasks, submitDocument, listSubmissionAuditLog, sendDocument } from '@/api/home/taskCenter/submission';
  153. import fileUpload from '@/components/FileUpload/index.vue';
  154. import DictTag from '@/components/DictTag/index.vue';
  155. import DocumentStatusTag from '@/components/DocumentStatusTag/index.vue';
  156. import AuditLogDialog from '@/components/AuditLogDialog/index.vue';
  157. import { Upload, DocumentCopy, Promotion } from '@element-plus/icons-vue';
  158. import { SubmissionTaskVO, SubmissionTaskSubmitForm } from '@/api/home/taskCenter/submission/types';
  159. const { proxy } = getCurrentInstance() as any;
  160. const { plan_document_type } = proxy.useDict('plan_document_type');
  161. const loading = ref(false);
  162. const submitButtonLoading = ref(false);
  163. // 当前任务
  164. const currentTask = ref<SubmissionTaskVO | null>(null);
  165. // 查询参数
  166. const queryParams = reactive({
  167. name: '',
  168. status: undefined as number | undefined,
  169. projectCode: '',
  170. projectName: '',
  171. centerName: '',
  172. pageNum: 1,
  173. pageSize: 10
  174. });
  175. // 任务列表数据
  176. const taskList = ref<SubmissionTaskVO[]>([]);
  177. const total = ref(0);
  178. // 递交对话框
  179. const submitDialog = reactive({
  180. visible: false,
  181. title: ''
  182. });
  183. // 递交表单ref
  184. const submitFormRef = ref<FormInstance>();
  185. // 递交表单数据
  186. const submitForm = ref({
  187. ossId: '',
  188. effectiveDate: ''
  189. });
  190. // 递交表单验证规则
  191. const submitRules = {
  192. ossId: [{ required: true, message: '请上传文件', trigger: 'change' }],
  193. effectiveDate: [{ required: true, message: '请选择生效日期', trigger: 'change' }]
  194. };
  195. // 审核记录对话框
  196. const auditLogDialog = reactive({
  197. visible: false,
  198. documentId: 0
  199. });
  200. /**
  201. * 解析时间格式
  202. * @param time 时间字符串
  203. * @param pattern 格式模式
  204. */
  205. const parseTime = (time: string, pattern = '{y}-{m}-{d}') => {
  206. if (!time) return '';
  207. const date = new Date(time);
  208. const formatObj: any = {
  209. y: date.getFullYear(),
  210. m: date.getMonth() + 1,
  211. d: date.getDate(),
  212. h: date.getHours(),
  213. i: date.getMinutes(),
  214. s: date.getSeconds(),
  215. a: date.getDay()
  216. };
  217. const time_str = pattern.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
  218. let value = formatObj[key];
  219. if (key === 'a') return ['日', '一', '二', '三', '四', '五', '六'][value];
  220. if (result.length > 0 && value < 10) {
  221. value = '0' + value;
  222. }
  223. return String(value);
  224. });
  225. return time_str;
  226. };
  227. /**
  228. * 计算逾期天数
  229. * @param deadline 截止日期
  230. * @param submitTime 递交日期
  231. * @param status 状态
  232. */
  233. const calculateOverdueDays = (deadline: string, submitTime: string, status: number) => {
  234. // 如果截止日期为空,返回 '-'
  235. if (!deadline) return '-';
  236. const deadlineDate = new Date(deadline);
  237. const today = new Date();
  238. // 设置时间为当天开始,避免时间部分影响比较
  239. today.setHours(0, 0, 0, 0);
  240. // 未递交的情况
  241. if (status === 0) {
  242. // 如果截止日期不早于今天,返回 '-'
  243. if (deadlineDate >= today) {
  244. return '-';
  245. }
  246. // 计算今天到截止日期的天数差
  247. const timeDiff = today.getTime() - deadlineDate.getTime();
  248. const dayDiff = Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
  249. return dayDiff > 0 ? dayDiff : '-';
  250. }
  251. // 已递交的情况
  252. else if (status === 1 || status === 2) {
  253. // 如果递交日期为空,按未递交处理
  254. if (!submitTime) {
  255. if (deadlineDate >= today) {
  256. return '-';
  257. }
  258. const timeDiff = today.getTime() - deadlineDate.getTime();
  259. const dayDiff = Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
  260. return dayDiff > 0 ? dayDiff : '-';
  261. }
  262. const submitDate = new Date(submitTime);
  263. // 设置时间为当天开始,避免时间部分影响比较
  264. submitDate.setHours(0, 0, 0, 0);
  265. // 如果递交日期早于等于截止日期,返回 '-'
  266. if (submitDate <= deadlineDate) {
  267. return '-';
  268. }
  269. // 计算递交日期与截止日期的天数差
  270. const timeDiff = submitDate.getTime() - deadlineDate.getTime();
  271. const dayDiff = Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
  272. return dayDiff > 0 ? dayDiff : '-';
  273. }
  274. // 其他状态返回 '-'
  275. else {
  276. return '-';
  277. }
  278. };
  279. /**
  280. * 获取任务列表
  281. */
  282. const getTaskList = async () => {
  283. try {
  284. loading.value = true;
  285. const res = await listSubmissionTasks(queryParams);
  286. if (res.code === 200) {
  287. taskList.value = res.rows || [];
  288. total.value = res.total || 0;
  289. } else {
  290. ElMessage.error(res.msg || '获取任务列表失败');
  291. }
  292. } catch (error) {
  293. console.error('获取任务列表失败:', error);
  294. ElMessage.error('获取任务列表失败');
  295. } finally {
  296. loading.value = false;
  297. }
  298. };
  299. /**
  300. * 处理搜索
  301. */
  302. const handleQuery = () => {
  303. queryParams.pageNum = 1;
  304. getTaskList();
  305. };
  306. /**
  307. * 重置搜索
  308. */
  309. const resetQuery = () => {
  310. queryParams.name = '';
  311. queryParams.status = undefined;
  312. queryParams.projectCode = '';
  313. queryParams.projectName = '';
  314. queryParams.centerName = '';
  315. handleQuery();
  316. };
  317. /**
  318. * 处理任务递交
  319. */
  320. const handleTaskSubmit = (row: SubmissionTaskVO) => {
  321. currentTask.value = row;
  322. submitForm.value = {
  323. ossId: '',
  324. effectiveDate: ''
  325. };
  326. submitDialog.visible = true;
  327. submitDialog.title = '递交文档';
  328. // 重置表单验证
  329. nextTick(() => {
  330. submitFormRef.value?.clearValidate();
  331. });
  332. };
  333. /**
  334. * 查看审核记录
  335. */
  336. const handleViewAuditLog = (row: SubmissionTaskVO) => {
  337. auditLogDialog.documentId = row.id;
  338. auditLogDialog.visible = true;
  339. };
  340. /**
  341. * 取消递交
  342. */
  343. const cancelSubmit = () => {
  344. submitDialog.visible = false;
  345. submitForm.value = {
  346. ossId: '',
  347. effectiveDate: ''
  348. };
  349. currentTask.value = null;
  350. };
  351. /**
  352. * 提交递交表单
  353. */
  354. const submitSubmitForm = () => {
  355. submitFormRef.value?.validate(async (valid: boolean) => {
  356. if (valid && currentTask.value) {
  357. submitButtonLoading.value = true;
  358. try {
  359. const submitData: SubmissionTaskSubmitForm = {
  360. documentId: currentTask.value.id,
  361. ossId: Number(submitForm.value.ossId),
  362. effectiveDate: submitForm.value.effectiveDate
  363. };
  364. await submitDocument(submitData);
  365. ElMessage.success('递交成功');
  366. submitDialog.visible = false;
  367. // 刷新任务列表
  368. await getTaskList();
  369. } catch (error) {
  370. console.error('递交失败:', error);
  371. ElMessage.error('递交失败');
  372. } finally {
  373. submitButtonLoading.value = false;
  374. }
  375. }
  376. });
  377. };
  378. /**
  379. * 处理寄送
  380. */
  381. const handleSend = (row: SubmissionTaskVO) => {
  382. ElMessageBox.confirm('确认已完成寄送?', '提示', {
  383. confirmButtonText: '确认',
  384. cancelButtonText: '取消',
  385. type: 'warning'
  386. })
  387. .then(async () => {
  388. try {
  389. await sendDocument(row.id);
  390. ElMessage.success('寄送确认成功');
  391. // 刷新任务列表
  392. await getTaskList();
  393. } catch (error) {
  394. console.error('寄送确认失败:', error);
  395. ElMessage.error('寄送确认失败');
  396. }
  397. })
  398. .catch(() => {
  399. // 用户取消操作
  400. });
  401. };
  402. onMounted(() => {
  403. getTaskList();
  404. });
  405. </script>
  406. <style scoped lang="scss">
  407. .submission-task-container {
  408. padding: 20px;
  409. .search-form {
  410. margin-bottom: 15px;
  411. }
  412. :deep(.el-table) {
  413. .cell {
  414. white-space: nowrap;
  415. }
  416. }
  417. .badge-dot {
  418. margin-right: 8px;
  419. vertical-align: middle;
  420. }
  421. }
  422. </style>