You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ocr-web/src/views/final/content/Content.vue

1078 lines
25 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<script lang="ts" setup>
import type { DataTableColumns, DataTableRowKey, PaginationProps } from 'naive-ui'
import { NButton, NDataTable, useDialog, useMessage } from 'naive-ui'
import {
computed,
defineEmits,
h,
nextTick,
onBeforeMount,
onMounted,
onUnmounted,
reactive,
ref,
unref,
watch,
} from 'vue'
import { rowPropKeys } from 'naive-ui/es/legacy-grid/src/Row'
import { useRoute, useRouter } from 'vue-router'
import {
CustomTabelModal,
ImportExcelModal,
ListAction,
RepeatModal,
RepeatTaskTableModal,
StatusItem,
} from '../comp'
import { getFinalList } from '@/api/final'
import { audit } from '@/api/task/task'
import SvgIcon from '@/components/Icon/SvgIcon.vue'
import type { RowData } from '@/config/final'
import { findKey, headRules } from '@/config/final'
import { useWindowSizeFn } from '@/hooks/event/useWindowSizeFn'
import { useUser } from '@/store/modules/user'
import { useFinal } from '@/store/modules/final'
import { getViewportOffset } from '@/utils/domUtils'
import { isBoolean } from '@/utils/is'
import { useDictionary } from '@/store/modules/dictonary'
import NotPassed from '@/components/Approval/NotPassed.vue'
import type { ApprovalParam } from '/#/api'
import emitter from '@/utils/mitt'
import { formatToDateHMS } from '@/utils/dateUtil'
import { getAllfieldList, getfieldList, savefield } from '@/api/home/filter'
const emit = defineEmits(['changeShow'])
function changeContent() {
emit('changeShow')
}
const checkedRowKeys = ref([])
const dicStore = useDictionary()
const izstatusList = ref([])
const router = useRouter()
const route = useRoute()
const reviewType = 0
const actionsColumns = {
title: '操作',
key: 'actions',
minWidth: 200,
fixed: 'right',
render(row) {
return h(ListAction, {
id: row.id,
status: row.states,
trigger: (action) => {
actionHandler(action, row)
},
})
},
}
const columnsRef = ref<DataTableColumns<RowData>>([])
// const columns: DataTableColumns<RowData> = [
// {
// type: "selection",
// fixed: "left",
// width: 50,
// },
// {
// title: "任务Id",
// key: "id",
// fixed: "left",
// width: 190,
// },
// {
// title: "任务名称",
// key: "fromtaskname",
// fixed: "left",
// width: 200,
// ellipsis: {
// tooltip: true,
// },
// },
// {
// title: "审批节点",
// key: "taskname",
// 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" });
// },
// render(row: any) {
// const item: any = izstatusList.value.find(
// (item: any) => item.value == row.states
// );
// return item ? item.label : "";
// },
// },
// {
// title: "图片相似度",
// key: "similarityscore",
// 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" });
// },
// render(row: any) {
// return row.similarityscore ? `${row.similarityscore}%` : "";
// },
// },
// {
// title: "提报时间",
// key: "createdate",
// 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" });
// },
// render(row: any) {
// return formatToDateHMS(row.createdate || 0);
// },
// },
// {
// title: "更新时间",
// key: "updatetime",
// width: 200,
// render(row: any) {
// return row.updatetime ? formatToDateHMS(row.updatetime) : "";
// },
// },
// {
// title: "操作",
// key: "actions",
// width: 200,
// fixed: "right",
// render(row) {
// return h(Action, {
// id: row.id,
// status: row.states,
// trigger: (action) => {
// actionHandler(action, row);
// },
// });
// },
// },
// ];
async function getColumns() {
columnsRef.value = [
{
type: 'selection',
fixed: 'left',
width: 50,
disabled(row: any) {
return row.states !== 2
},
},
]
const userStore = useUser()
const userInfo = userStore.getUserInfo
let res
res = await getAllfieldList(reviewType) // 所有筛选项目
const allList = res.data
res = await getfieldList(reviewType, userInfo.id) // 当前用户选择的项目
const useList = res.data
const userFieldFixed = useList.userFieldFixed?.split(',')
const userFieldUnFixed = useList.userFieldUnFixed?.split(',')
allList?.map((v) => {
if (!userFieldFixed?.length && !userFieldUnFixed?.length) {
if (v.isrequired == 2) {
columnsRef.value.push({
title: v.fieldDesc,
fixed: 'left',
key: v.name,
width: 120,
})
}
}
if (userFieldFixed?.find(v2 => v2 == v.name)) {
columnsRef.value.push({
title: v.fieldDesc,
key: v.name,
fixed: 'left',
width: 120,
})
}
if (userFieldUnFixed?.find(v2 => v2 == v.name)) {
columnsRef.value.push({
title: v.fieldDesc,
key: v.name,
width: 120,
})
}
})
columnsRef.value.push(actionsColumns as any)
formatColumns()
console.log(columnsRef.value, 'columnsRef')
}
async function formatColumns() {
// TODO处理特殊字段
let index
index = columnsRef.value.findIndex(v => v.title == '任务ID')
if (index > -1) {
columnsRef.value[index] = {
title: '任务ID',
key: columnsRef.value[index].key, // "id"
fixed: columnsRef.value[index].fixed || undefined,
width: 200,
render(row) {
const item: any = izstatusList.value.find(
(item: any) => item.value == row.states,
)
return h(
NButton,
{
'strong': true,
'tertiary': true,
'text': true,
'size': 'small',
'text-color': '#507AFD',
'onClick': () => goDetail(row),
},
{ default: () => row.id },
)
},
}
}
index = columnsRef.value.findIndex(v => v.title == '所属项目')
if (index > -1) {
columnsRef.value[index] = {
title: '所属项目',
key: columnsRef.value[index].key, // "fromtaskname"
fixed: columnsRef.value[index].fixed || undefined,
width: 200,
ellipsis: {
tooltip: true,
},
}
}
index = columnsRef.value.findIndex(v => v.title == '任务名称')
if (index > -1) {
columnsRef.value[index] = {
title: '任务名称',
key: columnsRef.value[index].key, // "fromtaskname"
fixed: columnsRef.value[index].fixed || undefined,
width: 200,
ellipsis: {
tooltip: true,
},
}
}
index = columnsRef.value.findIndex(v => v.title == '审批状态')
if (index > -1) {
columnsRef.value[index] = {
title: '审批状态',
key: columnsRef.value[index].key,
fixed: columnsRef.value[index].fixed || undefined,
width: 120,
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' })
},
render(row) {
const item: any = izstatusList.value.find(
(item: any) => item.value == row.states,
)
return h(StatusItem, {
id: row.id,
status: row.states,
label: item ? item.label : '',
})
},
}
}
index = columnsRef.value.findIndex(v => v.title == '图片相似度')
if (index > -1) {
columnsRef.value[index] = {
title: '图片相似度',
key: columnsRef.value[index].key,
fixed: columnsRef.value[index].fixed || undefined,
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' })
},
render(row: any) {
return h(
'div',
{
id: row.id,
style: { color: row.similarityscore === 100 ? '#FF4E4F' : '' },
},
{ default: () => (row.similarityscore ? `${row.similarityscore}%` : '') },
)
},
}
}
index = columnsRef.value.findIndex(v => v.title == '提报时间')
if (index > -1) {
columnsRef.value[index] = {
title: '提报时间',
key: columnsRef.value[index].key,
fixed: columnsRef.value[index].fixed || undefined,
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' })
},
render(row: any) {
return formatToDateHMS(row.createdate || 0)
},
}
}
index = columnsRef.value.findIndex(v => v.title == '更新时间')
if (index > -1) {
columnsRef.value[index] = {
title: '更新时间',
key: columnsRef.value[index].key,
fixed: columnsRef.value[index].fixed || undefined,
width: 200,
render(row: any) {
return row.updatetime ? formatToDateHMS(row.updatetime) : ''
},
}
}
}
const deviceHeight = ref(600)
onMounted(() => {
emitter.on('filter-final', refreshHandler)
getColumns()
nextTick(() => {
computeListHeight()
})
})
onBeforeMount(() => {
dicStore.fetchizstatusListt()
})
watch(
() => dicStore.izstatusList,
(newval) => {
izstatusList.value = newval
},
)
onUnmounted(() => {
emitter.off('filter-final', refreshHandler)
})
const tableRef = ref<InstanceType<typeof NDataTable>>()
const rowKey = (row: RowData) => row.id
const loading = ref(true)
const total = ref(0)
const pagination = reactive({
page: 1,
pageCount: 10,
pageSize: 10,
showSizePicker: true,
pageSizes: [
{
label: '10 每页',
value: 10,
},
{
label: '15 每页',
value: 15,
},
{
label: '30 每页',
value: 30,
},
{
label: '50 每页',
value: 50,
},
],
showQuickJumper: true,
prefix: () => `${total.value} 条数据`,
})
const tableData = ref<Array<RowData>>([])
const selectionIds = ref<DataTableRowKey[]>([])
const dialog = useDialog()
const message = useMessage()
const finalStore = useFinal()
async function query(page: number, pageSize: number, filterId?: any) {
const asideParmas = unref(finalStore.getAsideValue)
// 有过滤配置的时候优先使用过滤配置,不要使用左侧参数
const params = filterId ? { userSearchId: filterId } : asideParmas
const result = await getFinalList({
sortorder: 'asc',
pageSize,
currPage: page,
sortname: '',
...params,
})
const { data, pageCount, totalCount } = result
tableData.value = data
total.value = totalCount
pagination.page = page
pagination.pageCount = Math.ceil(totalCount / pageSize)
loading.value = false
}
async function handlePageChange(currentPage) {
if (loading.value)
return
const { pageSize } = pagination
pagination.page = currentPage
await query(currentPage, pageSize)
}
async function handlePageSizeChange(currentPageSize) {
if (loading.value)
return
const { page } = pagination
pagination.pageSize = currentPageSize
await query(page, currentPageSize)
}
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 notPassModalRef = 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, actionsColumns]
formatColumns()
}
function actionHandler(action: any, row: any) {
const { key } = action
switch (key) {
case 'view':
goDetail(row)
break
case 'reset':
resetHandler()
break
case 'approval':
singleApproval(row)
break
case 'reject':
rejectHandler([row])
break
default:
break
}
}
// states:1未提交2待审批3通过4不通过
function validate(items: any[]) {
if (items.length === 0)
return '至少选中一个任务'
return null
}
function goDetail(row) {
router.push({ name: 'final-detail', query: { id: row.id, packageid: row.packageid } })
}
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))
}
const showActions = computed(() => {
return selectionIds.value.length
})
// 单个审批通过
function singleApproval(row) {
const param = {
result: true,
comment: '',
disposeType: '',
disposeTypeId: '',
failCauseId: '',
failCauseName: '',
flowTaskInfoList: [
{
formId: row.id,
taskId: row.taskId,
taskName: row.fromTaskName,
},
],
}
doAudit(param)
}
// 批量通过
function batchApproval() {
const items: any = getSelectItems()
const msg = validate(items)
if (msg !== null) {
message.error(msg)
return
}
console.log(items)
const list: any = []
items.forEach((item) => {
list.push({
formId: item.id,
taskId: item.taskId,
taskName: item.fromtaskname,
})
})
const param = {
result: true,
comment: '',
disposeType: '',
disposeTypeId: '',
failCauseId: '',
failCauseName: '',
flowTaskInfoList: list,
}
doAudit(param)
}
// 小结批量不通过
function repeatBatchReject(items) {
console.log(items)
rejectHandler(items)
}
// 批量不通过
function batchReject() {
const items: any = getSelectItems()
rejectHandler(items)
}
// 审核不通过
function rejectHandler(list) {
const msg = validate(list)
if (msg !== null) {
message.error(msg)
return
}
const modal = unref(notPassModalRef)! as any
modal.showModal(list)
}
// 审核通过
function doAudit(param: any) {
dialog.info({
title: '确认提示',
content: '确认给该任务审批为【通过】吗?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
audit(param).then((res) => {
const { code } = res
if (code === 'OK') {
message.success(res.message)
reload()
}
else {
message.error(res.message)
}
})
},
onNegativeClick: () => {},
})
}
function switchBatch() {
selectionIds.value = []
checkedRowKeys.value = []
}
function reload() {
selectionIds.value = []
checkedRowKeys.value = []
const { page, pageSize } = unref(tableRef.value?.pagination) as PaginationProps
query(page!, pageSize!)
}
watch(
() => finalStore.asideValue,
(newVal, oldVal) => {
refreshHandler()
},
{ deep: true },
)
function reset() {
pagination.page = 1
pagination.pageCount = 1
}
async function refreshHandler(searchId?: any) {
reset()
query(pagination.page, pagination.pageSize, searchId)
}
function filterTableData(keyword) {
tableData.value = tableData.value.filter(item => item.id.includes(keyword))
}
defineExpose({
filterTableData,
})
</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" @click="changeContent" />
</div>
<div>
<SvgIcon style="margin-right: 6px;" size="13" name="summary" @click="showModal(repeatModalRef)" />
<NButton class="xjcc" text @click="showModal(repeatModalRef)">
</NButton>
<div v-show="!showActions" style="display: flex; align-items: center">
<div class="btn">
<SvgIcon style="margin-right: 6px" size="22" name="batch" />
批量审批
</div>
</div>
<div v-show="showActions" class="batch">
<NButton text @click="switchBatch()">
<template #icon>
<SvgIcon name="revoke" />
</template>
取消
</NButton>
<img class="btn-approval btn-left" src="@/assets/images/task/btn-not-pass.png" alt="" @click.stop="batchReject">
<SvgIcon size="24" name="vs" />
<img
class="btn-approval"
src="@/assets/images/task/btn-pass.png"
alt=""
@click.stop="batchApproval"
>
</div>
<n-popover
ref="popover"
:style="{ padding: '0px' }"
style="width: 148px"
:show-arrow="false"
placement="bottom-start"
trigger="click"
>
<template #trigger>
<div class="icon-wrap">
<SvgIcon size="20" name="more-blue" />
</div>
</template>
<ul class="wrapper-header-action">
<li @click="importHandler">
<SvgIcon size="20" name="import" /><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>
<!-- <li>
<SvgIcon size="20" name="look" /><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="column"
@click="showModal(customTabelRef)"
/>
</div>
<div class="wrapper-content">
<NDataTable
ref="tableRef"
v-model:checked-row-keys="checkedRowKeys"
remote
:columns="columnsRef"
:scroll-x="1250"
:max-height="maxHeight"
:data="tableData"
:loading="loading"
:pagination="pagination"
:row-key="rowKey"
@update:page="handlePageChange"
@update-page-size="handlePageSizeChange"
@update:checked-row-keys="handleCheck"
/>
</div>
<CustomTabelModal ref="customTabelRef" @commit="commitHandler" />
<ImportExcelModal
ref="importExcelRef"
:on-success="sucessHandler"
:header-config="headRules"
/>
<NotPassed ref="notPassModalRef" @success="reload" />
<RepeatModal
ref="repeatModalRef"
@reject="repeatBatchReject"
@viewrepeat="showModal(repeatTaskTableModalRef)"
/>
<RepeatTaskTableModal ref="repeatTaskTableModalRef" />
</div>
</template>
<style lang="less" scoped>
.color-red {
color: #ff4e4f !important;
}
.batch {
display: flex;
align-items: center;
margin-right: 10px;
.btn-approval {
width: 68px;
height: 28px;
cursor: pointer;
}
.btn-left {
margin-left: 16px;
}
}
.icon-wrap {
display: flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
background: rgba(80, 122, 253, 0.1);
border-radius: 8px;
cursor: pointer;
}
.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: #507afd;
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.3);
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;
}
}
:deep(.n-data-table .n-data-table-td.n-data-table-td--fixed-right) {
z-index: 100 !important;
}
</style>