index.vue 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. <template>
  2. <div class="main-wrap flex flex-col gap-y-2 p-4">
  3. <NCard>
  4. <div class="mb-2 flex gap-x-4">
  5. <NForm ref="formRef" :model="form" inline label-placement="left">
  6. <NFormItem label="站点" path="sn">
  7. <NInput v-model:value="form.sn" style="width: 250px" clearable />
  8. </NFormItem>
  9. <NFormItem label="进度" path="progress">
  10. <NSelect v-model:value="form.progress" :options="progressOptions" style="width: 120px" clearable />
  11. </NFormItem>
  12. </NForm>
  13. <div class="flex gap-2">
  14. <NButton type="success" @click="createBatchDownload">
  15. <template #icon>
  16. <span class="i-ph:plus-circle" />
  17. </template>
  18. 创建任务
  19. </NButton>
  20. <NButton type="info" @click="handleQueryClick" :loading="isRequestLoading" :disabled="isRequestLoading">
  21. <template #icon>
  22. <span class="i-ph:magnifying-glass" />
  23. </template>
  24. 查询
  25. </NButton>
  26. <NButton type="warning" @click="resetForm">
  27. <template #icon>
  28. <span class="i-ph:arrow-clockwise" />
  29. </template>
  30. 重置
  31. </NButton>
  32. </div>
  33. </div>
  34. <div>
  35. <NDataTable ref="dataTableRef" v-model:checked-row-keys="checkedRowKeys" :remote="true" :min-height="166.6"
  36. :max-height="maxHeight" size="small" :columns="tableColumns" :data="tableData" :row-key="tableRowKey"
  37. :loading="isRequestLoading" striped :single-line="false" :row-props="rowProps" />
  38. <div class="mt-3 flex items-end justify-between">
  39. <div class="flex items-center gap-x-3">
  40. <span>已选择&nbsp;{{ checkedRowKeys.length }}&nbsp; 条</span>
  41. <NButtonGroup size="small" :ghost="true">
  42. <NButton :disabled="isSelectMultiple" @click="handleDownloadClick" secondary type="info">
  43. 下载
  44. </NButton>
  45. <NButton :disabled="isSelectMultiple" @click="handleDeleteClick" secondary type="info">
  46. 删除
  47. </NButton>
  48. </NButtonGroup>
  49. </div>
  50. <NPagination v-bind="tablePagination" :prefix="paginationPrefix" />
  51. </div>
  52. </div>
  53. </NCard>
  54. <NDropdown placement="bottom-start" trigger="manual" v-bind="dropdownOptions" :show="showDropdown" />
  55. </div>
  56. </template>
  57. <script setup lang="tsx">
  58. import { useRouter } from 'vue-router'
  59. import { NButton, NDataTable, NForm, NNumberAnimation, NInput, NEllipsis } from 'naive-ui'
  60. import type { DataTableColumns, PaginationProps, DropdownProps } from 'naive-ui'
  61. import { useDataTable } from '@/composable/comp/useDataTable'
  62. import { useResettableReactive } from '@/composable/useResettable'
  63. import { getBatchDownloadList, createBatchDownload as createBatchDownloadTask } from '@/service/batchDownload'
  64. import type { BatchDownloadFilter, BatchDownloadInfo, BatchDownloadConfig } from '@/service/batchDownload/interface'
  65. import ModalData from './ModalData.vue'
  66. defineOptions({
  67. name: 'BatchDownload',
  68. })
  69. const router = useRouter()
  70. const message = useMessage()
  71. const modal = useModal()
  72. const dialog = useDialog()
  73. const formRef = useTemplateRef<InstanceType<typeof NForm>>('formRef')
  74. const dataTableRef = useTemplateRef<InstanceType<typeof NDataTable>>('dataTableRef')
  75. const { maxHeight } = useDataTable(dataTableRef)
  76. const isRequestLoading = ref(false)
  77. const showDropdown = ref(false)
  78. const checkedRowKeys = ref<Array<number | string>>([])
  79. const contextmenu = ref<BatchDownloadInfo | undefined>(undefined)
  80. const isSelectMultiple = ref(false)
  81. type FilterForm = BatchDownloadFilter
  82. const [form, , resetForm] = useResettableReactive<FilterForm>({
  83. sn: undefined,
  84. })
  85. const progressOptions = [
  86. { label: 'pending', value: 'pending' },
  87. { label: 'running', value: 'running' },
  88. { label: 'error', value: 'error' },
  89. { label: 'failed', value: 'failed' }
  90. { label: 'complete', value: 'complete' },
  91. ]
  92. const handleQueryClick = () => {
  93. // 请求查询数据
  94. handleListQuery()
  95. }
  96. const prevUserListTotal = ref(0)
  97. const paginationPrefix: PaginationProps['prefix'] = (info) => {
  98. const { itemCount } = info
  99. return (
  100. itemCount && (
  101. <div>
  102. <span>总 & nbsp; </span>
  103. < NNumberAnimation
  104. from={prevUserListTotal.value}
  105. to={itemCount}
  106. onFinish={() => {
  107. prevUserListTotal.value = itemCount
  108. }}
  109. />
  110. <span> & nbsp; 条 </span>
  111. </div>
  112. )
  113. )
  114. }
  115. const dropdownOptions = reactive<DropdownProps>({
  116. x: 0,
  117. y: 0,
  118. options: [
  119. {
  120. label: () => <span class='text-green-500'> 下载 </span>,
  121. key: 'download',
  122. disabled: true,
  123. },
  124. {
  125. label: () => <span class='text-red-500'> 删除 </span>,
  126. key: 'delete',
  127. disabled: true,
  128. },
  129. ],
  130. onClickoutside: () => {
  131. showDropdown.value = false
  132. },
  133. onSelect: (key: string) => {
  134. showDropdown.value = false
  135. if (contextmenu.value === undefined) return
  136. if (key === 'download') {
  137. itemDownload(contextmenu.value.id)
  138. } else if (key === 'delete') {
  139. itemDelete(contextmenu.value.id)
  140. }
  141. },
  142. })
  143. function createBatchDownload() {
  144. const title = '新增数据批量下载'
  145. const handleSubmitClick = async (data: BatchDownloadConfig) => {
  146. let promise: Promise<void> | undefined
  147. promise = createBatchDownloadTask(data)
  148. const result = await promise?.then(async () => {
  149. handleListQuery()
  150. message.success('创建成功')
  151. return true
  152. }).catch((err) => {
  153. message.error(`请求失败。\n${err}`)
  154. return false
  155. })
  156. if (!result) return
  157. m.destroy()
  158. }
  159. function handleCancelClick() {
  160. m.destroy()
  161. }
  162. const m = modal.create({
  163. title,
  164. preset: 'card',
  165. draggable: true,
  166. style: {
  167. width: '500px',
  168. },
  169. content: () => (
  170. <ModalData
  171. onSubmit={handleSubmitClick}
  172. onCancel={handleCancelClick}
  173. />
  174. ),
  175. })
  176. }
  177. const tableRowKey = (rowData: BatchDownloadInfo) => {
  178. return rowData.id
  179. }
  180. function createColumns(): DataTableColumns<BatchDownloadInfo> {
  181. return [
  182. {
  183. type: 'selection',
  184. fixed: 'left'
  185. },
  186. {
  187. title: 'SN',
  188. key: 'sn',
  189. width: 200,
  190. fixed: 'left',
  191. render(rowData, rowIndex) {
  192. return h(NEllipsis, {
  193. lineClamp: 3,
  194. tooltip: {
  195. placement: "right-start", // ⬅️ Tooltip 显示在右侧
  196. style: {
  197. maxHeight: "200px", // ⬅️ 多行时滚动
  198. overflowY: "auto",
  199. whiteSpace: "pre-line" // ⬅️ \n 正常换行
  200. }
  201. }
  202. },
  203. {
  204. default: () =>
  205. h("span", { style: { whiteSpace: "pre-line" } }, rowData.sn.join("\n"))
  206. })
  207. },
  208. },
  209. {
  210. title: '时间范围',
  211. key: 'timeRange',
  212. width: 100,
  213. },
  214. {
  215. title: '进度',
  216. key: 'progress',
  217. },
  218. {
  219. title: '任务结果',
  220. key: 'result'
  221. },
  222. {
  223. title: '操作',
  224. key: 'operate',
  225. width: 200,
  226. fixed: 'right',
  227. render(row) {
  228. return h('div', { class: 'flex justify-center gap-4 p-x-4' }, [
  229. h(NButton, {
  230. type: "success",
  231. strong: true,
  232. size: 'small',
  233. disabled: row.progress === "pending" || row.progress === "running",
  234. onClick: () => itemDownload(row.id)
  235. }, {
  236. default: () => '下载'
  237. }),
  238. h(NButton, {
  239. type: "error",
  240. strong: true,
  241. size: 'small',
  242. disabled: row.progress === "pending" || row.progress === "running",
  243. onClick: () => itemDelete(row.id)
  244. }, {
  245. default: () => '删除'
  246. })
  247. ])
  248. }
  249. }
  250. ]
  251. }
  252. // 表格右键菜单
  253. function rowProps(row: BatchDownloadInfo) {
  254. return {
  255. onContextmenu: (e: MouseEvent) => {
  256. e.preventDefault()
  257. showDropdown.value = false
  258. nextTick().then(() => {
  259. contextmenu.value = row
  260. showDropdown.value = true
  261. dropdownOptions.x = e.clientX
  262. dropdownOptions.y = e.clientY
  263. })
  264. },
  265. }
  266. }
  267. const tableData = ref<BatchDownloadInfo[]>([])
  268. const tableColumns = createColumns()
  269. const tablePagination = reactive<PaginationProps>({
  270. page: 1,
  271. pageSize: 10,
  272. showSizePicker: true,
  273. pageSizes: [10, 20, 50, 100],
  274. itemCount: 0,
  275. showQuickJumper: true,
  276. showQuickJumpDropdown: true,
  277. onUpdatePage: (page: number) => {
  278. tablePagination.page = page
  279. handleListQuery()
  280. },
  281. onUpdatePageSize: (pageSize: number) => {
  282. tablePagination.pageSize = pageSize
  283. tablePagination.page = 1
  284. handleListQuery()
  285. },
  286. })
  287. const itemDownload = (id: string) => {
  288. // TODO
  289. }
  290. const itemDelete = (id: string) => {
  291. // TODO
  292. }
  293. // 批量下载
  294. const handleDownloadClick = () => {
  295. }
  296. // 批量删除
  297. const handleDeleteClick = () => {
  298. }
  299. const queryBatchDownloadList = async (pageSize: number, pageNum: number, filter?: BatchDownloadFilter) => {
  300. const responseList = await getBatchDownloadList(pageSize, pageNum, filter)
  301. if (responseList.data != null) {
  302. tableData.value = responseList.data
  303. } else {
  304. tableData.value = []
  305. }
  306. tablePagination.itemCount = responseList.total
  307. }
  308. const handleListQuery = async () => {
  309. const pageSize = tablePagination.pageSize ?? 10
  310. const page = tablePagination.page ?? 1
  311. // 处理过滤条件
  312. const filter: FilterForm = {
  313. sn: form.sn,
  314. progress: form.progress,
  315. }
  316. await queryBatchDownloadList(pageSize, page, filter)
  317. }
  318. onMounted(async () => {
  319. // 请求查询数据
  320. await handleListQuery()
  321. })
  322. </script>