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.

817 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.

<template>
<div style="padding: 20px">
<el-card shadow="always">
<template #header>
<p>{{ title }}</p>
<vxe-toolbar ref="toolbar" custom print export>
<template #buttons>
<div class="selects">
<el-select
v-if="$route.params.type === 'all'"
style="width: 100px"
size="small"
v-model="select.department_id"
placeholder="部门..."
>
<el-option
v-for="item in departments"
:key="item.id"
:value="item.id"
:label="item.name"
></el-option>
</el-select>
<el-select
style="width: 180px"
filterable
clearable
size="small"
v-model="select.custom_model_id"
>
<el-option
v-for="item in models"
:key="item.id"
:value="item.id"
:label="item.name"
></el-option>
</el-select>
<el-select
style="width: 100px"
size="small"
v-model="select.date_type"
>
<el-option
v-for="item in dataTypes"
:key="item.value"
:value="item.value"
:label="item.label"
></el-option>
</el-select>
<el-date-picker
style="width: 240px"
value-format="yyyy-MM-dd"
size="small"
:value="select.date_range ? select.date_range.split('~') : ''"
type="daterange"
align="right"
unlink-panels
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
:picker-options="pickerOptions"
@input="
([start, end]) => (select.date_range = `${start}~${end}`)
"
>
</el-date-picker>
<el-input
clearable
v-model="select.keyword"
style="width: 160px"
placeholder="请输入关键词"
size="small"
></el-input>
<el-button
icon="el-icon-search"
type="primary"
size="small"
@click="getList(true)"
>搜索</el-button
>
<!-- <el-button-->
<!-- v-if="$route.path === '/flow/list/todo'"-->
<!-- icon="el-icon-edit"-->
<!-- type="primary"-->
<!-- size="small"-->
<!-- plain-->
<!-- @click="multiDeal"-->
<!-- >批量审批</el-button-->
<!-- >-->
</div>
</template>
</vxe-toolbar>
<div class="todo-statistics" v-if="$route.path === '/flow/list/todo'">
<el-tag
v-for="(model, index) in todoTotal"
:key="model.custom_model_id"
:effect="select.custom_model_id === model.custom_model_id ? 'dark' : 'plain'"
:type="['success', 'info', 'warning', 'danger'][index%4]"
@click="select.custom_model_id = model.custom_model_id,getList()">
{{ model.custom_model ? model.custom_model.name : '' }}{{ model.total }}
</el-tag>
</div>
</template>
<template #default>
<div>
<vxe-table
ref="table"
stripe
style="margin-top: 10px"
:loading="loading"
keep-source
:column-config="{ resizable: true }"
:print-config="{}"
:export-config="{}"
:expand-config="{
accordion: true,
padding: true,
}"
:checkbox-config="{
reserve: true,
highlight: true,
range: true,
checkMethod: ({ row }) => {
if ($route.path !== '/flow/list/todo') {
return true
} else {
if (!select.custom_model_id) return false
if (multiDealFlows.length === 0) {
return true
} else {
const firstFlow = multiDealFlows[0]
return row.current_node.id === firstFlow.current_node.id
}
}
}
}"
:row-config="{ keyField: 'id' }"
:custom-config="{ mode: 'popup' }"
:menu-config="menuConfig"
:data="list"
@cell-click="cellClickEvent"
@cell-dblclick="cellDblclickEvent"
@checkbox-change="checkboxChange"
@checkbox-all="checkboxChange"
@menu-click="contextMenuClickEvent"
>
<vxe-column type="checkbox" width="50" align="center"></vxe-column>
<vxe-column :visible="$store.getters.device === 'mobile'" field="m-operate" type="expand" title="操作" width="60" align="center">
<template #content="{ row }">
<div>
<div class="mobile-desc">
<div class="mobile-desc-row">
<div class="mobile-desc-row__title">
流程名称
</div>
<div class="mobile-desc-row__value">
{{ row['custom_model'] ? row['custom_model']['name'] :'' }}
</div>
</div>
<div class="mobile-desc-row">
<div class="mobile-desc-row__title">
发起人
</div>
<div class="mobile-desc-row__value">
{{ row['creator'] ? row['creator']['name'] :'' }}
</div>
</div>
<div class="mobile-desc-row">
<div class="mobile-desc-row__title">
发起人
</div>
<div class="mobile-desc-row__value">
{{ (row['last_log'] && row['last_log']['user']) ? row['last_log']['user']['name'] :'' }}
</div>
</div>
<div class="mobile-desc-row">
<div class="mobile-desc-row__title">
当前节点
</div>
<div class="mobile-desc-row__value">
{{ (row['current_node']) ? row['current_node']['name'] :'' }}
</div>
</div>
<div class="mobile-desc-row">
<div class="mobile-desc-row__title">
简要
</div>
<div class="mobile-desc-row__value">
{{ row['_simple'] }}
</div>
</div>
<div class="mobile-desc-row">
<div class="mobile-desc-row__title">
发起日期
</div>
<div class="mobile-desc-row__value">
{{ row['created_at'] }}
</div>
</div>
<div class="mobile-desc-row">
<div class="mobile-desc-row__title">
当前状态
</div>
<div class="mobile-desc-row__value">
<el-tag
size="mini"
:type="statusColor.get(row.status)"
effect="dark"
>{{ myStatus.get(row.status) }}</el-tag
>
</div>
</div>
</div>
<template v-if="$route.params.type !== 'all'">
<el-button
v-if="row.my_log"
type="primary"
size="mini"
@click="handle(row)"
>办理</el-button
>
<el-button
plain
type="primary"
size="mini"
@click="copyTo(row)"
>抄送</el-button
>
<el-button
v-if="row.can_recall"
plain
type="danger"
size="mini"
@click="recall(row)"
>撤回</el-button
>
<el-button
v-if="row.can_delete || ($store.state.user.adminId === 1)"
plain
type="danger"
size="mini"
@click="destroy(row)"
>删除</el-button
>
</template>
<el-button plain type="success" size="mini" @click="detail(row)"
>查看</el-button
>
</div>
</template>
</vxe-column>
<vxe-column
min-width="220"
header-align="center"
field="title"
title="工作名称"
show-overflow
:title-prefix="{ content: '点击工作名称查看简要。\n批量审批请先选择流程类型并勾选同一节点的流程。' }"
></vxe-column>
<vxe-column
:visible="$route.params.type === 'all'"
width="80"
align="center"
field="id"
title="流水号"
></vxe-column>
<vxe-column
:visible="$store.getters.device === 'desktop'"
width="80"
title="发起人"
align="center"
field="creator.name"
></vxe-column>
<vxe-column
:visible="$route.params.type !== 'all' && $store.getters.device === 'desktop'"
width="80"
title="承办人员"
align="center"
field="last_log.user.name"
></vxe-column>
<vxe-column
:visible="$store.getters.device === 'desktop'"
align="center"
min-width="140"
field="custom_model.name"
title="流程名称"
></vxe-column>
<vxe-column
:visible="$store.getters.device === 'desktop'"
header-align="center"
min-width="240"
field="_simple"
title="简要"
></vxe-column>
<vxe-column
:visible="$route.params.type !== 'all' && $store.getters.device === 'desktop'"
align="center"
width="140"
field="current_node.name"
title="当前节点"
></vxe-column>
<vxe-column
:visible="$route.params.type !== 'all' && $store.getters.device === 'desktop'"
title="收藏状态"
width="90"
field="my_fav"
align="center"
>
<template #default="{ row }">
<el-button
:type="row.my_fav ? 'success' : 'primary'"
size="mini"
@click="toggleFav(row)"
>{{ row.my_fav ? "已收藏" : "未收藏" }}</el-button
>
</template>
</vxe-column>
<vxe-column
:visible="$store.getters.device === 'desktop'"
width="164"
field="no"
align="center"
title="编号"
></vxe-column>
<vxe-column
width="200"
:visible="$route.params.type === 'all' && $store.getters.device === 'desktop'"
align="center"
field="created_at"
title="发起日期"
:formatter="
({ cellValue }) =>
$moment(cellValue).format('YYYY-MM-DD HH:mm:ss')
"
:export-method="
({ row, column, options }) =>
$moment(row['created_at']).format('YYYY-MM-DD HH:mm:ss')
"
></vxe-column>
<vxe-column
:visible="$store.getters.device === 'desktop'"
width="80"
align="center"
field="status"
title="当前状态"
:formatter="({ cellValue }) => myStatus.get(cellValue)"
:export-method="
({ row, column, options }) => myStatus.get(row['status'])
"
>
<template #default="{ row }">
<el-tag
size="mini"
:type="statusColor.get(row.status)"
effect="dark"
>{{ myStatus.get(row.status) }}</el-tag
>
</template>
</vxe-column>
<vxe-column
:visible="$store.getters.device === 'desktop'"
min-width="280"
header-align="center"
field="operate"
title="操作"
fixed="right"
>
<template #default="{ row }">
<template v-if="$route.params.type !== 'all'">
<el-button
v-if="row.my_log"
type="primary"
size="mini"
@click="handle(row)"
>办理</el-button
>
<el-button
plain
type="primary"
size="mini"
@click="copyTo(row)"
>抄送</el-button
>
<el-button
v-if="row.can_recall"
plain
type="danger"
size="mini"
@click="recall(row)"
>撤回</el-button
>
<el-button
v-if="row.can_delete"
plain
type="danger"
size="mini"
@click="destroy(row)"
>删除</el-button
>
</template>
<template v-else>
<el-button
v-if="$store.state.user.adminId === 1"
plain
type="danger"
size="mini"
@click="destroy(row)"
>删除</el-button
>
</template>
<el-button plain type="success" size="mini" @click="detail(row)"
>查看</el-button
>
</template>
</vxe-column>
</vxe-table>
<el-pagination
style="margin-top: 10px"
@size-change="
(e) => {
select.page_size = e;
select.page = 1;
getList();
}
"
@current-change="
(e) => {
select.page = e;
getList();
}
"
:current-page.sync="select.page"
:page-sizes="[10, 20, 30, 50, 100]"
:page-size.sync="select.page_size"
:small="$store.getters.device === 'mobile'"
:layout="$store.getters.device === 'desktop' ? 'total, ->, prev, pager, next, sizes, jumper' : 'total, ->, prev, pager, next'"
:total="total"
></el-pagination>
</div>
</template>
</el-card>
<share ref="share" :is-show.sync="isShowShare" :flow="pickedFlow"></share>
<list-popover :is-show.sync="isShowListPopover" :id="listPopoverId" :pos="listPopoverPos" />
<multi-deal :is-show.sync="isShowMultiDeal" :deal-flows="multiDealFlows" @refresh="getList" />
</div>
</template>
<script>
import { flowList, toggleFav, destroy, recall, updateFlowTime, todoTotal } from "@/api/flow";
import moment from "moment/moment";
import ListPopover from "./components/ListPopover.vue";
import MultiDeal from "./components/MultiDeal.vue"
import share from "./components/share.vue";
import { departmentListNoAuth } from "@/api/common"
export default {
name: "flowList",
components: {
share,
ListPopover,
MultiDeal
},
data() {
return {
todoTotal: [],
isShowMultiDeal: false,
multiDealFlows: [],
isShowListPopover: false,
listPopoverId: "",
listPopoverPos: {
top: 100,
left: 100
},
myStatus: new Map([
[-1, "已退回"],
[0, "办理中"],
[1, "已完成"],
]),
statusColor: new Map([
[-1, "warning"],
[0, ""],
[1, "success"],
]),
pickerOptions: {
shortcuts: [
{
text: "一年前",
onClick(picker) {
picker.$emit("pick", [
moment().subtract(1, "years").toDate(),
new Date(),
]);
},
},
{
text: "一月前",
onClick(picker) {
picker.$emit("pick", [
moment().subtract(1, "months").toDate(),
new Date(),
]);
},
},
{
text: "一周前",
onClick(picker) {
picker.$emit("pick", [
moment().subtract(1, "weeks").toDate(),
new Date(),
]);
},
},
{
text: "一周后",
onClick(picker) {
picker.$emit("pick", [
new Date(),
moment().add(1, "weeks").toDate(),
]);
},
},
{
text: "一月后",
onClick(picker) {
picker.$emit("pick", [
new Date(),
moment().add(1, "months").toDate(),
]);
},
},
{
text: "一年后",
onClick(picker) {
picker.$emit("pick", [
new Date(),
moment().add(1, "years").toDate(),
]);
},
},
],
},
select: {
page: 1,
page_size: 20,
sort_name: "",
sort_type: "",
keyword: "",
department_id: "",
is_fav: "",
custom_model_id: "",
date_range: "",
date_type: "create_date",
},
loading: false,
list: [],
total: 0,
title: "",
models: [],
dataTypes: [
{
value: "create_date",
label: "录入时间",
},
{
value: "happened_date",
label: "经办时间",
},
],
departments: [],
isShowShare: false,
pickedFlow: {},
};
},
methods: {
async getDepartments() {
try {
this.departments = await departmentListNoAuth({
page: 1,
page_size: 9999
})
} catch (err) {
console.error(err)
}
},
contextMenuClickEvent({ menu, row, column }) {
switch (menu.code) {
case 'edit':
// 示例
if (row && column) {
const target = this.$router.resolve({
path: '/flow/edit',
query: {
flow_id: row.id
}
})
window.open(target.href, '_blank')
}
break
default:
}
},
contentFormatter(row) {
const { data, fields } = row
let text = ''
fields.forEach((field, index) => {
let options = []
if (field.show_in_list && field.type !== 'relation' && data[field.name]) {
if (field?.selection_model) {
options = field.selection_model_items || [];
}
text += `${field.label}:${(field?.selection_model ? options.find(j => j.id === data[field.name])?.name : data[field.name]) ?? '-'}\n`
}
row._simple = text
})
},
cellClickEvent(e) {
if (e.column?.field !== 'title') return
this.listPopoverId = e.row.id
this.listPopoverPos.top = e.$event.clientX
this.listPopoverPos.left = e.$event.clientY
this.isShowListPopover = true
},
handle(row) {
this.$router.push(
`/flow/create?module_id=${row.custom_model.id}&flow_id=${row.id}`
);
},
detail(row) {
this.$router.push(
`/flow/detail?module_id=${row.custom_model.id}&flow_id=${row.id}`
);
},
copyTo(row) {
this.pickedFlow = row;
this.isShowShare = true;
},
async toggleFav(row) {
try {
await toggleFav({
id: row.id,
});
await this.getList();
} catch (err) {}
},
async recall(row) {
try {
await recall({ id: row.id });
await this.getList();
} catch (err) {}
},
async destroy(row) {
if (row.can_delete || (this.$store.state.user.adminId === 1)) {
try {
await destroy({ id: row.id });
await this.getList();
} catch (err) {}
}
},
async getList(refresh=false) {
if (this.loading) return
this.loading = true;
try {
if (refresh) {
this.select.page = 1
}
const res = await flowList(this.$route.params.type, this.select, false);
res.data?.data?.forEach(i => this.contentFormatter(i))
this.list = res?.data?.data || [];
this.total = res?.data?.total ?? 0;
this.models = res.customModels;
this.title = res.pageTitle;
this.loading = false;
} catch (err) {
console.error(err);
this.loading = false;
}
},
async cellDblclickEvent({ row, column }) {
if(this.$store.state.user.username !== 'admin') return
if(column.field === 'created_at' || column.field === 'updated_at') {
this.$prompt('请输入时间', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /^(?:19|20)[0-9][0-9]-(?:(?:0[1-9])|(?:1[0-2]))-(?:(?:[0-2][1-9])|(?:[1-3][0-1])) (?:(?:[0-2][0-3])|(?:[0-1][0-9])):[0-5][0-9]:[0-5][0-9]$/,
inputErrorMessage: '时间格式不正确'
}).then(({ value }) => {
updateFlowTime({
id: row.id,
date: value
}).then(_ => {
this.$message.success('更新成功')
this.getList()
})
})
}
},
checkboxChange() {
const reserveRecords = this.$refs['table']?.getCheckboxReserveRecords() || []
const records = this.$refs['table']?.getCheckboxRecords(true) || []
this.multiDealFlows = [...reserveRecords, ...records]
if (this.multiDealFlows.length > 0) {
const firstFlow = this.multiDealFlows[0]
this.multiDealFlows = this.multiDealFlows.filter(flow => {
if (flow.current_node.id === firstFlow.current_node.id) {
return true
} else {
this.$refs['table'].setCheckboxRow(flow, false)
}
})
}
},
async getTodoTotal() {
try {
const res = await todoTotal()
console.log(res)
this.todoTotal = res?.todo || []
} catch (err) {
console.error(err)
}
},
multiDeal() {
if (this.multiDealFlows.length === 0) {
this.$message.warning('请先勾选需要审批的流程')
return
}
this.isShowMultiDeal = true
}
},
computed: {
menuConfig() {
if (this.$store.state.user.adminId === 1) {
return {
className: 'my-menus',
body: {
options: [
[
{ code: 'edit', name: '编辑', prefixConfig: { icon: 'vxe-icon-feedback' } },
],
]
}
}
} else {
return {}
}
}
},
watch: {
'select.custom_model_id': {
handler: function () {
this.$refs['table'].clearCheckboxRow()
this.$refs['table'].clearCheckboxReserve()
this.multiDealFlows = []
}
}
},
activated() {
this.getTodoTotal();
this.getList();
},
created() {
this.getDepartments();
this.getTodoTotal();
this.getList();
},
mounted() {
this.$nextTick(() => {
if (this.$refs["table"] && this.$refs["toolbar"]) {
this.$refs["table"].connect(this.$refs["toolbar"]);
}
});
},
};
</script>
<style scoped lang="scss">
.selects > * + * {
margin: 2px 0 2px 6px;
}
.el-tag {
cursor: pointer;
}
.el-tag + .el-tag {
margin-left: 4px;
}
.mobile-desc {
&-row {
display: flex;
margin-bottom: 20px;
&__title {
flex-basis: 20vw;
font-weight: 600;
text-align: right;
}
&__value {
flex-basis: 52vw;
padding-left: 4vw;
}
}
}
</style>