Compare commits

...

7 Commits

@ -16,6 +16,15 @@ export function save (data) {
})
}
export function saveMyself (data) {
return request({
url: "/api/backend/user/myself-save",
method: "post",
data
})
}
export function destroy (data) {
return request({
url: "/api/backend/user/delete",

@ -8,6 +8,16 @@ import { flowList } from "@/api/flow";
import MobilePicker from '@/components/MobilePicker/index.vue';
import MobileMultipleSelect from "@/components/MobileMultipleSelect/index.vue";
import { Message } from 'element-ui'
function isJSON(str) {
if (typeof str !== 'string') return false;
try {
const result = JSON.parse(str);
const type = Object.prototype.toString.call(result);
return type === '[object Object]' || type === '[object Array]';
} catch (e) {
return false;
}
}
/**
* @param {String} device 'desktop' | 'mobile'
* @param {Object} info field参数
@ -428,12 +438,17 @@ export default function formBuilder(
break;
case "relation-flow":
if (!this.flows[info.name]) {
let extraParam = {}
if (isJSON(info.stub)) {
extraParam = JSON.parse(info.stub)
}
flowList("all", {
page: 1,
page_size: 9999,
is_simple: 1,
custom_model_id: info.stub,
custom_model_id: isJSON(info.stub) ? '' : info.stub,
is_auth: 1,
...extraParam
}).then((res) => {
this.$set(this.flows, info.name, res.data.data);
});
@ -441,6 +456,7 @@ export default function formBuilder(
formItem = h(
"el-select",
{
ref: `relation-flow-${info.name}`,
props: {
value: target[info.name]
? target[info.name]
@ -484,13 +500,42 @@ export default function formBuilder(
h("div", {
}, [
h("span", {},option.title),
h("span", {
h("el-button", {
style: {
color: '#999',
float: 'right',
'font-size': '12px'
float: 'right'
},
props: {
type: 'primary',
size: 'mini',
icon: 'el-icon-search'
},
on: {
click: e => {
e.stopPropagation()
let target = this.$router.resolve({
path: "/flow/detail",
query: {
module_id: option.custom_model_id,
flow_id: option.id,
isSinglePage: 1,
},
});
this.modalRender = (h) =>
h("iframe", {
attrs: {
src: target.href,
},
style: {
border: "none",
width: "100%",
height: "100%",
},
});
this.isShowModal = true;
this.$refs[`relation-flow-${info.name}`]?.blur()
}
}
},option.no)
}, '查看')
])
])
)
@ -728,12 +773,17 @@ export default function formBuilder(
break;
case "relation-flow":
if (!this.flows[info.name]) {
let extraParam = {}
if (isJSON(info.stub)) {
extraParam = JSON.parse(info.stub)
}
flowList("all", {
page: 1,
page_size: 9999,
is_simple: 1,
custom_model_id: info.stub,
custom_model_id: isJSON(info.stub) ? '' : info.stub,
is_auth: 1,
...extraParam
}).then((res) => {
this.$set(this.flows, info.name, res.data.data);
});
@ -1356,12 +1406,17 @@ export default function formBuilder(
break;
case "relation-flow":
if (!this.flows[info.name]) {
let extraParam = {}
if (isJSON(info.stub)) {
extraParam = JSON.parse(info.stub)
}
flowList("all", {
page: 1,
page_size: 9999,
is_simple: 1,
custom_model_id: info.stub,
custom_model_id: isJSON(info.stub) ? '' : info.stub,
is_auth: 1,
...extraParam
}).then((res) => {
this.$set(this.flows, info.name, res.data.data);
});
@ -1670,12 +1725,17 @@ export default function formBuilder(
break;
case "relation-flow":
if (!this.flows[info.name]) {
let extraParam = {}
if (isJSON(info.stub)) {
extraParam = JSON.parse(info.stub)
}
flowList("all", {
page: 1,
page_size: 9999,
is_simple: 1,
custom_model_id: info.stub,
custom_model_id: isJSON(info.stub) ? '' : info.stub,
is_auth: 1,
...extraParam
}).then((res) => {
this.$set(this.flows, info.name, res.data.data);
});

@ -15,6 +15,30 @@
</el-date-picker>
<el-button icon="el-icon-search" type="primary" plain size="small" style="margin-left: 10px;" @click="getList"></el-button>
<el-button icon="el-icon-plus" type="primary" size="small" style="margin-left: 10px;" @click="isShowAdd = true">补卡</el-button>
<el-popover
placement="bottom-start"
title="导入"
width="400"
trigger="click">
<template #reference>
<el-button style="margin-left: 10px;" type="primary" size="small" icon="el-icon-upload">导入</el-button>
</template>
<template #default>
<el-upload
ref="upload"
:action="action"
:on-success="uploadSuccess"
accept=".xls,.xlsx"
:headers="{
'Authorization': 'Bearer ' + getToken()
}"
:auto-upload="false">
<el-button slot="trigger" size="small" type="primary">选取文件</el-button>
<el-button style="margin-left: 10px;" size="small" type="success" @click="uploadData"></el-button>
<div slot="tip" class="el-upload__tip">只能上传xls/xlsx文件</div>
</el-upload>
</template>
</el-popover>
</template>
</vxe-toolbar>
<vxe-table
@ -28,24 +52,45 @@
:min-height="400"
:export-config="{}"
:print-config="{}"
:edit-config="{ trigger: 'manual', mode: 'row', showStatus: true, autoClear: false, expandALl: true }"
align="center"
:column-config="{ resizable: true }"
:data="tableData"
>
<vxe-column type="seq" width="58" align="center" />
<vxe-column width="120" field="date" title="日期" align="center" />
<vxe-column field="attendance.sign_in_at" title="签到时间" width="200"></vxe-column>
<vxe-column field="attendance.sign_in_at_address" title="签到地址" width="140"></vxe-column>
<vxe-column field="attendance.sign_in_at_location" title="签到坐标" width="140"></vxe-column>
<vxe-column field="attendance.sign_in_at" title="签到时间" width="200" :edit-render="{}">
<template #edit="{ row }">
<el-date-picker style="width: 100%;" v-model="row.attendance.sign_in_at" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" size="small"></el-date-picker>
</template>
</vxe-column>
<vxe-column field="attendance.sign_in_at_address" title="签到地址" width="140" :edit-render="{ name: 'input' }"></vxe-column>
<vxe-column field="attendance.sign_in_at_location" title="签到坐标" width="140" :edit-render="{ name: 'input' }"></vxe-column>
<vxe-column field="attendance.sign_out_at" title="签退时间" width="200"></vxe-column>
<vxe-column field="attendance.sign_out_at_address" title="签退地址" width="140"></vxe-column>
<vxe-column field="attendance.sign_out_at_location" title="签退坐标" width="140"></vxe-column>
<vxe-column field="attendance.sign_out_at" title="签退时间" width="200" :edit-render="{}">
<template #edit="{ row }">
<el-date-picker style="width: 100%;" v-model="row.attendance.sign_out_at" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" size="small"></el-date-picker>
</template>
</vxe-column>
<vxe-column field="attendance.sign_out_at_address" title="签退地址" width="140" :edit-render="{ name: 'input' }"></vxe-column>
<vxe-column field="attendance.sign_out_at_location" title="签退坐标" width="140" :edit-render="{ name: 'input' }"></vxe-column>
<vxe-column field="attendance.status" title="类型" width="140" :edit-render="{ name: 'select', options: [{ label: '-',value: ''},{ label: '',value: 1},{ label: '',value: 2},{ label: '',value: 3}] }" />
<vxe-column fixed="right" field="operate" title="操作" min-width="160" header-align="center" align="left">
<template #default="{ row }">
<template v-if="isActiveStatus(row)">
<el-button size="small" type="primary" @click="saveRowEvent(row)"></el-button>
<el-button size="small" type="primary" plain @click="cancelRowEvent(row)"></el-button>
</template>
<template v-else>
<el-button size="small" type="warning" @click="editRowEvent(row)"></el-button>
</template>
</template>
</vxe-column>
</vxe-table>
</card-container>
<AddSign :is-show.sync="isShowAdd" :users="users"></AddSign>
<AddSign :is-show.sync="isShowAdd" :users="users" @refresh="getList"></AddSign>
</div>
</template>
@ -53,12 +98,18 @@
import { userListNoAuth } from "@/api/common";
import { index } from '@/api/attendance';
import AddSign from "./components/AddSign.vue";
import { deepCopy } from "@/utils";
import { updateSign } from "@/api/attendance";
import { getToken } from "@/utils/auth";
export default {
components: {
AddSign
},
data() {
return {
action: `${process.env.VUE_APP_BASE_API}/api/oa/statistics/import-overtime`,
isShowAdd: false,
select: {
month: this.$moment().format('YYYY-MM'),
@ -70,6 +121,59 @@ export default {
}
},
methods: {
getToken,
uploadData() {
this.$refs.upload.submit();
},
uploadSuccess(response) {
console.log(response)
if (response.hasOwnProperty('errcode')) {
this.$message.error(response.errmsg)
} else {
this.$message.success(`已导入${response.total}`)
this.getList(true)
}
},
editRowEvent(row) {
if (!row.attendance.id) {
this.$message.warning("当前日期无打卡")
return
}
if (this.$refs['table']) {
this.$refs['table'].setEditRow(row)
}
},
cancelRowEvent(row) {
if (this.$refs['table']) {
this.$refs['table'].clearEdit().then(() => {
//
this.$refs['table'].revertData(row)
})
}
},
async saveRowEvent(row) {
try {
await this.$confirm('确认保存?', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消'
})
await this.$refs['table'].clearEdit()
console.log(row)
this.loading = true
await updateSign({
user_id: this.select.user_id,
date: row.date,
sign_in_at: row.attendance.sign_in_at,
sign_out_at: row.attendance.sign_out_at,
status: row.attendance.status
})
await this.getList()
this.loading = false
} catch (err) {
this.loading = false
}
},
async getUsers () {
try {
const res = await userListNoAuth({
@ -93,6 +197,13 @@ export default {
}
},
computed: {
isActiveStatus() {
return function(row) {
if (this.$refs['table']) {
return this.$refs['table'].isEditByRow(row)
}
}
}
},
created() {
this.getUsers()

@ -170,23 +170,34 @@
<div class="mycard flow">
<div class="mycard__title">
<span>常用流程</span>
<div>
<el-button
size="mini"
type="primary"
style="padding: 5px 10px"
@click="openQucik"
>批量更改</el-button>
</div>
</div>
<div class="mycard__body flow__body">
<div
v-for="item in usualFlows"
:key="item.key"
@click="toCreate(item)">
<div class="flow-cover">
<template v-if="item.icon">
<Icon class="flow__body--icon" :icon="item.icon"></Icon>
</template>
<template v-else>
<i class="el-icon-edit flow__body--icon"></i>
</template>
<!-- 自己的 常用流程 -->
<div
v-for="item in usualFlows"
:key="item.key"
@click="toCreate(item)">
<div class="flow-cover">
<template v-if="item.icon">
<Icon class="flow__body--icon" :icon="item.icon"></Icon>
</template>
<template v-else>
<i class="el-icon-edit flow__body--icon"></i>
</template>
</div>
<span class="flow__body--name">{{item.name}}
</span>
</div>
<span class="flow__body--name">{{ item.name }}</span>
</div>
</div>
</div>
</div>
@ -285,6 +296,48 @@
frameborder="0"
/>
</vxe-modal>
<!-- 编辑菜单 -->
<vxe-modal
v-model="isShowQuick"
:z-index="zIndex"
transfer
show-zoom
resize
show-footer
show-confirm-button
show-cancel-button
type="confirm"
:fullscreen="$store.getters.device === 'mobile'"
title="常用流程"
:width="defaultModalSize.width"
:height="defaultModalSize.height"
esc-closable
:padding="false"
>
<div class="mycard flow dashboard-container">
<div class="mycard__body flow__body" style="width: 100%;grid-template-columns: repeat(6, 1fr);">
<div
v-for="item in flows"
:key="item.key"
@click="changeCheck(item)">
<div class="flow-cover">
<template v-if="item.icon">
<Icon class="flow__body--icon" :icon="item.icon"></Icon>
</template>
<template v-else>
<i class="el-icon-edit flow__body--icon"></i>
</template>
</div>
<span class="flow__body--name">
<el-checkbox @change="(e)=>{return changeCheck(item,e)}" v-model="item.checked">{{ item.name }}</el-checkbox>
</span>
</div>
</div>
</div>
<template #footer>
<el-button type="primary" :loading="quickLoading" @click="updatequick"></el-button>
</template>
</vxe-modal>
</div>
</template>
@ -299,6 +352,9 @@ import { index as configIndex } from "@/api/config";
import { index } from "@/api/attendance";
import {isExternal} from "@/utils/validate";
import { defaultModalSize } from "@/settings";
import {saveMyself} from "@/api/user"
import {getInfo} from "@/api/me"
export default {
name: "Dashboard",
components: {
@ -310,7 +366,8 @@ export default {
modalUrl: "",
zIndex: PopupManager.nextZIndex(),
isShowModal: false,
isShowQuick:false,
quickLoading:false,
weatherIcon: new Map([
["晴", "el-icon-sunny"],
["阴", "el-icon-cloudy"],
@ -436,22 +493,29 @@ export default {
console.error(err);
}
},
//
changeCheck(item){
this.$set(item,'checked',!item.checked)
this.$forceUpdate()
},
async getUsualFlows() {
try {
await this.getFlows();
const res = await configIndex({
filter: [
{
key: "key",
op: "eq",
value: "dashboard_menus",
},
],
});
this.usualFlows = this.flows.filter(
(i) => res.data[0]?.value?.indexOf(i.id.toString()) !== -1
);
console.log(this.usualFlows);
const res = await getInfo()
this.usualFlows = res.quick_enter?res.quick_enter:[]
// const res = await configIndex({
// filter: [
// {
// key: "key",
// op: "eq",
// value: "dashboard_menus",
// },
// ],
// });
// this.usualFlows = this.flows.filter(
// (i) => res.data[0]?.value?.indexOf(i.id.toString()) !== -1
// );
console.log(res,this.usualFlows);
} catch (err) {
console.error(err);
}
@ -464,6 +528,29 @@ export default {
console.error(err);
}
},
openQucik(){
this.flows.map(flow => {
const isChecked = this.usualFlows.some(usualFlow => usualFlow.id === flow.id);
flow.checked = isChecked
});
this.isShowQuick = true
},
updatequick(){
this.quickLoading = true
let arr = this.flows.filter((i)=> i.checked===true)
saveMyself({
quick_enter:arr
}).then(res=>{
this.$message({
type: 'success',
message: '更改成功!'
})
this.isShowQuick = false
this.quickLoading = false
this.getUsualFlows()
})
},
async getTotal() {
try {
const res = await axios.get(`${process.env.VUE_APP_BASE_API}/api/oa/statistics/notifications`,{
@ -667,6 +754,9 @@ $btn-colors: linear-gradient(90deg, #d4bbfd 0%, #af7bff 100%),
font-weight: bold;
padding-left: 20px;
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
&::before {
content: "";
@ -815,6 +905,8 @@ $btn-colors: linear-gradient(90deg, #d4bbfd 0%, #af7bff 100%),
font-size: 14px;
padding-top: 20px;
text-align: center;
width: 100%;
height: 40px;
}
}
}

@ -1,7 +1,7 @@
<script>
import {deepCopy} from "@/utils";
import formBuilder from "@/utils/formBuilder";
import {PopupManager} from "element-ui/lib/utils/popup";
import { PopupManager } from "element-ui/lib/utils/popup";
import request from '@/utils/request'
import moment from "moment/moment";
import {defaultModalSize} from "@/settings";

@ -0,0 +1,95 @@
<template>
<div class="steps" v-if="!/\/detail/.test($route.path) && logs.length > 0">
<el-steps :space="120" finish-status="success" align-center>
<el-step
v-for="(logGroup, index) in groupedLogs"
v-if="!(logGroup[0].flow_node_id === currentNode.id && isLastGroup(index))"
:title="logGroup[0].node ? logGroup[0].node.name : '节点已调整'"
:status="logGroup[0].status !== -1 ? 'success' : 'error'"
icon="el-icon-circle-check"
>
<template #title>
<template v-if="logGroup[0].status === -1">
<el-tooltip effect="dark" :content="logGroup[0].reason" placement="bottom">
<span>{{ logGroup[0].node ? logGroup[0].node.name : '节点已调整' }}</span>
</el-tooltip>
</template>
<template v-else>
{{ logGroup[0].node ? logGroup[0].node.name : '节点已调整' }}
</template>
</template>
</el-step>
<el-step status="finish" :title="currentNode.name + '(正在办理)'" icon="el-icon-edit"></el-step>
<el-step v-if="currentNode.nextNodes && currentNode.nextNodes.length" status="wait" icon="el-icon-right">
<template #title>
<div style="max-width: 180px;overflow: hidden;white-space: nowrap;text-overflow: ellipsis;">
<span v-for="(nextNode, index) in ((currentNode.nextNodes && currentNode.nextNodes instanceof Array) ? currentNode.nextNodes : [])">{{ index === 0 ? '' : ',' }}{{ nextNode.name }}</span>
</div>
<div v-if="currentNode.nextNodes">{{ currentNode.nextNodes.length }}</div>
</template>
</el-step>
</el-steps>
<el-divider></el-divider>
</div>
</template>
<script>
export default {
props: {
logs: {
type: Array,
default: () => []
},
currentNode: {
type: Object,
default: () => ({})
}
},
data() {
return {}
},
methods: {
//
isLastGroup(index) {
return index === this.groupedLogs.length - 1;
}
},
computed: {
groupedLogs() {
const groups = {};
this.logs.forEach(log => {
const key = log.jointly_sign_id;
if (!groups[key]) {
groups[key] = [];
}
groups[key].push(log);
});
return Object.values(groups);
}
},
}
</script>
<style scoped lang="scss">
::v-deep .el-step__title {
font-size: 14px;
line-height: 1.5;
}
::v-deep .el-step__icon.is-icon {
border-radius: 100%;
box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
width: 36px;
height: 36px;
border: solid 2px;
}
::v-deep .el-step.is-center .el-step__line {
top: 50%;
}
@media (max-width: 768px) {
::v-deep .el-steps--horizontal {
display: flex;
flex-wrap: wrap;
}
}
</style>

@ -27,7 +27,7 @@
</el-radio>
</el-radio-group>
<h4>请输入退回原因</h4>
<h4><span style="color: #F56C6C;"> * </span>请输入退回原因</h4>
<el-input v-model="form.reason" type="textarea" :autosize="{ minRows: 2 }"></el-input>
</div>
@ -84,6 +84,10 @@ export default {
this.$message.warning("请选择退回步骤!")
return
}
if(!this.form.reason) {
this.$message.warning("请填写退回原因!")
return
}
try {
this.form.id = this.flow.id;
const res = await rollback(this.form)

@ -14,37 +14,38 @@
</template>
<template>
<div class="steps" v-if="!/\/detail/.test($route.path)">
<el-steps :space="120" finish-status="success" align-center>
<template v-if="!isFirstNode">
<el-step
v-for="step in config.logs"
v-if="(step.flow_node_id !== node.id && step.status !== -1)"
:title="step.node.name"
:status="step.status !== -1 ? 'success' : 'error'"
icon="el-icon-circle-check"
></el-step>
</template>
<el-step
:title="node.name"
status="finish"
icon="el-icon-edit"
></el-step>
<el-step
icon="el-icon-right"
status="wait"
>
<template #title>
<div style="max-width: 180px;overflow: hidden;white-space: nowrap;text-overflow: ellipsis;">
<span v-for="(nextNode, index) in ((node.nextNodes && node.nextNodes instanceof Array) ? node.nextNodes : [])">{{ index === 0 ? '' : ',' }}{{ nextNode.name }}</span>
</div>
<div v-if="node.nextNodes">{{ node.nextNodes.length }}</div>
</template>
</el-step>
</el-steps>
<Steps :logs="config.logs" :current-node="node"></Steps>
<!-- <div class="steps" v-if="!/\/detail/.test($route.path)">-->
<!-- <el-steps :space="120" finish-status="success" align-center>-->
<!-- <template v-if="!isFirstNode">-->
<!-- <el-step-->
<!-- v-for="step in config.logs"-->
<!-- v-if="(step.flow_node_id !== node.id && step.status !== -1)"-->
<!-- :title="step.node.name"-->
<!-- :status="step.status !== -1 ? 'success' : 'error'"-->
<!-- icon="el-icon-circle-check"-->
<!-- ></el-step>-->
<!-- </template>-->
<!-- <el-step-->
<!-- :title="node.name"-->
<!-- status="finish"-->
<!-- icon="el-icon-edit"-->
<!-- ></el-step>-->
<!-- <el-step-->
<!-- icon="el-icon-right"-->
<!-- status="wait"-->
<!-- >-->
<!-- <template #title>-->
<!-- <div style="max-width: 180px;overflow: hidden;white-space: nowrap;text-overflow: ellipsis;">-->
<!-- <span v-for="(nextNode, index) in ((node.nextNodes && node.nextNodes instanceof Array) ? node.nextNodes : [])">{{ index === 0 ? '' : ',' }}{{ nextNode.name }}</span>-->
<!-- </div>-->
<!-- <div v-if="node.nextNodes">{{ node.nextNodes.length }}</div>-->
<!-- </template>-->
<!-- </el-step>-->
<!-- </el-steps>-->
<el-divider></el-divider>
</div>
<!-- <el-divider></el-divider>-->
<!-- </div>-->
<div class="form-container" id="print-content">
<DesktopForm
@ -207,6 +208,14 @@
>
</template>
<template v-else>
<el-button
v-if="$store.state.user.adminId === 1 && $route.query.flow_id"
icon="el-icon-arrow-left"
type="danger"
size="small"
@click="isShowRollback = true"
>退回</el-button
>
<el-button plain size="small" @click="$router.go(-1)"></el-button>
<el-button plain size="small" @click="print(false)"></el-button>
<el-button plain size="small" @click="print(true)">()</el-button>
@ -239,6 +248,7 @@
</template>
<script>
import Steps from "./components/Steps.vue";
import DesktopForm from "./DesktopForm.vue";
import MobileForm from "./MobileForm.vue";
import assign from "./components/assign.vue";
@ -260,6 +270,7 @@ import { print } from "@/utils/print";
import JSONBigint from 'json-bigint'
export default {
components: {
Steps,
DesktopForm,
MobileForm,
assign,

@ -63,6 +63,34 @@
"
>
</el-date-picker>
<el-select
style="width: 150px"
size="small"
v-model="select.status"
placeholder="请选择状态"
clearable
>
<el-option
v-for="item in myStatusList"
:key="item.value"
:value="item.value"
:label="item.label"
></el-option>
</el-select>
<el-select
style="width: 150px"
size="small"
v-model="select.flow_log_cancel"
placeholder="请选择我退回的"
clearable
>
<el-option
v-for="item in [{value:1,label:'我退回过'}]"
:key="item.value"
:value="item.value"
:label="item.label"
></el-option>
</el-select>
<el-input
clearable
v-model="select.keyword"
@ -218,6 +246,14 @@
>
</div>
</div>
<div class="mobile-desc-row">
<div class="mobile-desc-row__title">
我是否退回过
</div>
<div class="mobile-desc-row__value">
{{ row.flow_log_cancel?'是':'否' }}
</div>
</div>
</div>
<template v-if="$route.params.type !== 'all'">
<el-button
@ -324,6 +360,7 @@
>
</template>
</vxe-column>
<vxe-column
:visible="$store.getters.device === 'desktop'"
width="164"
@ -366,6 +403,19 @@
>
</template>
</vxe-column>
<vxe-column
:visible="$store.getters.device === 'desktop'"
width="120"
align="center"
field="status"
title="我是否退回过"
>
<template #default="{ row }">
{{ row.flow_log_cancel?'是':'否' }}
</template>
</vxe-column>
<vxe-column
:visible="$store.getters.device === 'desktop'"
min-width="280"
@ -493,6 +543,18 @@ export default {
[0, "办理中"],
[1, "已完成"],
]),
myStatusList:[
{
value:-1,
label:'已退回'
},{
value:0,
label:'办理中'
},{
value:1,
label:'已完成'
}
],
statusColor: new Map([
[-1, "warning"],
[0, ""],
@ -563,6 +625,8 @@ export default {
sort_name: "",
sort_type: "",
keyword: "",
flow_log_cancel:'',
status:'',
department_id: "",
is_fav: "",
custom_model_id: "",

Loading…
Cancel
Save