list.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. <template>
  2. <div>
  3. <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
  4. <div v-show="showSearch" class="mb-[10px]">
  5. <el-card shadow="hover">
  6. <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="110px">
  7. <el-form-item :label="t('qc.task.search.taskName')" prop="name" style="width: 300px">
  8. <el-input v-model="queryParams.name" :placeholder="t('qc.task.search.taskNamePlaceholder')" clearable @keyup.enter="handleQuery" />
  9. </el-form-item>
  10. <el-form-item :label="t('qc.task.search.initiator')" prop="initiator" style="width: 300px">
  11. <el-input
  12. v-model="queryParams.initiator"
  13. :placeholder="t('qc.task.search.initiatorPlaceholder')"
  14. clearable
  15. @keyup.enter="handleQuery"
  16. />
  17. </el-form-item>
  18. <el-form-item :label="t('qc.task.search.projectId')" prop="projectId" style="width: 300px">
  19. <el-input
  20. v-model="queryParams.projectId"
  21. :placeholder="t('qc.task.search.projectIdPlaceholder')"
  22. clearable
  23. @keyup.enter="handleQuery"
  24. />
  25. </el-form-item>
  26. <el-form-item :label="t('qc.task.search.deadline')" style="width: 300px">
  27. <el-date-picker
  28. v-model="dateRangeDeadline"
  29. value-format="YYYY-MM-DD HH:mm:ss"
  30. type="daterange"
  31. :range-separator="t('qc.task.search.rangeSeparator')"
  32. :start-placeholder="t('qc.task.search.startDate')"
  33. :end-placeholder="t('qc.task.search.endDate')"
  34. :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
  35. />
  36. </el-form-item>
  37. <el-form-item :label="t('qc.task.search.createBy')" prop="createBy" style="width: 300px">
  38. <el-input v-model="queryParams.createBy" :placeholder="t('qc.task.search.createByPlaceholder')" clearable @keyup.enter="handleQuery" />
  39. </el-form-item>
  40. <el-form-item :label="t('qc.task.search.createTime')" style="width: 300px">
  41. <el-date-picker
  42. v-model="dateRangeCreateTime"
  43. value-format="YYYY-MM-DD HH:mm:ss"
  44. type="daterange"
  45. :range-separator="t('qc.task.search.rangeSeparator')"
  46. :start-placeholder="t('qc.task.search.startDate')"
  47. :end-placeholder="t('qc.task.search.endDate')"
  48. :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
  49. />
  50. </el-form-item>
  51. <el-form-item :label="t('qc.task.search.updateBy')" prop="updateBy" style="width: 300px">
  52. <el-input v-model="queryParams.updateBy" :placeholder="t('qc.task.search.updateByPlaceholder')" clearable @keyup.enter="handleQuery" />
  53. </el-form-item>
  54. <el-form-item :label="t('qc.task.search.updateTime')" style="width: 300px">
  55. <el-date-picker
  56. v-model="dateRangeUpdateTime"
  57. value-format="YYYY-MM-DD HH:mm:ss"
  58. type="daterange"
  59. :range-separator="t('qc.task.search.rangeSeparator')"
  60. :start-placeholder="t('qc.task.search.startDate')"
  61. :end-placeholder="t('qc.task.search.endDate')"
  62. :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
  63. />
  64. </el-form-item>
  65. <el-form-item>
  66. <el-button type="primary" icon="Search" @click="handleQuery">{{ t('qc.task.button.search') }}</el-button>
  67. <el-button icon="Refresh" @click="resetQuery">{{ t('qc.task.button.reset') }}</el-button>
  68. </el-form-item>
  69. </el-form>
  70. </el-card>
  71. </div>
  72. </transition>
  73. <el-card shadow="never">
  74. <template #header>
  75. <el-row :gutter="10" class="mb8">
  76. <el-col :span="1.5">
  77. <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['qc:task:add']">{{ t('qc.task.button.add') }}</el-button>
  78. </el-col>
  79. <el-col :span="1.5">
  80. <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['qc:task:remove']">{{
  81. t('qc.task.button.delete')
  82. }}</el-button>
  83. </el-col>
  84. <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
  85. </el-row>
  86. </template>
  87. <el-table v-loading="loading" border :data="taskList" @selection-change="handleSelectionChange">
  88. <el-table-column type="selection" width="55" align="center" />
  89. <el-table-column :label="t('qc.task.table.index')" align="center" prop="id" v-if="true" />
  90. <el-table-column :label="t('qc.task.table.taskName')" align="center" prop="name" width="200" />
  91. <el-table-column :label="t('qc.task.table.initiator')" align="center" prop="initiator" width="150">
  92. <template #default="scope">
  93. <span>{{ scope.row.initiatorName || scope.row.initiator }}</span>
  94. </template>
  95. </el-table-column>
  96. <el-table-column :label="t('qc.task.table.projectId')" align="center" prop="projectId" width="200">
  97. <template #default="scope">
  98. <span>{{ scope.row.projectName || scope.row.projectId }}</span>
  99. </template>
  100. </el-table-column>
  101. <el-table-column :label="t('qc.task.table.deadline')" align="center" prop="deadline" width="180">
  102. <template #default="scope">
  103. <span>{{ parseTime(scope.row.deadline, '{y}-{m}-{d}') }}</span>
  104. </template>
  105. </el-table-column>
  106. <el-table-column :label="t('qc.task.table.status')" align="center" prop="status" width="100">
  107. <template #default="scope">
  108. <el-tag v-if="scope.row.status === 0" type="info">{{ t('qc.task.status.notStarted') }}</el-tag>
  109. <el-tag v-else-if="scope.row.status === 1" type="warning">{{ t('qc.task.status.inProgress') }}</el-tag>
  110. <el-tag v-else-if="scope.row.status === 2" type="success">{{ t('qc.task.status.completed') }}</el-tag>
  111. <el-tag v-else type="info">{{ t('qc.task.status.unknown') }}</el-tag>
  112. </template>
  113. </el-table-column>
  114. <el-table-column :label="t('qc.task.table.completion')" align="center" width="200">
  115. <template #default="scope">
  116. <div style="width: 100%; padding: 5px 0">
  117. <el-progress
  118. :percentage="Math.round((Number(scope.row.schedule) || 0) * 100 * 100) / 100"
  119. :stroke-width="14"
  120. :show-text="true"
  121. :format="(percentage) => `${percentage}%`"
  122. :empty-color="'#e0e0e0'"
  123. color="#409eff"
  124. style="width: 100%"
  125. />
  126. </div>
  127. </template>
  128. </el-table-column>
  129. <el-table-column :label="t('qc.task.table.note')" align="center" prop="note" />
  130. <el-table-column :label="t('qc.task.table.createBy')" align="center" prop="createBy" width="150" />
  131. <el-table-column :label="t('qc.task.table.createTime')" align="center" prop="createTime" width="180" />
  132. <el-table-column :label="t('qc.task.table.updateBy')" align="center" prop="updateBy" width="150" />
  133. <el-table-column :label="t('qc.task.table.updateTime')" align="center" prop="updateTime" width="180" />
  134. <el-table-column :label="t('qc.task.table.action')" align="center" fixed="right" width="240" class-name="small-padding fixed-width">
  135. <template #default="scope">
  136. <el-button
  137. v-hasPermi="['qc:task:query']"
  138. type="info"
  139. icon="View"
  140. style="padding: 0 5px; font-size: 10px; height: 24px; margin-right: 5px"
  141. @click="handleView(scope.row.id)"
  142. >
  143. {{ t('qc.task.button.view') }}
  144. </el-button>
  145. <el-button
  146. v-if="scope.row.status === 0 && scope.row.initiator === userStore.userId"
  147. v-hasPermi="['qc:task:start']"
  148. type="primary"
  149. icon="VideoPlay"
  150. style="padding: 0 5px; font-size: 10px; height: 24px; margin-right: 5px"
  151. @click="handleStart(scope.row)"
  152. >
  153. {{ t('qc.task.button.start') }}
  154. </el-button>
  155. <el-button
  156. v-hasPermi="['qc:task:remove']"
  157. type="danger"
  158. icon="Delete"
  159. style="padding: 0 5px; font-size: 10px; height: 24px"
  160. @click="handleDelete(scope.row)"
  161. >
  162. {{ t('qc.task.button.delete') }}
  163. </el-button>
  164. </template>
  165. </el-table-column>
  166. </el-table>
  167. <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
  168. </el-card>
  169. <!-- 新增/编辑任务对话框 -->
  170. <el-dialog v-model="dialogVisible" :title="dialogTitle" width="900px" append-to-body @close="handleDialogClose">
  171. <el-form ref="taskFormRef" :model="taskForm" :rules="taskRules" label-width="160px">
  172. <el-row :gutter="20">
  173. <el-col :span="12">
  174. <el-form-item :label="t('qc.task.form.taskName')" prop="name">
  175. <el-input v-model="taskForm.name" :placeholder="t('qc.task.form.taskNamePlaceholder')" clearable />
  176. </el-form-item>
  177. </el-col>
  178. <el-col :span="12">
  179. <el-form-item :label="t('qc.task.form.projectId')" prop="projectId">
  180. <el-select v-model="taskForm.projectId" :placeholder="t('qc.task.form.projectIdPlaceholder')" clearable filterable style="width: 100%">
  181. <el-option v-for="project in projectList" :key="project.id" :label="project.name" :value="project.id" />
  182. </el-select>
  183. </el-form-item>
  184. </el-col>
  185. </el-row>
  186. <el-row :gutter="20">
  187. <el-col :span="12">
  188. <el-form-item :label="t('qc.task.form.deadline')" prop="deadline">
  189. <el-date-picker
  190. v-model="taskForm.deadline"
  191. type="date"
  192. :placeholder="t('qc.task.form.deadlinePlaceholder')"
  193. value-format="YYYY-MM-DD"
  194. style="width: 100%"
  195. />
  196. </el-form-item>
  197. </el-col>
  198. <el-col :span="12">
  199. <el-form-item :label="t('qc.task.form.proportion')" prop="proportion">
  200. <el-input-number
  201. v-model="taskForm.proportion"
  202. :min="1"
  203. :max="100"
  204. :placeholder="t('qc.task.form.proportionPlaceholder')"
  205. style="width: 100%"
  206. />
  207. <span style="margin-left: 8px; color: #909399">{{ t('qc.task.form.proportionUnit') }}</span>
  208. </el-form-item>
  209. </el-col>
  210. </el-row>
  211. <el-row>
  212. <el-col :span="24">
  213. <el-form-item :label="t('qc.task.form.note')" prop="note">
  214. <el-input v-model="taskForm.note" type="textarea" :rows="3" :placeholder="t('qc.task.form.notePlaceholder')" />
  215. </el-form-item>
  216. </el-col>
  217. </el-row>
  218. </el-form>
  219. <template #footer>
  220. <div class="dialog-footer">
  221. <el-button @click="dialogVisible = false">{{ t('qc.task.button.cancel') }}</el-button>
  222. <el-button type="primary" @click="submitForm" :loading="submitLoading">{{ t('qc.task.button.confirm') }}</el-button>
  223. </div>
  224. </template>
  225. </el-dialog>
  226. </div>
  227. </template>
  228. <script setup name="TaskList" lang="ts">
  229. import { listTask, getTask, delTask, startTask, addTask, updateTask } from '@/api/qc/task';
  230. import { TaskVO, TaskQuery, TaskForm } from '@/api/qc/task/types';
  231. import { listProject } from '@/api/document/folder';
  232. import { useUserStore } from '@/store/modules/user';
  233. import { useI18n } from 'vue-i18n';
  234. const { proxy } = getCurrentInstance() as ComponentInternalInstance;
  235. const { t } = useI18n();
  236. const userStore = useUserStore();
  237. const taskList = ref<TaskVO[]>([]);
  238. const loading = ref(true);
  239. const showSearch = ref(true);
  240. const ids = ref<Array<string | number>>([]);
  241. const single = ref(true);
  242. const multiple = ref(true);
  243. const total = ref(0);
  244. const dateRangeDeadline = ref<[DateModelType, DateModelType]>(['', '']);
  245. const dateRangeCreateTime = ref<[DateModelType, DateModelType]>(['', '']);
  246. const dateRangeUpdateTime = ref<[DateModelType, DateModelType]>(['', '']);
  247. const queryFormRef = ref<ElFormInstance>();
  248. const taskFormRef = ref<ElFormInstance>();
  249. // 对话框相关
  250. const dialogVisible = ref(false);
  251. const dialogTitle = ref('');
  252. const submitLoading = ref(false);
  253. const projectList = ref<any[]>([]);
  254. // 表单数据
  255. const taskForm = ref<TaskForm>({
  256. id: undefined,
  257. name: undefined,
  258. initiator: undefined,
  259. projectId: undefined,
  260. proportion: undefined,
  261. deadline: undefined,
  262. status: 0,
  263. note: undefined
  264. });
  265. // 表单验证规则
  266. const taskRules = {
  267. name: [{ required: true, message: t('qc.task.rule.taskNameRequired'), trigger: 'blur' }],
  268. projectId: [{ required: true, message: t('qc.task.rule.projectIdRequired'), trigger: 'change' }],
  269. deadline: [{ required: true, message: t('qc.task.rule.deadlineRequired'), trigger: 'change' }],
  270. proportion: [{ required: true, message: t('qc.task.rule.proportionRequired'), trigger: 'blur' }]
  271. };
  272. const data = reactive<PageData<any, TaskQuery>>({
  273. queryParams: {
  274. pageNum: 1,
  275. pageSize: 10,
  276. name: undefined,
  277. initiator: undefined,
  278. projectId: undefined,
  279. status: undefined,
  280. createBy: undefined,
  281. updateBy: undefined,
  282. params: {
  283. deadline: undefined,
  284. createTime: undefined,
  285. updateTime: undefined
  286. }
  287. }
  288. });
  289. const { queryParams } = toRefs(data);
  290. /** 查询文档质控任务列表 */
  291. const getList = async () => {
  292. loading.value = true;
  293. queryParams.value.params = {};
  294. proxy?.addDateRange(queryParams.value, dateRangeDeadline.value, 'Deadline');
  295. proxy?.addDateRange(queryParams.value, dateRangeCreateTime.value, 'CreateTime');
  296. proxy?.addDateRange(queryParams.value, dateRangeUpdateTime.value, 'UpdateTime');
  297. const res = await listTask(queryParams.value);
  298. taskList.value = res.rows;
  299. total.value = res.total;
  300. loading.value = false;
  301. };
  302. /** 搜索按钮操作 */
  303. const handleQuery = () => {
  304. queryParams.value.pageNum = 1;
  305. getList();
  306. };
  307. /** 重置按钮操作 */
  308. const resetQuery = () => {
  309. dateRangeDeadline.value = ['', ''];
  310. dateRangeCreateTime.value = ['', ''];
  311. dateRangeUpdateTime.value = ['', ''];
  312. queryFormRef.value?.resetFields();
  313. handleQuery();
  314. };
  315. /** 多选框选中数据 */
  316. const handleSelectionChange = (selection: TaskVO[]) => {
  317. ids.value = selection.map((item) => item.id);
  318. single.value = selection.length != 1;
  319. multiple.value = !selection.length;
  320. };
  321. /** 新增按钮操作 */
  322. const handleAdd = () => {
  323. resetForm();
  324. dialogTitle.value = t('qc.task.dialog.addTask');
  325. dialogVisible.value = true;
  326. loadProjectList();
  327. };
  328. /** 加载项目列表 */
  329. const loadProjectList = async () => {
  330. try {
  331. const res = await listProject({ pageNum: 1, pageSize: 1000 });
  332. projectList.value = res.rows || [];
  333. } catch (error) {
  334. console.error(t('qc.task.message.loadProjectFailed'), error);
  335. }
  336. };
  337. /** 重置表单 */
  338. const resetForm = () => {
  339. taskForm.value = {
  340. id: undefined,
  341. name: undefined,
  342. initiator: undefined,
  343. projectId: undefined,
  344. proportion: undefined,
  345. deadline: undefined,
  346. status: 0,
  347. note: undefined
  348. };
  349. taskFormRef.value?.resetFields();
  350. };
  351. /** 对话框关闭 */
  352. const handleDialogClose = () => {
  353. resetForm();
  354. };
  355. /** 提交表单 */
  356. const submitForm = async () => {
  357. if (!taskFormRef.value) return;
  358. await taskFormRef.value.validate(async (valid) => {
  359. if (valid) {
  360. submitLoading.value = true;
  361. try {
  362. // 设置发起人为当前登录用户
  363. const submitData = {
  364. ...taskForm.value,
  365. initiator: userStore.userId
  366. };
  367. if (submitData.id) {
  368. await updateTask(submitData);
  369. proxy?.$modal.msgSuccess(t('qc.task.message.editSuccess'));
  370. } else {
  371. await addTask(submitData);
  372. proxy?.$modal.msgSuccess(t('qc.task.message.addSuccess'));
  373. }
  374. dialogVisible.value = false;
  375. await getList();
  376. } catch (error) {
  377. console.error(t('qc.task.message.submitFailed'), error);
  378. } finally {
  379. submitLoading.value = false;
  380. }
  381. }
  382. });
  383. };
  384. /** 查看任务详情 */
  385. const handleView = (taskId: string | number) => {
  386. proxy?.$router.push({
  387. path: '/task/detail',
  388. query: { id: taskId }
  389. });
  390. };
  391. /** 开始任务 */
  392. const handleStart = async (row: TaskVO) => {
  393. try {
  394. await proxy?.$modal.confirm(t('qc.task.message.startConfirm', { name: row.name }));
  395. const res = await startTask(row.id);
  396. if (res.code === 200) {
  397. proxy?.$modal.msgSuccess(t('qc.task.message.startSuccess'));
  398. getList(); // 重新获取列表
  399. } else {
  400. proxy?.$modal.msgError(res.msg || t('qc.task.message.startFailed'));
  401. }
  402. } catch (error) {
  403. console.error(t('qc.task.message.startFailed'), error);
  404. }
  405. };
  406. /** 删除按钮操作 */
  407. const handleDelete = async (row?: TaskVO) => {
  408. const _ids = row?.id || ids.value;
  409. await proxy?.$modal.confirm(t('qc.task.message.deleteConfirm', { ids: _ids })).finally(() => (loading.value = false));
  410. await delTask(_ids);
  411. proxy?.$modal.msgSuccess(t('qc.task.message.deleteSuccess'));
  412. await getList();
  413. };
  414. onMounted(() => {
  415. getList();
  416. });
  417. </script>