Compare commits

...

37 Commits

@ -20,6 +20,7 @@
"echarts": "^5.0.0",
"element-ui": "^2.15.14",
"exceljs": "^4.4.0",
"file-saver": "^2.0.5",
"js-cookie": "2.2.0",
"json-bigint": "^1.0.0",
"moment": "^2.29.4",
@ -33,7 +34,8 @@
"vuex": "3.1.0",
"vxe-pc-ui": "^3.1.0",
"vxe-table": "^3.8.25",
"vxe-table-plugin-export-xlsx": "^3.3.4"
"vxe-table-plugin-export-xlsx": "^3.3.4",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@vue/cli-plugin-babel": "4.4.4",

@ -51,3 +51,11 @@ export function preDistance(params) {
params
})
}
export function leaveByYear(params) {
return request({
method: 'get',
url: '/api/oa/statistics/leave-by-year',
params
})
}

@ -0,0 +1,19 @@
import request from '@/utils/request'
export function leaveList(params, isLoading = true) {
return request({
method: 'get',
url: '/api/oa/chart/leave',
params,
isLoading
})
}
export function overtimeList(params,isLoading = true) {
return request({
method: 'get',
url: '/api/oa/chart/overtime',
params,
isLoading
})
}

@ -105,6 +105,15 @@ export function todoTotal() {
})
}
// 批量审批检查节点
export function getNextNodeBatch(params) {
return request({
url: '/api/oa/flow/get-next-node-batch',
method: 'get',
params,
})
}
//流转
export function assign(flow_id, data) {
return request({

@ -0,0 +1 @@
<svg t="1739774928951" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6189" width="200" height="200"><path d="M822.276658 170.174061h-100.687434v97.920327c0 29.88111-24.221616 54.103942-54.103942 54.103942s-54.103942-24.221616-54.103942-54.103942V170.174061H411.022476v97.920327c0 29.88111-24.221616 54.103942-54.103943 54.103942s-54.103942-24.221616-54.103942-54.103942V170.174061H201.724558c-68.261801 0-124.109934 55.848132-124.109934 124.108717v496.439734c0 68.260585 55.848132 124.11115 124.109934 124.11115h620.5521c68.258152 0 124.108717-55.848132 124.108718-124.11115V294.282778c0.001216-68.260585-55.849348-124.108717-124.108718-124.108717z m62.053751 620.550884c0 34.128468-27.925282 62.05375-62.053751 62.053751H201.724558c-34.129684 0-62.054967-27.925282-62.054967-62.053751V418.392712h744.660818v372.332233z" fill="" p-id="6190"></path><path d="M387.581732 263.376318c0 17.134172-13.890271 31.024443-31.024443 31.024442-17.134172 0-31.023226-13.890271-31.023226-31.024442V139.141104c0.001216-17.132956 13.889054-31.023226 31.024442-31.023226 17.134172 0 31.024443 13.890271 31.024443 31.023226l-0.001216 124.235214zM698.149697 263.376318c0 17.134172-13.889054 31.024443-31.024443 31.024442-17.134172 0-31.024443-13.890271-31.024443-31.024442V139.141104c0-17.132956 13.890271-31.023226 31.024443-31.023226 17.135388 0 31.024443 13.890271 31.024443 31.023226v124.235214z" fill="" p-id="6191"></path><path d="M354.367929 559.588165c15.552968-16.534531 32.589835-41.346057 51.085059-74.438229 10.696238-22.372823 21.893597-32.110608 33.570181-29.191462 14.595731 2.919146 20.43524 11.198575 17.514878 24.812743-0.981563 1.960693-2.43992 3.899493-4.37872 5.838293-1.96191 4.881056-2.919146 7.800202-2.919146 8.757438h205.799808c13.614168 0.980347 20.912034 8.278212 21.893596 21.893597 0 14.595731-6.817423 21.893597-20.432807 21.893596H555.789018v45.24555h94.872251c11.676585 0.981563 18.47333 7.798986 20.43524 20.43524-0.981563 13.637278-7.297866 20.432807-18.976883 20.432807H555.789018v71.520298h109.467982c11.676585 0.981563 17.514877 8.279428 17.514878 21.893597-0.981563 13.638494-7.297866 20.434023-18.974451 20.434023h-108.008409v49.625486c-0.981563 11.676585-8.279428 17.992887-21.893597 18.97445-14.595731 0-21.893597-5.837076-21.893597-17.513661v-51.085058h-151.795602c-12.656931 0-19.476787-6.795529-20.434024-20.434024 0.957237-13.614168 7.777092-20.912034 20.434024-21.893596h33.571397v-77.357375c-1.96191-24.311623 10.695022-35.988207 37.947685-35.030971h80.27652v-45.245549h-86.114813c-13.637278 23.351953-27.731889 40.868047-42.32762 52.543415-13.638494 9.737785-25.315079 10.217012-35.029754 1.459573-7.800202-9.714675-5.838292-20.91325 5.838292-33.570181z m81.736094 75.897801v61.30207h75.896585v-71.519082h-65.680789c-7.798986 0.980347-11.197358 4.378719-10.215796 10.217012z" fill="" p-id="6192"></path></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

@ -110,4 +110,3 @@ if (window.__POWERED_BY_WUJIE__) {
}
}).$mount("#app")
}

