|
|
<script lang="ts" setup>
|
|
|
import { computed, h, nextTick, onMounted, reactive, ref, unref } from 'vue'
|
|
|
import { NDataTable, useDialog, useMessage } from 'naive-ui'
|
|
|
import type { DataTableColumns, DataTableRowKey, PaginationProps } from 'naive-ui'
|
|
|
import { Action, CustomTabelModal, ImportExcelModal, RepeatModal, RepeatTaskTableModal } from '../comp'
|
|
|
import ConfrimModal from '@/views/task/modal/ConfrimModal.vue'
|
|
|
import { useWindowSizeFn } from '@/hooks/event/useWindowSizeFn'
|
|
|
import { getViewportOffset } from '@/utils/domUtils'
|
|
|
import type { RowData } from '@/config/final'
|
|
|
import { findKey, headRules } from '@/config/final'
|
|
|
import { getFinalList } from '@/api/final'
|
|
|
import { isBoolean } from '@/utils/is'
|
|
|
import { useUser } from '@/store/modules/user'
|
|
|
import type { ApprovalParam } from '/#/api'
|
|
|
import { audit } from '@/api/task/task'
|
|
|
import SvgIcon from '@/components/Icon/SvgIcon.vue'
|
|
|
|
|
|
const columns: DataTableColumns<RowData> = [
|
|
|
{
|
|
|
type: 'selection',
|
|
|
fixed: 'left',
|
|
|
width: 50,
|
|
|
},
|
|
|
{
|
|
|
title: '任务Id',
|
|
|
key: 'id',
|
|
|
fixed: 'left',
|
|
|
width: 100,
|
|
|
},
|
|
|
{
|
|
|
title: '任务名称',
|
|
|
key: 'fromtaskname',
|
|
|
fixed: 'left',
|
|
|
width: 150,
|
|
|
},
|
|
|
{
|
|
|
title: '审批节点',
|
|
|
key: 'approvalnode',
|
|
|
width: 100,
|
|
|
},
|
|
|
{
|
|
|
title: '审批状态',
|
|
|
key: 'states',
|
|
|
width: 100,
|
|
|
sorter: 'default',
|
|
|
renderSorterIcon: ({ order }) => {
|
|
|
if (order === false)
|
|
|
return h(SvgIcon, { name: 'sort-2' })
|
|
|
if (order === 'ascend')
|
|
|
return h(SvgIcon, { name: 'sort-1' })
|
|
|
if (order === 'descend')
|
|
|
return h(SvgIcon, { name: 'sort-3' })
|
|
|
},
|
|
|
},
|
|
|
{
|
|
|
title: '图片相似度',
|
|
|
key: 'similarity',
|
|
|
width: 150,
|
|
|
sorter: 'default',
|
|
|
renderSorterIcon: ({ order }) => {
|
|
|
if (order === false)
|
|
|
return h(SvgIcon, { name: 'sort-2' })
|
|
|
if (order === 'ascend')
|
|
|
return h(SvgIcon, { name: 'sort-1' })
|
|
|
if (order === 'descend')
|
|
|
return h(SvgIcon, { name: 'sort-3' })
|
|
|
},
|
|
|
},
|
|
|
{
|
|
|
title: '提报时间',
|
|
|
key: 'fromuptime',
|
|
|
width: 200,
|
|
|
sorter: 'default',
|
|
|
renderSorterIcon: ({ order }) => {
|
|
|
if (order === false)
|
|
|
return h(SvgIcon, { name: 'sort-2' })
|
|
|
if (order === 'ascend')
|
|
|
return h(SvgIcon, { name: 'sort-1' })
|
|
|
if (order === 'descend')
|
|
|
return h(SvgIcon, { name: 'sort-3' })
|
|
|
},
|
|
|
},
|
|
|
{
|
|
|
title: '更新时间',
|
|
|
key: 'updatetime',
|
|
|
width: 200,
|
|
|
},
|
|
|
{
|
|
|
title: '操作',
|
|
|
key: 'actions',
|
|
|
width: 200,
|
|
|
fixed: 'right',
|
|
|
render(row) {
|
|
|
return h(
|
|
|
Action,
|
|
|
{
|
|
|
id: row.id,
|
|
|
status: row.states,
|
|
|
trigger: actionHandler,
|
|
|
},
|
|
|
)
|
|
|
},
|
|
|
},
|
|
|
]
|
|
|
|
|
|
const deviceHeight = ref(600)
|
|
|
|
|
|
onMounted(() => {
|
|
|
nextTick(() => {
|
|
|
computeListHeight()
|
|
|
})
|
|
|
})
|
|
|
|
|
|
const columnsRef = ref(columns)
|
|
|
const tableRef = ref<InstanceType<typeof NDataTable>>()
|
|
|
const rowKey = (row: RowData) => row.id
|
|
|
const loading = ref(true)
|
|
|
const pagination = reactive({
|
|
|
page: 1,
|
|
|
pageCount: 1,
|
|
|
pageSize: 10,
|
|
|
})
|
|
|
const tableData = ref<Array<RowData>>([])
|
|
|
const selectionIds = ref<DataTableRowKey[]>([])
|
|
|
const userStore = useUser()
|
|
|
const dialog = useDialog()
|
|
|
const message = useMessage()
|
|
|
|
|
|
async function query(page: number, pageSize: number) {
|
|
|
const result = await getFinalList({ sortorder: 'asc', pageSize, currPage: page, sortname: '' })
|
|
|
const { data, pageCount } = result
|
|
|
tableData.value = data
|
|
|
pagination.page = page
|
|
|
pagination.pageCount = pageCount
|
|
|
loading.value = false
|
|
|
}
|
|
|
|
|
|
async function handlePageChange(currentPage) {
|
|
|
if (loading.value)
|
|
|
return
|
|
|
|
|
|
const { pageSize } = pagination
|
|
|
await query(currentPage, pageSize)
|
|
|
}
|
|
|
|
|
|
function handleCheck(rowKeys: DataTableRowKey[]) {
|
|
|
selectionIds.value = rowKeys
|
|
|
}
|
|
|
|
|
|
async function computeListHeight() {
|
|
|
const table = unref(tableRef)
|
|
|
if (!table)
|
|
|
return
|
|
|
const tableEl: any = table?.$el
|
|
|
const headEl = tableEl.querySelector('.n-data-table-thead ')
|
|
|
const { bottomIncludeBody } = getViewportOffset(headEl)
|
|
|
const headerH = 64
|
|
|
let paginationH = 2
|
|
|
const marginH = 60
|
|
|
if (!isBoolean(unref(pagination))) {
|
|
|
const paginationEl = tableEl.querySelector('.n-data-table__pagination') as HTMLElement
|
|
|
if (paginationEl) {
|
|
|
const offsetHeight = paginationEl.offsetHeight
|
|
|
paginationH += offsetHeight || 0
|
|
|
}
|
|
|
else {
|
|
|
paginationH += 28
|
|
|
}
|
|
|
}
|
|
|
let height
|
|
|
= bottomIncludeBody - (headerH + paginationH + marginH)
|
|
|
const maxHeight = 800
|
|
|
height = maxHeight && maxHeight < height ? maxHeight : height
|
|
|
deviceHeight.value = height
|
|
|
}
|
|
|
|
|
|
useWindowSizeFn(computeListHeight)
|
|
|
|
|
|
const maxHeight = computed(() => {
|
|
|
return tableData.value.length ? `${unref(deviceHeight)}px` : 'auto'
|
|
|
})
|
|
|
|
|
|
query(pagination.page, pagination.pageSize)
|
|
|
|
|
|
const customTabelRef = ref(null)
|
|
|
const importExcelRef = ref(null)
|
|
|
const rejectModalRef = ref(null)
|
|
|
const repeatModalRef = ref(null)
|
|
|
const repeatTaskTableModalRef = ref(null)
|
|
|
|
|
|
function showModal(modalRef: any) {
|
|
|
const modal = unref(modalRef)! as any
|
|
|
modal.showModal()
|
|
|
}
|
|
|
|
|
|
const popover = ref<ComponentRef | null>(null)
|
|
|
|
|
|
function importHandler() {
|
|
|
(popover.value as any).setShow(false)
|
|
|
showModal(importExcelRef)
|
|
|
}
|
|
|
|
|
|
function exportHandler() {
|
|
|
loading.value = true
|
|
|
import('@/utils/exportExcel').then((excel) => {
|
|
|
const tHeader = ['任务Id', '任务名称', '审批节点', '审批状态', '图片相似度', '提报时间', '更新时间']
|
|
|
const filterVal = ['id', 'name', 'approvalnode', 'approvalstatus', 'similarity', 'uptime', 'updatetime']
|
|
|
const list = tableData.value
|
|
|
const data = formatJson(filterVal, list)
|
|
|
excel.export_json_to_excel({
|
|
|
header: tHeader,
|
|
|
data,
|
|
|
filename: 'data',
|
|
|
autoWidth: true,
|
|
|
bookType: 'xlsx',
|
|
|
})
|
|
|
loading.value = false
|
|
|
})
|
|
|
}
|
|
|
|
|
|
function formatJson(filterVal, jsonDataList) {
|
|
|
return jsonDataList.map(v => filterVal.map((j) => {
|
|
|
return v[j]
|
|
|
}))
|
|
|
}
|
|
|
|
|
|
function sucessHandler(excelData) {
|
|
|
const data = excelData.content.map((item) => {
|
|
|
const obj = {}
|
|
|
Object.keys(item).forEach((key) => {
|
|
|
const k = findKey(columns, key)
|
|
|
obj[k] = item[key]
|
|
|
})
|
|
|
return obj
|
|
|
})
|
|
|
|
|
|
tableData.value = data
|
|
|
}
|
|
|
|
|
|
function commitHandler(columns) {
|
|
|
columnsRef.value = columns
|
|
|
}
|
|
|
|
|
|
function actionHandler(action: any) {
|
|
|
const { key } = action
|
|
|
switch (key) {
|
|
|
case 'view':
|
|
|
break
|
|
|
case 'reset':
|
|
|
resetHandler()
|
|
|
break
|
|
|
case 'approval':
|
|
|
approvalHandler()
|
|
|
break
|
|
|
case 'reject':
|
|
|
rejectHandler()
|
|
|
break
|
|
|
default:
|
|
|
break
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// states:1未提交,2待审批,3通过,4不通过
|
|
|
function validate(items: any[]) {
|
|
|
if (items.length === 0)
|
|
|
return '至少选中一个任务'
|
|
|
|
|
|
const useInfo = userStore.getUserInfo
|
|
|
const username = useInfo.loginname
|
|
|
|
|
|
for (const item of items) {
|
|
|
const { iztrueorfalse, states, assignee } = item
|
|
|
if (iztrueorfalse === null)
|
|
|
return '未判别真假'
|
|
|
|
|
|
else if (states !== 2)
|
|
|
return '审批状态不合法'
|
|
|
|
|
|
else if (assignee !== username)
|
|
|
return '审批人不一致'
|
|
|
}
|
|
|
|
|
|
return null
|
|
|
}
|
|
|
|
|
|
function resetHandler() {
|
|
|
dialog.info({
|
|
|
title: '确认提示',
|
|
|
content: '确认重置当前选中的任务的审批吗?',
|
|
|
positiveText: '确定',
|
|
|
negativeText: '取消',
|
|
|
onPositiveClick: async () => {
|
|
|
// TODO:需要支持重置
|
|
|
// const result = await resetApproval()
|
|
|
},
|
|
|
onNegativeClick: () => { },
|
|
|
})
|
|
|
}
|
|
|
|
|
|
function getSelectItems() {
|
|
|
return tableData.value.filter(item => selectionIds.value.includes(item.id))
|
|
|
}
|
|
|
|
|
|
function approvalHandler() {
|
|
|
const items = getSelectItems()
|
|
|
const msg = validate(items)
|
|
|
|
|
|
if (msg !== null) {
|
|
|
message.error(msg)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
dialog.info({
|
|
|
title: '确认提示',
|
|
|
content: '确认给该任务审批为【通过】吗?',
|
|
|
positiveText: '确定',
|
|
|
negativeText: '取消',
|
|
|
onPositiveClick: () => {
|
|
|
approval(items)
|
|
|
},
|
|
|
onNegativeClick: () => { },
|
|
|
})
|
|
|
}
|
|
|
|
|
|
function approval(items) {
|
|
|
const formIds: string[] = items.map(item => item.id)
|
|
|
const taskIds: string[] = items.map(item => item.taskId)
|
|
|
const tasknames: string[] = items.map(item => item.taskname)
|
|
|
|
|
|
const param: ApprovalParam = {
|
|
|
formid: formIds,
|
|
|
taskId: taskIds,
|
|
|
approvd: true,
|
|
|
taskComment: 'approval',
|
|
|
taskname: tasknames,
|
|
|
}
|
|
|
|
|
|
doAudit(param)
|
|
|
}
|
|
|
|
|
|
function rejectHandler() {
|
|
|
const items = getSelectItems()
|
|
|
const msg = validate(items)
|
|
|
|
|
|
if (msg !== null) {
|
|
|
message.error(msg)
|
|
|
return
|
|
|
}
|
|
|
|
|
|
const modal = unref(rejectModalRef)! as any
|
|
|
modal.showModal()
|
|
|
}
|
|
|
|
|
|
function reject(idOrDesc: string, backId: string, isOther: boolean) {
|
|
|
const items = getSelectItems()
|
|
|
const formIds: string[] = items.map(item => item.id)
|
|
|
const taskIds: string[] = items.map(item => item.fromtaskid)
|
|
|
const tasknames: string[] = items.map(item => item.fromtaskname)
|
|
|
|
|
|
const param: ApprovalParam = {
|
|
|
formid: formIds,
|
|
|
taskId: taskIds,
|
|
|
approvd: false,
|
|
|
taskComment: idOrDesc,
|
|
|
taskname: isOther ? tasknames : ['其他'],
|
|
|
}
|
|
|
|
|
|
doAudit(param)
|
|
|
}
|
|
|
|
|
|
function doAudit(param: any) {
|
|
|
audit(param).then((res) => {
|
|
|
const { code } = res
|
|
|
if (code === 'OK')
|
|
|
reload()
|
|
|
else message.error(res.message)
|
|
|
})
|
|
|
}
|
|
|
|
|
|
function reload() {
|
|
|
const { page, pageSize } = unref(tableRef.value?.pagination) as PaginationProps
|
|
|
query(page!, pageSize!)
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<template>
|
|
|
<div class="wrapper">
|
|
|
<div class="wrapper-header">
|
|
|
<div class="wrapper-header-left">
|
|
|
<span class="wrapper-header-font">任务管理列表</span>
|
|
|
<SvgIcon size="32" name="magnifying" />
|
|
|
</div>
|
|
|
<div>
|
|
|
<n-button class="xjcc" text @click="showModal(repeatModalRef)">
|
|
|
小结查重
|
|
|
</n-button>
|
|
|
<div class="btn">
|
|
|
<SvgIcon style="margin-right: 6px;" size="14" name="tf" />
|
|
|
批量审批
|
|
|
</div>
|
|
|
<n-popover
|
|
|
ref="popover" :style="{ padding: '0px' }" style="width: 148px" :show-arrow="false"
|
|
|
placement="bottom-start" trigger="click"
|
|
|
>
|
|
|
<template #trigger>
|
|
|
<SvgIcon style="cursor: pointer;" size="20" name="more-ver" />
|
|
|
</template>
|
|
|
<ul class="wrapper-header-action">
|
|
|
<li @click="importHandler">
|
|
|
<SvgIcon size="20" name="download" /><span style="margin-left: 5px;">批量导入数据</span>
|
|
|
</li>
|
|
|
<li @click="exportHandler">
|
|
|
<SvgIcon size="20" name="download" /><span style="margin-left: 5px;">导出待审数据</span>
|
|
|
</li>
|
|
|
<li>
|
|
|
<SvgIcon size="20" name="download" /><span style="margin-left: 5px;">导出全部数据</span>
|
|
|
</li>
|
|
|
</ul>
|
|
|
</n-popover>
|
|
|
</div>
|
|
|
</div>
|
|
|
<!-- <div class="wrapper-statistic">
|
|
|
<div v-for="i in 7" :key="i" class="item">
|
|
|
<div class="icon" />
|
|
|
<div class="content">
|
|
|
<span class="num">6399</span>
|
|
|
<span class="label">任务总数</span>
|
|
|
</div>
|
|
|
<div v-if="i === 7" style="display: flex;align-items: center;">
|
|
|
<div class="divider" />
|
|
|
<SvgIcon size="18" name="setting" />
|
|
|
</div>
|
|
|
</div>
|
|
|
</div> -->
|
|
|
<div class="wrapper-settings">
|
|
|
<SvgIcon style="cursor: pointer;" size="18" name="setting" @click="showModal(customTabelRef)" />
|
|
|
</div>
|
|
|
<div class="wrapper-content">
|
|
|
<NDataTable
|
|
|
ref="tableRef" remote :columns="columnsRef" :scroll-x="1250" :max-height="maxHeight" :data="tableData"
|
|
|
:loading="loading" :pagination="pagination" :row-key="rowKey" @update:page="handlePageChange"
|
|
|
@update:checked-row-keys="handleCheck"
|
|
|
/>
|
|
|
</div>
|
|
|
|
|
|
<CustomTabelModal ref="customTabelRef" @commit="commitHandler" />
|
|
|
<ImportExcelModal ref="importExcelRef" :on-success="sucessHandler" :header-config="headRules" />
|
|
|
<ConfrimModal ref="rejectModalRef" @commit="reject" />
|
|
|
<RepeatModal ref="repeatModalRef" @reject="showModal(rejectModalRef)" @viewrepeat="showModal(repeatTaskTableModalRef)" />
|
|
|
<RepeatTaskTableModal ref="repeatTaskTableModalRef" />
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<style lang="less" scoped>
|
|
|
.wrapper {
|
|
|
display: flex;
|
|
|
flex: 1;
|
|
|
overflow: hidden;
|
|
|
flex-direction: column;
|
|
|
box-sizing: border-box;
|
|
|
margin-left: 16px;
|
|
|
width: 100%;
|
|
|
background: #FFF;
|
|
|
padding: 0px 24px 24px 24px;
|
|
|
|
|
|
&-header {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
box-sizing: border-box;
|
|
|
height: 64px;
|
|
|
width: 100%;
|
|
|
|
|
|
div {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
.xjcc{
|
|
|
font-weight: bold;
|
|
|
color: #6a80fc;
|
|
|
margin-right: 20px;
|
|
|
}
|
|
|
|
|
|
.title {
|
|
|
color: #507afd;
|
|
|
font-weight: bold;
|
|
|
font-size: 14px;
|
|
|
margin-left: 8px;
|
|
|
}
|
|
|
|
|
|
.btn {
|
|
|
width: 118px;
|
|
|
height: 36px;
|
|
|
background: linear-gradient(135deg, #5b85f8, #3c6cf0);
|
|
|
border-radius: 17px;
|
|
|
box-shadow: 0px 2px 6px 0px rgba(116, 153, 253, 0.30);
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
color: #FFF;
|
|
|
margin-right: 6px;
|
|
|
cursor: pointer;
|
|
|
}
|
|
|
|
|
|
.more {
|
|
|
width: 30px;
|
|
|
height: 30px;
|
|
|
line-height: 30px;
|
|
|
opacity: 0.1;
|
|
|
background: linear-gradient(144deg, #73a7f9 0%, #3e6ef1 96%);
|
|
|
border-radius: 8px;
|
|
|
}
|
|
|
|
|
|
&-font {
|
|
|
font-size: 18px;
|
|
|
font-weight: bold;
|
|
|
color: #333333;
|
|
|
line-height: 25px;
|
|
|
margin-right: 8px;
|
|
|
}
|
|
|
|
|
|
&-action {
|
|
|
padding: 8px;
|
|
|
|
|
|
li {
|
|
|
height: 32px;
|
|
|
line-height: 32px;
|
|
|
cursor: pointer;
|
|
|
|
|
|
&:hover {
|
|
|
background-color: #f3f8ff;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
&-settings {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: flex-end;
|
|
|
padding: 12px 0px;
|
|
|
}
|
|
|
|
|
|
&-statistic {
|
|
|
width: 100%;
|
|
|
height: 76px;
|
|
|
background: #f9fafb;
|
|
|
border-radius: 4px;
|
|
|
display: flex;
|
|
|
padding: 0px 10px;
|
|
|
justify-content: space-between;
|
|
|
|
|
|
.item {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
padding: 20px 0;
|
|
|
|
|
|
.icon {
|
|
|
background-color: #ecf2fe;
|
|
|
width: 50px;
|
|
|
height: 50px;
|
|
|
border-radius: 50px;
|
|
|
}
|
|
|
|
|
|
.content {
|
|
|
text-align: center;
|
|
|
margin-left: 12px;
|
|
|
|
|
|
.num {
|
|
|
font-weight: bold;
|
|
|
color: #202020;
|
|
|
font-size: 18px;
|
|
|
}
|
|
|
|
|
|
.label {
|
|
|
color: #202020;
|
|
|
opacity: 0.6;
|
|
|
}
|
|
|
|
|
|
span {
|
|
|
display: block;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.divider {
|
|
|
background-color: #e8e8e8;
|
|
|
margin: 0px 10px;
|
|
|
width: 3px;
|
|
|
height: 20px;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
&-content {
|
|
|
flex: auto;
|
|
|
background: #FFF;
|
|
|
}
|
|
|
}
|
|
|
</style>
|