Merge branch 'develop' of https://e.coding.net/ctcdevteam/hberp/hberp into develop

This commit is contained in:
shilixia 2021-10-14 10:21:42 +08:00
commit 9592cb5fbc
20 changed files with 694 additions and 444 deletions

View File

@ -162,6 +162,14 @@ export function ticketAccpet(id,data) {
data
})
}
//撤回工单,允许创建人在指定状态撤回工单至初始状态
export function getTicketRetreat(id,data) {
return request({
url: `/wf/ticket/${id}/retreat/`,
method: 'post',
data
})
}
//工单详情
export function getTicketDetail(id) {
return request({
@ -169,7 +177,8 @@ export function getTicketDetail(id) {
method: 'get'
})
}
//工单详情
//工单流转
export function getTicketTransitions(id) {
return request({
url: `/wf/ticket/${id}/transitions/`,

View File

@ -0,0 +1,140 @@
<template>
<div style="width: 100%;height: 100%;">
<div class="testTracking">
<canvas style="display:none;" id="myCanvas" ref="canvas" :width="videoWidth" :height="videoHeight"></canvas>
<!--图片展示-->
<video ref="video" id="myVideo" :width="videoWidth" :height="videoHeight" autoplay style="display: block;margin:0 auto;border: 2px solid #333333;"></video>
<!--确认-->
<Button type="primary" @click="setImage" class="takePhoto">拍照</Button>
<div id="res"></div>
</div>
</div>
</template>
<script>
import {faceLogin} from "@/api/testModel";
export default {
props:['src'],
data () {
return {
videoWidth: 500,
videoHeight: 400,
videoArr:[],//所有的摄像头
modelSel:'',//
myInterval: null,
imgSrc: '',
isHasFace:false,//默认没有人脸
tracker:null,
}
},
created(){
},
mounted(){
this.callCamera();
this.changePhoto();
},
methods: {
// 调用摄像头
callCamera () {
// H5调用电脑摄像头API
navigator.mediaDevices.getUserMedia({
video: true
}).then(success => {
// 摄像头开启成功
this.$refs['video'].srcObject = success;
// 实时拍照效果
this.$refs['video'].play()
}).catch(error => {
alert('摄像头开启失败,请检查摄像头是否可用!')
})
},
// 拍照
setImage () {
let canvas = document.getElementById("myCanvas");
let context = canvas.getContext('2d');
let video = document.getElementById("myVideo");
context.drawImage(video,0,0,90,68);
let image = new Image();
image = canvas.toDataURL('image/png');
document.getElementById('res').innerHTML = '<img src="'+image+'">';
let imgData = {base64:image};
faceLogin(imgData).then((res) => {
if (res.code >= 200) {
this.$message.success("成功");
}
});
},
// 关闭摄像头
closeCamera () {
if (!this.$refs['video'].srcObject) return;
let stream = this.$refs['video'].srcObject;
let tracks = stream.getTracks();
tracks.forEach(track => {
track.stop()
});
this.$refs['video'].srcObject = null
},
//切换本地摄像头
changePhoto(){
/**得到所有的设备*/
navigator.mediaDevices.enumerateDevices()
.then((devices)=> {
this.videoArr = [];
devices.forEach((item)=> {
if(item.kind == 'videoinput'){
this.videoArr.push({
'label': item.kind,
'id': item.deviceId
})
}
});
})
.catch(err=>{
});
},
/**打开摄像头*/
getUserMedia(constraints, success, error) {
if (navigator.mediaDevices.getUserMedia) {
//最新的标准API
navigator.mediaDevices.getUserMedia(constraints).then(success=>{
// 摄像头开启成功
this.$refs['video'].srcObject = success;
// 实时拍照效果
this.$refs['video'].play()
}).catch(error);
} else if (navigator.webkitGetUserMedia) {
//webkit核心浏览器
navigator.webkitGetUserMedia(constraints,success, error)
} else if (navigator.mozGetUserMedia) {
//firfox浏览器
navigator.mozGetUserMedia(constraints, success, error);
} else if (navigator.getUserMedia) {
//旧版API
navigator.getUserMedia(constraints, success, error);
}
},
setFace(data){
this.isHasFace = data;
}
},
}
</script>
<style scoped>
.testTracking {
width: 100%;
position: relative;
}
.takePhoto{
margin: 30px auto;
display: block;
}
</style>

View File

@ -53,7 +53,7 @@ const actions = {
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
const { data } = response
sessionStorage.setItem('userId',data.id);
if (!data) {
reject('验证失败,重新登陆.')
}

View File

@ -1,273 +1,273 @@
<template>
<template>
<div class="app-container">
<div class="box-card">
<el-row :gutter="12">
<el-col :span="8">
<el-card shadow="always">
<div class="box-card">
<el-row :gutter="12">
<el-col :span="8">
<el-card shadow="always">
<div slot="header" class="clearfix">
<span>CPU</span>
</div>
<el-row class="row">
<div slot="header" class="clearfix">
<span>CPU</span>
</div>
<el-row class="row">
<el-col :span="8">
属性
</el-col>
<el-col :span="8">
</el-col>
</el-row>
<el-row class="rowlist">
<el-col :span="8">
逻辑核心数
</el-col>
<el-col :span="8">
{{cpuData.count}}
</el-col>
</el-row>
<el-row class="rowlist">
<el-col :span="8">
物理核心数
</el-col>
<el-col :span="8">
{{cpuData.lcount}}
</el-col>
</el-row>
<el-row class="rowlist">
<el-col :span="8">
当前使用率
</el-col>
<el-col :span="8">
{{cpuData.percent}}%
</el-col>
</el-row>
</el-card>
</el-col>
<el-col :span="8">
属性
<el-card shadow="always">
<div slot="header" class="clearfix">
<span>内存</span>
</div>
<el-row class="row">
<el-col :span="8">
属性
</el-col>
<el-col :span="8">
</el-col>
</el-row>
<el-row class="rowlist">
<el-col :span="8">
使用率
</el-col>
<el-col :span="8">
{{memoryData.percent}}%
</el-col>
</el-row>
<el-row class="rowlist">
<el-col :span="8">
总内存
</el-col>
<el-col :span="8">
{{memoryData.total}}
</el-col>
</el-row>
<el-row class="rowlist">
<el-col :span="8">
已用内存
</el-col>
<el-col :span="8">
{{memoryData.used}}
</el-col>
</el-row>
</el-card>
</el-col>
<el-col :span="8">
</el-col>
</el-row>
<el-row class="rowlist">
<el-col :span="8">
逻辑核心数
<el-card shadow="always">
<div slot="header" class="clearfix">
<span>硬盘</span>
</div>
<el-row class="row">
<el-col :span="8">
属性
</el-col>
<el-col :span="8">
</el-col>
</el-row>
<el-row class="rowlist">
<el-col :span="8">
已用百分比
</el-col>
<el-col :span="8">
{{diskData.percent}}%
</el-col>
</el-row>
<el-row class="rowlist">
<el-col :span="8">
总大小
</el-col>
<el-col :span="8">
{{diskData.total}}
</el-col>
</el-row>
<el-row class="rowlist">
<el-col :span="8">
已用大小
</el-col>
<el-col :span="8">
{{diskData.used}}
</el-col>
</el-row>
</el-card>
</el-col>
<el-col :span="8">
{{cpuData.count}}
</el-col>
</el-row>
<el-row class="rowlist">
<el-col :span="8">
物理核心数
</el-col>
<el-col :span="8">
{{cpuData.lcount}}
</el-col>
</el-row>
<el-row class="rowlist">
<el-col :span="8">
当前使用率
</el-col>
<el-col :span="8">
{{cpuData.percent}}%
</el-col>
</el-row>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="always">
<div slot="header" class="clearfix">
<span>内存</span>
</div>
<el-row class="row">
<el-col :span="8">
属性
</el-col>
<el-col :span="8">
</el-col>
</el-row>
<el-row class="rowlist">
<el-col :span="8">
使用率
</el-col>
<el-col :span="8">
{{memoryData.percent}}%
</el-col>
</el-row>
<el-row class="rowlist">
<el-col :span="8">
总内存
</el-col>
<el-col :span="8">
{{memoryData.total}}
</el-col>
</el-row>
<el-row class="rowlist">
<el-col :span="8">
已用内存
</el-col>
<el-col :span="8">
{{memoryData.used}}
</el-col>
</el-row>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="always">
<div slot="header" class="clearfix">
<span>硬盘</span>
</div>
<el-row class="row">
<el-col :span="8">
属性
</el-col>
<el-col :span="8">
</el-col>
</el-row>
<el-row class="rowlist">
<el-col :span="8">
已用百分比
</el-col>
<el-col :span="8">
{{diskData.percent}}%
</el-col>
</el-row>
<el-row class="rowlist">
<el-col :span="8">
总大小
</el-col>
<el-col :span="8">
{{diskData.total}}
</el-col>
</el-row>
<el-row class="rowlist">
<el-col :span="8">
已用大小
</el-col>
<el-col :span="8">
{{diskData.used}}
</el-col>
</el-row>
</el-card>
</el-col>
</el-row>
</div>
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>日志列表</span>
</div>
<div>
<el-input
v-model="listQuery.name"
placeholder="日志名称"
style="width: 300px"
class="filter-item"
@keyup.enter.native="handleFilter"
/>
<el-button
class="filter-item"
type="primary"
icon="el-icon-search"
@click="handleFilter"
>搜索</el-button
>
<el-button
class="filter-item"
type="primary"
icon="el-icon-refresh-left"
@click="resetFilter"
>重置</el-button
>
</el-row>
</div>
<el-table
:data="tableData"
style="width: 100%;height:400px">
<el-table-column
prop="name"
label="日志名称"
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>日志列表</span>
</div>
<div>
<el-input
v-model="listQuery.name"
placeholder="日志名称"
style="width: 300px"
class="filter-item"
@keyup.enter.native="handleFilter"
/>
<el-button
class="filter-item"
type="primary"
icon="el-icon-search"
@click="handleFilter"
>搜索
</el-button>
<el-button
class="filter-item"
type="primary"
icon="el-icon-refresh-left"
@click="resetFilter"
>重置
</el-button>
</div>
<el-table
:data="tableData"
style="width: 100%;height:400px"
>
<el-table-column
prop="name"
label="日志名称"
>
</el-table-column>
<el-table-column
prop="size"
label="日志大小"
</el-table-column>
<el-table-column
prop="size"
label="日志大小"
>
</el-table-column>
<el-table-column
fixed="right"
label="操作"
width="100">
<template slot-scope="scope">
<el-button @click="handleClick(scope.row)" type="text" size="small">查看详情</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog
:visible.sync="dialogVisible"
width="80%"
height:300px
>
<div v-html="logdec"> </div>
</el-dialog>
</el-card>
</div>
</template>
</el-table-column>
<el-table-column
fixed="right"
label="操作"
width="100"
>
<template slot-scope="scope">
<el-button @click="handleClick(scope.row)" type="text" size="small">查看详情</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog
:visible.sync="dialogVisible"
width="80%"
title="日志详情"
>
<div v-html="logdec" class="dialogDiv"></div>
</el-dialog>
</el-card>
</div>
</template>
<script>
import {getlogList,getLog,getServerList} from "@/api/moritor";
const defaultCMA = {
import {getlogList, getLog, getServerList} from "@/api/moritor";
}
export default {
components: { },
data() {
return {
tableData:[],
cpuData:[],
diskData:[],
memoryData:[],
dialogVisible: false,
logdec:"",
text:"",
listQuery: {},
};
},
computed: {},
watch: {},
created() {
this.getlogList();
this.getServerList();
},
methods: {
getlogList() {
getlogList(this.listQuery).then((response) => {
if(response.data) {
this.tableData=response.data;
}
});
const defaultCMA = {}
export default {
components: {},
data() {
return {
tableData: [],
cpuData: [],
diskData: [],
memoryData: [],
dialogVisible: false,
logdec: "",
text: "",
listQuery: {},
};
},
handleFilter() {
computed: {},
watch: {},
created() {
this.getlogList();
this.getServerList();
},
resetFilter() {
methods: {
this.getlogList();
getlogList() {
getlogList(this.listQuery).then((response) => {
if (response.data) {
this.tableData = response.data;
}
});
},
handleFilter() {
this.getlogList();
},
resetFilter() {
this.getlogList();
},
getServerList() {
getServerList().then((response) => {
if (response.data) {
this.cpuData = response.data.cpu;
this.diskData = response.data.disk;
this.memoryData = response.data.memory;
}
});
},
handleClick(row) {
this.dialogVisible = true;
getLog(row.name).then((response) => {
if (response.data) {
this.logdec = response.data.replace(/\n/gm, "<br/>")
}
});
},
},
getServerList() {
getServerList().then((response) => {
if (response.data) {
this.cpuData = response.data.cpu;
this.diskData = response.data.disk;
this.memoryData = response.data.memory;
}
});
},
handleClick(row){
this.dialogVisible = true;
getLog(row.name).then((response) => {
if (response.data) {
this.logdec=response.data.replace(/\n/gm,"<br/>")
}
});
},
},
};
};
</script>
<style>
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track{
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.2);
background-color: #fefefe;
border-radius: 7px;
}
::-webkit-scrollbar-thumb{
border-radius: 7px;
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.2);
background-color: #f5f5f5;
}
.dialogDiv{
height: 70vh;
overflow-y: scroll;
}
.text {
font-size: 14px;
}
@ -281,17 +281,28 @@ export default {
display: table;
content: "";
}
.clearfix:after {
clear: both
}
.box-card {
width: 90%;
margin:0 auto;
margin-top:30px;
margin: 0 auto;
margin-top: 30px;
}
.row{border-bottom: 1px solid #dfe6ec;color: #909399;font-weight: 500;padding: 10px;}
.rowlist{
border-bottom: 1px solid #dfe6ec;color:#606266;font-weight: 500;padding: 10px;
.row {
border-bottom: 1px solid #dfe6ec;
color: #909399;
font-weight: 500;
padding: 10px;
}
.rowlist {
border-bottom: 1px solid #dfe6ec;
color: #606266;
font-weight: 500;
padding: 10px;
}
</style>

View File

@ -17,13 +17,22 @@
<span>保存的图片</span>
<div id="res"></div>
</div>
<button @click="takePhoto()">拍照</button>
<el-dialog :visible.sync="limitedPhoto" title="人脸登录">
<div class="testTracking">
<faceLogin name="faceLogin"></faceLogin>
</div>
</el-dialog>
</div>
</template>
<script>
import faceLogin from '@/views/testModel/faceLogin'
let preDrawAry = [];
let img = new Image();
// let img = new Image();
export default {
components:{
faceLogin
},
data() {
return {
canvas:null,
@ -36,6 +45,7 @@
lineW:3,
colorF:"#e42343",
imgData:'',
limitedPhoto:false,
}
},
mounted(){
@ -52,25 +62,26 @@
setTimeout(function(){
_this.draw();
},1000)
/*
this.ctx .clearRect(0, 0, 700, 500);
let imgs = new Image();
imgs.src ="./../../assets/404_images/404.png";
setTimeout(function(){
this.draw();
this.ctx.drawImage(imgs,0,0,700,500);
},1000)*/
/* imgs.onload=function(){
this.ctx.drawImage(imgs,0,0,700,500);
this.ctx.closePath();
let pattern=this.ctx.createPattern(imgs,"no-repeat")//不加
this.ctx.fillStyle=pattern;
this.ctx.fillRect(0,0,700,500);
}*/
},
methods: {
takePhoto(){
this.limitedPhoto = true;
},
takeClick(){
let canvas = document.getElementById("myCanvas");
let context = canvas.getContext('2d');
let video = document.getElementById("myVideo");
context.drawImage(video,0,0,90,68);
let image = new Image();
image = canvas.toDataURL('image/png');
document.getElementById('res').innerHTML = '<img src="'+image+'">';
let imgData = {base64:image};
faceLogin(imgData).then((res) => {
if (res.code >= 200) {
this.$message.success("成功");
}
});
},
draw(){
debugger;
let canvasImg = document.getElementById("canvasImg");
@ -238,24 +249,6 @@
var image = new Image();
image = canvas.toDataURL('image/png');
document.getElementById('res').innerHTML = '<img style="border: 1px solid #666666;" src="'+image+'">';
console.log(image);
debugger;
/*img.setAttribute('crossOrigin', 'Anonymous') ;// 解决某些图片跨域有些图片仍不可使用)
img.src = 'url';
img.onload = (imgs) => {
let base64 = this.setBase64(imgs);
var arr = base64.split(',');
var mime = arr[0].match(/:(.*?);/)[1] ;// 获取图片的类型 image/jpg)
var bstr = atob(arr[1]); // 将base64转码
var n = bstr.length ;// 获得转码长度
var u8arr = new Uint8Array(n); // 获得length个为0的数组
while (n--) {
u8arr[n] = bstr.charCodeAt(n) // 获得unicode码
}
this.file = new File([u8arr], 'faceImg', { type: mime }); // 生成文件
console.log(this.file);
this.url = base64
}*/
},
setBase64(img) {
let ctx = document.createElement('canvas');

View File

@ -61,7 +61,9 @@
<el-table-column align="center" label="操作">
<template slot-scope="scope">
<el-link v-if="scope.row.state_.distribute_type==1&&scope.row.participant_type==2" type="danger" @click="handleGetTicket(scope)">接单</el-link>
<el-link v-else-if="scope.row.act_state==1&&scope.row.participant_type!==2" type="primary" @click="handleDetail(scope)">处理</el-link>
<el-link v-else-if="(scope.row.act_state==1||scope.row.act_state==3)&&scope.row.participant_type!==2" type="primary" @click="handleDetail(scope)">处理</el-link>
<!--如果state_.retreat为可退回则显示撤回按钮-->
<el-link v-if="scope.row.state_.enable_retreat&&userId==scope.row.create_by" type="danger" @click="handleRetreat(scope)">撤回</el-link>
<el-link type="success" @click="handlePicture(scope)">查看流程图</el-link>
</template>
</el-table-column>
@ -147,9 +149,10 @@
<el-table-column label="创建时间" min-width="100" prop="create_time">
</el-table-column>
<el-table-column align="center" label="操作">
<template slot-scope="scope">
<template slot-scope="scope" v-if="scope.row.state_">
<el-link v-if="scope.row.state_.distribute_type==1&&scope.row.participant_type==2" type="danger" @click="handleGetTicket(scope)">接单</el-link>
<el-link v-else-if="scope.row.act_state==1&&scope.row.participant_type!==2" type="primary" @click="handleDetail(scope)">处理</el-link>
<el-link v-else-if="(scope.row.act_state==1||scope.row.act_state==3)&&scope.row.participant_type!==2" type="primary" @click="handleDetail(scope)">处理</el-link>
<el-link v-if="scope.row.state_.enable_retreat" type="danger" @click="handleRetreat(scope)">撤回</el-link>
<el-link type="success" @click="handlePicture(scope)">查看流程图</el-link>
</template>
</el-table-column>
@ -225,120 +228,21 @@
</svg>
</div>
</div>
<el-dialog :visible.sync="limitedStep" title="工单处理">
<el-steps :active="actives" spac="400px" align-center="" style="padding-top: 20px;">
<el-step :title="item.name" v-for="item in flowSteps "
:key="item.id" @click.native=stepclick(item.id)>
</el-step>
</el-steps>
<el-dialog :visible.sync="limitedRetreat" title="工单撤回">
<el-row>
<el-form ref="Form" :model="handleForm" label-width="100px" label-position="right" :rules="handleRule">
<el-col :span="12">
<el-form-item label="标题" style="margin-bottom: 0">
<span>{{ticketDetail.title}}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="工作流" style="margin-bottom: 0">
<span v-if="ticketDetail.workflow_">{{ticketDetail.workflow_.name}}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="流水号" style="margin-bottom: 0">
<span>{{ticketDetail.sn}}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="创建时间" style="margin-bottom: 0">
<span>{{ticketDetail.create_time}}</span>
</el-form-item>
</el-col>
<el-col v-for="item in ticketDetail.ticket_data_" :key="item.id" :span="12">
<el-form-item :label="item.field_name" v-if="item.field_state==='1'" style="margin-bottom: 0">
<span>{{ticketDetail.ticket_data[item.field_key]}}</span>
</el-form-item>
<el-form-item :label="item.field_name" v-else>
<template v-if="item.field_type=='string'">
<el-input v-model="ticketDetail.ticket_data[item.field_key]" :placeholder="item.description"/>
</template>
<template v-if="item.field_type==='int'">
<el-input v-model="ticketDetail.ticket_data[item.field_key]" type="number" :placeholder="item.description" oninput="value=value.replace(/[^\d]/g,'')" />
</template>
<template v-if="item.field_type==='float'">
<el-input v-model="ticketDetail.ticket_data[item.field_key]" type="number" :placeholder="item.description" />
</template>
<template v-if="item.field_type==='date'">
<el-date-picker
v-model="ticketDetail.ticket_data[item.field_key]"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
style="width: 100%"
>
</el-date-picker>
</template>
<template v-if="item.field_type==='datetime'">
<el-date-picker
v-model="ticketDetail.ticket_data[item.field_key]"
type="datetime"
placeholder="选择日期"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 100%"
>
</el-date-picker>
</template>
<template v-if="item.field_type==='select'">
<el-select style="width: 100%" v-model="ticketDetail.ticket_data[item.field_key]" placeholder="请选择">
<el-option
v-for="item1 in item.field_choice"
:key="item1"
:label="item1"
:value="item1"
>
</el-option>
</el-select>
</template>
<template v-if="item.field_type==='selects'">
<el-select style="width: 100%" multiple v-model="ticketDetail.ticket_data[item.field_key]" placeholder="请选择">
<el-option
v-for="item1 in item.field_choice"
:key="item1"
:label="item1"
:value="item1"
>
</el-option>
</el-select>
</template>
<template v-if="item.field_type==='textarea'">
<el-input type="textarea" :rows="3" v-model="ticketDetail.ticket_data[item.field_key]" placeholder="内容" />
</template>
<template v-if="item.field_type==='file'">
<el-upload
ref="upload"
:action="upUrl"
:on-preview="handlePreview"
:on-success="handleUpSuccess"
:on-remove="handleRemove"
:headers="upHeaders"
:file-list="fileList"
:limit="1"
accept=".doc,.docx,.xls,.xlsx,.ppt,.pptx"
>
<el-button size="small" type="primary">上传文件</el-button>
</el-upload>
</template>
</el-form-item>
</el-col>
<el-col>
<el-form-item label="审批意见">
<el-input v-model="handleForm.suggestion" placeholder="审批意见"/>
</el-form-item>
</el-col>
<el-col :span="2" style="height: 1px;"></el-col>
<el-col :span="20" style="margin:6vh 0">
<el-form-item label="撤回原因">
<el-input type="textarea" :rows="3" v-model="handleForm.suggestion" placeholder="撤回原因"/>
</el-form-item>
</el-col>
</el-form>
</el-row>
<div style="text-align: center">
<el-button v-for="item in operationBtn" :key="item.id" class="filter-item" type="primary" @click="operationSubmit(item.id)">{{item.name}}</el-button>
<el-button class="filter-item" type="" @click="retreatCancel">取消</el-button>
<el-button class="filter-item" type="primary" @click="retreatSubmit">确定</el-button>
</div>
</el-dialog>
<el-dialog :visible.sync="limitedAdd" title="新增工单">
@ -439,7 +343,7 @@
<script>
import { upUrl, upHeaders } from "@/api/file";
import {getWorkflowList,getWfCustomfieldList,createTicket,getWfStateList,getTickets,ticketAccpet,getWfTransitionList,
ticketHandle,getWfFlowSteps,getTicketDetail,getTicketTransitions,getTicketFlowlog } from "@/api/workflow";
ticketHandle,getWfFlowSteps,getTicketDetail,getTicketTransitions,getTicketFlowlog,getTicketRetreat} from "@/api/workflow";
import Pagination from "@/components/Pagination";
import dagreD3 from 'dagre-d3'
import * as d3 from 'd3'
@ -467,8 +371,6 @@
transition:''
},
handleForm:{
transition:'',
ticket_data:{},
suggestion:'',
},
handleRule:{},
@ -486,6 +388,7 @@
listLoading:false,
limitedStep:false,
limitedAdd:false,
limitedRetreat:false,
nodes: [],
tooltip:null,
edges: [],
@ -504,6 +407,7 @@
},
mounted(){
debugger;
this.userId = sessionStorage.getItem('userId')
this.workflow = this.$route.params.workflow;
if(this.workflow){//有传参
this.pageForm.workflow = parseInt(this.workflow);
@ -659,6 +563,30 @@
}
});
},
retreatCancel(){
this.limitedRetreat = false;
},
retreatSubmit(){
this.$confirm("确认撤回吗?", "温馨提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
getTicketRetreat(this.retreatId,this.handleForm).then(res=>{
this.limitedRetreat = false;
this.getList();
this.$message.success("成功");
})
})
.catch((err) => {
console.error(err);
});
},
handleRetreat(scope){
this.limitedRetreat = true;
this.retreatId = scope.row.id;
},
handlePicture(scope){
let that = this;
that.dialogVisible = true;
@ -863,15 +791,6 @@
handleDetail(scope){
this.$router.push({name:"ticketHandle",params:{ticketId:scope.row.id,workflow:scope.row.workflow}})
},
operationSubmit(id){
this.handleForm.transition = id;
this.handleForm.ticket_data = this.ticketDetail.ticket_data;
ticketHandle(this.ticketId,this.handleForm).then(res=>{
if (res.data){
this.limitedStep = false;
}
})
},
//接单
handleGetTicket(scope){
let ticketId = scope.row.id;

View File

@ -23,6 +23,7 @@ class EquipmentSerializer(ModelSerializer):
def get_step_(self, obj):
return Step.objects.filter(equipments=obj).values('id', 'name', 'number')
class EquipmentSimpleSerializer(ModelSerializer):
class Meta:
model = Equipment

View File

@ -0,0 +1,26 @@
# Generated by Django 3.2.6 on 2021-10-14 01:44
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('em', '0009_auto_20210916_1108'),
('mtm', '0021_auto_20211013_0856'),
]
operations = [
migrations.AddField(
model_name='subproduction',
name='process',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='subproduction_process', to='mtm.process', verbose_name='隶属大工序'),
preserve_default=False,
),
migrations.AlterField(
model_name='step',
name='equipments',
field=models.ManyToManyField(related_name='step_equips', to='em.Equipment', verbose_name='使用设备'),
),
]

View File

@ -64,7 +64,7 @@ class Step(CommonAModel):
number = models.CharField('步骤编号', max_length=100, null=True, blank=True)
instruction_content = models.TextField('相应操作指导', null=True, blank=True)
sort = models.IntegerField('排序号', default=1)
equipments = models.ManyToManyField(Equipment, verbose_name='使用设备')
equipments = models.ManyToManyField(Equipment, verbose_name='使用设备', related_name='step_equips')
class Meta:
verbose_name = '工序步骤'
@ -134,6 +134,7 @@ class SubProduction(CommonAModel):
"""
name = models.CharField('命名', max_length=50, null=True, blank=True)
product = models.ForeignKey(Material, verbose_name='产品', on_delete=models.CASCADE)
process = models.ForeignKey(Process, verbose_name='隶属大工序', on_delete=models.CASCADE, related_name='subproduction_process')
sort = models.IntegerField('排序号', default=1)
class Meta:

View File

@ -17,8 +17,9 @@ class MaterialDetailSerializer(serializers.ModelSerializer):
fields = '__all__'
def get_processes_(self, obj):
steps = UsedStep.objects.filter(subproduction__product=obj).values_list('step', flat=True)
objs = Process.objects.filter(step_process__id__in=steps).distinct().order_by('number')
# steps = UsedStep.objects.filter(subproduction__product=obj).values_list('step', flat=True)
# objs = Process.objects.filter(step_process__id__in=steps).distinct().order_by('number')
objs = Process.objects.filter(subproduction_process__product=obj, subproduction_process__is_deleted=False, is_deleted=False).order_by('number')
return ProcessSimpleSerializer(instance=objs, many=True).data
@ -61,6 +62,7 @@ class StepDetailSerializer(serializers.ModelSerializer):
return queryset
class SubProductionSerializer(serializers.ModelSerializer):
process_ = ProcessSimpleSerializer(source='process', read_only=True)
class Meta:
model = SubProduction
fields = '__all__'

View File

@ -74,8 +74,8 @@ class SubProductionViewSet(CreateUpdateModelAMixin, ModelViewSet):
产品生产分解增删改查
"""
perms_map={'*':'*'}
queryset = SubProduction.objects.all()
filterset_fields = ['product']
queryset = SubProduction.objects.select_related('process').all()
filterset_fields = ['product', 'process']
search_fields = ['name']
serializer_class = SubProductionSerializer
ordering = ['sort']

View File

@ -15,3 +15,10 @@ class ProductionPlanSerializer(serializers.ModelSerializer):
class Meta:
model = ProductionPlan
fields ='__all__'
class ResourceCalSerializer(serializers.Serializer):
id = serializers.IntegerField(label='产品ID')
count = serializers.IntegerField(label='生产数量')
class ResourceCalListSerializer(serializers.ListSerializer):
child = ResourceCalSerializer()

View File

@ -1,4 +1,4 @@
from apps.pm.views import ProductionPlanViewSet
from apps.pm.views import ProductionPlanViewSet, ResourceViewSet
from django.db.models import base
from rest_framework import urlpatterns
from django.urls import path, include
@ -6,7 +6,7 @@ from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('productionplan', ProductionPlanViewSet, basename='productionplan')
router.register('resource', ResourceViewSet, basename='resource')
urlpatterns = [
path('', include(router.urls)),
]

View File

@ -1,6 +1,9 @@
from rest_framework.views import APIView
from apps.em.models import Equipment
from apps.em.serializers import EquipmentSerializer
from apps.mtm.models import InputMaterial, Step, SubProduction, UsedStep
from apps.system.mixins import CreateUpdateModelAMixin
from apps.pm.serializers import ProductionPlanCreateFromOrderSerializer, ProductionPlanSerializer
from apps.pm.serializers import ProductionPlanCreateFromOrderSerializer, ProductionPlanSerializer, ResourceCalListSerializer, ResourceCalSerializer
from rest_framework.mixins import CreateModelMixin, ListModelMixin
from apps.pm.models import ProductionPlan
from rest_framework.viewsets import GenericViewSet, ModelViewSet
@ -8,6 +11,7 @@ from django.shortcuts import render
from apps.sam.models import Order
from rest_framework.exceptions import APIException
from rest_framework.response import Response
from rest_framework.decorators import action
# Create your views here.
def updateOrderPlanedCount(order):
@ -52,5 +56,50 @@ class ProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, CreateModel
updateOrderPlanedCount(instance.order)
return Response()
class ResourceCalculate(APIView):
pass
class ResourceViewSet(GenericViewSet):
perms_map = {'*': '*'}
@action(methods=['post'], detail=False, perms_map={'get':'*'}, serializer_class=ResourceCalListSerializer)
def cal(self, request, pk=None):
"""
物料消耗计算
"""
rdata = request.data
serializer = self.get_serializer(data=rdata)
serializer.is_valid(raise_exception=True)
res_d_list = []
res = []
for i in rdata:
materials = InputMaterial.objects.filter(subproduction__product__id=i['id'],
subproduction__is_deleted=False, is_deleted=False, material__type__in=[3,4]).order_by('material__number')\
.values('material__id', 'material__name', 'material__number', 'material__type', 'count', 'material__count')
l_m = list(materials)
for m in l_m:
if m['material__id'] in res_d_list:
index = res_d_list.index(m['material__id'])
res[index]['count'] = res[index]['count'] + m['count']*i['count']
else:
res_d_list.append(m['material__id'])
res.append({'id':m['material__id'], 'name':m['material__name'],
'type':m['material__type'], 'number':m['material__number'],
'count':m['count']*i['count'], 'inv_count':m['material__count']})
return Response(res)
@action(methods=['post'], detail=False, perms_map={'get':'*'}, serializer_class=ResourceCalListSerializer)
def cal_equip(self, request, pk=None):
"""
设备状态查看
"""
rdata = request.data
serializer = self.get_serializer(data=rdata)
serializer.is_valid(raise_exception=True)
rdata_l = []
for i in rdata:
rdata_l.append(i['id'])
subproductions = SubProduction.objects.filter(product__id__in=rdata_l, is_deleted=False)
steps = Step.objects.filter(usedsteps__is_deleted=False, usedsteps__subproduction__in=subproductions)
equips = Equipment.objects.filter(step_equips__in=steps, is_deleted=False)
serializer = EquipmentSerializer(instance=equips, many=True)
return Response(serializer.data)

View File

@ -0,0 +1,19 @@
# Generated by Django 3.2.6 on 2021-10-14 01:44
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('wf', '0012_ticketflow_intervene_type'),
]
operations = [
migrations.AlterField(
model_name='ticketflow',
name='transition',
field=models.ForeignKey(blank=True, help_text='与worklow.Transition关联 为空时表示认为干预的操作', null=True, on_delete=django.db.models.deletion.CASCADE, to='wf.transition', verbose_name='流转id'),
),
]

View File

@ -201,7 +201,7 @@ class TicketFlow(BaseModel):
工单流转日志
"""
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE, verbose_name='关联工单', related_name='ticketflow_ticket')
transition = models.ForeignKey(Transition, verbose_name='流转id', help_text='与worklow.Transition关联0时表示认为干预的操作', on_delete=models.CASCADE, null=True, blank=True)
transition = models.ForeignKey(Transition, verbose_name='流转id', help_text='与worklow.Transition关联时表示认为干预的操作', on_delete=models.CASCADE, null=True, blank=True)
suggestion = models.CharField('处理意见', max_length=10000, default='', blank=True)
participant_type = models.IntegerField('处理人类型', default=0, help_text='0.无处理人,1.个人,2.多人', choices=State.state_participanttype_choices)
participant = models.ForeignKey(User, verbose_name='处理人', on_delete=models.SET_NULL, null=True, blank=True, related_name='ticketflow_participant')

View File

@ -130,3 +130,13 @@ class TicketHandleSerializer(serializers.Serializer):
class TicketRetreatSerializer(serializers.Serializer):
suggestion = serializers.CharField(label="撤回原因", required = False)
class TicketCloseSerializer(serializers.Serializer):
suggestion = serializers.CharField(label="关闭原因", required = False)
class TicketAddNodeSerializer(serializers.Serializer):
suggestion = serializers.CharField(label="加签说明", required = False)
add_node_man = serializers.IntegerField(label='加签人')
class TicketAddNodeEndSerializer(serializers.Serializer):
suggestion = serializers.CharField(label="加签意见", required = False)

View File

@ -33,6 +33,17 @@ class WfService(object):
except:
raise Exception('工作流状态配置错误')
@staticmethod
def get_workflow_end_state(workflow:Workflow):
"""
获取工作流结束状态
"""
try:
wf_state_obj = State.objects.get(workflow=workflow, type=State.STATE_TYPE_END, is_deleted=False)
return wf_state_obj
except:
raise Exception('工作流状态配置错误')
@staticmethod
def get_workflow_custom_fields(workflow:Workflow):
"""
@ -124,7 +135,7 @@ class WfService(object):
"""
到达结束状态
"""
return dict(destination_participant_type=State.PARTICIPANT_TYPE_PERSONAL,
return dict(destination_participant_type=0,
destination_participant=0,
multi_all_person={})
multi_all_person_dict = {}

View File

@ -1,9 +1,10 @@
from apps.system.models import User
from apps.wf.filters import TicketFilterSet
from django.core.exceptions import AppRegistryNotReady
from rest_framework.response import Response
from rest_framework import serializers
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin
from apps.wf.serializers import CustomFieldSerializer, StateSerializer, TicketCreateSerializer, TicketFlowSerializer, TicketFlowSimpleSerializer, TicketHandleSerializer, TicketRetreatSerializer, TicketSerializer, TransitionSerializer, WorkflowSerializer, TicketListSerializer, TicketDetailSerializer
from apps.wf.serializers import CustomFieldSerializer, StateSerializer, TicketAddNodeEndSerializer, TicketAddNodeSerializer, TicketCloseSerializer, TicketCreateSerializer, TicketFlowSerializer, TicketFlowSimpleSerializer, TicketHandleSerializer, TicketRetreatSerializer, TicketSerializer, TransitionSerializer, WorkflowSerializer, TicketListSerializer, TicketDetailSerializer
from django.shortcuts import get_object_or_404, render
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from rest_framework.decorators import action, api_view
@ -11,6 +12,7 @@ from apps.wf.models import CustomField, Ticket, Workflow, State, Transition, Tic
from apps.system.mixins import CreateUpdateCustomMixin, CreateUpdateModelAMixin, OptimizationMixin
from apps.wf.services import WfService
from rest_framework.exceptions import APIException, PermissionDenied
from rest_framework import status
# Create your views here.
class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
@ -225,7 +227,7 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
ticket.act_state = Ticket.TICKET_ACT_STATE_ONGOING
if transition.attribute_type == Transition.TRANSITION_ATTRIBUTE_TYPE_REFUSE:
transition.act_state = Ticket.TICKET_ACT_STATE_BACK
ticket.act_state = Ticket.TICKET_ACT_STATE_BACK
# 只更新必填和可选的字段
for key, value in ticket.state.state_fields.items():
@ -312,16 +314,66 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
participant=request.user, transition=None)
return Response()
@action(methods=['post'], detail=True, perms_map={'post':'*'})
@action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=TicketAddNodeSerializer)
def add_node(self, request, pk=None):
"""
加签
"""
ticket = self.get_object()
data = request.data
add_user = User.objects.get(pk=data['add_node_man'])
ticket.participant_type = State.PARTICIPANT_TYPE_PERSONAL
ticket.participant = add_user.id
ticket.in_add_node = True
ticket.add_node_man = request.user
ticket.save()
# 更新流转记录
suggestion = request.data.get('suggestion', '') # 加签说明
TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket),
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_ADD_NODE,
participant=request.user, transition=None)
return Response()
@action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=TicketAddNodeEndSerializer)
def add_node_end(self, request, pk=None):
"""
加签完成
"""
ticket = self.get_object()
ticket.participant_type = State.PARTICIPANT_TYPE_PERSONAL
ticket.in_add_node = False
ticket.add_node_man = None
ticket.participant = ticket.add_node_man.id
ticket.save()
# 更新流转记录
suggestion = request.data.get('suggestion', '') # 加签意见
TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket),
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_ADD_NODE_END,
participant=request.user, transition=None)
return Response()
@action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=TicketCloseSerializer)
def close(self, request, pk=None):
"""
关闭工单(超级管理员或者创建人在初始状态)
关闭工单(创建人在初始状态)
"""
ticket = self.get_object()
if ticket.state.type == State.STATE_TYPE_START and ticket.create_by==request.user:
end_state = WfService.get_workflow_end_state(ticket.workflow)
ticket.state = end_state
ticket.participant_type = 0
ticket.participant = 0
ticket.act_state = Ticket.TICKET_ACT_STATE_CLOSED
ticket.save()
# 更新流转记录
suggestion = request.data.get('suggestion', '') # 关闭原因
TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket),
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_CLOSE,
participant=request.user, transition=None)
return Response()
else:
return Response('工单不可关闭', status=status.HTTP_400_BAD_REQUEST)
class TicketFlowViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):

View File

@ -56,7 +56,7 @@ class FitJSONRenderer(JSONRenderer):
data = data[prefix]
if isinstance(data, list):
data = data[0]
response_body.msg = prefix + ":" + data # 取一部分放入msg,方便前端alert
response_body.msg = prefix + ":" + str(data) # 取一部分放入msg,方便前端alert
else:
response_body.data = data
renderer_context.get("response").status_code = 200 # 统一成200响应,用code区分