xy 9 months ago
parent 19d8b5fd45
commit 61ba730ab3

@ -18,6 +18,15 @@ export function sign(params,isLoading = true) {
})
}
export function updateSign(params, isLoading = true) {
return request({
method: 'get',
url: '/api/oa/attendance/update-sign',
params,
isLoading
})
}
export function signIp(params,isLoading = true) {
return request({
method: 'get',

@ -96,6 +96,14 @@ export function flowList(type,params,isLoading = false) {
})
}
export function listGroup(params,isLoading) {
return request({
method: 'get',
url: '/api/oa/flow/list-groups',
params,
isLoading
})
}
// 统计
export function todoTotal() {
return request({

@ -0,0 +1,95 @@
import axios from "axios";
import { getToken } from "@/utils/auth";
import { Loading, Message } from "element-ui";
/*
* @params {string} url 请求拼接地址
* @params {string} method get|post
* @params {object} info 请求参数params或data
* @params {string} filename 文件名
*/
let loading;
export async function download(url, method = "get", info, filename, paramsSerializer) {
loading = Loading.service({
lock: true,
background: "rgba(0,0,0,0.4)",
text: "文件正在生成中...",
});
let options = {
baseURL: process.env.VUE_APP_BASE_API,
url,
method,
responseType: "blob",
timeout: 10000,
headers: {
Accept: "application/json",
"Content-Type": "application/json; charset=utf-8",
withCredentials: true,
Authorization: "Bearer " + getToken(),
},
paramsSerializer
};
if (method === "get") {
Object.defineProperty(options, "params", {
value: info,
enumerable: true,
writable: false,
});
}
if (method === "post") {
Object.defineProperty(options, "data", {
value: info,
enumerable: true,
writable: false,
});
}
try {
const response = await axios.request(options);
loading.close();
// 提取文件名
if (!filename) {
filename =
response.headers["content-disposition"]?.match(/filename=(.*)/)[1] ||
`${new Date().valueOf()}.xlsx`;
}
// 将二进制流转为blob
const blob = new Blob([response.data], {
type: "application/octet-stream",
});
if (typeof window.navigator.msSaveBlob !== "undefined") {
// 兼容IEwindow.navigator.msSaveBlob以本地方式保存文件
window.navigator.msSaveBlob(blob, decodeURI(filename));
} else {
// 创建新的URL并指向File对象或者Blob对象的地址
const blobURL = window.URL.createObjectURL(blob);
// 创建a标签用于跳转至下载链接
const tempLink = document.createElement("a");
tempLink.style.display = "none";
tempLink.href = blobURL;
tempLink.setAttribute("download", decodeURI(filename));
// 兼容某些浏览器不支持HTML5的download属性
if (typeof tempLink.download === "undefined") {
tempLink.setAttribute("target", "_blank");
}
// 挂载a标签
document.body.appendChild(tempLink);
tempLink.click();
document.body.removeChild(tempLink);
// 释放blob URL地址
window.URL.revokeObjectURL(blobURL);
}
} catch (err) {
console.error(err);
loading.close();
Message({
type: "error",
message: err,
});
}
}

@ -3,14 +3,18 @@
<card-container>
<vxe-toolbar print custom export>
<template #buttons>
<el-select v-model="select.user_id" size="small" style="width: 160px;">
<el-option v-for="item in users" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
<el-date-picker
:value="select.month"
v-model="select.month"
type="month"
size="small"
value-format="yyyy-MM-dd"
style="width: 160px;">
value-format="yyyy-MM"
style="width: 160px;margin-left: 10px;">
</el-date-picker>
<el-button icon="el-icon-search" type="primary" plain size="small" style="margin-left: 6px;" @click="getList"></el-button>
<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>
</template>
</vxe-toolbar>
<vxe-table
@ -24,33 +28,63 @@
:min-height="400"
:export-config="{}"
:print-config="{}"
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_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-table>
</card-container>
<AddSign :is-show.sync="isShowAdd" :users="users"></AddSign>
</div>
</template>
<script>
import { index } from '@/api/attendance'
import { userListNoAuth } from "@/api/common";
import { index } from '@/api/attendance';
import AddSign from "./components/AddSign.vue";
export default {
components: {
AddSign
},
data() {
return {
isShowAdd: false,
select: {
month: this.$moment().format('YYYY-MM')
month: this.$moment().format('YYYY-MM'),
user_id: this.$store.state.user.adminId
},
loading: false,
tableData: [],
users: []
}
},
methods: {
async getUsers () {
try {
const res = await userListNoAuth({
page: 1,
rows: 9999
})
this.users = res.data
} catch (err) {
}
},
async getList () {
this.loading = true
try {
const res = await index(this.select,false)
console.log(res)
const { attendances } = await index(this.select,false)
this.tableData = attendances
this.loading = false
} catch (err) {
console.error(err)
@ -58,8 +92,10 @@ export default {
}
}
},
computed: {},
computed: {
},
created() {
this.getUsers()
this.getList()
}
}

@ -0,0 +1,130 @@
<template>
<div>
<vxe-modal
:z-index="zIndex"
:value="isShow"
show-footer
title="补卡"
show-confirm-button
:width="defaultModalSize.sWidth"
:height="defaultModalSize.sHeight"
esc-closable
transfer
:fullscreen="$store.getters.device === 'mobile'"
@input="e => $emit('update:isShow',e)"
>
<el-form ref="elForm" :model="form" :rules="rules" label-position="top" label-width="100">
<el-form-item label="用户" prop="user_id">
<el-select style="width: 100%;" v-model="form.user_id">
<el-option v-for="i in users" :key="i.id" :label="i.name" :value="i.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="日期" prop="date">
<el-date-picker style="width: 100%;" v-model="form.date" value-format="yyyy-MM-dd"></el-date-picker>
</el-form-item>
<el-form-item label="上班打卡时间" prop="sign_in_at">
<el-date-picker type="datetime" style="width: 100%;" v-model="form.sign_in_at" value-format="yyyy-MM-dd HH:mm:ss"></el-date-picker>
</el-form-item>
<el-form-item label="下班打卡时间" prop="sign_out_at">
<el-date-picker type="datetime" style="width: 100%;" v-model="form.sign_out_at" value-format="yyyy-MM-dd HH:mm:ss"></el-date-picker>
</el-form-item>
<el-form-item label="类型" prop="status">
<el-select style="width: 100%;" v-model="form.status">
<el-option label="正常" :value="1"></el-option>
<el-option label="补卡" :value="2"></el-option>
<el-option label="外勤" :value="3"></el-option>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button type="primary" :loading="loading" @click="submit"></el-button>
</template>
</vxe-modal>
</div>
</template>
<script>
import { updateSign } from '@/api/attendance';
import {defaultModalSize} from "@/settings";
import {PopupManager} from "element-ui/lib/utils/popup";
export default {
props: {
isShow: {
type: Boolean,
default: false,
required: true
},
users: {
type: Array,
default: () => []
}
},
data() {
return {
zIndex: PopupManager.nextZIndex(),
defaultModalSize,
loading: false,
form: {
user_id: '',
date: '',
sign_in_at: '',
sign_out_at: '',
status: ''
},
rules: {
user_id: [
{ required: true, message: '请选择', trigger: 'change' }
],
date: [
{ required: true, message: '请选择', trigger: 'change' }
],
sign_in_at: [
{ required: true, message: '请选择', trigger: 'change' }
],
sign_out_at: [
{ required: true, message: '请选择', trigger: 'change' }
],
status: [
{ required: true, message: '请选择', trigger: 'change' }
],
}
}
},
computed: {},
methods: {
submit() {
this.$refs['elForm'].validate(async valid => {
if (valid) {
this.loading = true
try {
await updateSign(this.form)
this.$message.success('新增成功')
this.$emit('refresh')
this.$emit('update:isShow', false)
this.loading = false
this.$refs['elForm'].resetFields()
} catch (err) {
this.loading = false
}
}
})
}
},
watch: {
isShow(newVal) {
if(newVal) {
this.zIndex = PopupManager.nextZIndex()
}
}
},
}
</script>
<style scoped lang="scss">
</style>

@ -0,0 +1,164 @@
<template>
<div>
<vxe-modal
:z-index="zIndex"
:value="isShow"
show-footer
title="特殊日期"
show-confirm-button
:width="defaultModalSize.width"
:height="defaultModalSize.height"
esc-closable
transfer
:fullscreen="$store.getters.device === 'mobile'"
@input="e => $emit('update:isShow',e)"
>
<div>
<h4>当前导出流程 {{ config.name }}</h4>
<h4 style="display: inline-block;">导出文件名</h4>
<el-input style="max-width: 220px;" size="small" v-model="fileName"></el-input>
<h4>需要导出字段</h4>
<el-checkbox :indeterminate="isIndeterminate" v-model="checkAll" @change="handleCheckAllChange"></el-checkbox>
<div style="margin: 15px 0;"></div>
<el-checkbox-group v-model="selectedFields" @change="handleCheckedFieldsChange">
<el-checkbox v-for="field in config.fields" :label="field.name" :key="field.id">{{ field.label }}</el-checkbox>
</el-checkbox-group>
</div>
<template #footer>
<el-button type="primary" :loading="loading" @click="confirm"></el-button>
</template>
</vxe-modal>
</div>
</template>
<script>
import {fieldConfig} from "@/api/flow"
import {defaultModalSize} from "@/settings";
import {PopupManager} from "element-ui/lib/utils/popup";
import {download} from "@/utils/downloadRequest"
export default {
props: {
isShow: {
type: Boolean,
default: false,
required: true
},
select: {
type: Object,
default: () => ({})
}
},
data() {
return {
zIndex: PopupManager.nextZIndex(),
defaultModalSize,
loading: false,
config: {
fields: []
},
fileName: `导出_${this.$moment().valueOf()}`,
selectedFields: [],
checkAll: false,
isIndeterminate: false
}
},
computed: {},
methods: {
handleCheckedFieldsChange(value) {
let checkedCount = value.length;
this.checkAll = checkedCount === this.config?.fields?.length;
this.isIndeterminate = checkedCount > 0 && checkedCount < this.config?.fields?.length;
},
handleCheckAllChange(val) {
this.selectedFields = val ? this.config.fields?.map(i => i.name) : [];
this.isIndeterminate = false;
},
async getFields(id) {
try {
const { customModel } = await fieldConfig(id)
this.config = customModel
this.config.fields.forEach(i => {
i.name = 'data.' + i.name
})
this.fileName = `${customModel.name}_${this.$moment().valueOf()}`
} catch (err) {
console.error(id)
}
},
async confirm() {
// paramsSerializer
const paramsSerializer = (params) => {
const parts = [];
//
const serialize = (obj, parentKey) => {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const value = obj[key];
const fullKey = parentKey ? `${parentKey}[${key}]` : key;
if (value && typeof value === 'object' && !Array.isArray(value)) {
//
serialize(value, fullKey);
} else if (Array.isArray(value)) {
//
value.forEach((item, index) => {
if (item && typeof item === 'object') {
//
serialize(item, `${fullKey}[${index}]`);
} else {
//
parts.push(`${encodeURIComponent(`${fullKey}[${index}]`)}=${encodeURIComponent(item)}`);
}
});
} else {
//
parts.push(`${encodeURIComponent(fullKey)}=${encodeURIComponent(value)}`);
}
}
}
};
//
serialize(params, '');
//
return parts.join('&');
};
try {
let export_fields = {}
this.selectedFields?.forEach(field => {
let key = field
export_fields[key] = this.config?.fields?.find(j => j.name === key)?.label
})
await download('/api/oa/flow/list-groups', 'get', {
...this.select,
type: this.$route.params.type,
is_export: 1,
export_name: this.fileName,
export_fields
}, this.fileName+'.xlsx', paramsSerializer)
} catch (err) {
console.error(err)
}
}
},
watch: {
isShow(newVal) {
if(newVal) {
this.zIndex = PopupManager.nextZIndex()
}
},
"select.custom_model_id"(newVal) {
this.getFields(newVal)
}
},
}
</script>
<style scoped lang="scss">
</style>

@ -77,6 +77,14 @@
@click="getList(true)"
>搜索</el-button
>
<el-button
v-if="select.custom_model_id"
icon="el-icon-download"
type="primary"
size="small"
@click="isShowFieldExport = true"
>导出</el-button
>
<!-- <el-button-->
<!-- v-if="$route.path === '/flow/list/todo'"-->
<!-- icon="el-icon-edit"-->
@ -446,6 +454,7 @@
<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" />
<field-export :is-show.sync="isShowFieldExport" :select="select" />
</div>
</template>
@ -455,16 +464,20 @@ import moment from "moment/moment";
import ListPopover from "./components/ListPopover.vue";
import MultiDeal from "./components/MultiDeal.vue"
import share from "./components/share.vue";
import FieldExport from "./components/FieldExport.vue";
import { departmentListNoAuth } from "@/api/common"
export default {
name: "flowList",
components: {
share,
ListPopover,
MultiDeal
MultiDeal,
FieldExport
},
data() {
return {
isShowFieldExport: false,
todoTotal: [],
isShowMultiDeal: false,
multiDealFlows: [],

Loading…
Cancel
Save