@ -67,7 +67,7 @@ const actions = {
// get user info
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
getInfo(getToken()).then(response => {
const { name, avatar, id, role, department, username, year_holiday } = response
@ -88,7 +88,7 @@ const actions = {
// user logout
logout({ commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
logout(getToken()).then(() => {
removeToken() // must remove token first
resetRouter()
commit('RESET_STATE')

@ -1,12 +1,13 @@
import { CreateElement, VNode } from "vue";
import moment from "moment";
import { getToken } from "@/utils/auth";
import { deepCopy, formatFileSize, formatTime } from "@/utils/index";
import { deepCopy, formatFileSize, formatTime, debounce } from "@/utils/index";
import { uploadSize } from "@/settings";
import axios from "axios";
import { flowList } from "@/api/flow";
import MobilePicker from '@/components/MobilePicker/index.vue';
import MobileMultipleSelect from "@/components/MobileMultipleSelect/index.vue";
import { Message } from 'element-ui'
/**
* @param {String} device 'desktop' | 'mobile'
* @param {Object} info field参数
@ -448,11 +449,17 @@ export default function formBuilder(
.map((i) => Number(i))
: [],
clearable: true,
placeholder: info.help_text,
placeholder: info.help_text || '输入标题或编号搜索流程..',
multiple: true,
filterable: true,
"reserve-keyword": true,
loading: this.flowSelectLoading,
"filter-method": debounce((query) => {
this.tempFlowList = this.flows[info.name].filter(flow => new RegExp(query, 'i').test(flow.title + flow.no))
console.log(query)
if (this.tempFlowList.length === 0) {
Message.warning("未搜索到匹配流程")
}
}, 500).bind(this)
},
attrs: {
placeholder: info.help_text,
@ -463,16 +470,29 @@ export default function formBuilder(
on: {
input: (e) => {
this.$set(target, info.name, e.toString());
this.tempFlowList.length = 0;
},
},
},
this.flows[info.name]?.map((option) =>
(this.tempFlowList.length > 0 ? this.tempFlowList : this.flows[info.name])?.map((option) =>
h("el-option", {
props: {
label: option.title,
value: option.id,
},
})
}, [
h("div", {
}, [
h("span", {},option.title),
h("span", {
style: {
color: '#999',
float: 'right',
'font-size': '12px'
}
},option.no)
])
])
)
);
break;
@ -907,7 +927,7 @@ export default function formBuilder(
h(
"span",
log.updated_at
? this.$moment(log.updated_at).format("YYYY年MM月DD")
? this.$moment(log.updated_at).format("YYYY年MM月DD")
: ""
),
])
@ -921,6 +941,9 @@ export default function formBuilder(
: h(
"el-form-item",
{
attrs: {
'data-field-id': info.id
},
props: {
prop: info.name,
label: info.label_show ? info.label : "",
@ -994,7 +1017,7 @@ export default function formBuilder(
h(
"div",
this.$moment(log.updated_at).format(
"YYYY年MM月DD日 HH时mm分"
"YYYY年MM月DD日"
)
),
]);
@ -1761,7 +1784,7 @@ export default function formBuilder(
h(
"span",
log.updated_at
? this.$moment(log.updated_at).format("YYYY年MM月DD")
? this.$moment(log.updated_at).format("YYYY年MM月DD")
: ""
),
])
@ -1772,6 +1795,7 @@ export default function formBuilder(
});
return row
? h("el-form-item", {
ref: info.name,
props: {
prop: info.name,
label: (!info.label_show || info.type === 'relation') ? "" : info.label,
@ -1842,7 +1866,7 @@ export default function formBuilder(
h(
"div",
this.$moment(log.updated_at).format(
"YYYY年MM月DD日 HH时mm分"
"YYYY年MM月DD日"
)
),
]);

@ -189,7 +189,7 @@ export function debounce(fn, delay = 500) {
return function _debounce() {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn()
fn.apply(this, arguments)
}, delay)
}
}

@ -1,4 +1,5 @@
import store from '@/store'
import moment from 'moment'
/**
* @param{string} printJs 打印模版
* @param{boolean} isLog 是否带审批
@ -10,8 +11,8 @@ export async function print(printJs, isLog, form, logContent) {
const staticMap = new Map([
['apply_name', `<span>${form.creator?.name}</span>`],
['apply_department_name', `<span>${form.creator_department?.name}</span>`],
['apply_sign', form.creator?.sign_file ? `<img src="${form.creator?.sign_file}" alt="${form.creator?.name}" style="max-height: 80px;max-width: 120px;">` : `<span>${form.creator?.name}</span>`],
['created_at', `<span>${form.created_at}</span>`],
['apply_sign', form.creator?.sign_file ? `<img src="${form.creator?.sign_file?.url}" alt="${form.creator?.name}" style="max-height: 80px;max-width: 120px;">` : `<span>${form.creator?.name}</span>`],
['created_at', `<span>${moment(form.created_at).format('YYYY年MM月DD日')}</span>`],
])
let printStr = printJs
@ -38,7 +39,7 @@ export async function print(printJs, isLog, form, logContent) {
})
let subFormBody = subForm.content.match(/<table(.*?)<\/table>/g)[0]
let subFormStyle = subForm.content.match(/<style>(.*?)<\/style>/g)[0]
printStr = printStr.replace('</style>',`</style>${subFormStyle}<style>.vxe-table { width: 100%;border-top: 1px solid;border-left: 1px solid; } .vxe-table * { width: auto !important;white-space: initial; }.vxe-table:not(.is--print) .col--ellipsis > div { word-break: break-all;white-space: normal;overflow: initial; }.tblPrint .vxe-table td,.tblPrint .vxe-table th { font-size: 20px;padding: 0; }</style>`)
printStr = printStr.replace('</style>',`</style>${subFormStyle}`)
printStr = printStr.replace(fieldMath,subFormBody)
console.log(fieldMath, printStr)
} else {
@ -49,13 +50,14 @@ export async function print(printJs, isLog, form, logContent) {
console.log('未找到name属性');
}
}
printStr = printStr.replace('</style>',`.vxe-table { width: 100%;border-top: 1px solid;border-left: 1px solid; } .vxe-table * { width: auto !important;white-space: initial; }.vxe-table:not(.is--print) .col--ellipsis > div { word-break: break-all;white-space: normal;overflow: initial; }.tblPrint .vxe-table td,.tblPrint .vxe-table th { font-size: 20px;padding: 0; }.el-image__inner{width:100%;height:100%}</style>`)
if(isLog) {
const logStyle = logContent.match(/<style>(.*?)<\/style>/g)
let totalLogStyle= ''
logStyle.forEach(item => {
totalLogStyle += item
})
printStr = printStr.replace('</style>',`</style>${totalLogStyle}<style>.vxe-table { width: 100%; } .vxe-table * { width: auto !important;white-space: initial; }.vxe-table:not(.is--print) .col--ellipsis > div { word-break: break-all;white-space: normal;overflow: initial; }.tblPrint .vxe-table td,.tblPrint .vxe-table th { font-size: 20px;padding: 0; }</style>`)
printStr = printStr.replace('</style>',`</style>${totalLogStyle}<style>.vxe-table { width: 100%; } .vxe-table * { width: auto !important;white-space: initial; }.vxe-table:not(.is--print) .col--ellipsis > div { word-break: break-all;white-space: normal;overflow: initial; }.tblPrint .vxe-table td,.tblPrint .vxe-table th { font-size: 20px;padding: 0; }.el-image__inner{width:100%;height:100%}</style>`)
const logBody = logContent.match(/<table(.*?)<\/table>/g)[0]
printStr = printStr.replace('</table>\n<style>',`</talbe>${logBody}<style>`)
}

@ -8,11 +8,11 @@
type="daterange"
size="small"
value-format="yyyy-MM-dd"
clearable
style="width: 260px;"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期">
end-placeholder="结束日期"
@input="e => (e && e.length === 2) ? (select.start_date = e[0],select.end_date = e[1]) : (select.start_date = '',select.end_date = '')">
</el-date-picker>
<el-button icon="el-icon-search" type="primary" plain size="small" style="margin-left: 6px;" @click="getList"></el-button>
</template>
@ -128,6 +128,28 @@
</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"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
></el-pagination>
</card-container>
</div>
</template>
@ -151,7 +173,7 @@ export default {
tableData: [],
select: {
page: 1,
page_size: 9999,
page_size: 10,
start_date: this.$moment().subtract(1, 'months').format('YYYY-MM-DD'),
end_date: this.$moment().add(1, 'months').format('YYYY-MM-DD')
},

@ -0,0 +1,78 @@
<template>
<div>
<card-container>
<vxe-grid v-bind="tableData">
<template #toolbarButtons>
<el-date-picker v-model="select.year" type="year" size="small" value-format="yyyy"></el-date-picker>
<el-button icon="el-icon-search" type="primary" plain size="small" style="margin-left: 6px;" @click="getData"></el-button>
</template>
</vxe-grid>
</card-container>
</div>
</template>
<script>
import { leaveByYear } from '@/api/attendance'
export default {
data() {
return {
select: {
year: new Date().getFullYear().toString()
},
tableData: {}
}
},
methods: {
async getData() {
try {
const res = await leaveByYear(this.select)
if (res[0].other) {
this.tableData = {
minHeight: 200,
maxHeight: 600,
toolbarConfig: {
print: true,
export: true,
custom: true,
slots: {
buttons: 'toolbarButtons'
}
},
sortConfig: {
},
columns: [
{
title: '姓名',
field: 'name',
width: 120,
align: 'center',
filters: [{ data: '' }],
filterRender: {
name: 'input'
}
},
...Object.keys(res[0].other)?.map(key => ({
title: key,
width: 'auto',
field: 'other',
align: 'center',
formatter: ({ cellValue }) => cellValue[key] ?? 0,
}))
],
data: res
}
}
} catch (err) {
console.error(err)
}
}
},
computed: {},
created() {
this.getData()
}
}
</script>
<style scoped lang="scss">
</style>

@ -15,8 +15,7 @@
<el-tag v-if="dayAttendances(data.day).attendance.sign_in_at" effect="dark" size="mini">{{ $moment(dayAttendances(data.day).attendance.sign_in_at).format('HH:mm:ss') }}</el-tag>
<el-tag v-if="dayAttendances(data.day).attendance.sign_out_at" effect="dark" type="success" size="mini">{{ $moment(dayAttendances(data.day).attendance.sign_out_at).format('HH:mm:ss') }}退</el-tag>
<el-tag v-if="dayAttendances(data.day).attendance.chuchai && dayAttendances(data.day).attendance.chuchai instanceof Array && dayAttendances(data.day).attendance.chuchai[0]" effect="dark" type="warning" size="mini"></el-tag>
<el-tag v-if="dayAttendances(data.day).attendance.chuchai && dayAttendances(data.day).attendance.chuchai instanceof Array && dayAttendances(data.day).attendance.chuchai[1]" effect="dark" type="warning" size="mini"></el-tag>
<el-tag v-if="dayAttendances(data.day).attendance.chuchai && dayAttendances(data.day).attendance.chuchai instanceof Array && (dayAttendances(data.day).attendance.chuchai[0] || dayAttendances(data.day).attendance.chuchai[1])" effect="dark" type="warning" size="mini"></el-tag>
<el-tag v-if="dayAttendances(data.day).attendance.qingxiujia && dayAttendances(data.day).attendance.qingxiujia instanceof Array && dayAttendances(data.day).attendance.qingxiujia[0]" effect="dark" type="info" size="mini"></el-tag>
<el-tag v-if="dayAttendances(data.day).attendance.qingxiujia && dayAttendances(data.day).attendance.qingxiujia instanceof Array && dayAttendances(data.day).attendance.qingxiujia[1]" effect="dark" type="info" size="mini"></el-tag>
@ -45,10 +44,10 @@
{{ dayAttendances(selectDay).on_duty_schedules.status ? '已值班' : '待值班' }}
</el-tag>
<template v-if="selectDay && dayAttendances(selectDay).attendance.chuchai && dayAttendances(selectDay).attendance.chuchai instanceof Array && dayAttendances(selectDay).attendance.chuchai[0]">
<template v-if="selectDay && dayAttendances(selectDay).attendance.chuchai && dayAttendances(selectDay).attendance.chuchai instanceof Array && (dayAttendances(selectDay).attendance.chuchai[0] ||dayAttendances(selectDay).attendance.chuchai[1])">
<el-descriptions :column="2" size="mini" border>
<template #title>
<el-tag effect="dark" type="warning" size="mini">上午出差</el-tag>
<el-tag effect="dark" type="warning" size="mini">出差</el-tag>
</template>
<el-descriptions-item label="目的地">{{ dayAttendances(selectDay).attendance.chuchai[0].mudidi }}</el-descriptions-item>
<el-descriptions-item label="交通方式">{{ dayAttendances(selectDay).attendance.chuchai[0].jiaotongfangshi }}</el-descriptions-item>
@ -57,18 +56,6 @@
</el-descriptions>
</template>
<template v-if="selectDay && dayAttendances(selectDay).attendance.chuchai && dayAttendances(selectDay).attendance.chuchai instanceof Array && dayAttendances(selectDay).attendance.chuchai[1]">
<el-descriptions :column="2" size="mini" border>
<template #title>
<el-tag effect="dark" type="warning" size="mini">下午出差</el-tag>
</template>
<el-descriptions-item label="目的地">{{ dayAttendances(selectDay).attendance.chuchai[1].mudidi }}</el-descriptions-item>
<el-descriptions-item label="交通方式">{{ dayAttendances(selectDay).attendance.chuchai[1].jiaotongfangshi }}</el-descriptions-item>
<el-descriptions-item label="开始时间">{{ dayAttendances(selectDay).attendance.chuchai[1].kaishi }}</el-descriptions-item>
<el-descriptions-item label="结束时间">{{ dayAttendances(selectDay).attendance.chuchai[1].jieshu }}</el-descriptions-item>
</el-descriptions>
</template>
<template v-if="selectDay && dayAttendances(selectDay).attendance.jiaban && dayAttendances(selectDay).attendance.jiaban instanceof Array && dayAttendances(selectDay).attendance.jiaban.filter(i => i).length > 0">
<el-descriptions :column="2" size="mini" border v-for="item in dayAttendances(selectDay).attendance.jiaban.filter(i => i)" :key="item.id">
<template #title>
@ -189,7 +176,7 @@ export default {
this.clockLogs = await index({
month: this.$moment(this.day).format('YYYY-MM')
})
this.$emit('today-attendance', this.clockLogs.today_attendance.details)
this.$emit('today-attendance', this.clockLogs.today_attendance)
} catch (err) {
console.error(err)
}

@ -3,9 +3,23 @@
<CardContainer>
<div>
<el-button v-if="isShowDuty" type="primary" style="display: block;margin: auto auto 20px;" @click="startDuty"></el-button>
<button class="sign-btn" @click="clockIn">
<span>{{ isOutSign ? ' 外勤' : '' }}打卡</span>
</button>
<div style="display: flex;">
<button class="sign-btn" v-if="signStatus === 1" @click="singType=1,clockIn(false)">
<span>上班打卡</span>
</button>
<button class="sign-btn" v-if="signStatus === 2 || signStatus === 0" @click="singType=1,clockIn(true)">
<span>上班外勤打卡</span>
</button>
<template v-if="todayAttendance.sign_in_at">
<button class="sign-btn" v-if="signStatus === 1" @click="singType=2,clockIn(false)">
<span>下班打卡</span>
</button>
<button class="sign-btn" v-if="signStatus === 2 || signStatus === 0" @click="singType=2,clockIn(true)">
<span>下班外勤打卡</span>
</button>
</template>
</div>
<div class="sign-info">
<div class="sign-statue">
<div>打卡状态 <el-tag size="small" effect="dark" type="primary">{{ (isGetLocation || isIpSign) ? (isOutSign ? '外勤打卡' : '可打卡') : '不可打卡' }}</el-tag></div>
@ -21,10 +35,10 @@
</div>
</div>
<div class="sign-log" v-if="todayAttendance.length > 0">
<div class="sign-log" v-if="todayAttendance.details && todayAttendance.details.length > 0">
<el-timeline>
<el-timeline-item
v-for="(item) in todayAttendance"
v-for="(item) in todayAttendance.details"
:key="item.id"
:type="item.sign_at_image ? 'warning' : 'primary'"
:timestamp="item.sign_at">
@ -84,8 +98,10 @@ export default {
},
data() {
return {
singType: 1,
uploadSize,
isInUni: false,
signStatus: 0,
// start
loading: false,
action: process.env.VUE_APP_UPLOAD_API,
@ -107,7 +123,7 @@ export default {
},
maxDistance: '',
nowDistance: '',
todayAttendance: [],
todayAttendance: {},
//
isShowDuty: false,
@ -160,7 +176,9 @@ export default {
if (this.isIpSign) {
const res = await signIp({
image_id: this.imageId,
remark: this.remark
remark: this.remark,
status: 3,
type: this.singType
})
} else {
const res = await sign({
@ -180,7 +198,7 @@ export default {
this.loading = false
}
}, 1000, true),
clockIn: throttle(async function() {
clockIn: throttle(async function(isOut=true) {
try {
if (!this.isInUni && !this.isIpSign) {
await this.getLocation()
@ -195,12 +213,15 @@ export default {
return
}
if(!this.isGetLocation && !this.isIpSign) return
if(this.isOutSign) {
if(isOut) {
this.isShow = true
return
}
if (this.isIpSign) {
const res = await signIp()
const res = await signIp({
status: 1,
type: this.singType
})
} else {
const res = await sign({
location: `${this.pos.lng},${this.pos.lat}`,
@ -262,10 +283,12 @@ export default {
async preIp() {
try {
const res = await preIp()
this.isOutSign = false
const { result } = await preIp()
this.signStatus = result
// this.isOutSign = false
} catch (err) {
this.isOutSign = true
// this.isOutSign = true
}
},
async pos2Address(lat, lng) {

@ -0,0 +1,194 @@
<template>
<div>
<card-container>
<vxe-toolbar print custom export>
<template #buttons>
<el-date-picker v-model="select.month"
type="month" size="small" value-format="yyyy-MM"
placeholder="月份" format="yyyy-MM"/>
<el-select style="width:250px;margin-left:6px" size="small" @change="changeDepartment" v-model="select.department_id" placeholder="请选择">
<el-option v-for="item in departments" :key="item.id" :label="item.name" :value="item.id">
</el-option>
</el-select>
<el-button icon="el-icon-search" type="primary" plain size="small" style="margin-left: 6px;"
@click="getList">搜索</el-button>
</template>
</vxe-toolbar>
<vxe-table ref="table" stripe :border='true' style="margin-top: 10px;" :loading="loading"
:max-height="1400" :min-height="400"
:export-config="{type: 'xlsx',filename:exportName,sheetName:exportName}"
:print-config="{}" :column-config="{ resizable: true }"
:expand-config="{
visibleMethod: () => false,
trigger: 'manual'
}" :data="tableData"
:merge-cells="mergeCells"
>
<!-- :span-method="mergeCells" -->
<vxe-column width="240" header-align="center" align="center" field="department_name" title="科室"></vxe-column>
<vxe-column width="180" header-align="center" align="center" field="user.name" title="姓名"></vxe-column>
<vxe-column header-align="center" align="center" field="over_off" title="结余调休时间">
<template #default="{ row }">
<div>
{{row.over_off=='0.00'?'0':row.over_off}}
</div>
</template>
</vxe-column>
<vxe-column header-align="center" align="center" field="overtime" title="本月加班时间">
<template #default="{ row }">
<div>
{{row.overtime=='0.00'?'0':row.overtime}}
</div>
</template>
</vxe-column>
<vxe-column header-align="center" align="center" field="time_off" title="本月调休时间">
<template #default="{ row }">
<div>
{{row.time_off=='0.00'?'0':row.time_off}}
</div>
</template>
</vxe-column>
<vxe-column header-align="center" align="center" field="expire" title="过期时间">
<template #default="{ row }">
<div>
{{row.expire=='0.00'?'0':row.expire}}
</div>
</template>
</vxe-column>
<vxe-column header-align="center" align="center" field="has_time_off" title="剩余调休时间">
<template #default="{ row }">
<div>
{{row.has_time_off=='0.00'?'0':row.has_time_off}}
</div>
</template>
</vxe-column>
</vxe-table>
</card-container>
</div>
</template>
<script>
import {
leaveList
} from "@/api/chart"
import {
departmentListNoAuth
} from "@/api/common.js"
export default {
data() {
return {
loading: false,
tableData: [],
select: {
page: 1,
page_size: 10,
month: this.$moment().format('YYYY-MM'),
department_id: ''
},
departments:[],
mergeCells:[],
exportName:'调休统计表',
dName:'全部科室'
}
},
methods: {
async getList() {
this.loading = true;
try {
const res = await leaveList(this.select, false);
let _arr = res?.timeOff || []
_arr.forEach(item=>{
item.department_name = item.user.department.name
item.sortnumber = item.user.department.sortnumber
})
_arr.sort((a,b)=>{
return a.sortnumber - b.sortnumber
})
this.tableData = _arr;
this.mergeCells = this.generateMergeCells(this.tableData)
let date = this.$moment(this.select.month).format("YYYY年MM月")
this.exportName = `${date}${this.dName}调休统计表`
this.loading = false;
} catch (err) {
console.error(err);
this.loading = false;
}
},
async getDepartmentList() {
try {
const res = await departmentListNoAuth();
let arr = res
this.departments = arr
this.departments.unshift({
id: '',
name: '全部'
})
} catch (err) {
console.error(err);
}
},
changeDepartment(e){
if(e){
this.departments.map(item=>{
if(item.id==e){
this.dName = item.name
}
})
}else{
this.dName = '全部科室'
}
},
//
generateMergeCells(data) {
const columns = this.$refs.table.getColumns()
let mergeCells = [];
const columnsToMerge = ['department_name'];
columnsToMerge.forEach(key => {
const col = columns.findIndex(item => item['field'] === key);
if (col === -1) {
return;
}
for (let i = 0; i < data.length; i++) {
let rowspan = 1;
const currentValue = data[i][key];
for (let j = i + 1; j < data.length; j++) {
if (data[j][key] === currentValue) {
rowspan++;
} else {
break;
}
}
if (rowspan > 1) {
mergeCells.push({
row: i,
col,
rowspan,
colspan: 1
});
i += rowspan - 1;
}
}
});
return mergeCells;
},
},
computed: {},
created() {
this.getDepartmentList()
this.getList()
},
mounted() {
this.$nextTick(() => {
if (this.$refs["table"] && this.$refs["toolbar"]) {
this.$refs["table"].connect(this.$refs["toolbar"]);
}
});
},
}
</script>
<style scoped lang="scss">
</style>

@ -0,0 +1,203 @@
<template>
<div>
<card-container>
<vxe-toolbar print custom export>
<template #buttons>
<el-date-picker v-model="select.month" type="month" size="small" value-format="yyyy-MM" placeholder="月份"
format="yyyy-MM" />
<el-select style="width:250px;margin-left:6px" size="small" @change="changeDepartment"
v-model="select.department_id" placeholder="请选择">
<el-option v-for="item in departments" :key="item.id" :label="item.name" :value="item.id">
</el-option>
</el-select>
<el-button icon="el-icon-search" type="primary" plain size="small" style="margin-left: 6px;"
@click="getList">搜索</el-button>
</template>
</vxe-toolbar>
<vxe-table ref="table" stripe :border='true' style="margin-top: 10px;" :loading="loading" :max-height="1400"
:min-height="400" :export-config="{type: 'xlsx',filename:exportName,sheetName:exportName}" :print-config="{}"
:column-config="{ resizable: true }" :expand-config="{
visibleMethod: () => false,
trigger: 'manual'
}" :data="tableData" :merge-cells="mergeCells">
<!-- :span-method="spanMethods" -->
<vxe-column width="180" header-align="center" align="center" field="department_name" title="科室"></vxe-column>
<vxe-column width="180" header-align="center" align="center" field="name" title="姓名"></vxe-column>
<vxe-column width="180" header-align="center" align="center" field="kaishiriqi" title="日期">
<template #default="{ row }">
{{row.kaishiriqi?row.kaishiriqi.substring(0,11):''}}
</template>
</vxe-column>
<vxe-column header-align="center" align="center" field="yuanyinshuoming" title="加班事由"></vxe-column>
<vxe-column :export-method="exportjbMethod" header-align="center" align="center" field="jiabanshijian"
title="加班时间">
<template #default="{ row }">
{{row.kaishiriqi?row.kaishiriqi.substring(11,row.kaishiriqi.length):''}}-{{row.kaishiriqi?row.jieshushijian.substring(11,row.jieshushijian.length):''}}
</template>
</vxe-column>
<vxe-column header-align="center" align="center" field="jiabanshichang" title="加班时长(h)"></vxe-column>
<!-- <vxe-column header-align="center" align="center" field="day" title="day"></vxe-column> -->
<vxe-column header-align="center" align="center" field="allDay" title="本月累计(d)"></vxe-column>
</vxe-table>
</card-container>
</div>
</template>
<script>
import {
overtimeList
} from "@/api/chart"
import {
departmentListNoAuth
} from "@/api/common.js"
import * as XLSX from "xlsx";
import {
saveAs
} from "file-saver";
export default {
data() {
return {
loading: false,
tableData: [],
select: {
page: 1,
page_size: 10,
month: this.$moment().format('YYYY-MM'),
department_id: ''
},
departments: [],
mergeCells: [],
exportName: '加班统计表',
dName: '全部科室'
}
},
methods: {
async getList() {
this.loading = true;
try {
const res = await overtimeList(this.select, false);
console.log(res);
let data = res?.users || [];
this.tableData = this.overtimeData(data)
let date = this.$moment(this.select.month).format("YYYY年MM月")
this.exportName = `${date}${this.dName}加班统计表`
this.loading = false;
} catch (err) {
console.error(err);
this.loading = false;
}
},
async getDepartmentList() {
try {
const res = await departmentListNoAuth();
console.log(res);
let arr = res
this.departments = arr
this.departments.unshift({
id: '',
name: '全部'
})
} catch (err) {
console.error(err);
}
},
overtimeData(arr) {
let _arr = []
const result = [];
_arr = arr.filter(item => item.overtime.length > 0)
_arr.forEach(item => {
const {
id,
name,
department,
overtime
} = item;
const allDay = overtime.reduce((sum, overtimeItem) => sum + parseFloat(overtimeItem.day), 0);
overtime.forEach(overtimeItem => {
overtimeItem.pid = id;
overtimeItem.name = name;
overtimeItem.department_name = department.name
overtimeItem.sortnumber = department.sortnumber
overtimeItem.department_id = department.id
overtimeItem.allDay = parseFloat(allDay).toFixed(2);
result.push(overtimeItem);
});
result.sort((a, b) => {
return a.sortnumber - b.sortnumber
})
});
this.mergeCells = this.generateMergeCells(result)
return result;
},
exportjbMethod({
row
}) {
return `${row.kaishiriqi?row.kaishiriqi.substring(11,row.kaishiriqi.length):''}-${row.kaishiriqi?row.jieshushijian.substring(11,row.jieshushijian.length):''}`
},
changeDepartment(e) {
if (e) {
this.departments.map(item => {
if (item.id == e) {
this.dName = item.name
}
})
} else {
this.dName = '全部科室'
}
},
//
generateMergeCells(data) {
const columns = this.$refs.table.getColumns()
let mergeCells = [];
const columnsToMerge = ['department_name', 'name', 'allDay'];
columnsToMerge.forEach(key => {
const col = columns.findIndex(item => item['field'] === key);
if (col === -1) {
return;
}
for (let i = 0; i < data.length; i++) {
let rowspan = 1;
const currentValue = data[i][key];
for (let j = i + 1; j < data.length; j++) {
if (data[j][key] === currentValue) {
rowspan++;
} else {
break;
}
}
if (rowspan > 1) {
mergeCells.push({
row: i,
col,
rowspan,
colspan: 1
});
i += rowspan - 1;
}
}
});
return mergeCells;
},
},
computed: {},
created() {
this.getDepartmentList()
this.getList()
},
mounted() {
this.$nextTick(() => {
if (this.$refs["table"] && this.$refs["toolbar"]) {
this.$refs["table"].connect(this.$refs["toolbar"]);
}
});
},
}
</script>
<style scoped lang="scss">
</style>

@ -118,15 +118,9 @@
</div>
<div
class="sign-away"
v-if="getTodayAttendance(data.day) && getTodayAttendance(data.day).chuchai && getTodayAttendance(data.day).chuchai instanceof Array && getTodayAttendance(data.day).chuchai[0]"
v-if="getTodayAttendance(data.day) && getTodayAttendance(data.day).chuchai && getTodayAttendance(data.day).chuchai instanceof Array && (getTodayAttendance(data.day).chuchai[0] || getTodayAttendance(data.day).chuchai[1])"
>
上午出差
</div>
<div
class="sign-away"
v-if="getTodayAttendance(data.day) && getTodayAttendance(data.day).chuchai && getTodayAttendance(data.day).chuchai instanceof Array && getTodayAttendance(data.day).chuchai[1]"
>
下午出差
出差
</div>
<div
class="sign-leave"

@ -64,6 +64,8 @@ export default {
},
data() {
return {
//
customValidate: () => {},
// modal
defaultModalSize,
zIndex: PopupManager.nextZIndex(),
@ -143,12 +145,14 @@ export default {
},
],
flows: {},
tempFlowList: []
};
},
methods: {
getToken,
request,
async validate() {
this.customValidate()
const $elForm = this.$refs['elForm']
if ($elForm) {
await this.$refs['elForm'].validate()

@ -15,23 +15,42 @@
storage
show-footer
>
<div v-if="isValidDone" style="margin-bottom: 10px;">
<div style="color: #606266;font-weight: 600;font-size: 14px;line-height: 2;">可批量办理流程分组</div>
<div>
<el-tag style="margin-right: 4px;" v-for="(value, key) in batchData" :key="key">
流水号{{ tagFlowId(value) }}
</el-tag>
</div>
</div>
<vxe-table
ref="table"
stripe
keep-source
show-overflow
:column-config="{ resizable: true }"
:print-config="{}"
:export-config="{}"
:row-config="{ keyField: 'id' }"
:custom-config="{ mode: 'popup' }"
:data="dealFlows"
:checkbox-config="{
checkMethod: ({ row }) => {
return isValidDone
}
}"
>
<vxe-column
type="checkbox"
width="50"
align="center"
></vxe-column>
<vxe-column
min-width="140"
header-align="center"
field="title"
title="工作名称"
show-overflow
></vxe-column>
<vxe-column
width="80"
@ -39,6 +58,12 @@
field="id"
title="流水号"
></vxe-column>
<vxe-column
header-align="center"
min-width="240"
field="_simple"
title="简要"
></vxe-column>
<vxe-column
width="80"
title="发起人"
@ -79,7 +104,7 @@
"
></vxe-column>
</vxe-table>
<template v-if="device === 'desktop'">
<template>
<DesktopForm
:device="device"
ref="desktopForm"
@ -97,54 +122,38 @@
:logs="config.logs"
></DesktopForm>
</template>
<template v-else>
<MobileForm
:device="device"
ref="mobileForm"
:config="config"
:is-first-node="isFirstNode"
:need-flow-title="false"
:sub-form="subConfig"
:fields="fields"
:original-form="form"
:readable="[]"
:script-content="scriptContent"
:writeable="writeableFields"
:rules="rules"
:sub-rules="subRules"
:logs="config.logs"
></MobileForm>
</template>
<template #footer>
<el-button
icon="el-icon-document-add"
type="info"
size="small"
@click="submit('only-submit')"
@click="saveData('only-submit')"
>暂存不流转</el-button
>
<el-button type="primary" size="small" @click="submit('assign')"
<el-button v-if="!isValidDone" type="primary" size="small" @click="saveData('assign')"
>检查流转 <i class="el-icon-s-check"></i
></el-button>
<el-button v-else type="primary" size="small" @click="submit('assign')"
>保存并流转 <i class="el-icon-right"></i
></el-button>
</template>
</vxe-modal>
<assign :visible.sync="isShowAssign" :result="result" multiple :multipleIds="dealFlows.map(flow => flow.id)" @refresh="$emit('update:isShow', false),$emit('refresh')" />
<assign :visible.sync="isShowAssign" multiple :multipleIds="pickRowIds" @refresh="$emit('update:isShow', false),$emit('refresh')" />
</div>
</template>
<script>
import { PopupManager } from 'element-ui/lib/utils/popup'
import { defaultModalSize } from '@/settings'
import {create, deal, fieldConfig, preDeal} from '@/api/flow'
import { create, deal, fieldConfig, preDeal, getNextNodeBatch } from '@/api/flow'
import { validation, validationName } from "@/utils/validate";
import MobileForm from "@/views/flow/MobileForm.vue";
import DesktopForm from "@/views/flow/DesktopForm.vue";
import assign from './assign.vue'
import {deepCopy} from "@/utils";
import { deepCopy } from "@/utils";
export default {
components: {
DesktopForm, MobileForm,assign
DesktopForm,assign
},
props: {
isShow: {
@ -170,7 +179,10 @@ export default {
flows: [],
isShowAssign: false,
result: {},
batchData: null,
isValidDone: false,
pickRowIds: []
}
},
methods: {
@ -345,31 +357,61 @@ export default {
}
},
async submit(type) {
async validFlow() {
let loading;
try {
loading = this.$loading({
lock: true,
text: "验证是否能批量审批",
spinner: "el-icon-loading",
background: "rgba(0, 0, 0, 0.8)",
});
const { list } = await getNextNodeBatch({
ids: this.dealFlows.map(i => i.id).toString()
})
this.batchData = {}
list?.forEach((item, index) => {
let nextNodeIds = item.current_node?.nextNodes?.sort((a, b) => a.id - b.id)?.map(j => j.id)?.toString()
if (this.batchData.hasOwnProperty(nextNodeIds)) {
this.batchData[nextNodeIds].push({
data: item.flow_id,
trend: item.is_trend
})
} else {
Object.defineProperty(this.batchData, nextNodeIds, {
value: [{
data: item.flow_id,
trend: item.is_trend
}],
enumerable: true,
writable: true,
configurable: false
})
}
})
this.isValidDone = true
let longerData = Object.values(this.batchData).reduce((pre, cur) => (pre.length >= cur.filter(j => !j.trend)?.length) ? pre : cur.map(j => j.data), [])
this.$refs['table']?.setCheckboxRow(this.dealFlows.filter(flow => longerData.indexOf(flow.id) !== -1), true)
} catch (err) {
console.error(err)
} finally {
loading.close()
}
},
async saveData(type) {
if (window.$_uploading) {
this.$message.warning("文件正在上传中")
return
}
let copyForm;
if (this.device === "desktop") {
try {
await this.$refs['desktopForm'].validate()
} catch (err) {
console.warn(err)
this.$message.warning('数据校验失败')
return
}
copyForm = deepCopy(this.$refs["desktopForm"].form);
} else {
try {
await this.$refs['mobileForm'].validate()
} catch (err) {
console.warn(err)
this.$message.warning('数据校验失败')
return
}
copyForm = deepCopy(this.$refs["mobileForm"].form);
try {
await this.$refs['desktopForm'].validate()
} catch (err) {
console.warn(err)
this.$message.warning('数据校验失败')
return
}
copyForm = deepCopy(this.$refs["desktopForm"].form);
const uploadHandler = (form, fields) => {
let keys = Object.keys(form)
keys.forEach(key => {
@ -404,28 +446,25 @@ export default {
}
copyForm["current_node_id"] = this.config.currentNode.id;
try {
let callback;
let callback = () => {}
switch (type) {
case "only-submit":
if (this.$route.query.flow_id) {
copyForm["temporary_save"] = 1;
}
copyForm["temporary_save"] = 1;
callback = () => {
this.$emit('update:isShow', false)
this.$emit('refresh')
}
break;
case "assign":
if (this.$route.query.flow_id) {
copyForm["temporary_save"] = 0;
copyForm["temporary_save"] = 0;
callback = () => {
this.validFlow()
}
callback = () => (this.isShowAssign = true);
break;
}
const resArr = await Promise.all(this.dealFlows.map(flow => deal(flow.id, copyForm)))
const { flow, is_last_handled_log } = resArr[0]
this.result = flow;
if (!is_last_handled_log) {
await this.$alert(
"办理成功,其他会签办理完成后,由最后办理的成员流转到下一节点。",
@ -439,11 +478,29 @@ export default {
this.$emit('refresh')
}
}
callback();
callback()
} catch (err) {
console.error(err);
}
},
async submit() {
function areAllElementsInArray(subsetArray, mainArray) {
return subsetArray.every(element => mainArray.includes(element));
}
this.pickRowIds = this.$refs['table']?.getCheckboxRecords(true)?.map(i => i.id)
console.log(345, this.pickRowIds)
let groupAllowIds = Object.values(this.batchData)?.map(group => {
return group.filter(j => !j.trend).map(j => j.data)
})
for(let i = 0;i < groupAllowIds.length;i++) {
const group = groupAllowIds[i]
if (areAllElementsInArray(this.pickRowIds, group)) {
this.isShowAssign = true
return
}
}
this.$message.warning("存在不符合批量流转流程")
},
},
computed: {
device() {
@ -467,6 +524,11 @@ export default {
},
isFirstNode() {
return this.config?.logs?.length === 0 || this.config?.currentNode?.category === 'start'
},
tagFlowId() {
return function (data) {
return data?.filter(i => !i.trend)?.map(i => i.data)?.toString()
}
}
},
watch: {
@ -477,6 +539,10 @@ export default {
if (this.dealFlows instanceof Array && this.dealFlows.length > 0) {
this.getConfig()
}
} else {
this.batchData = null
this.isValidDone = false
this.pickRowIds.length = 0
}
}
}

@ -136,7 +136,7 @@ export default {
config: Object,
result: {
type: Object,
required: true
default: () => ({})
},
},
data() {
@ -205,7 +205,7 @@ export default {
async getPreShare() {
try {
const res = await preShare(this.result.id);
const res = await preShare(this.multipleIds[0] || this.result?.id);
this.shareConfig = res;
this.form.cc_users = res.exists_users;
} catch (err) {
@ -216,7 +216,7 @@ export default {
async getNextNode() {
try {
const res = await getNextNode({
id: this.result.id
id: this.multipleIds[0] || this.result?.id
})
this.node = res.currentNode || {}
} catch(err) {
@ -229,7 +229,7 @@ export default {
const nodeIds = this.node?.nextNodes?.map(node => node.id);
this.form.next_node_id = nodeIds[0];
const res = await Promise.all(nodeIds.map(nodeId => getNextNodeUsers({
id: this.result.id,
id: this.multipleIds[0] || this.result?.id,
next_node_id: nodeId,
},false)));

@ -58,9 +58,9 @@
:sub-form="subConfig"
:fields="fields"
:original-form="form"
:readable="readableFields"
:readable.sync="readableFields"
:script-content="scriptContent"
:writeable="writeableFields"
:writeable.sync="writeableFields"
:rules="rules"
:sub-rules="subRules"
:logs="config.logs"
@ -279,6 +279,8 @@ export default {
isShowAssign: false,
info: [],
config: {},
writeableFields: [],
readableFields: [],
subConfig: new Map(),
myStatus: new Map([
[-2, "会签退回"],
@ -562,6 +564,10 @@ export default {
}
});
this.config = res;
this.readableFields = /\/detail/.test(this.$route.path)
? this.fields?.map((i) => i.id)
: this.config?.currentNode?.readable || [];
this.writeableFields = this.config?.currentNode?.writeable || [];
// form
this.generateForm(this.form, fields);
// form
@ -633,6 +639,10 @@ export default {
}
});
this.config = res;
this.readableFields = /\/detail/.test(this.$route.path)
? this.fields?.map((i) => i.id)
: this.config?.currentNode?.readable || [];
this.writeableFields = this.config?.currentNode?.writeable || [];
this.generateForm(this.form, fields);
this.handleDefaultJSON();
this.form = Object.assign({}, this.form);
@ -663,6 +673,10 @@ export default {
}
});
this.config = res;
this.readableFields = /\/detail/.test(this.$route.path)
? this.fields?.map((i) => i.id)
: this.config?.currentNode?.readable || [];
this.writeableFields = this.config?.currentNode?.writeable || [];
this.generateForm(this.form, fields);
this.handleDefaultJSON();
const { data } = res?.flow;
@ -741,6 +755,8 @@ export default {
})
}
}
} else if (form[key].length > 0) {
} else {
form[key] = ''
}
@ -859,14 +875,15 @@ export default {
fields() {
return this.config?.customModel?.fields || [];
},
readableFields() {
return /\/detail/.test(this.$route.path)
? this.fields?.map((i) => i.id)
: this.config?.currentNode?.readable || [];
},
writeableFields() {
return this.config?.currentNode?.writeable || [];
},
// data
// readableFields() {
// return /\/detail/.test(this.$route.path)
// ? this.fields?.map((i) => i.id)
// : this.config?.currentNode?.readable || [];
// },
// writeableFields() {
// return this.config?.currentNode?.writeable || [];
// },
node() {
return this.config?.currentNode || {};
},

@ -45,39 +45,38 @@
<el-divider></el-divider>
</div>
<div style="margin-bottom: 10px;">
<el-button type="primary" @click="execScript('write')">js</el-button>
<el-button type="primary" @click="execScript('read')">js</el-button>
</div>
<div class="form-container" id="print-content">
<template v-if="device === 'desktop'">
<DesktopForm
:device="device"
ref="desktopForm"
:config="config"
:is-first-node="isFirstNode"
:sub-form="subConfig"
:fields="fields"
:original-form="form"
:readable="readableFields"
:writeable="writeableFields"
:rules="{}"
:sub-rules="{}"
:logs="config.logs"
></DesktopForm>
</template>
<template v-else>
<MobileForm
:device="device"
ref="mobileForm"
:config="config"
:is-first-node="isFirstNode"
:sub-form="subConfig"
:fields="fields"
:original-form="form"
:readable="readableFields"
:writeable="writeableFields"
:rules="{}"
:sub-rules="{}"
:logs="config.logs"
></MobileForm>
</template>
<el-form :model="flowLinkForm" label-position="right" label-width="130px">
<el-form-item label="out_pay_id">
<el-input v-model="flowLinkForm.out_pay_id"></el-input>
</el-form-item>
<el-form-item label="out_away_id">
<el-input v-model="flowLinkForm.out_away_id"></el-input>
</el-form-item>
<el-form-item label="out_contract_id">
<el-input v-model="flowLinkForm.out_contract_id"></el-input>
</el-form-item>
</el-form>
<DesktopForm
:device="device"
ref="desktopForm"
:config="config"
:is-first-node="isFirstNode"
:sub-form="subConfig"
:fields="fields"
:original-form="form"
:readable="readableFields"
:writeable="writeableFields"
:rules="{}"
:sub-rules="{}"
:script-content="scriptContent"
:logs="config.logs"
></DesktopForm>
</div>
</template>
@ -243,6 +242,11 @@ export default {
[0, ""],
[1, "success"],
]),
flowLinkForm: {
out_pay_id: '',
out_away_id: '',
out_contract_id: ''
},
form: {},
result: {},
@ -252,9 +256,17 @@ export default {
subRules: {},
flows: [],
csrf_token: '',
scriptContent: '',
};
},
methods: {
execScript(mode='write') {
if (mode === 'write') {
this.scriptContent = this.writeScriptContent
} else if (mode === 'read') {
this.scriptContent = this.readScriptContent
}
},
// urldefault_json
handleDefaultJSON() {
try {
@ -503,6 +515,10 @@ export default {
this.generateForm(this.form, fields);
this.form = Object.assign({}, this.form);
const { data } = res?.flow;
this.flowLinkForm.out_contract_id = res?.flow?.out_contract_id;
this.flowLinkForm.out_away_id = res?.flow?.out_away_id;
this.flowLinkForm.out_pay_id = res?.flow?.out_pay_id;
for (let key in data) {
try {
let jsonObj = JSON.parse(data[key]);
@ -557,25 +573,14 @@ export default {
return
}
let copyForm;
if (this.device === "desktop") {
try {
await this.$refs['desktopForm'].validate()
} catch (err) {
console.warn(err)
this.$message.warning('数据校验失败')
return
}
copyForm = deepCopy(this.$refs["desktopForm"].form);
} else {
try {
await this.$refs['mobileForm'].validate()
} catch (err) {
console.warn(err)
this.$message.warning('数据校验失败')
return
}
copyForm = deepCopy(this.$refs["mobileForm"].form);
try {
await this.$refs['desktopForm'].validate()
} catch (err) {
console.warn(err)
this.$message.warning('数据校验失败')
return
}
copyForm = deepCopy(this.$refs["desktopForm"].form);
const uploadHandler = (form) => {
let keys = Object.keys(form)
keys.forEach(key => {
@ -610,7 +615,7 @@ export default {
copyForm.id = this.$route.query.flow_id;
const { flow, is_last_handled_log } = await save(
this.$route.query.flow_id,
copyForm
Object.assign(copyForm, this.flowLinkForm)
);
this.result = flow;
this.$message.success("操作成功")
@ -659,12 +664,11 @@ export default {
node() {
return this.config?.currentNode || {};
},
scriptContent() {
if (this.config?.customModel?.view_js && this.$route.query.flow_id && /\/detail/.test(this.$route.path)) {
return this.config?.customModel?.view_js;
} else if (this.config?.customModel?.js && !/\/detail/.test(this.$route.path)) {
return this.config?.customModel?.js;
}
writeScriptContent() {
return this.config?.customModel?.js;
},
readScriptContent() {
return this.config?.customModel?.view_js;
},
diffTime() {
return function (end, start) {
@ -684,7 +688,7 @@ export default {
];
},
isFirstNode() {
return this.config?.logs?.length === 0 || this.config?.currentNode?.category === 'start'
return this.config?.logs?.length === 0 || this.config?.currentNode?.category === 'start' || this.$route.path === '/flow/edit'
}
},
created() {

@ -6,6 +6,20 @@
<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
@ -60,18 +74,18 @@
icon="el-icon-search"
type="primary"
size="small"
@click="getList"
@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
>
<!-- <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>
@ -95,7 +109,6 @@
style="margin-top: 10px"
:loading="loading"
keep-source
show-overflow
:column-config="{ resizable: true }"
:print-config="{}"
:export-config="{}"
@ -123,19 +136,81 @@
}"
:row-config="{ keyField: 'id' }"
:custom-config="{ mode: 'popup' }"
:data="list"
: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"
@ -176,10 +251,11 @@
</template>
</vxe-column>
<vxe-column
min-width="100"
min-width="220"
header-align="center"
field="title"
title="工作名称"
show-overflow
:title-prefix="{ content: '点击工作名称查看简要。\n批量审批请先选择流程类型并勾选同一节点的流程。' }"
></vxe-column>
<vxe-column
@ -190,33 +266,42 @@
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'"
:visible="$route.params.type !== 'all' && $store.getters.device === 'desktop'"
width="80"
title="承办人员"
align="center"
field="last_log.user.name"
></vxe-column>
<vxe-column
header-align="center"
:visible="$store.getters.device === 'desktop'"
align="center"
min-width="140"
field="custom_model.name"
title="流程名称"
></vxe-column>
<vxe-column
:visible="$route.params.type !== 'all'"
: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'"
:visible="$route.params.type !== 'all' && $store.getters.device === 'desktop'"
title="收藏状态"
width="90"
field="my_fav"
@ -232,6 +317,7 @@
</template>
</vxe-column>
<vxe-column
:visible="$store.getters.device === 'desktop'"
width="164"
field="no"
align="center"
@ -239,7 +325,7 @@
></vxe-column>
<vxe-column
width="200"
:visible="$route.params.type === 'all'"
:visible="$route.params.type === 'all' && $store.getters.device === 'desktop'"
align="center"
field="created_at"
title="发起日期"
@ -253,6 +339,7 @@
"
></vxe-column>
<vxe-column
:visible="$store.getters.device === 'desktop'"
width="80"
align="center"
field="status"
@ -348,7 +435,8 @@
:current-page.sync="select.page"
:page-sizes="[10, 20, 30, 50, 100]"
:page-size.sync="select.page_size"
layout="total, sizes, prev, pager, next, jumper"
: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>
@ -367,6 +455,7 @@ 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: {
@ -484,11 +573,23 @@ export default {
},
],
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':
@ -506,6 +607,20 @@ export default {
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
@ -553,12 +668,15 @@ export default {
}
},
async getList() {
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);
console.log(res);
res.data?.data?.forEach(i => this.contentFormatter(i))
this.list = res?.data?.data || [];
this.total = res?.data?.total ?? 0;
this.models = res.customModels;
@ -635,6 +753,8 @@ export default {
]
}
}
} else {
return {}
}
}
},
@ -652,6 +772,7 @@ export default {
this.getList();
},
created() {
this.getDepartments();
this.getTodoTotal();
this.getList();
},
@ -675,4 +796,21 @@ export default {
.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>

Loading…
Cancel
Save